codehere 0.5.0-alpha.6 → 0.6.0-alpha.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 +18 -8
- package/dist/application/use-cases/generate-narration.js +111 -0
- package/dist/application/use-cases/generate-thread-title.js +58 -4
- package/dist/application/use-cases/narrator-prompt-template.js +158 -0
- package/dist/index.js +75 -6
- package/dist/infrastructure/cost/budget-config.js +113 -0
- package/dist/infrastructure/cost/spend-aggregator.js +147 -0
- package/dist/infrastructure/hooks/claude-sdk-hook.js +280 -4
- package/dist/infrastructure/hooks/hook-mode.js +71 -0
- package/dist/infrastructure/hooks/mirror-tool-call-to-audit.js +43 -0
- package/dist/infrastructure/hooks/pre-prompt-scan.js +122 -0
- package/dist/infrastructure/orchestration/aider-adapter.js +28 -0
- package/dist/infrastructure/orchestration/artifacts-extractor.js +128 -8
- package/dist/infrastructure/orchestration/claude-code-adapter.js +1 -0
- package/dist/infrastructure/orchestration/codex-adapter.js +14 -0
- package/dist/infrastructure/orchestration/failover-classifier.js +151 -0
- package/dist/infrastructure/orchestration/failover-policy.js +106 -0
- package/dist/infrastructure/orchestration/gemini-adapter.js +116 -14
- package/dist/infrastructure/orchestration/opencode-adapter.js +100 -18
- package/dist/infrastructure/orchestration/progress-aggregator.js +141 -6
- package/dist/infrastructure/orchestration/session-manager.js +95 -9
- package/dist/infrastructure/protocols/mcp-server-impl.js +533 -14
- package/dist/infrastructure/protocols/ui-page.js +2598 -212
- package/dist/infrastructure/security/permissions-config.js +234 -0
- package/dist/infrastructure/storage/project-thread-mirror.js +346 -24
- package/dist/infrastructure/storage/project-thread-reader.js +23 -1
- package/dist/infrastructure/storage/project-thread-title-backfill.js +28 -0
- package/dist/presentation/cli/commands/budget-command.js +79 -0
- package/dist/presentation/cli/commands/narrator-command.js +101 -0
- package/dist/presentation/cli/commands/permission-command.js +116 -0
- package/dist/presentation/cli/commands/scan-tool-command.js +36 -7
- package/dist/utils/typo-guard.js +77 -17
- package/package.json +1 -1
|
@@ -25,6 +25,9 @@ import { AUDIT_FILE } from '../hooks/claude-sdk-hook.js';
|
|
|
25
25
|
import { appendSpawned, appendCompleted, appendFinalReport, loadRecent } from './session-persistence.js';
|
|
26
26
|
import { generateFinalReport, printFinalReportToTerminal } from '../../application/use-cases/generate-final-report.js';
|
|
27
27
|
import { mirrorSpawnedToThread, mirrorCompletedToThread } from '../storage/project-thread-mirror.js';
|
|
28
|
+
import { scanPromptBeforeSpawn } from '../hooks/pre-prompt-scan.js';
|
|
29
|
+
import { generateNarration } from '../../application/use-cases/generate-narration.js';
|
|
30
|
+
import { randomUUID } from 'crypto';
|
|
28
31
|
// ── Agent registry ─────────────────────────────────────────────────────────────
|
|
29
32
|
// All backends codehere can spawn. Order here drives the default UI dropdown
|
|
30
33
|
// order. Built-in adapters first; user-config-driven ones appended from
|
|
@@ -47,19 +50,25 @@ const REGISTRY = [
|
|
|
47
50
|
// Conductor is appended to the same array so UIs that iterate REGISTRY see it.
|
|
48
51
|
REGISTRY.push(buildConductorAdapter(() => REGISTRY));
|
|
49
52
|
export function availableAgents() {
|
|
50
|
-
return REGISTRY.map((a) => ({
|
|
53
|
+
return REGISTRY.map((a) => ({
|
|
54
|
+
id: a.id,
|
|
55
|
+
label: a.label,
|
|
56
|
+
installed: a.isInstalled(),
|
|
57
|
+
...(a.description ? { description: a.description } : {}),
|
|
58
|
+
}));
|
|
51
59
|
}
|
|
52
60
|
/** Per-agent readiness across the full registry. Powers the setup-aware UI. */
|
|
53
61
|
export function readinessForAllAgents() {
|
|
54
62
|
return REGISTRY.map((a) => {
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
+
const base = a.checkReadiness
|
|
64
|
+
? a.checkReadiness()
|
|
65
|
+
: a.isInstalled()
|
|
66
|
+
? { id: a.id, label: a.label, state: 'ready', installed: true }
|
|
67
|
+
: { id: a.id, label: a.label, state: 'needs_install', installed: false };
|
|
68
|
+
// v0.6 S5 — mirror the adapter's "when to use me" description into the
|
|
69
|
+
// readiness payload so the dashboard dropdown can surface it as a
|
|
70
|
+
// plain-English hover tooltip without a separate API call.
|
|
71
|
+
return a.description ? { ...base, description: a.description } : base;
|
|
63
72
|
});
|
|
64
73
|
}
|
|
65
74
|
export function getAdapter(id) {
|
|
@@ -115,6 +124,14 @@ export class SessionManager {
|
|
|
115
124
|
* reads via getFinalReport() through the /api/tasks serializer so non-technical
|
|
116
125
|
* users see the prose summary without opening a terminal. */
|
|
117
126
|
finalReportBySession = new Map();
|
|
127
|
+
/** v0.6 M3 · PM Narrator. Mid-task plain-English summaries cached per
|
|
128
|
+
* session so the API can serve the latest narration without re-running
|
|
129
|
+
* the LLM call on every frontend poll. Latest-only — older narrations
|
|
130
|
+
* are overwritten when a new one fires. Per claim_user_persona HIGH:
|
|
131
|
+
* non-technical users read narration, not tool calls. */
|
|
132
|
+
narrationBySession = new Map();
|
|
133
|
+
narrationTimers = new Map();
|
|
134
|
+
narrationStepsAtLastFire = new Map();
|
|
118
135
|
spawn(prompt, agentId = 'claude-code', options) {
|
|
119
136
|
const adapter = getAdapter(agentId);
|
|
120
137
|
if (!adapter) {
|
|
@@ -123,6 +140,24 @@ export class SessionManager {
|
|
|
123
140
|
if (!adapter.isInstalled()) {
|
|
124
141
|
throw new Error(`Agent ${adapter.label} (${adapter.id}) is not installed on this machine`);
|
|
125
142
|
}
|
|
143
|
+
// Cross-vendor pre-prompt scan (task #110, slice 1). Closes the gap
|
|
144
|
+
// the FAQ now claims as "v0.5.x → v0.6 track": every adapter — not
|
|
145
|
+
// just claude-code — gets a safety scan on the user's prompt before
|
|
146
|
+
// spawn. Catches API-key paste, prompt-injection patterns, etc.
|
|
147
|
+
// Always writes one audit entry (synthetic toolName 'pre_prompt');
|
|
148
|
+
// refuses spawn ONLY when getHookMode() === 'block' AND the prompt
|
|
149
|
+
// has critical/high findings. Other modes (advisory/log-only/off)
|
|
150
|
+
// log + proceed.
|
|
151
|
+
const preScanSessionId = randomUUID();
|
|
152
|
+
const scan = scanPromptBeforeSpawn({
|
|
153
|
+
sessionId: preScanSessionId,
|
|
154
|
+
agent: agentId,
|
|
155
|
+
prompt,
|
|
156
|
+
cwd: options?.cwd,
|
|
157
|
+
});
|
|
158
|
+
if (!scan.allowed) {
|
|
159
|
+
throw new Error(scan.blockReason || 'pre-prompt scan blocked the spawn');
|
|
160
|
+
}
|
|
126
161
|
const session = adapter.spawn(prompt, options);
|
|
127
162
|
this.sessions.set(session.sessionId, session);
|
|
128
163
|
// Persistence: write the spawned record immediately so a daemon crash still
|
|
@@ -215,10 +250,61 @@ export class SessionManager {
|
|
|
215
250
|
.catch(() => {
|
|
216
251
|
// generateFinalReport never throws by contract; defensive only.
|
|
217
252
|
});
|
|
253
|
+
// v0.6 M3 — clean up the narration timer for this session.
|
|
254
|
+
const t = this.narrationTimers.get(session.sessionId);
|
|
255
|
+
if (t) {
|
|
256
|
+
clearInterval(t);
|
|
257
|
+
this.narrationTimers.delete(session.sessionId);
|
|
258
|
+
}
|
|
218
259
|
unsubscribe();
|
|
219
260
|
});
|
|
261
|
+
// v0.6 M3 — PM Narrator. Mid-task plain-English summaries fired
|
|
262
|
+
// every 30s while the session is running. Heuristic: only fire if
|
|
263
|
+
// the step count grew by ≥3 since the last narration (otherwise
|
|
264
|
+
// we'd narrate the same state with new words). Fire-and-forget;
|
|
265
|
+
// failures (no claude binary, timeout, malformed output) silently
|
|
266
|
+
// skip — narration is enrichment, not the primary surface.
|
|
267
|
+
this.startNarrationTimer(session);
|
|
220
268
|
return session;
|
|
221
269
|
}
|
|
270
|
+
/** v0.6 M3 — periodic narration loop per session. */
|
|
271
|
+
startNarrationTimer(session) {
|
|
272
|
+
if (process.env.CODEHERE_DISABLE_NARRATOR === '1')
|
|
273
|
+
return;
|
|
274
|
+
this.narrationStepsAtLastFire.set(session.sessionId, 0);
|
|
275
|
+
const intervalMs = Number(process.env.CODEHERE_NARRATOR_INTERVAL_MS ?? 30_000);
|
|
276
|
+
const minStepDelta = Number(process.env.CODEHERE_NARRATOR_MIN_STEPS ?? 3);
|
|
277
|
+
const timer = setInterval(() => {
|
|
278
|
+
// Skip if session has settled.
|
|
279
|
+
if (session.status !== 'running')
|
|
280
|
+
return;
|
|
281
|
+
const steps = session.steps ?? [];
|
|
282
|
+
const lastFireSteps = this.narrationStepsAtLastFire.get(session.sessionId) ?? 0;
|
|
283
|
+
if (steps.length - lastFireSteps < minStepDelta)
|
|
284
|
+
return;
|
|
285
|
+
// Mark BEFORE the async call to prevent double-firing if the LLM
|
|
286
|
+
// is slow.
|
|
287
|
+
this.narrationStepsAtLastFire.set(session.sessionId, steps.length);
|
|
288
|
+
const elapsedMs = Date.now() - session.startedAt;
|
|
289
|
+
void generateNarration({
|
|
290
|
+
sessionId: session.sessionId,
|
|
291
|
+
prompt: session.prompt,
|
|
292
|
+
agent: session.agent,
|
|
293
|
+
elapsedMs,
|
|
294
|
+
steps: [...steps],
|
|
295
|
+
})
|
|
296
|
+
.then((out) => {
|
|
297
|
+
if (out)
|
|
298
|
+
this.narrationBySession.set(session.sessionId, out);
|
|
299
|
+
})
|
|
300
|
+
.catch(() => { });
|
|
301
|
+
}, intervalMs);
|
|
302
|
+
this.narrationTimers.set(session.sessionId, timer);
|
|
303
|
+
}
|
|
304
|
+
/** v0.6 M3 — latest narration for a session, or undefined. */
|
|
305
|
+
getNarration(sessionId) {
|
|
306
|
+
return this.narrationBySession.get(sessionId);
|
|
307
|
+
}
|
|
222
308
|
/** Persisted (read-only) sessions from prior daemon runs. Cached at boot. */
|
|
223
309
|
listPersisted() {
|
|
224
310
|
return this.persistedCache;
|