codehere 0.2.1 → 0.3.0-rc.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +88 -68
- package/dist/domain/entities/task.js +4 -4
- package/dist/error-handler.js +4 -9
- package/dist/index.js +253 -1219
- package/dist/infrastructure/config/product-context.js +7 -6
- package/dist/infrastructure/hooks/claude-sdk-hook.js +335 -63
- package/dist/infrastructure/observability/execution-tracer.js +11 -6
- package/dist/infrastructure/orchestration/agent-adapter.js +13 -0
- package/dist/infrastructure/orchestration/agent-installer.js +101 -0
- package/dist/infrastructure/orchestration/agents-config-seed.js +56 -0
- package/dist/infrastructure/orchestration/aider-adapter.js +319 -0
- package/dist/infrastructure/orchestration/artifacts-extractor.js +175 -0
- package/dist/infrastructure/orchestration/claude-code-adapter.js +352 -0
- package/dist/infrastructure/orchestration/codex-adapter.js +310 -0
- package/dist/infrastructure/orchestration/generic-cli-adapter.js +553 -0
- package/dist/infrastructure/orchestration/opencode-adapter.js +275 -0
- package/dist/infrastructure/orchestration/progress-aggregator.js +189 -0
- package/dist/infrastructure/orchestration/readiness.js +106 -0
- package/dist/infrastructure/orchestration/session-manager.js +272 -0
- package/dist/infrastructure/orchestration/session-persistence.js +109 -0
- package/dist/infrastructure/protocols/mcp-helpers.js +10 -30
- package/dist/infrastructure/protocols/mcp-server-impl.js +359 -21
- package/dist/infrastructure/protocols/ui-page.js +1827 -0
- package/dist/infrastructure/security/file-collection.js +115 -0
- package/dist/infrastructure/security/trust-scorer.js +301 -56
- package/dist/infrastructure/storage/task-repository.js +8 -7
- package/dist/monitoring.js +10 -49
- package/dist/presentation/cli/commands/help-command.js +96 -139
- package/dist/presentation/cli/commands/hook-command.js +17 -24
- package/dist/presentation/cli/commands/report-command.js +59 -72
- package/dist/presentation/cli/commands/setup-command.js +4 -4
- package/dist/presentation/cli/commands/status-command.js +334 -43
- package/dist/presentation/cli/commands/trust-command.js +115 -99
- package/dist/presentation/cli/error-display.js +17 -21
- package/dist/ui.js +17 -12
- package/package.json +35 -67
- package/dist/agents/base.js +0 -101
- package/dist/agents/orchestrator-model-driven.js +0 -141
- package/dist/agents/react-orchestrator-agent.js +0 -54
- package/dist/application/agents/execution-agent.js +0 -508
- package/dist/application/agents/multi-agent-orchestrator.js +0 -295
- package/dist/application/agents/planning-agent.js +0 -433
- package/dist/application/agents/validation-agent.js +0 -317
- package/dist/application/architectures/plan-and-act.js +0 -242
- package/dist/application/index.js +0 -12
- package/dist/application/services/agent-factory.js +0 -28
- package/dist/application/services/dependency-container.js +0 -596
- package/dist/application/services/git-context-service.js +0 -83
- package/dist/application/services/intelligent-router.js +0 -934
- package/dist/application/services/tool-executor-service.js +0 -94
- package/dist/application/use-cases/ask-question-use-case.js +0 -338
- package/dist/application/use-cases/edit-file-use-case.js +0 -830
- package/dist/application/use-cases/explain-file-use-case.js +0 -50
- package/dist/application/use-cases/index-codebase-use-case.js +0 -113
- package/dist/application/use-cases/planning-use-case.js +0 -276
- package/dist/application/use-cases/react-orchestration-use-case.js +0 -329
- package/dist/application/use-cases/reflexion-use-case.js +0 -30
- package/dist/application/workflows/deterministic-workflows.js +0 -189
- package/dist/ast-parser.js +0 -331
- package/dist/cache.js +0 -180
- package/dist/chat.js +0 -251
- package/dist/code-analysis.js +0 -327
- package/dist/context.js +0 -187
- package/dist/docs.js +0 -146
- package/dist/domain/entities/code-chunk.js +0 -6
- package/dist/domain/entities/decision.js +0 -9
- package/dist/domain/entities/edit.js +0 -6
- package/dist/domain/entities/knowledge-node.js +0 -9
- package/dist/domain/entities/knowledge-preservation.js +0 -10
- package/dist/domain/entities/memory.js +0 -6
- package/dist/domain/entities/pattern.js +0 -7
- package/dist/domain/entities/plan.js +0 -6
- package/dist/domain/entities/query.js +0 -6
- package/dist/domain/errors/embeddings-not-found-error.js +0 -13
- package/dist/domain/index.js +0 -19
- package/dist/domain/interfaces/ai-service.interface.js +0 -6
- package/dist/domain/interfaces/code-analyzer.interface.js +0 -6
- package/dist/domain/interfaces/decision-repository.interface.js +0 -9
- package/dist/domain/interfaces/embedding-repository.interface.js +0 -6
- package/dist/domain/interfaces/knowledge-graph-repository.interface.js +0 -9
- package/dist/domain/interfaces/knowledge-preservation-repository.interface.js +0 -8
- package/dist/domain/interfaces/memory-repository.interface.js +0 -6
- package/dist/domain/interfaces/pattern-repository.interface.js +0 -6
- package/dist/domain/interfaces/verification-service.interface.js +0 -6
- package/dist/domain/services/cross-team-pattern-sharing.js +0 -125
- package/dist/domain/services/decision-recorder.js +0 -223
- package/dist/domain/services/decision-retriever.js +0 -133
- package/dist/domain/services/environment-mode-manager.js +0 -22
- package/dist/domain/services/graded-memory-activation.js +0 -69
- package/dist/domain/services/knowledge-access.js +0 -132
- package/dist/domain/services/knowledge-extractor.js +0 -247
- package/dist/domain/services/knowledge-graph-builder.js +0 -268
- package/dist/domain/services/knowledge-graph-query.js +0 -187
- package/dist/domain/services/memory-retriever.js +0 -82
- package/dist/domain/services/memory-synthesizer.js +0 -91
- package/dist/domain/services/pattern-extractor.js +0 -442
- package/dist/domain/services/query-intent-classifier.js +0 -140
- package/dist/domain/services/react-loop.js +0 -186
- package/dist/domain/services/risk-tier-detector.js +0 -78
- package/dist/edit.js +0 -319
- package/dist/embed.js +0 -459
- package/dist/formatter.js +0 -168
- package/dist/git.js +0 -198
- package/dist/infrastructure/ai/cohere-ai-service.js +0 -607
- package/dist/infrastructure/ai/command-r-evaluation.js +0 -206
- package/dist/infrastructure/ai/model-config.js +0 -84
- package/dist/infrastructure/ai/provider-ai-service.js +0 -477
- package/dist/infrastructure/cache/embedding-cache.js +0 -175
- package/dist/infrastructure/cache/query-result-cache.js +0 -66
- package/dist/infrastructure/cache/response-cache.js +0 -48
- package/dist/infrastructure/cache/security-scan-cache.js +0 -47
- package/dist/infrastructure/chunking/ast-code-chunker.js +0 -19
- package/dist/infrastructure/chunking/code-aware-chunker.js +0 -397
- package/dist/infrastructure/context/context-compressor.js +0 -329
- package/dist/infrastructure/context/conversation-context.js +0 -128
- package/dist/infrastructure/context/session-manager.js +0 -94
- package/dist/infrastructure/cost/cost-tracker.js +0 -360
- package/dist/infrastructure/file-scanner/file-scanner.js +0 -106
- package/dist/infrastructure/governance/nist-ai-rmf.js +0 -292
- package/dist/infrastructure/governance/policy-as-code.js +0 -258
- package/dist/infrastructure/governance/prompt-to-code-lineage.js +0 -239
- package/dist/infrastructure/index.js +0 -7
- package/dist/infrastructure/observability/prompt-versioner.js +0 -145
- package/dist/infrastructure/protocols/a2a-metadata.js +0 -137
- package/dist/infrastructure/protocols/a2a-protocol.js +0 -314
- package/dist/infrastructure/protocols/mcp-oauth.js +0 -184
- package/dist/infrastructure/protocols/mcp-server.js +0 -333
- package/dist/infrastructure/protocols/streaming-handler.js +0 -103
- package/dist/infrastructure/providers/cohere-provider.js +0 -444
- package/dist/infrastructure/providers/fake-provider.js +0 -130
- package/dist/infrastructure/providers/local-llm-provider.js +0 -330
- package/dist/infrastructure/providers/model-config.types.js +0 -9
- package/dist/infrastructure/providers/model-provider.interface.js +0 -8
- package/dist/infrastructure/providers/openai-provider.js +0 -413
- package/dist/infrastructure/providers/openrouter-provider.js +0 -399
- package/dist/infrastructure/providers/operation-provider-resolver.js +0 -105
- package/dist/infrastructure/providers/provider-config-loader.js +0 -120
- package/dist/infrastructure/providers/provider-registry.js +0 -88
- package/dist/infrastructure/safety/pattern-detector.js +0 -133
- package/dist/infrastructure/security/ai-sast-scanner.js +0 -290
- package/dist/infrastructure/security/docker-sandbox.js +0 -178
- package/dist/infrastructure/security/enhanced-security-gate.js +0 -151
- package/dist/infrastructure/security/license-scanner.js +0 -167
- package/dist/infrastructure/security/provider-bias-detector.js +0 -164
- package/dist/infrastructure/security/sandbox-executor.js +0 -64
- package/dist/infrastructure/serialization/toon-serializer.js +0 -119
- package/dist/infrastructure/storage/memory-stream-service.js +0 -204
- package/dist/infrastructure/storage/plan-repository.js +0 -96
- package/dist/infrastructure/storage/sqlite-decision-repository.js +0 -232
- package/dist/infrastructure/storage/sqlite-embedding-repository.js +0 -311
- package/dist/infrastructure/storage/sqlite-knowledge-graph-repository.js +0 -417
- package/dist/infrastructure/storage/sqlite-knowledge-preservation-repository.js +0 -230
- package/dist/infrastructure/storage/sqlite-memory-repository.js +0 -229
- package/dist/infrastructure/storage/sqlite-pattern-repository.js +0 -249
- package/dist/infrastructure/storage/task-helpers.js +0 -197
- package/dist/infrastructure/telemetry/telemetry-service.js +0 -167
- package/dist/infrastructure/ux/appropriate-friction.js +0 -213
- package/dist/infrastructure/ux/bi-directional-learning.js +0 -337
- package/dist/infrastructure/ux/capability-boundaries.js +0 -42
- package/dist/infrastructure/ux/comprehensive-formatter.js +0 -117
- package/dist/infrastructure/ux/contextual-feature-discovery.js +0 -10
- package/dist/infrastructure/ux/expectation-management.js +0 -327
- package/dist/infrastructure/ux/feature-discovery.js +0 -14
- package/dist/infrastructure/ux/feedback-prompt.js +0 -45
- package/dist/infrastructure/ux/hitl-validation.js +0 -297
- package/dist/infrastructure/ux/progress-indicator.js +0 -116
- package/dist/infrastructure/ux/progressive-disclosure.js +0 -236
- package/dist/infrastructure/ux/review-checkpoint.js +0 -72
- package/dist/infrastructure/ux/staged-feature-intro.js +0 -24
- package/dist/infrastructure/ux/stream-markdown.js +0 -151
- package/dist/infrastructure/ux/syntax-highlighter.js +0 -91
- package/dist/infrastructure/ux/team-standards.js +0 -196
- package/dist/infrastructure/ux/transparency.js +0 -71
- package/dist/infrastructure/validation/agent-feedback.js +0 -55
- package/dist/infrastructure/validation/agent-validation-helper.js +0 -137
- package/dist/infrastructure/validation/review-handler.js +0 -40
- package/dist/infrastructure/validation/summary-aggregator.js +0 -70
- package/dist/infrastructure/validation/summary-extractor.js +0 -45
- package/dist/infrastructure/validation/trace-summary.js +0 -52
- package/dist/infrastructure/verification/verification-service.js +0 -47
- package/dist/infrastructure/xai/attention-visualizer.js +0 -172
- package/dist/infrastructure/xai/cot-visualizer.js +0 -283
- package/dist/infrastructure/xai/decision-tree-log.js +0 -212
- package/dist/infrastructure/xai/interactive-xai.js +0 -260
- package/dist/infrastructure/xai/uncertainty-quantifier.js +0 -198
- package/dist/intelligent-retrieval.js +0 -272
- package/dist/intent/classifier.js +0 -2
- package/dist/intent/context.js +0 -2
- package/dist/iterative-refinement.js +0 -189
- package/dist/learning.js +0 -232
- package/dist/log.js +0 -31
- package/dist/migrate.js +0 -133
- package/dist/parallel-processor.js +0 -530
- package/dist/planner.js +0 -198
- package/dist/policy.js +0 -26
- package/dist/presentation/cli/commands/ask-command.js +0 -314
- package/dist/presentation/cli/commands/cost-command.js +0 -191
- package/dist/presentation/cli/commands/decision-command.js +0 -207
- package/dist/presentation/cli/commands/docs-command.js +0 -25
- package/dist/presentation/cli/commands/fix-command.js +0 -53
- package/dist/presentation/cli/commands/knowledge-command.js +0 -129
- package/dist/presentation/cli/commands/learning-dashboard-command.js +0 -183
- package/dist/presentation/cli/commands/models-command.js +0 -194
- package/dist/presentation/cli/commands/orchestrate-command.js +0 -207
- package/dist/presentation/cli/commands/plan-command.js +0 -246
- package/dist/presentation/cli/commands/product-command.js +0 -199
- package/dist/presentation/cli/commands/react-command.js +0 -181
- package/dist/presentation/cli/commands/review-command.js +0 -154
- package/dist/presentation/cli/commands/run-command.js +0 -188
- package/dist/presentation/cli/commands/smart-command.js +0 -395
- package/dist/presentation/cli/commands/suggest-command.js +0 -138
- package/dist/presentation/cli/commands/trace-command.js +0 -129
- package/dist/presentation/cli/commands/undo-command.js +0 -150
- package/dist/query-optimizer.js +0 -205
- package/dist/refactor.js +0 -116
- package/dist/review.js +0 -206
- package/dist/search.js +0 -163
- package/dist/session.js +0 -652
- package/dist/task-verification.js +0 -336
- package/dist/testgen.js +0 -166
- package/dist/token-optimizer.js +0 -277
- package/dist/tools/bash.js +0 -213
- package/dist/tools/edit.js +0 -235
- package/dist/tools/executor.js +0 -153
- package/dist/tools/git-tool.js +0 -292
- package/dist/tools/parser.js +0 -158
- package/dist/tools/registry.js +0 -302
- package/dist/tools/search.js +0 -100
- package/dist/types.js +0 -2
- package/dist/utils/check-api-status.js +0 -100
- package/dist/utils/diff-parser.js +0 -162
- package/dist/verification/backup.js +0 -259
- package/dist/verification/compiler.js +0 -264
- package/dist/verification/index.js +0 -273
- 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({
|
|
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 => {
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
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',
|
|
203
|
+
jsonrpc: '2.0',
|
|
204
|
+
id,
|
|
193
205
|
result: {
|
|
194
206
|
protocolVersion: '2024-11-05',
|
|
195
207
|
capabilities: { tools: {} },
|
|
196
|
-
serverInfo: { name: 'codehere', version:
|
|
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',
|
|
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:
|
|
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 => {
|
|
595
|
+
req.on('data', (chunk) => {
|
|
596
|
+
body += chunk;
|
|
597
|
+
});
|
|
260
598
|
req.on('end', () => {
|
|
261
599
|
try {
|
|
262
600
|
const mcpReq = JSON.parse(body);
|