codehere 0.5.0-alpha.3 → 0.5.0-alpha.5
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 +73 -0
- package/dist/infrastructure/orchestration/aider-adapter.js +59 -0
- package/dist/infrastructure/orchestration/claude-code-adapter.js +30 -7
- 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 +20 -0
- package/dist/infrastructure/protocols/mcp-server-impl.js +102 -4
- package/dist/infrastructure/protocols/ui-page.js +509 -19
- package/dist/infrastructure/storage/cross-tool-agents.js +251 -0
- package/dist/infrastructure/storage/project-thread-migration.js +232 -0
- package/dist/infrastructure/storage/project-thread-mirror.js +159 -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
|
@@ -24,6 +24,7 @@ import { seedAgentsConfig } from './agents-config-seed.js';
|
|
|
24
24
|
import { AUDIT_FILE } from '../hooks/claude-sdk-hook.js';
|
|
25
25
|
import { appendSpawned, appendCompleted, loadRecent } from './session-persistence.js';
|
|
26
26
|
import { generateFinalReport, printFinalReportToTerminal } from '../../application/use-cases/generate-final-report.js';
|
|
27
|
+
import { mirrorSpawnedToThread, mirrorCompletedToThread } from '../storage/project-thread-mirror.js';
|
|
27
28
|
// ── Agent registry ─────────────────────────────────────────────────────────────
|
|
28
29
|
// All backends codehere can spawn. Order here drives the default UI dropdown
|
|
29
30
|
// order. Built-in adapters first; user-config-driven ones appended from
|
|
@@ -130,6 +131,15 @@ export class SessionManager {
|
|
|
130
131
|
cwd,
|
|
131
132
|
startedAt: session.startedAt,
|
|
132
133
|
});
|
|
134
|
+
// v0.6.0 M1+M7 — forward-mirror to per-project thread.jsonl alongside
|
|
135
|
+
// sessions.jsonl. Best-effort; mirror failures don't break the spawn path.
|
|
136
|
+
mirrorSpawnedToThread({
|
|
137
|
+
sessionId: session.sessionId,
|
|
138
|
+
agent: session.agent,
|
|
139
|
+
prompt,
|
|
140
|
+
cwd,
|
|
141
|
+
startedAt: session.startedAt,
|
|
142
|
+
});
|
|
133
143
|
const unsubscribe = session.subscribe((event) => {
|
|
134
144
|
if (event.type !== 'exit')
|
|
135
145
|
return;
|
|
@@ -147,6 +157,16 @@ export class SessionManager {
|
|
|
147
157
|
steps: session.steps ? [...session.steps] : undefined,
|
|
148
158
|
artifacts: session.artifacts,
|
|
149
159
|
});
|
|
160
|
+
// v0.6.0 M1+M7 — forward-mirror completion to project thread.
|
|
161
|
+
mirrorCompletedToThread({
|
|
162
|
+
sessionId: session.sessionId,
|
|
163
|
+
cwd,
|
|
164
|
+
status: session.status,
|
|
165
|
+
durationMs: Number(data?.durationMs ?? completedAt - session.startedAt),
|
|
166
|
+
completedAt,
|
|
167
|
+
steps: session.steps ? [...session.steps] : undefined,
|
|
168
|
+
artifacts: session.artifacts,
|
|
169
|
+
});
|
|
150
170
|
// PM-UX Layer 3 · Final Report MVP. Fire-and-forget; the report appears
|
|
151
171
|
// in the terminal a few seconds after exit. Failure modes return null
|
|
152
172
|
// silently (claude CLI not installed, timeout, non-zero exit). No SSE,
|
|
@@ -25,9 +25,11 @@ 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';
|
|
31
33
|
import { installAgent } from '../orchestration/agent-installer.js';
|
|
32
34
|
import { detectReadiness } from '../orchestration/readiness.js';
|
|
33
35
|
// ── Tool definitions ─────────────────────────────────────────────────────────
|
|
@@ -81,6 +83,28 @@ const TOOLS = [
|
|
|
81
83
|
// ── Tool handlers ────────────────────────────────────────────────────────────
|
|
82
84
|
import { homedir } from 'os';
|
|
83
85
|
const AUDIT_FILE_PATH = join(homedir(), '.codehere', 'audit', 'hook-events.jsonl');
|
|
86
|
+
/**
|
|
87
|
+
* Dev-only behavior-observer injection (v0.6.0 — elu joined the team roster
|
|
88
|
+
* 2026-05-03 as behavior reviewer). Activated only when CODEHERE_DEV_ELU=1
|
|
89
|
+
* is set in the founder's local env; never in shipped releases. The script's
|
|
90
|
+
* public key is meant for client-side embedding (analytics tracking key, not
|
|
91
|
+
* a secret) so committing the URL is safe.
|
|
92
|
+
*
|
|
93
|
+
* Why runtime injection instead of editing TASK_HTML/HISTORY_HTML directly:
|
|
94
|
+
* the templates stay identical between dev and release builds. The only
|
|
95
|
+
* delta is the env var. This keeps the release surface clean (zero-telemetry
|
|
96
|
+
* value per PRD §1.5 #2 stays intact) while letting elu observe founder
|
|
97
|
+
* dogfood sessions.
|
|
98
|
+
*
|
|
99
|
+
* Removal trigger lives in team_roster.md memory: if elu produces zero
|
|
100
|
+
* unique findings within one month, remove this helper + roster entry.
|
|
101
|
+
*/
|
|
102
|
+
const ELU_DEV_SCRIPT = '<script async src="https://elu.dev/v1/elu_pk_live_HrKo9yFFLx03tz9xuSKFcE2Jpv.js"></script>';
|
|
103
|
+
function withDevElu(html) {
|
|
104
|
+
if (process.env.CODEHERE_DEV_ELU !== '1')
|
|
105
|
+
return html;
|
|
106
|
+
return html.replace('</head>', `${ELU_DEV_SCRIPT}\n</head>`);
|
|
107
|
+
}
|
|
84
108
|
function handleTrust(params) {
|
|
85
109
|
const { filepath, code, verbose } = params;
|
|
86
110
|
let content;
|
|
@@ -270,13 +294,19 @@ export function startMCPServer(options = {}) {
|
|
|
270
294
|
// v0.3 primary UI: task intake + active sessions
|
|
271
295
|
if (req.method === 'GET' && (req.url === '/' || req.url === '/index.html')) {
|
|
272
296
|
res.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8' });
|
|
273
|
-
res.end(TASK_HTML);
|
|
297
|
+
res.end(withDevElu(TASK_HTML));
|
|
274
298
|
return;
|
|
275
299
|
}
|
|
276
300
|
// History / audit feed (what we built Day 0; now secondary)
|
|
277
301
|
if (req.method === 'GET' && (req.url === '/history' || req.url === '/history/')) {
|
|
278
302
|
res.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8' });
|
|
279
|
-
res.end(HISTORY_HTML);
|
|
303
|
+
res.end(withDevElu(HISTORY_HTML));
|
|
304
|
+
return;
|
|
305
|
+
}
|
|
306
|
+
// v0.6.0 M1 — project thread view; consumes /api/projects + /api/projects/:slug
|
|
307
|
+
if (req.method === 'GET' && (req.url === '/threads' || req.url === '/threads/' || req.url?.startsWith('/threads?'))) {
|
|
308
|
+
res.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8' });
|
|
309
|
+
res.end(withDevElu(THREAD_HTML));
|
|
280
310
|
return;
|
|
281
311
|
}
|
|
282
312
|
// Backwards-compat: /ui used to be the Mission Control audit view — now at /history
|
|
@@ -453,8 +483,28 @@ export function startMCPServer(options = {}) {
|
|
|
453
483
|
const all = readinessForAllAgents();
|
|
454
484
|
const auto = all.find((a) => a.id === 'conductor');
|
|
455
485
|
const agents = all.filter((a) => a.id !== 'conductor');
|
|
486
|
+
// v0.6.0 M8 — surface cross-tool agent definitions (.opencode/agents/,
|
|
487
|
+
// .claude/agents/, .codehere/agents/) alongside the built-in adapters.
|
|
488
|
+
// These don't have a `state` (ready/needs_install) — they're definitions
|
|
489
|
+
// not runtimes. UI can render them as "available specialists" tagged
|
|
490
|
+
// with their source format. Best-effort: failures fall back to no
|
|
491
|
+
// cross-tool agents listed.
|
|
492
|
+
let crossTool = [];
|
|
493
|
+
try {
|
|
494
|
+
const result = readCrossToolAgents(process.cwd());
|
|
495
|
+
crossTool = result.agents.map((a) => ({
|
|
496
|
+
name: a.name,
|
|
497
|
+
description: a.description.length > 240 ? a.description.slice(0, 237) + '…' : a.description,
|
|
498
|
+
sourceFormat: a.sourceFormat,
|
|
499
|
+
mode: a.mode,
|
|
500
|
+
sourcePath: a.sourcePath,
|
|
501
|
+
}));
|
|
502
|
+
}
|
|
503
|
+
catch {
|
|
504
|
+
// best-effort; ignore parse failures
|
|
505
|
+
}
|
|
456
506
|
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
457
|
-
res.end(JSON.stringify({ agents, auto }));
|
|
507
|
+
res.end(JSON.stringify({ agents, auto, crossTool }));
|
|
458
508
|
}
|
|
459
509
|
catch (e) {
|
|
460
510
|
res.writeHead(500, { 'Content-Type': 'application/json' });
|
|
@@ -475,6 +525,49 @@ export function startMCPServer(options = {}) {
|
|
|
475
525
|
}
|
|
476
526
|
return;
|
|
477
527
|
}
|
|
528
|
+
// ── v0.6.0 M1 project thread routes ──────────────────────────────────────
|
|
529
|
+
// GET /api/projects — list all known projects (slug, cwd, title, taskCount, lastActivityAt)
|
|
530
|
+
// GET /api/projects/:slug — single project thread (meta + tasks merged by sessionId)
|
|
531
|
+
// Reader source of truth: ~/.codehere/projects/<slug>/thread.jsonl (per-project append-only log
|
|
532
|
+
// populated by migration + forward-mirror). Drives the new thread-view UI.
|
|
533
|
+
if (req.method === 'GET' && req.url === '/api/projects') {
|
|
534
|
+
try {
|
|
535
|
+
const projects = listProjects();
|
|
536
|
+
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
537
|
+
res.end(JSON.stringify({ projects }));
|
|
538
|
+
}
|
|
539
|
+
catch (e) {
|
|
540
|
+
res.writeHead(500, { 'Content-Type': 'application/json' });
|
|
541
|
+
res.end(JSON.stringify({ error: 'list_projects_failed', detail: String(e) }));
|
|
542
|
+
}
|
|
543
|
+
return;
|
|
544
|
+
}
|
|
545
|
+
if (req.method === 'GET' && req.url?.startsWith('/api/projects/')) {
|
|
546
|
+
try {
|
|
547
|
+
// Slug is the segment after /api/projects/. Strip optional query string.
|
|
548
|
+
const slug = req.url.replace('/api/projects/', '').split('?')[0];
|
|
549
|
+
// Slug shape is sha1[:12] (12 hex chars) OR the literal 'unknown' bucket.
|
|
550
|
+
// Reject anything else to keep the surface tight (and stop ../ traversal).
|
|
551
|
+
if (!/^([a-f0-9]{12}|unknown)$/.test(slug)) {
|
|
552
|
+
res.writeHead(400, { 'Content-Type': 'application/json' });
|
|
553
|
+
res.end(JSON.stringify({ error: 'invalid_slug' }));
|
|
554
|
+
return;
|
|
555
|
+
}
|
|
556
|
+
const thread = readProjectThread(slug);
|
|
557
|
+
if (!thread) {
|
|
558
|
+
res.writeHead(404, { 'Content-Type': 'application/json' });
|
|
559
|
+
res.end(JSON.stringify({ error: 'project_not_found', slug }));
|
|
560
|
+
return;
|
|
561
|
+
}
|
|
562
|
+
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
563
|
+
res.end(JSON.stringify({ thread }));
|
|
564
|
+
}
|
|
565
|
+
catch (e) {
|
|
566
|
+
res.writeHead(500, { 'Content-Type': 'application/json' });
|
|
567
|
+
res.end(JSON.stringify({ error: 'read_project_failed', detail: String(e) }));
|
|
568
|
+
}
|
|
569
|
+
return;
|
|
570
|
+
}
|
|
478
571
|
// ── F1 task orchestration routes ─────────────────────────────────────────
|
|
479
572
|
// POST /api/task — { prompt, cwd? } → { sessionId }
|
|
480
573
|
// GET /api/tasks — list all sessions
|
|
@@ -538,6 +631,11 @@ export function startMCPServer(options = {}) {
|
|
|
538
631
|
startedAt: session.startedAt,
|
|
539
632
|
status: session.status,
|
|
540
633
|
agent: session.agent,
|
|
634
|
+
// Iter 13 — include canAppendMessage so the freshly-attached
|
|
635
|
+
// session card can correctly enable/disable reply input on the
|
|
636
|
+
// first render. Without this, ALL just-spawned sessions render
|
|
637
|
+
// with reply disabled even when the adapter supports it.
|
|
638
|
+
canAppendMessage: session.canAppendMessage === true,
|
|
541
639
|
}));
|
|
542
640
|
}
|
|
543
641
|
catch (e) {
|