codehere 0.5.0-alpha.4 → 0.5.0-alpha.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/application/use-cases/generate-final-report.js +22 -6
- package/dist/application/use-cases/generate-thread-title.js +120 -0
- package/dist/index.js +18 -0
- package/dist/infrastructure/orchestration/aider-adapter.js +59 -0
- package/dist/infrastructure/orchestration/codex-adapter.js +76 -0
- package/dist/infrastructure/orchestration/conductor-adapter.js +11 -1
- package/dist/infrastructure/orchestration/gemini-adapter.js +58 -0
- package/dist/infrastructure/orchestration/opencode-adapter.js +62 -0
- package/dist/infrastructure/orchestration/session-manager.js +42 -1
- package/dist/infrastructure/orchestration/session-persistence.js +28 -0
- package/dist/infrastructure/protocols/mcp-server-impl.js +129 -4
- package/dist/infrastructure/protocols/ui-page.js +616 -17
- package/dist/infrastructure/storage/cross-tool-agents.js +251 -0
- package/dist/infrastructure/storage/project-thread-mirror.js +192 -0
- package/dist/infrastructure/storage/project-thread-reader.js +140 -0
- package/dist/infrastructure/storage/project-thread-title-backfill.js +182 -0
- package/package.json +4 -2
|
@@ -25,9 +25,13 @@ import { fileURLToPath } from 'url';
|
|
|
25
25
|
import { collectFilesForMCP, scoreFileForMCP } from './mcp-helpers.js';
|
|
26
26
|
import { scanPayload, calculateRiskScore, AUDIT_FILE } from '../hooks/claude-sdk-hook.js';
|
|
27
27
|
import { getVersion } from '../../utils/version.js';
|
|
28
|
-
import { TASK_HTML, HISTORY_HTML } from './ui-page.js';
|
|
28
|
+
import { TASK_HTML, HISTORY_HTML, THREAD_HTML } from './ui-page.js';
|
|
29
29
|
import { SessionManager, availableAgents, readinessForAllAgents, getAdapter, allAdapters, } from '../orchestration/session-manager.js';
|
|
30
30
|
import { isAbsolute } from 'path';
|
|
31
|
+
import { readCrossToolAgents } from '../storage/cross-tool-agents.js';
|
|
32
|
+
import { listProjects, readProjectThread } from '../storage/project-thread-reader.js';
|
|
33
|
+
import { mirrorReplyToThread } from '../storage/project-thread-mirror.js';
|
|
34
|
+
import { randomUUID } from 'crypto';
|
|
31
35
|
import { installAgent } from '../orchestration/agent-installer.js';
|
|
32
36
|
import { detectReadiness } from '../orchestration/readiness.js';
|
|
33
37
|
// ── Tool definitions ─────────────────────────────────────────────────────────
|
|
@@ -81,6 +85,28 @@ const TOOLS = [
|
|
|
81
85
|
// ── Tool handlers ────────────────────────────────────────────────────────────
|
|
82
86
|
import { homedir } from 'os';
|
|
83
87
|
const AUDIT_FILE_PATH = join(homedir(), '.codehere', 'audit', 'hook-events.jsonl');
|
|
88
|
+
/**
|
|
89
|
+
* Dev-only behavior-observer injection (v0.6.0 — elu joined the team roster
|
|
90
|
+
* 2026-05-03 as behavior reviewer). Activated only when CODEHERE_DEV_ELU=1
|
|
91
|
+
* is set in the founder's local env; never in shipped releases. The script's
|
|
92
|
+
* public key is meant for client-side embedding (analytics tracking key, not
|
|
93
|
+
* a secret) so committing the URL is safe.
|
|
94
|
+
*
|
|
95
|
+
* Why runtime injection instead of editing TASK_HTML/HISTORY_HTML directly:
|
|
96
|
+
* the templates stay identical between dev and release builds. The only
|
|
97
|
+
* delta is the env var. This keeps the release surface clean (zero-telemetry
|
|
98
|
+
* value per PRD §1.5 #2 stays intact) while letting elu observe founder
|
|
99
|
+
* dogfood sessions.
|
|
100
|
+
*
|
|
101
|
+
* Removal trigger lives in team_roster.md memory: if elu produces zero
|
|
102
|
+
* unique findings within one month, remove this helper + roster entry.
|
|
103
|
+
*/
|
|
104
|
+
const ELU_DEV_SCRIPT = '<script async src="https://elu.dev/v1/elu_pk_live_HrKo9yFFLx03tz9xuSKFcE2Jpv.js"></script>';
|
|
105
|
+
function withDevElu(html) {
|
|
106
|
+
if (process.env.CODEHERE_DEV_ELU !== '1')
|
|
107
|
+
return html;
|
|
108
|
+
return html.replace('</head>', `${ELU_DEV_SCRIPT}\n</head>`);
|
|
109
|
+
}
|
|
84
110
|
function handleTrust(params) {
|
|
85
111
|
const { filepath, code, verbose } = params;
|
|
86
112
|
let content;
|
|
@@ -270,13 +296,19 @@ export function startMCPServer(options = {}) {
|
|
|
270
296
|
// v0.3 primary UI: task intake + active sessions
|
|
271
297
|
if (req.method === 'GET' && (req.url === '/' || req.url === '/index.html')) {
|
|
272
298
|
res.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8' });
|
|
273
|
-
res.end(TASK_HTML);
|
|
299
|
+
res.end(withDevElu(TASK_HTML));
|
|
274
300
|
return;
|
|
275
301
|
}
|
|
276
302
|
// History / audit feed (what we built Day 0; now secondary)
|
|
277
303
|
if (req.method === 'GET' && (req.url === '/history' || req.url === '/history/')) {
|
|
278
304
|
res.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8' });
|
|
279
|
-
res.end(HISTORY_HTML);
|
|
305
|
+
res.end(withDevElu(HISTORY_HTML));
|
|
306
|
+
return;
|
|
307
|
+
}
|
|
308
|
+
// v0.6.0 M1 — project thread view; consumes /api/projects + /api/projects/:slug
|
|
309
|
+
if (req.method === 'GET' && (req.url === '/threads' || req.url === '/threads/' || req.url?.startsWith('/threads?'))) {
|
|
310
|
+
res.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8' });
|
|
311
|
+
res.end(withDevElu(THREAD_HTML));
|
|
280
312
|
return;
|
|
281
313
|
}
|
|
282
314
|
// Backwards-compat: /ui used to be the Mission Control audit view — now at /history
|
|
@@ -453,8 +485,28 @@ export function startMCPServer(options = {}) {
|
|
|
453
485
|
const all = readinessForAllAgents();
|
|
454
486
|
const auto = all.find((a) => a.id === 'conductor');
|
|
455
487
|
const agents = all.filter((a) => a.id !== 'conductor');
|
|
488
|
+
// v0.6.0 M8 — surface cross-tool agent definitions (.opencode/agents/,
|
|
489
|
+
// .claude/agents/, .codehere/agents/) alongside the built-in adapters.
|
|
490
|
+
// These don't have a `state` (ready/needs_install) — they're definitions
|
|
491
|
+
// not runtimes. UI can render them as "available specialists" tagged
|
|
492
|
+
// with their source format. Best-effort: failures fall back to no
|
|
493
|
+
// cross-tool agents listed.
|
|
494
|
+
let crossTool = [];
|
|
495
|
+
try {
|
|
496
|
+
const result = readCrossToolAgents(process.cwd());
|
|
497
|
+
crossTool = result.agents.map((a) => ({
|
|
498
|
+
name: a.name,
|
|
499
|
+
description: a.description.length > 240 ? a.description.slice(0, 237) + '…' : a.description,
|
|
500
|
+
sourceFormat: a.sourceFormat,
|
|
501
|
+
mode: a.mode,
|
|
502
|
+
sourcePath: a.sourcePath,
|
|
503
|
+
}));
|
|
504
|
+
}
|
|
505
|
+
catch {
|
|
506
|
+
// best-effort; ignore parse failures
|
|
507
|
+
}
|
|
456
508
|
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
457
|
-
res.end(JSON.stringify({ agents, auto }));
|
|
509
|
+
res.end(JSON.stringify({ agents, auto, crossTool }));
|
|
458
510
|
}
|
|
459
511
|
catch (e) {
|
|
460
512
|
res.writeHead(500, { 'Content-Type': 'application/json' });
|
|
@@ -475,6 +527,49 @@ export function startMCPServer(options = {}) {
|
|
|
475
527
|
}
|
|
476
528
|
return;
|
|
477
529
|
}
|
|
530
|
+
// ── v0.6.0 M1 project thread routes ──────────────────────────────────────
|
|
531
|
+
// GET /api/projects — list all known projects (slug, cwd, title, taskCount, lastActivityAt)
|
|
532
|
+
// GET /api/projects/:slug — single project thread (meta + tasks merged by sessionId)
|
|
533
|
+
// Reader source of truth: ~/.codehere/projects/<slug>/thread.jsonl (per-project append-only log
|
|
534
|
+
// populated by migration + forward-mirror). Drives the new thread-view UI.
|
|
535
|
+
if (req.method === 'GET' && req.url === '/api/projects') {
|
|
536
|
+
try {
|
|
537
|
+
const projects = listProjects();
|
|
538
|
+
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
539
|
+
res.end(JSON.stringify({ projects }));
|
|
540
|
+
}
|
|
541
|
+
catch (e) {
|
|
542
|
+
res.writeHead(500, { 'Content-Type': 'application/json' });
|
|
543
|
+
res.end(JSON.stringify({ error: 'list_projects_failed', detail: String(e) }));
|
|
544
|
+
}
|
|
545
|
+
return;
|
|
546
|
+
}
|
|
547
|
+
if (req.method === 'GET' && req.url?.startsWith('/api/projects/')) {
|
|
548
|
+
try {
|
|
549
|
+
// Slug is the segment after /api/projects/. Strip optional query string.
|
|
550
|
+
const slug = req.url.replace('/api/projects/', '').split('?')[0];
|
|
551
|
+
// Slug shape is sha1[:12] (12 hex chars) OR the literal 'unknown' bucket.
|
|
552
|
+
// Reject anything else to keep the surface tight (and stop ../ traversal).
|
|
553
|
+
if (!/^([a-f0-9]{12}|unknown)$/.test(slug)) {
|
|
554
|
+
res.writeHead(400, { 'Content-Type': 'application/json' });
|
|
555
|
+
res.end(JSON.stringify({ error: 'invalid_slug' }));
|
|
556
|
+
return;
|
|
557
|
+
}
|
|
558
|
+
const thread = readProjectThread(slug);
|
|
559
|
+
if (!thread) {
|
|
560
|
+
res.writeHead(404, { 'Content-Type': 'application/json' });
|
|
561
|
+
res.end(JSON.stringify({ error: 'project_not_found', slug }));
|
|
562
|
+
return;
|
|
563
|
+
}
|
|
564
|
+
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
565
|
+
res.end(JSON.stringify({ thread }));
|
|
566
|
+
}
|
|
567
|
+
catch (e) {
|
|
568
|
+
res.writeHead(500, { 'Content-Type': 'application/json' });
|
|
569
|
+
res.end(JSON.stringify({ error: 'read_project_failed', detail: String(e) }));
|
|
570
|
+
}
|
|
571
|
+
return;
|
|
572
|
+
}
|
|
478
573
|
// ── F1 task orchestration routes ─────────────────────────────────────────
|
|
479
574
|
// POST /api/task — { prompt, cwd? } → { sessionId }
|
|
480
575
|
// GET /api/tasks — list all sessions
|
|
@@ -560,6 +655,7 @@ export function startMCPServer(options = {}) {
|
|
|
560
655
|
// in completedAtBySession map (populated when exit event fires).
|
|
561
656
|
const completedAt = manager.getCompletedAt(s.sessionId);
|
|
562
657
|
const finalReport = manager.getFinalReport(s.sessionId);
|
|
658
|
+
const cwd = manager.getCwd(s.sessionId);
|
|
563
659
|
return {
|
|
564
660
|
sessionId: s.sessionId,
|
|
565
661
|
prompt: s.prompt,
|
|
@@ -573,6 +669,10 @@ export function startMCPServer(options = {}) {
|
|
|
573
669
|
// Iter 12 — surface canAppendMessage so the UI knows whether to
|
|
574
670
|
// render the reply input or a disabled "follow-up unsupported" state.
|
|
575
671
|
canAppendMessage: s.canAppendMessage === true,
|
|
672
|
+
// v0.6.x Slack-shape spike — surface cwd so the dashboard's
|
|
673
|
+
// "Continue thread" intake button can find the most recent
|
|
674
|
+
// in-memory session for the current folder.
|
|
675
|
+
...(cwd !== undefined ? { cwd } : {}),
|
|
576
676
|
provenance: 'spawned',
|
|
577
677
|
};
|
|
578
678
|
});
|
|
@@ -650,7 +750,32 @@ export function startMCPServer(options = {}) {
|
|
|
650
750
|
res.end(JSON.stringify({ error: 'text is required' }));
|
|
651
751
|
return;
|
|
652
752
|
}
|
|
753
|
+
const replyStartedAt = Date.now();
|
|
653
754
|
const out = await session.appendMessage(text);
|
|
755
|
+
const replyCompletedAt = Date.now();
|
|
756
|
+
// v0.6.x — forward-mirror the reply turn into the project thread
|
|
757
|
+
// so /threads sees follow-ups as visible chronological turns
|
|
758
|
+
// (not just the original spawn). Best-effort: mirror failures
|
|
759
|
+
// never break the reply path.
|
|
760
|
+
try {
|
|
761
|
+
const replyCwd = manager.getCwd(sessionId);
|
|
762
|
+
if (replyCwd) {
|
|
763
|
+
mirrorReplyToThread({
|
|
764
|
+
parentSessionId: sessionId,
|
|
765
|
+
replySessionId: randomUUID(),
|
|
766
|
+
agent: session.agent,
|
|
767
|
+
prompt: text,
|
|
768
|
+
cwd: replyCwd,
|
|
769
|
+
startedAt: replyStartedAt,
|
|
770
|
+
completedAt: replyCompletedAt,
|
|
771
|
+
durationMs: out.durationMs,
|
|
772
|
+
result: out.result,
|
|
773
|
+
});
|
|
774
|
+
}
|
|
775
|
+
}
|
|
776
|
+
catch {
|
|
777
|
+
/* mirror is best-effort */
|
|
778
|
+
}
|
|
654
779
|
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
655
780
|
res.end(JSON.stringify({ sessionId, ...out }));
|
|
656
781
|
}
|