codehere 0.6.0-alpha.1 → 0.6.0
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/index.js +14 -0
- package/dist/infrastructure/orchestration/context-handoff.js +143 -0
- package/dist/infrastructure/orchestration/failover-config.js +77 -0
- package/dist/infrastructure/orchestration/graphify-context.js +256 -0
- package/dist/infrastructure/orchestration/session-manager.js +420 -6
- package/dist/infrastructure/protocols/cloud-sync.js +208 -0
- package/dist/infrastructure/protocols/mcp-server-impl.js +122 -10
- package/dist/infrastructure/protocols/ui-page-v2.js +505 -0
- package/dist/infrastructure/protocols/ui-page.js +303 -37
- package/dist/infrastructure/storage/project-thread-mirror.js +25 -15
- package/dist/presentation/cli/repl.js +29 -0
- package/package.json +2 -1
|
@@ -26,6 +26,7 @@ import { collectFilesForMCP, scoreFileForMCP } from './mcp-helpers.js';
|
|
|
26
26
|
import { scanPayload, calculateRiskScore, AUDIT_FILE, verifyAuditChain, } from '../hooks/claude-sdk-hook.js';
|
|
27
27
|
import { getVersion } from '../../utils/version.js';
|
|
28
28
|
import { TASK_HTML, HISTORY_HTML, THREAD_HTML } from './ui-page.js';
|
|
29
|
+
import { TASK_HTML_V2 } from './ui-page-v2.js';
|
|
29
30
|
import { SessionManager, availableAgents, readinessForAllAgents, getAdapter, allAdapters, } from '../orchestration/session-manager.js';
|
|
30
31
|
import { isAbsolute } from 'path';
|
|
31
32
|
import { readCrossToolAgents } from '../storage/cross-tool-agents.js';
|
|
@@ -319,8 +320,14 @@ export function startMCPServer(options = {}) {
|
|
|
319
320
|
res.end(JSON.stringify({ status: 'ok', server: 'codehere-mcp', version: getVersion(), tools: TOOLS.length }));
|
|
320
321
|
return;
|
|
321
322
|
}
|
|
322
|
-
//
|
|
323
|
+
// v2 Task-Centric PM UI (primary — served at /)
|
|
323
324
|
if (req.method === 'GET' && (req.url === '/' || req.url === '/index.html')) {
|
|
325
|
+
res.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8' });
|
|
326
|
+
res.end(withDevElu(TASK_HTML_V2));
|
|
327
|
+
return;
|
|
328
|
+
}
|
|
329
|
+
// v1 dashboard (legacy — at /v1)
|
|
330
|
+
if (req.method === 'GET' && (req.url === '/v1' || req.url === '/v1/')) {
|
|
324
331
|
res.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8' });
|
|
325
332
|
res.end(withDevElu(TASK_HTML));
|
|
326
333
|
return;
|
|
@@ -394,6 +401,58 @@ export function startMCPServer(options = {}) {
|
|
|
394
401
|
});
|
|
395
402
|
return;
|
|
396
403
|
}
|
|
404
|
+
// ── P4 SSE — session lifecycle events (realtime /v2 updates) ────────────
|
|
405
|
+
// Tails sessions.jsonl and pushes "change" events so the /v2 UI can
|
|
406
|
+
// re-fetch task state instantly instead of waiting 8s for the next poll.
|
|
407
|
+
// Same pattern as /events (audit log tail) but for sessions.
|
|
408
|
+
const SESSIONS_FILE = join(homedir(), '.codehere', 'sessions.jsonl');
|
|
409
|
+
if (req.method === 'GET' && req.url === '/api/events/sessions') {
|
|
410
|
+
res.writeHead(200, {
|
|
411
|
+
'Content-Type': 'text/event-stream',
|
|
412
|
+
'Cache-Control': 'no-cache',
|
|
413
|
+
Connection: 'keep-alive',
|
|
414
|
+
});
|
|
415
|
+
// Send current session state on connect
|
|
416
|
+
try {
|
|
417
|
+
const manager = SessionManager.getInstance();
|
|
418
|
+
const sessions = manager.list().map(s => ({
|
|
419
|
+
sessionId: s.sessionId,
|
|
420
|
+
status: s.status,
|
|
421
|
+
agent: s.agent,
|
|
422
|
+
}));
|
|
423
|
+
res.write(`data: ${JSON.stringify({ type: 'init', sessions })}\n\n`);
|
|
424
|
+
}
|
|
425
|
+
catch {
|
|
426
|
+
res.write(`data: ${JSON.stringify({ type: 'init', sessions: [] })}\n\n`);
|
|
427
|
+
}
|
|
428
|
+
// Tail sessions.jsonl for changes
|
|
429
|
+
let lastSize = 0;
|
|
430
|
+
if (existsSync(SESSIONS_FILE)) {
|
|
431
|
+
try {
|
|
432
|
+
lastSize = statSync(SESSIONS_FILE).size;
|
|
433
|
+
}
|
|
434
|
+
catch { /* first time */ }
|
|
435
|
+
}
|
|
436
|
+
const onChange = () => {
|
|
437
|
+
try {
|
|
438
|
+
if (!existsSync(SESSIONS_FILE))
|
|
439
|
+
return;
|
|
440
|
+
const currentSize = statSync(SESSIONS_FILE).size;
|
|
441
|
+
if (currentSize <= lastSize)
|
|
442
|
+
return;
|
|
443
|
+
lastSize = currentSize;
|
|
444
|
+
res.write(`data: ${JSON.stringify({ type: 'change' })}\n\n`);
|
|
445
|
+
}
|
|
446
|
+
catch { /* transient */ }
|
|
447
|
+
};
|
|
448
|
+
watchFile(SESSIONS_FILE, { interval: 500 }, onChange);
|
|
449
|
+
const ping = setInterval(() => res.write(': ping\n\n'), 15000);
|
|
450
|
+
req.on('close', () => {
|
|
451
|
+
clearInterval(ping);
|
|
452
|
+
unwatchFile(SESSIONS_FILE, onChange);
|
|
453
|
+
});
|
|
454
|
+
return;
|
|
455
|
+
}
|
|
397
456
|
// ── F2 agent registry ─────────────────────────────────────────────────────
|
|
398
457
|
if (req.method === 'GET' && req.url === '/api/agents') {
|
|
399
458
|
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
@@ -1013,13 +1072,12 @@ export function startMCPServer(options = {}) {
|
|
|
1013
1072
|
}
|
|
1014
1073
|
if (req.method === 'GET' && req.url === '/api/tasks') {
|
|
1015
1074
|
const spawned = manager.list().map((s) => {
|
|
1016
|
-
// Bug 2 sister-fix (May 2): include completedAt for in-memory exited
|
|
1017
|
-
// sessions so the UI can compute duration. TaskSession interface
|
|
1018
|
-
// intentionally lacks completedAt; SessionManager tracks it separately
|
|
1019
|
-
// in completedAtBySession map (populated when exit event fires).
|
|
1020
1075
|
const completedAt = manager.getCompletedAt(s.sessionId);
|
|
1021
1076
|
const finalReport = manager.getFinalReport(s.sessionId);
|
|
1022
1077
|
const cwd = manager.getCwd(s.sessionId);
|
|
1078
|
+
const continuedFrom = manager.getContinuedFrom(s.sessionId);
|
|
1079
|
+
const continuedAs = manager.getContinuedAs(s.sessionId);
|
|
1080
|
+
const failedReport = manager.hasFailedReport(s.sessionId);
|
|
1023
1081
|
return {
|
|
1024
1082
|
sessionId: s.sessionId,
|
|
1025
1083
|
prompt: s.prompt,
|
|
@@ -1030,13 +1088,11 @@ export function startMCPServer(options = {}) {
|
|
|
1030
1088
|
artifacts: s.artifacts,
|
|
1031
1089
|
...(completedAt !== undefined ? { completedAt, durationMs: completedAt - s.startedAt } : {}),
|
|
1032
1090
|
...(finalReport !== undefined ? { finalReport } : {}),
|
|
1033
|
-
|
|
1034
|
-
// render the reply input or a disabled "follow-up unsupported" state.
|
|
1091
|
+
...(failedReport ? { failedReport: true } : {}),
|
|
1035
1092
|
canAppendMessage: s.canAppendMessage === true,
|
|
1036
|
-
// v0.6.x Slack-shape spike — surface cwd so the dashboard's
|
|
1037
|
-
// "Continue thread" intake button can find the most recent
|
|
1038
|
-
// in-memory session for the current folder.
|
|
1039
1093
|
...(cwd !== undefined ? { cwd } : {}),
|
|
1094
|
+
...(continuedFrom !== undefined ? { continuedFrom } : {}),
|
|
1095
|
+
...(continuedAs !== undefined ? { continuedAs } : {}),
|
|
1040
1096
|
provenance: 'spawned',
|
|
1041
1097
|
};
|
|
1042
1098
|
});
|
|
@@ -1207,6 +1263,62 @@ export function startMCPServer(options = {}) {
|
|
|
1207
1263
|
res.end(JSON.stringify({ error: 'Method not allowed for this path' }));
|
|
1208
1264
|
return;
|
|
1209
1265
|
}
|
|
1266
|
+
// v0.6.x P0 — GET /api/failover-offers
|
|
1267
|
+
// Returns active failover offers so the dashboard can surface a
|
|
1268
|
+
// "Claude rate-limited — switch to Codex?" banner. Offers expire
|
|
1269
|
+
// after 60 s (handled by SessionManager).
|
|
1270
|
+
if (req.method === 'GET' && req.url === '/api/failover-offers') {
|
|
1271
|
+
const offers = SessionManager.getInstance().getFailoverOffers();
|
|
1272
|
+
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
1273
|
+
res.end(JSON.stringify({ offers }));
|
|
1274
|
+
return;
|
|
1275
|
+
}
|
|
1276
|
+
// v0.6.x P0 — POST /api/failover-accept
|
|
1277
|
+
// User clicked "Switch" on the failoffer banner. Spawns a
|
|
1278
|
+
// replacement session on the target agent, preserving the
|
|
1279
|
+
// original prompt + cwd.
|
|
1280
|
+
if (req.method === 'POST' && req.url === '/api/failover-accept') {
|
|
1281
|
+
let body = '';
|
|
1282
|
+
req.on('data', (c) => { body += c; });
|
|
1283
|
+
req.on('end', () => {
|
|
1284
|
+
let parsed;
|
|
1285
|
+
try {
|
|
1286
|
+
const raw = JSON.parse(body);
|
|
1287
|
+
if (raw === null || typeof raw !== 'object' || Array.isArray(raw)) {
|
|
1288
|
+
res.writeHead(400, { 'Content-Type': 'application/json' });
|
|
1289
|
+
res.end(JSON.stringify({ error: 'invalid_body' }));
|
|
1290
|
+
return;
|
|
1291
|
+
}
|
|
1292
|
+
parsed = raw;
|
|
1293
|
+
}
|
|
1294
|
+
catch {
|
|
1295
|
+
res.writeHead(400, { 'Content-Type': 'application/json' });
|
|
1296
|
+
res.end(JSON.stringify({ error: 'invalid_body' }));
|
|
1297
|
+
return;
|
|
1298
|
+
}
|
|
1299
|
+
if (typeof parsed.sessionId !== 'string' || typeof parsed.targetAgent !== 'string') {
|
|
1300
|
+
res.writeHead(400, { 'Content-Type': 'application/json' });
|
|
1301
|
+
res.end(JSON.stringify({ error: 'missing_sessionId_or_targetAgent' }));
|
|
1302
|
+
return;
|
|
1303
|
+
}
|
|
1304
|
+
try {
|
|
1305
|
+
const result = SessionManager.getInstance().acceptFailover(parsed.sessionId, parsed.targetAgent);
|
|
1306
|
+
if (result) {
|
|
1307
|
+
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
1308
|
+
res.end(JSON.stringify({ ok: true, newSessionId: result.sessionId }));
|
|
1309
|
+
}
|
|
1310
|
+
else {
|
|
1311
|
+
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
1312
|
+
res.end(JSON.stringify({ ok: false, error: 'target_unavailable', detail: `Could not spawn ${parsed.targetAgent}. Check if it's installed and authenticated.` }));
|
|
1313
|
+
}
|
|
1314
|
+
}
|
|
1315
|
+
catch (e) {
|
|
1316
|
+
res.writeHead(500, { 'Content-Type': 'application/json' });
|
|
1317
|
+
res.end(JSON.stringify({ error: 'failover_failed', detail: String(e) }));
|
|
1318
|
+
}
|
|
1319
|
+
});
|
|
1320
|
+
return;
|
|
1321
|
+
}
|
|
1210
1322
|
// MCP endpoint — POST jsonrpc; reject other verbs with 405 + Allow header
|
|
1211
1323
|
// (rc.17: was 404, which is wrong HTTP semantic — UX_OBSERVATIONS P3).
|
|
1212
1324
|
if (req.url === '/mcp') {
|