codehere 0.8.0 → 1.0.2
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 +37 -184
- package/dist/application/use-cases/check-claimed-tests.js +81 -0
- package/dist/application/use-cases/detect-test-weakening.js +237 -0
- package/dist/application/use-cases/rerun-tests.js +329 -0
- package/dist/index.js +74 -1004
- package/dist/infrastructure/orchestration/artifacts-extractor.js +2 -265
- package/dist/presentation/cli/commands/check-command.js +251 -0
- package/dist/presentation/cli/commands/help-command.js +22 -286
- package/dist/ui.js +19 -438
- package/dist/utils/version.js +0 -12
- package/package.json +18 -27
- package/dist/application/use-cases/domain-trust.js +0 -110
- package/dist/application/use-cases/generate-final-report.js +0 -134
- package/dist/application/use-cases/generate-narration.js +0 -128
- package/dist/application/use-cases/generate-thread-title.js +0 -178
- package/dist/application/use-cases/narrator-prompt-template.js +0 -158
- package/dist/application/use-cases/thread-export.js +0 -155
- package/dist/application/use-cases/verify-claim.js +0 -274
- package/dist/application/use-cases/write-pass.js +0 -277
- package/dist/domain/entities/task.js +0 -61
- package/dist/domain/services/codebase-detector.js +0 -91
- package/dist/error-handler.js +0 -251
- package/dist/infrastructure/completion/bash-completion.js +0 -25
- package/dist/infrastructure/completion/fish-completion.js +0 -22
- package/dist/infrastructure/completion/zsh-completion.js +0 -30
- package/dist/infrastructure/config/config-manager.js +0 -173
- package/dist/infrastructure/config/product-context.js +0 -67
- package/dist/infrastructure/context/adapters/drawio-source.js +0 -157
- package/dist/infrastructure/context/adapters/figma-source.js +0 -163
- package/dist/infrastructure/context/adapters/github-issue-source.js +0 -159
- package/dist/infrastructure/context/adapters/linear-source.js +0 -190
- package/dist/infrastructure/context/context-resolver.js +0 -144
- package/dist/infrastructure/context/context-source.js +0 -114
- package/dist/infrastructure/context/oauth-flow.js +0 -207
- package/dist/infrastructure/context/oauth-store.js +0 -119
- package/dist/infrastructure/context/registry-bootstrap.js +0 -99
- package/dist/infrastructure/context/write-pass-resolver.js +0 -106
- package/dist/infrastructure/cost/budget-config.js +0 -113
- package/dist/infrastructure/cost/cost-router.js +0 -104
- package/dist/infrastructure/cost/spend-aggregator.js +0 -147
- package/dist/infrastructure/hooks/claude-sdk-hook.js +0 -888
- package/dist/infrastructure/hooks/hook-mode.js +0 -71
- package/dist/infrastructure/hooks/mirror-tool-call-to-audit.js +0 -43
- package/dist/infrastructure/hooks/pre-prompt-scan.js +0 -122
- package/dist/infrastructure/hooks/session-analyzer.js +0 -177
- package/dist/infrastructure/observability/execution-tracer.js +0 -227
- package/dist/infrastructure/orchestration/agent-adapter.js +0 -13
- package/dist/infrastructure/orchestration/agent-installer.js +0 -101
- package/dist/infrastructure/orchestration/agents-config-seed.js +0 -56
- package/dist/infrastructure/orchestration/aider-adapter.js +0 -447
- package/dist/infrastructure/orchestration/cassette-recorder.js +0 -120
- package/dist/infrastructure/orchestration/claude-code-adapter.js +0 -478
- package/dist/infrastructure/orchestration/codex-adapter.js +0 -538
- package/dist/infrastructure/orchestration/conductor-adapter.js +0 -377
- package/dist/infrastructure/orchestration/context-handoff.js +0 -144
- package/dist/infrastructure/orchestration/control/codex-control-surface.js +0 -99
- package/dist/infrastructure/orchestration/control/control-surface.js +0 -15
- package/dist/infrastructure/orchestration/failover-classifier.js +0 -151
- package/dist/infrastructure/orchestration/failover-config.js +0 -77
- package/dist/infrastructure/orchestration/failover-policy.js +0 -106
- package/dist/infrastructure/orchestration/gemini-adapter.js +0 -583
- package/dist/infrastructure/orchestration/generic-cli-adapter.js +0 -558
- package/dist/infrastructure/orchestration/graphify-context.js +0 -254
- package/dist/infrastructure/orchestration/memory-injector.js +0 -380
- package/dist/infrastructure/orchestration/mocks/mock-agent-repl.js +0 -233
- package/dist/infrastructure/orchestration/mocks/mock-codex-app-server.js +0 -73
- package/dist/infrastructure/orchestration/mocks/mock-synthesis.js +0 -83
- package/dist/infrastructure/orchestration/mocks/resolve-agent-spawn.js +0 -37
- package/dist/infrastructure/orchestration/opencode-adapter.js +0 -510
- package/dist/infrastructure/orchestration/progress-aggregator.js +0 -689
- package/dist/infrastructure/orchestration/readiness.js +0 -103
- package/dist/infrastructure/orchestration/session-manager.js +0 -1091
- package/dist/infrastructure/orchestration/session-persistence.js +0 -258
- package/dist/infrastructure/orchestration/thread-key-store.js +0 -127
- package/dist/infrastructure/orchestration/write-pass-store.js +0 -46
- package/dist/infrastructure/protocols/cloud-sync.js +0 -207
- package/dist/infrastructure/protocols/mcp-helpers.js +0 -25
- package/dist/infrastructure/protocols/mcp-server-impl.js +0 -2853
- package/dist/infrastructure/protocols/probe-port.js +0 -36
- package/dist/infrastructure/protocols/thread-phase.js +0 -105
- package/dist/infrastructure/protocols/ui-page-v2.js +0 -4418
- package/dist/infrastructure/protocols/ui-page.js +0 -2377
- package/dist/infrastructure/protocols/verify-page.js +0 -983
- package/dist/infrastructure/security/file-collection.js +0 -118
- package/dist/infrastructure/security/input-validator.js +0 -152
- package/dist/infrastructure/security/permissions-config.js +0 -234
- package/dist/infrastructure/security/trust-scorer.js +0 -657
- package/dist/infrastructure/storage/cross-tool-agents.js +0 -268
- package/dist/infrastructure/storage/embeddings-path.js +0 -37
- package/dist/infrastructure/storage/project-thread-migration.js +0 -237
- package/dist/infrastructure/storage/project-thread-mirror.js +0 -524
- package/dist/infrastructure/storage/project-thread-reader.js +0 -172
- package/dist/infrastructure/storage/project-thread-title-backfill.js +0 -214
- package/dist/infrastructure/storage/task-repository.js +0 -206
- package/dist/monitoring.js +0 -212
- package/dist/presentation/cli/commands/audit-command.js +0 -126
- package/dist/presentation/cli/commands/budget-command.js +0 -79
- package/dist/presentation/cli/commands/config-command.js +0 -125
- package/dist/presentation/cli/commands/context-source-command.js +0 -198
- package/dist/presentation/cli/commands/hook-command.js +0 -265
- package/dist/presentation/cli/commands/init-command.js +0 -578
- package/dist/presentation/cli/commands/memory-command.js +0 -1031
- package/dist/presentation/cli/commands/narrator-command.js +0 -101
- package/dist/presentation/cli/commands/permission-command.js +0 -116
- package/dist/presentation/cli/commands/protocols-builder.js +0 -254
- package/dist/presentation/cli/commands/protocols-command.js +0 -84
- package/dist/presentation/cli/commands/repo-map-builder.js +0 -247
- package/dist/presentation/cli/commands/report-command.js +0 -387
- package/dist/presentation/cli/commands/scan-tool-command.js +0 -94
- package/dist/presentation/cli/commands/setup-command.js +0 -307
- package/dist/presentation/cli/commands/status-command.js +0 -661
- package/dist/presentation/cli/commands/thread-command.js +0 -288
- package/dist/presentation/cli/commands/trust-command.js +0 -306
- package/dist/presentation/cli/commands/verify-command.js +0 -233
- package/dist/presentation/cli/error-display.js +0 -336
- package/dist/presentation/cli/repl.js +0 -797
- package/dist/presentation/cli/task-deliverable.js +0 -127
- package/dist/testing/cassette.js +0 -125
- package/dist/testing/replay-adapter.js +0 -166
|
@@ -1,101 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Agent installer — runs the install command for a registered adapter.
|
|
3
|
-
*
|
|
4
|
-
* Principle #10 (non-technical users first-class): the user clicks a button,
|
|
5
|
-
* codehere runs the install, streams output back to the UI. No terminal, no
|
|
6
|
-
* copy-paste. If brew isn't present, we fall back to curl/sh or npm-based
|
|
7
|
-
* installers. On success, re-check readiness and tell the UI to refresh.
|
|
8
|
-
*
|
|
9
|
-
* Security: the command comes from adapter source code (built-ins) or from
|
|
10
|
-
* the user's own ~/.codehere/agents.yaml (generic adapters) — never from an
|
|
11
|
-
* HTTP request parameter. We refuse to execute an arbitrary command supplied
|
|
12
|
-
* over the wire.
|
|
13
|
-
*/
|
|
14
|
-
import { spawn } from 'child_process';
|
|
15
|
-
import { EventEmitter } from 'events';
|
|
16
|
-
/** Execute an install command in a shell so chained commands (&& / || / |)
|
|
17
|
-
* work. We need the user's shell rc loaded so brew / npm / path extensions
|
|
18
|
-
* resolve — default: `$SHELL -lc "<command>"`. */
|
|
19
|
-
export function runInstallCommand(command) {
|
|
20
|
-
const emitter = new EventEmitter();
|
|
21
|
-
const buffer = [];
|
|
22
|
-
let statusValue = 'running';
|
|
23
|
-
const startedAt = Date.now();
|
|
24
|
-
const emit = (event) => {
|
|
25
|
-
buffer.push(event);
|
|
26
|
-
emitter.emit('event', event);
|
|
27
|
-
};
|
|
28
|
-
const shell = process.env.SHELL || '/bin/zsh';
|
|
29
|
-
let child;
|
|
30
|
-
try {
|
|
31
|
-
child = spawn(shell, ['-lc', command], {
|
|
32
|
-
env: process.env,
|
|
33
|
-
stdio: ['ignore', 'pipe', 'pipe'],
|
|
34
|
-
});
|
|
35
|
-
}
|
|
36
|
-
catch (err) {
|
|
37
|
-
statusValue = 'failed';
|
|
38
|
-
const msg = err instanceof Error ? err.message : String(err);
|
|
39
|
-
queueMicrotask(() => emit({ type: 'error', data: msg }));
|
|
40
|
-
return {
|
|
41
|
-
get status() {
|
|
42
|
-
return statusValue;
|
|
43
|
-
},
|
|
44
|
-
subscribe(handler) {
|
|
45
|
-
for (const e of buffer)
|
|
46
|
-
handler(e);
|
|
47
|
-
emitter.on('event', handler);
|
|
48
|
-
return () => emitter.off('event', handler);
|
|
49
|
-
},
|
|
50
|
-
kill() {
|
|
51
|
-
/* not running */
|
|
52
|
-
},
|
|
53
|
-
};
|
|
54
|
-
}
|
|
55
|
-
child.stdout?.on('data', (chunk) => {
|
|
56
|
-
emit({ type: 'out', data: chunk.toString('utf-8') });
|
|
57
|
-
});
|
|
58
|
-
child.stderr?.on('data', (chunk) => {
|
|
59
|
-
emit({ type: 'err', data: chunk.toString('utf-8') });
|
|
60
|
-
});
|
|
61
|
-
child.on('exit', (code) => {
|
|
62
|
-
statusValue = code === 0 ? 'completed' : 'failed';
|
|
63
|
-
emit({ type: 'done', code, durationMs: Date.now() - startedAt });
|
|
64
|
-
});
|
|
65
|
-
child.on('error', (err) => {
|
|
66
|
-
statusValue = 'failed';
|
|
67
|
-
emit({ type: 'error', data: err.message });
|
|
68
|
-
});
|
|
69
|
-
return {
|
|
70
|
-
get status() {
|
|
71
|
-
return statusValue;
|
|
72
|
-
},
|
|
73
|
-
subscribe(handler) {
|
|
74
|
-
for (const e of buffer)
|
|
75
|
-
handler(e);
|
|
76
|
-
emitter.on('event', handler);
|
|
77
|
-
return () => emitter.off('event', handler);
|
|
78
|
-
},
|
|
79
|
-
kill() {
|
|
80
|
-
if (statusValue !== 'running')
|
|
81
|
-
return;
|
|
82
|
-
try {
|
|
83
|
-
child.kill('SIGTERM');
|
|
84
|
-
}
|
|
85
|
-
catch {
|
|
86
|
-
/* already dead */
|
|
87
|
-
}
|
|
88
|
-
},
|
|
89
|
-
};
|
|
90
|
-
}
|
|
91
|
-
/** Convenience: install a registered adapter. Returns null if adapter has no
|
|
92
|
-
* installHint configured or is already installed. */
|
|
93
|
-
export function installAgent(adapter) {
|
|
94
|
-
if (!adapter.checkReadiness)
|
|
95
|
-
return null;
|
|
96
|
-
const readiness = adapter.checkReadiness();
|
|
97
|
-
if (readiness.state !== 'needs_install' || !readiness.installHint?.command)
|
|
98
|
-
return null;
|
|
99
|
-
return runInstallCommand(readiness.installHint.command);
|
|
100
|
-
}
|
|
101
|
-
//# sourceMappingURL=agent-installer.js.map
|
|
@@ -1,56 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Seed ~/.codehere/agents.yaml on first daemon start.
|
|
3
|
-
*
|
|
4
|
-
* Ships with NO pre-populated adapters. Users who want codehere to drive a
|
|
5
|
-
* CLI agent beyond the built-in four (Claude Code, Codex, Aider, OpenCode)
|
|
6
|
-
* add their own entry to `~/.codehere/agents.yaml` and restart the daemon.
|
|
7
|
-
*
|
|
8
|
-
* Rationale: shipping scaffolded entries we hadn't actually verified on real
|
|
9
|
-
* machines produced a "9 agents supported" claim that wasn't honest. The
|
|
10
|
-
* generic-cli-adapter mechanism is still available — users opt in per agent
|
|
11
|
-
* by editing the YAML themselves.
|
|
12
|
-
*
|
|
13
|
-
* Re-seed never overwrites an existing file.
|
|
14
|
-
*/
|
|
15
|
-
import { existsSync, mkdirSync, writeFileSync } from 'fs';
|
|
16
|
-
import { homedir } from 'os';
|
|
17
|
-
import { join, dirname } from 'path';
|
|
18
|
-
const CONFIG_PATH = join(process.env.CODEHERE_HOME || homedir(), '.codehere', 'agents.yaml');
|
|
19
|
-
const DEFAULT_YAML = `# codehere — pluggable agent adapters
|
|
20
|
-
#
|
|
21
|
-
# Built-in adapters (no configuration needed — just install the CLI):
|
|
22
|
-
# - Claude Code
|
|
23
|
-
# - Codex
|
|
24
|
-
# - Aider
|
|
25
|
-
# - OpenCode
|
|
26
|
-
#
|
|
27
|
-
# To add a CLI-based AI coding agent not in the built-in list, uncomment
|
|
28
|
-
# and adapt the example below. Safety hook + audit log wrap every subprocess
|
|
29
|
-
# regardless of agent, so adding an adapter does not bypass any security
|
|
30
|
-
# layer.
|
|
31
|
-
#
|
|
32
|
-
# agents:
|
|
33
|
-
# - id: my-agent
|
|
34
|
-
# label: My Agent
|
|
35
|
-
# command: my-agent-cli # must be on PATH
|
|
36
|
-
# args: [--headless] # fixed args before the prompt
|
|
37
|
-
# prompt_via: argv # or "stdin"
|
|
38
|
-
# process_pattern: (?:^|/)my-agent-cli(?:\\\\s|$)
|
|
39
|
-
# format: text # or "jsonl" if the CLI streams JSON
|
|
40
|
-
# install_command: brew install my-agent-cli # optional · enables "Install" button
|
|
41
|
-
# install_url: https://example.com/docs # optional · docs link
|
|
42
|
-
|
|
43
|
-
agents: []
|
|
44
|
-
`;
|
|
45
|
-
export function seedAgentsConfig() {
|
|
46
|
-
try {
|
|
47
|
-
if (existsSync(CONFIG_PATH))
|
|
48
|
-
return;
|
|
49
|
-
mkdirSync(dirname(CONFIG_PATH), { recursive: true });
|
|
50
|
-
writeFileSync(CONFIG_PATH, DEFAULT_YAML, 'utf-8');
|
|
51
|
-
}
|
|
52
|
-
catch {
|
|
53
|
-
/* best-effort */
|
|
54
|
-
}
|
|
55
|
-
}
|
|
56
|
-
//# sourceMappingURL=agents-config-seed.js.map
|
|
@@ -1,447 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Aider adapter — third concrete AgentAdapter.
|
|
3
|
-
*
|
|
4
|
-
* `aider --message "<prompt>" --yes-always --no-check-update --no-pretty --no-stream`
|
|
5
|
-
*
|
|
6
|
-
* Aider's output is free-form text (not JSON), so the event log shows raw lines
|
|
7
|
-
* instead of semantic tool_use blocks. Step aggregator produces zero steps — but
|
|
8
|
-
* Final report still works via git-diff (artifacts-extractor is agent-agnostic)
|
|
9
|
-
* and token accounting works through Aider's cost summary line at session end.
|
|
10
|
-
*
|
|
11
|
-
* Same structural pattern as Claude Code / Codex adapters: emit buffer,
|
|
12
|
-
* SIGTERM→SIGKILL escalation, known-error fingerprints. Intentionally keeping
|
|
13
|
-
* the duplication for now — if we grow to 5+ adapters, extract a shared helper.
|
|
14
|
-
*/
|
|
15
|
-
import { spawn, execFileSync } from 'child_process';
|
|
16
|
-
import { EventEmitter } from 'events';
|
|
17
|
-
import { randomUUID } from 'crypto';
|
|
18
|
-
import { startRecording } from './cassette-recorder.js';
|
|
19
|
-
import { ProgressAggregator } from './progress-aggregator.js';
|
|
20
|
-
import { resolveAgentSpawn } from './mocks/resolve-agent-spawn.js';
|
|
21
|
-
import { applyMemoryPrefix } from './memory-injector.js';
|
|
22
|
-
import { mirrorToolCallToAudit } from '../hooks/mirror-tool-call-to-audit.js';
|
|
23
|
-
import { snapshotCwd, extractArtifacts } from './artifacts-extractor.js';
|
|
24
|
-
const HEADLESS_FLAGS = [
|
|
25
|
-
'--yes-always', // auto-accept all file-edit confirmations
|
|
26
|
-
'--no-check-update', // skip aider self-update probe
|
|
27
|
-
'--no-pretty', // disable ANSI colors in output
|
|
28
|
-
'--no-stream', // stream=false → one chunk per turn, cleaner stdout parsing
|
|
29
|
-
'--no-auto-commits', // don't auto-commit — leave file changes for artifacts-extractor
|
|
30
|
-
];
|
|
31
|
-
// v0.7 HIGH-1 fix — extended ERROR_PATTERNS for the 18+ provider families
|
|
32
|
-
// Aider supports. Pre-fix: 4 regexes shaped only for Anthropic + OpenAI text.
|
|
33
|
-
// Aider routes through LiteLLM which forwards each provider's native error
|
|
34
|
-
// message, so DeepSeek "Insufficient Balance", Ollama "connection refused",
|
|
35
|
-
// Bedrock "UnrecognizedClientException", Gemini "API key not valid", and even
|
|
36
|
-
// Anthropic's actual "invalid x-api-key" all fell through and never fired
|
|
37
|
-
// failover. Per docs/contracts/aider.md §3 + docs/contracts/00-FINDINGS.md HIGH-1.
|
|
38
|
-
const ERROR_PATTERNS = [
|
|
39
|
-
{
|
|
40
|
-
code: 'credit_low',
|
|
41
|
-
// Anthropic + OpenAI canonical + DeepSeek ("Insufficient Balance").
|
|
42
|
-
regex: /credit balance is too low|insufficient.*credit|no credits|you exceeded your current quota|insufficient.balance/i,
|
|
43
|
-
message: 'The API key has no credit (or balance is too low).',
|
|
44
|
-
recovery: 'Aider needs a billed API key with balance. Top up at your provider (console.anthropic.com / ' +
|
|
45
|
-
'platform.openai.com / platform.deepseek.com), or switch the task to Claude Code / Codex / Gemini ' +
|
|
46
|
-
'which use subscription or free-tier auth.',
|
|
47
|
-
},
|
|
48
|
-
{
|
|
49
|
-
code: 'auth_invalid',
|
|
50
|
-
// Anthropic + OpenAI + Anthropic's actual "invalid x-api-key" + Gemini's
|
|
51
|
-
// "API key not valid" + Bedrock's "UnrecognizedClientException" + generic.
|
|
52
|
-
regex: /invalid api key|invalid x-api-key|api key not valid|authentication.*failed|401 unauth|please provide your.*api key|unrecognizedclientexception|the security token included in the request is invalid/i,
|
|
53
|
-
message: 'Aider auth was rejected or missing.',
|
|
54
|
-
recovery: 'Check the API key for your model provider (ANTHROPIC_API_KEY, OPENAI_API_KEY, DEEPSEEK_API_KEY, ' +
|
|
55
|
-
'GEMINI_API_KEY, AWS_ACCESS_KEY_ID for Bedrock, etc.). Re-export and retry.',
|
|
56
|
-
},
|
|
57
|
-
{
|
|
58
|
-
code: 'rate_limit',
|
|
59
|
-
// Generic 429 + named providers (Gemini RESOURCE_EXHAUSTED, Bedrock ThrottlingException).
|
|
60
|
-
regex: /rate.?limit(ed)?|429|too many requests|resource.exhausted|throttlingexception/i,
|
|
61
|
-
message: 'Aider was rate-limited by the provider.',
|
|
62
|
-
recovery: 'Wait a minute and retry, or switch --model via extraArgs to a different provider.',
|
|
63
|
-
},
|
|
64
|
-
{
|
|
65
|
-
code: 'model_unavailable',
|
|
66
|
-
regex: /model.*(not found|not available|unavailable|does not exist)|invalidmodelid/i,
|
|
67
|
-
message: 'Aider could not find the model.',
|
|
68
|
-
recovery: 'Run `aider --list-models` and pass `--model <name>` via extraArgs.',
|
|
69
|
-
},
|
|
70
|
-
{
|
|
71
|
-
code: 'network_failure',
|
|
72
|
-
// Ollama (local), self-hosted, generic socket errors. Local providers
|
|
73
|
-
// typically fail at the transport layer with these strings.
|
|
74
|
-
regex: /connection refused|econnrefused|enotfound|fetch failed|getaddrinfo|network is unreachable/i,
|
|
75
|
-
message: "Aider couldn't reach the model provider.",
|
|
76
|
-
recovery: 'Network or provider endpoint issue. For local Ollama: confirm `ollama serve` is running. ' +
|
|
77
|
-
'For cloud providers: check internet + provider status page.',
|
|
78
|
-
},
|
|
79
|
-
];
|
|
80
|
-
function scanForKnownError(text) {
|
|
81
|
-
for (const p of ERROR_PATTERNS) {
|
|
82
|
-
if (p.regex.test(text))
|
|
83
|
-
return { code: p.code, message: p.message, recovery: p.recovery };
|
|
84
|
-
}
|
|
85
|
-
return null;
|
|
86
|
-
}
|
|
87
|
-
export function spawnAider(prompt, options = {}) {
|
|
88
|
-
const sessionId = randomUUID();
|
|
89
|
-
const startedAt = Date.now();
|
|
90
|
-
const emitter = new EventEmitter();
|
|
91
|
-
const buffer = [];
|
|
92
|
-
let statusValue = 'running';
|
|
93
|
-
let killRequested = false;
|
|
94
|
-
const cwd = options.cwd ?? process.cwd();
|
|
95
|
-
// F3 Task 8 — memory injection (same shape as every other adapter).
|
|
96
|
-
const injected = applyMemoryPrefix(prompt, cwd, options);
|
|
97
|
-
const finalPrompt = injected.prompt;
|
|
98
|
-
const snapshot = snapshotCwd(cwd);
|
|
99
|
-
// ProgressAggregator parses aider's stdout for `Running ...`, `Applied edit
|
|
100
|
-
// to ...`, and `Creating empty file ...` lines and emits Run/Edit/Write
|
|
101
|
-
// ProgressSteps. Observability parity (Path B) — see ingestAider() in
|
|
102
|
-
// progress-aggregator.ts. Blocking parity (Path A) needs the upstream
|
|
103
|
-
// `--pre-action-hook` PR.
|
|
104
|
-
const aggregator = new ProgressAggregator('aider');
|
|
105
|
-
let stepsSnapshot = [];
|
|
106
|
-
let artifactsSnapshot;
|
|
107
|
-
let textAccum = '';
|
|
108
|
-
let usageSnapshot;
|
|
109
|
-
let errorEmitted = false;
|
|
110
|
-
const emit = (event) => {
|
|
111
|
-
buffer.push(event);
|
|
112
|
-
emitter.emit('event', event);
|
|
113
|
-
if (aggregator.ingest(event)) {
|
|
114
|
-
stepsSnapshot = aggregator.steps.map((s) => ({ ...s }));
|
|
115
|
-
const snapEvt = { type: 'codehere:steps', timestamp: Date.now(), data: { steps: stepsSnapshot } };
|
|
116
|
-
buffer.push(snapEvt);
|
|
117
|
-
emitter.emit('event', snapEvt);
|
|
118
|
-
}
|
|
119
|
-
// Text accumulator for test-summary parsing + aider cost-line token extraction
|
|
120
|
-
if (event.type === 'stdout' && event.data?.line) {
|
|
121
|
-
const line = String(event.data.line);
|
|
122
|
-
textAccum += line + '\n';
|
|
123
|
-
// #110 slice 3 — mirror aider tool calls to the audit log. Match
|
|
124
|
-
// the same stdout patterns the aggregator parses (per
|
|
125
|
-
// learning_aider.md): "Running <cmd>" → Bash, "Applied edit to
|
|
126
|
-
// <path>" → Edit, "Creating empty file <path>" → Write.
|
|
127
|
-
// Strip ANSI as defense-in-depth (HEADLESS_FLAGS already includes
|
|
128
|
-
// --no-pretty but extraArgs could override).
|
|
129
|
-
const stripAnsi = (s) => s.replace(/\[[0-9;]*m/g, '');
|
|
130
|
-
const clean = stripAnsi(line).trim();
|
|
131
|
-
let toolName = '';
|
|
132
|
-
let input = '';
|
|
133
|
-
let mirrorMatch;
|
|
134
|
-
if ((mirrorMatch = clean.match(/^Running\s+(.+)$/))) {
|
|
135
|
-
toolName = 'Bash';
|
|
136
|
-
input = mirrorMatch[1];
|
|
137
|
-
}
|
|
138
|
-
else if ((mirrorMatch = clean.match(/^Applied edit to\s+(.+)$/))) {
|
|
139
|
-
toolName = 'Edit';
|
|
140
|
-
input = mirrorMatch[1];
|
|
141
|
-
}
|
|
142
|
-
else if ((mirrorMatch = clean.match(/^Creating empty file\s+(.+)$/))) {
|
|
143
|
-
toolName = 'Write';
|
|
144
|
-
input = mirrorMatch[1];
|
|
145
|
-
}
|
|
146
|
-
if (toolName && input) {
|
|
147
|
-
mirrorToolCallToAudit({ sessionId, agent: 'aider', toolName, toolInput: input, cwd });
|
|
148
|
-
}
|
|
149
|
-
// Aider emits a cost summary like:
|
|
150
|
-
// "Tokens: 1.2k sent, 412 received. Cost: $0.01 request, $0.01 session."
|
|
151
|
-
const m = line.match(/Tokens:\s*([\d.]+)([kM]?)\s*sent,\s*([\d.]+)([kM]?)\s*received/i);
|
|
152
|
-
if (m) {
|
|
153
|
-
const scale = (s) => (s === 'k' ? 1000 : s === 'M' ? 1_000_000 : 1);
|
|
154
|
-
const sent = Math.round(parseFloat(m[1]) * scale(m[2]));
|
|
155
|
-
const recv = Math.round(parseFloat(m[3]) * scale(m[4]));
|
|
156
|
-
usageSnapshot = {
|
|
157
|
-
inputTokens: (usageSnapshot?.inputTokens ?? 0) + sent,
|
|
158
|
-
outputTokens: (usageSnapshot?.outputTokens ?? 0) + recv,
|
|
159
|
-
};
|
|
160
|
-
}
|
|
161
|
-
}
|
|
162
|
-
if (!errorEmitted) {
|
|
163
|
-
const data = event.data;
|
|
164
|
-
let text = '';
|
|
165
|
-
if ((event.type === 'stderr' || event.type === 'stdout') && typeof data?.line === 'string') {
|
|
166
|
-
text = data.line;
|
|
167
|
-
}
|
|
168
|
-
if (text) {
|
|
169
|
-
const hit = scanForKnownError(text);
|
|
170
|
-
if (hit) {
|
|
171
|
-
errorEmitted = true;
|
|
172
|
-
const errEvent = {
|
|
173
|
-
type: 'codehere:error',
|
|
174
|
-
timestamp: Date.now(),
|
|
175
|
-
data: { code: hit.code, message: hit.message, recovery: hit.recovery },
|
|
176
|
-
};
|
|
177
|
-
buffer.push(errEvent);
|
|
178
|
-
emitter.emit('event', errEvent);
|
|
179
|
-
}
|
|
180
|
-
}
|
|
181
|
-
}
|
|
182
|
-
};
|
|
183
|
-
// Whenever a codehere:error fires, we treat the session as effectively failed
|
|
184
|
-
// even if the subprocess later exits 0. Many CLIs print a logical error to
|
|
185
|
-
// stdout then return success — the status pill would otherwise lie.
|
|
186
|
-
emitter.on('event', (ev) => {
|
|
187
|
-
if (ev.type === 'codehere:error' && statusValue === 'running') {
|
|
188
|
-
// Don't overwrite if exit already settled a final status.
|
|
189
|
-
// We rely on exit handler to flip running→completed/killed/error; we pre-empt
|
|
190
|
-
// that with 'error' here so the status pill is honest from the moment the
|
|
191
|
-
// error is detected.
|
|
192
|
-
statusValue = 'error';
|
|
193
|
-
}
|
|
194
|
-
});
|
|
195
|
-
const spawnEnv = { ...process.env, ...(options.env ?? {}) };
|
|
196
|
-
let child;
|
|
197
|
-
try {
|
|
198
|
-
const sp = resolveAgentSpawn('aider', 'aider', ['--message', finalPrompt, ...HEADLESS_FLAGS, ...(options.extraArgs ?? [])], finalPrompt);
|
|
199
|
-
child = spawn(sp.command, sp.args, {
|
|
200
|
-
cwd,
|
|
201
|
-
env: spawnEnv,
|
|
202
|
-
stdio: ['ignore', 'pipe', 'pipe'],
|
|
203
|
-
});
|
|
204
|
-
}
|
|
205
|
-
catch (err) {
|
|
206
|
-
statusValue = 'error';
|
|
207
|
-
const msg = err instanceof Error ? err.message : String(err);
|
|
208
|
-
queueMicrotask(() => emit({ type: 'error', timestamp: Date.now(), data: { message: msg } }));
|
|
209
|
-
return {
|
|
210
|
-
sessionId,
|
|
211
|
-
startedAt,
|
|
212
|
-
prompt,
|
|
213
|
-
agent: 'aider',
|
|
214
|
-
get status() {
|
|
215
|
-
return statusValue;
|
|
216
|
-
},
|
|
217
|
-
get steps() {
|
|
218
|
-
return stepsSnapshot;
|
|
219
|
-
},
|
|
220
|
-
get artifacts() {
|
|
221
|
-
return artifactsSnapshot;
|
|
222
|
-
},
|
|
223
|
-
subscribe(handler) {
|
|
224
|
-
for (const e of buffer)
|
|
225
|
-
handler(e);
|
|
226
|
-
emitter.on('event', handler);
|
|
227
|
-
return () => emitter.off('event', handler);
|
|
228
|
-
},
|
|
229
|
-
kill() {
|
|
230
|
-
/* already not running */
|
|
231
|
-
},
|
|
232
|
-
};
|
|
233
|
-
}
|
|
234
|
-
// Organic cassette recording (S2b · capture-first plan) · no-op unless
|
|
235
|
-
// CODEHERE_RECORD_CASSETTES=1, so zero overhead by default.
|
|
236
|
-
const recorder = startRecording({ adapter: 'aider', sessionId, prompt: finalPrompt, cwd });
|
|
237
|
-
let stdoutBuf = '';
|
|
238
|
-
child.stdout?.on('data', (chunk) => {
|
|
239
|
-
const text = chunk.toString('utf-8');
|
|
240
|
-
recorder.onStdout(text);
|
|
241
|
-
stdoutBuf += text;
|
|
242
|
-
let idx;
|
|
243
|
-
while ((idx = stdoutBuf.indexOf('\n')) >= 0) {
|
|
244
|
-
const line = stdoutBuf.slice(0, idx).trim();
|
|
245
|
-
stdoutBuf = stdoutBuf.slice(idx + 1);
|
|
246
|
-
if (!line)
|
|
247
|
-
continue;
|
|
248
|
-
emit({ type: 'stdout', timestamp: Date.now(), data: { line } });
|
|
249
|
-
}
|
|
250
|
-
});
|
|
251
|
-
child.stderr?.on('data', (chunk) => {
|
|
252
|
-
const text = chunk.toString('utf-8');
|
|
253
|
-
recorder.onStderr(text);
|
|
254
|
-
for (const line of text.split('\n')) {
|
|
255
|
-
if (line.trim())
|
|
256
|
-
emit({ type: 'stderr', timestamp: Date.now(), data: { line } });
|
|
257
|
-
}
|
|
258
|
-
});
|
|
259
|
-
child.on('exit', (code, signal) => {
|
|
260
|
-
recorder.onExit(code ?? -1, signal);
|
|
261
|
-
// Preserve 'error' if we already flipped to it on codehere:error.
|
|
262
|
-
statusValue =
|
|
263
|
-
killRequested || signal !== null ? 'killed' : errorEmitted ? 'error' : code === 0 ? 'completed' : 'error';
|
|
264
|
-
if (aggregator.finalize(statusValue === 'completed' ? 'completed' : statusValue === 'killed' ? 'killed' : 'error')) {
|
|
265
|
-
stepsSnapshot = aggregator.steps.map((s) => ({ ...s }));
|
|
266
|
-
emit({ type: 'codehere:steps', timestamp: Date.now(), data: { steps: stepsSnapshot } });
|
|
267
|
-
}
|
|
268
|
-
if (statusValue === 'completed') {
|
|
269
|
-
try {
|
|
270
|
-
artifactsSnapshot = { ...extractArtifacts(snapshot, textAccum), usage: usageSnapshot };
|
|
271
|
-
emit({ type: 'codehere:artifacts', timestamp: Date.now(), data: artifactsSnapshot });
|
|
272
|
-
}
|
|
273
|
-
catch {
|
|
274
|
-
/* best-effort */
|
|
275
|
-
}
|
|
276
|
-
}
|
|
277
|
-
emit({
|
|
278
|
-
type: 'exit',
|
|
279
|
-
timestamp: Date.now(),
|
|
280
|
-
data: { code, signal, status: statusValue, durationMs: Date.now() - startedAt },
|
|
281
|
-
});
|
|
282
|
-
});
|
|
283
|
-
child.on('error', (err) => {
|
|
284
|
-
statusValue = 'error';
|
|
285
|
-
emit({ type: 'error', timestamp: Date.now(), data: { message: err.message } });
|
|
286
|
-
});
|
|
287
|
-
return {
|
|
288
|
-
sessionId,
|
|
289
|
-
startedAt,
|
|
290
|
-
prompt,
|
|
291
|
-
agent: 'aider',
|
|
292
|
-
memoryInjection: injected.record,
|
|
293
|
-
get status() {
|
|
294
|
-
return statusValue;
|
|
295
|
-
},
|
|
296
|
-
get steps() {
|
|
297
|
-
return stepsSnapshot;
|
|
298
|
-
},
|
|
299
|
-
get artifacts() {
|
|
300
|
-
return artifactsSnapshot;
|
|
301
|
-
},
|
|
302
|
-
subscribe(handler) {
|
|
303
|
-
for (const e of buffer)
|
|
304
|
-
handler(e);
|
|
305
|
-
emitter.on('event', handler);
|
|
306
|
-
return () => emitter.off('event', handler);
|
|
307
|
-
},
|
|
308
|
-
kill() {
|
|
309
|
-
if (statusValue !== 'running')
|
|
310
|
-
return;
|
|
311
|
-
killRequested = true;
|
|
312
|
-
child.kill('SIGTERM');
|
|
313
|
-
setTimeout(() => {
|
|
314
|
-
if (statusValue === 'running') {
|
|
315
|
-
try {
|
|
316
|
-
child.kill('SIGKILL');
|
|
317
|
-
}
|
|
318
|
-
catch {
|
|
319
|
-
/* already dead */
|
|
320
|
-
}
|
|
321
|
-
}
|
|
322
|
-
}, 3000);
|
|
323
|
-
},
|
|
324
|
-
canAppendMessage: true,
|
|
325
|
-
/**
|
|
326
|
-
* v0.6.0 — Slice 2 of REPL persistence. Aider has no per-session UUID
|
|
327
|
-
* (unlike claude-code's session_id or codex's thread_id); it persists chat
|
|
328
|
-
* history to ./.aider.chat.history.md in the cwd. `--restore-chat-history`
|
|
329
|
-
* reads that file before processing the new --message, giving multi-turn
|
|
330
|
-
* continuity scoped to the cwd.
|
|
331
|
-
*
|
|
332
|
-
* Limitation: aider is single-cwd-single-session. Two concurrent aider
|
|
333
|
-
* sessions in the same cwd would collide on the same history file. Same
|
|
334
|
-
* limitation aider's own users have when running it interactively;
|
|
335
|
-
* codehere doesn't make it worse.
|
|
336
|
-
*
|
|
337
|
-
* Same shape as the other appendMessage implementations: one-shot Q&A,
|
|
338
|
-
* returns assistant text + duration, no streaming.
|
|
339
|
-
*/
|
|
340
|
-
appendMessage(text) {
|
|
341
|
-
const startedAt = Date.now();
|
|
342
|
-
return new Promise((resolve, reject) => {
|
|
343
|
-
let stdout = '';
|
|
344
|
-
let stderr = '';
|
|
345
|
-
// Route follow-ups through the mock seam too — under CODEHERE_MOCK=1 this
|
|
346
|
-
// hits the mock REPL, not real aider. (Was a real-spend leak.)
|
|
347
|
-
const sp2 = resolveAgentSpawn('aider', 'aider', ['--restore-chat-history', '--message', text, ...HEADLESS_FLAGS], text);
|
|
348
|
-
const followup = spawn(sp2.command, sp2.args, {
|
|
349
|
-
cwd,
|
|
350
|
-
env: spawnEnv,
|
|
351
|
-
stdio: ['ignore', 'pipe', 'pipe'],
|
|
352
|
-
});
|
|
353
|
-
followup.on('error', (err) => reject(err));
|
|
354
|
-
followup.stdout?.on('data', (chunk) => {
|
|
355
|
-
stdout += chunk.toString('utf-8');
|
|
356
|
-
});
|
|
357
|
-
followup.stderr?.on('data', (chunk) => {
|
|
358
|
-
stderr += chunk.toString('utf-8');
|
|
359
|
-
});
|
|
360
|
-
followup.on('exit', (code) => {
|
|
361
|
-
if (code !== 0) {
|
|
362
|
-
reject(new Error(`aider --restore-chat-history exited ${code}: ${stderr.slice(0, 300)}`));
|
|
363
|
-
return;
|
|
364
|
-
}
|
|
365
|
-
// Aider's --no-stream + --no-pretty output is plain text; the
|
|
366
|
-
// assistant's reply lands as a top-level paragraph after any
|
|
367
|
-
// tool-execution lines. Strip aider's own prefix lines (Added,
|
|
368
|
-
// Tokens, Cost, Applied, etc.) and keep the conversational body.
|
|
369
|
-
const stripped = stdout
|
|
370
|
-
.split('\n')
|
|
371
|
-
.filter((l) => {
|
|
372
|
-
const t = l.trim();
|
|
373
|
-
if (!t)
|
|
374
|
-
return false;
|
|
375
|
-
if (/^(Added|Removed|Tokens?:|Cost:|Applied|Creating|Running|Commit|Aider|Model:|Main model:|Weak model:)/i.test(t))
|
|
376
|
-
return false;
|
|
377
|
-
return true;
|
|
378
|
-
})
|
|
379
|
-
.join('\n')
|
|
380
|
-
.trim();
|
|
381
|
-
const result = stripped || '(aider returned no conversational output)';
|
|
382
|
-
resolve({ result, durationMs: Date.now() - startedAt });
|
|
383
|
-
});
|
|
384
|
-
});
|
|
385
|
-
},
|
|
386
|
-
};
|
|
387
|
-
}
|
|
388
|
-
export const AiderAdapter = {
|
|
389
|
-
id: 'aider',
|
|
390
|
-
label: 'Aider',
|
|
391
|
-
description: 'Best when you already know which file to change and what to change in it. Tight, focused edits — no plan-then-explore phase.',
|
|
392
|
-
processPattern: /(?:^|\/)aider(?:\s|$)/,
|
|
393
|
-
isInstalled() {
|
|
394
|
-
try {
|
|
395
|
-
const out = execFileSync('which', ['aider'], { encoding: 'utf-8', timeout: 2000 });
|
|
396
|
-
return out.trim().length > 0;
|
|
397
|
-
}
|
|
398
|
-
catch {
|
|
399
|
-
return false;
|
|
400
|
-
}
|
|
401
|
-
},
|
|
402
|
-
spawn(prompt, options) {
|
|
403
|
-
return spawnAider(prompt, options);
|
|
404
|
-
},
|
|
405
|
-
checkReadiness() {
|
|
406
|
-
const installed = this.isInstalled();
|
|
407
|
-
if (!installed) {
|
|
408
|
-
return {
|
|
409
|
-
id: this.id,
|
|
410
|
-
label: this.label,
|
|
411
|
-
state: 'needs_install',
|
|
412
|
-
installed: false,
|
|
413
|
-
installHint: { command: 'brew install aider', url: 'https://aider.chat/docs/install.html' },
|
|
414
|
-
};
|
|
415
|
-
}
|
|
416
|
-
// Aider has no subscription path — needs a BILLED API key with credit.
|
|
417
|
-
// If neither Anthropic nor OpenAI key is present → needs_auth.
|
|
418
|
-
// If a key IS present but the user is running on a Claude Code subscription
|
|
419
|
-
// (no billing configured), Aider will hit credit_low at runtime. We can't
|
|
420
|
-
// detect that pre-spawn without making an API call. Flag the risk in the
|
|
421
|
-
// detail string so the UI can surface it.
|
|
422
|
-
const hasAnthropic = !!process.env.ANTHROPIC_API_KEY;
|
|
423
|
-
const hasOpenai = !!process.env.OPENAI_API_KEY;
|
|
424
|
-
if (!hasAnthropic && !hasOpenai) {
|
|
425
|
-
return {
|
|
426
|
-
id: this.id,
|
|
427
|
-
label: this.label,
|
|
428
|
-
state: 'needs_auth',
|
|
429
|
-
installed: true,
|
|
430
|
-
authHint: {
|
|
431
|
-
detail: 'Aider needs a BILLED API key (Anthropic or OpenAI). Unlike Claude Code, it has no subscription path.',
|
|
432
|
-
url: 'https://console.anthropic.com',
|
|
433
|
-
},
|
|
434
|
-
};
|
|
435
|
-
}
|
|
436
|
-
return {
|
|
437
|
-
id: this.id,
|
|
438
|
-
label: this.label,
|
|
439
|
-
state: 'billed_key_required',
|
|
440
|
-
installed: true,
|
|
441
|
-
authHint: {
|
|
442
|
-
detail: 'API key detected. Aider requires the key to have billing credit (not just presence). If you run on a Claude Code subscription, Aider will fail at the first model call.',
|
|
443
|
-
},
|
|
444
|
-
};
|
|
445
|
-
},
|
|
446
|
-
};
|
|
447
|
-
//# sourceMappingURL=aider-adapter.js.map
|