@evomap/evolver-core 2.0.0-beta.17 → 2.0.0-beta.19

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.
Files changed (71) hide show
  1. package/dist/algo/candidateAssembly.js +21 -2
  2. package/dist/algo/cycleEngine.d.ts +12 -0
  3. package/dist/algo/cycleEngine.js +36 -4
  4. package/dist/algo/geneHealth.d.ts +2 -2
  5. package/dist/algo/geneHealth.js +5 -4
  6. package/dist/algo/geneSelection.d.ts +1 -1
  7. package/dist/algo/orchestrator.js +9 -2
  8. package/dist/assetstore/assetSidecarRecords.js +4 -0
  9. package/dist/assetstore/assetStoreHealth.js +41 -24
  10. package/dist/assetstore/assetStoreStorage.d.ts +1 -1
  11. package/dist/assetstore/assetStoreStorage.js +16 -7
  12. package/dist/assetstore/localJsonl.d.ts +2 -1
  13. package/dist/assetstore/localJsonl.js +54 -10
  14. package/dist/assetstore/provenance.d.ts +24 -0
  15. package/dist/assetstore/provenance.js +219 -12
  16. package/dist/assetstore/provider.d.ts +20 -1
  17. package/dist/assetstore/provider.js +34 -1
  18. package/dist/bootstrap/index.d.ts +2 -1
  19. package/dist/bootstrap/index.js +2 -1
  20. package/dist/bootstrap/v1EnvCompat.d.ts +110 -0
  21. package/dist/bootstrap/v1EnvCompat.js +256 -0
  22. package/dist/events/public.d.ts +1 -1
  23. package/dist/events/public.js +1 -1
  24. package/dist/events/reports.d.ts +2 -0
  25. package/dist/events/reports.js +4 -0
  26. package/dist/exec/autoExec.d.ts +18 -1
  27. package/dist/exec/autoExec.js +24 -9
  28. package/dist/exec/autonomousCycle.d.ts +19 -4
  29. package/dist/exec/autonomousCycle.js +63 -13
  30. package/dist/exec/claudeBridge.d.ts +25 -7
  31. package/dist/exec/claudeBridge.js +264 -29
  32. package/dist/exec/prompt.js +5 -1
  33. package/dist/exec/runnerRegistry.d.ts +68 -26
  34. package/dist/exec/runnerRegistry.js +307 -72
  35. package/dist/exec/selfPr.js +1 -7
  36. package/dist/feedback/envelope.d.ts +61 -0
  37. package/dist/feedback/envelope.js +168 -0
  38. package/dist/feedback/index.d.ts +1 -0
  39. package/dist/feedback/index.js +1 -0
  40. package/dist/hub/assetCallLog.d.ts +35 -1
  41. package/dist/hub/assetCallLog.js +124 -1
  42. package/dist/hub/bindings.d.ts +8 -1
  43. package/dist/hub/bindings.js +17 -6
  44. package/dist/hub/capability.d.ts +11 -1
  45. package/dist/hub/fake.d.ts +2 -2
  46. package/dist/hub/fake.js +1 -1
  47. package/dist/index.d.ts +3 -1
  48. package/dist/index.js +4 -1
  49. package/dist/mailbox/dispatch.d.ts +1 -1
  50. package/dist/mailbox/dispatch.js +22 -6
  51. package/dist/mailbox/envelope.d.ts +7 -1
  52. package/dist/mailbox/envelope.js +9 -2
  53. package/dist/mailbox/ipcServer.d.ts +10 -2
  54. package/dist/mailbox/ipcServer.js +163 -13
  55. package/dist/mailbox/store.d.ts +38 -2
  56. package/dist/mailbox/store.js +416 -27
  57. package/dist/signals/curriculum.d.ts +55 -0
  58. package/dist/signals/curriculum.js +202 -0
  59. package/dist/signals/expand.js +17 -6
  60. package/dist/signals/index.d.ts +2 -1
  61. package/dist/signals/index.js +2 -1
  62. package/dist/strategy/constraintAblation.js +115 -369
  63. package/dist/strategy/constraintAblationPredicates.d.ts +31 -0
  64. package/dist/strategy/constraintAblationPredicates.js +339 -0
  65. package/dist/trace/index.d.ts +2 -1
  66. package/dist/trace/index.js +2 -1
  67. package/dist/trace/proxyTurns.d.ts +31 -0
  68. package/dist/trace/proxyTurns.js +137 -0
  69. package/dist/verify/validation.d.ts +11 -1
  70. package/dist/verify/validation.js +31 -0
  71. package/package.json +4 -1
@@ -0,0 +1,256 @@
1
+ /**
2
+ * V1 → V2 environment variable compatibility layer (#698).
3
+ *
4
+ * Evolver V1 used several env var names that are no longer recognized by V2
5
+ * resolvers. When operators upgrade from V1 to V2 without updating their env
6
+ * files, these legacy names silently do nothing. This module detects the
7
+ * presence of deprecated V1 env vars and emits structured warnings so
8
+ * operators know which knobs to migrate.
9
+ *
10
+ * ## Migration map
11
+ *
12
+ * | V1 name | V2 equivalent | Notes |
13
+ * |---|---|---|
14
+ * | `OPENCLAW_WORKSPACE` | *(manual)* | Partial V2 support does not preserve V1 workspace and bridge semantics |
15
+ * | `EVOLVER_NO_PARENT_GIT` | *(none)* | V2 uses `EVOLVER_REPO_ROOT` or nearest Git root |
16
+ * | `EVOLVER_VERBOSE` | *(none)* | V2 has no global switch; opt in to feature-specific diagnostics manually |
17
+ * | `EVOLVER_AUTO_ISSUE` | *(none)* | V2 creates local drafts; submit requires explicit approval-gated flow |
18
+ * | `EVOLVER_ROLLBACK_MODE` | *(none)* | V2 uses worktree/snapshot/recovery policy |
19
+ * | `WORKER_ENABLED` | *(none)* | No merchant-worker resolver in V2 |
20
+ * | `WORKER_DOMAINS` | *(none)* | No merchant-worker resolver in V2 |
21
+ * | `WORKER_MAX_LOAD` | *(none)* | No merchant-worker resolver in V2 |
22
+ * | `EVOLVER_MEMORY_GRAPH_AUTO_ROTATE` | *(none)* | V2 LocalMemoryGraph always performs bounded maintenance |
23
+ * | `EVOLVER_MEMORY_GRAPH_MAX_SIZE_MB` | *(none)* | V2 LocalMemoryGraph uses a fixed 4 MiB active-file limit |
24
+ * | `EVOLVER_MEMORY_GRAPH_RETENTION_COUNT` | *(none)* | V2 LocalMemoryGraph retains three archives by default |
25
+ * | `GITHUB_TOKEN` | `GITHUB_TOKEN` | Already supported by V2 issue reporter and PR tooling |
26
+ *
27
+ * GITHUB_TOKEN is NOT deprecated — it's actively used. We only note its
28
+ * presence for observability.
29
+ */
30
+ /**
31
+ * The canonical table of V1 → V2 env var deprecations.
32
+ *
33
+ * Order matters for deterministic output; the scan iterates this list
34
+ * and checks each entry against the provided `env` object.
35
+ */
36
+ export const V1_DEPRECATION_TABLE = [
37
+ {
38
+ v1Name: 'OPENCLAW_WORKSPACE',
39
+ v2Equivalent: null,
40
+ migrationAction: 'manual',
41
+ guidance: 'OPENCLAW_WORKSPACE is still read directly by the pending-signals resolver. ' +
42
+ 'Do not copy it verbatim: identify the Git repo root before setting EVOLVER_REPO_ROOT, ' +
43
+ 'and migrate V1 bridge behavior separately.',
44
+ },
45
+ {
46
+ v1Name: 'EVOLVER_NO_PARENT_GIT',
47
+ v2Equivalent: null,
48
+ migrationAction: 'remove',
49
+ guidance: 'V2 uses EVOLVER_REPO_ROOT or the nearest Git root for workspace detection. ' +
50
+ 'Prefer EVOLVER_REPO_ROOT; EVOLVER_NO_PARENT_GIT has no V2 effect.',
51
+ },
52
+ {
53
+ v1Name: 'EVOLVER_VERBOSE',
54
+ v2Equivalent: null,
55
+ migrationAction: 'remove',
56
+ guidance: 'V2 has no global verbose switch. Remove this variable and opt in to a ' +
57
+ 'feature-specific flag such as EVOLVER_HOOK_VERBOSE=1 only when hook diagnostics are needed.',
58
+ },
59
+ {
60
+ v1Name: 'EVOLVER_AUTO_ISSUE',
61
+ v2Equivalent: null,
62
+ migrationAction: 'remove',
63
+ guidance: 'V2 does not auto-submit issues. It creates local drafts and requires ' +
64
+ 'explicit approval-gated submission via `evolver issue-report submit`.',
65
+ },
66
+ {
67
+ v1Name: 'EVOLVER_ROLLBACK_MODE',
68
+ v2Equivalent: null,
69
+ migrationAction: 'remove',
70
+ guidance: 'V2 uses worktree, snapshot, and recovery policy instead of legacy hard-reset. ' +
71
+ 'Remove this variable; it has no effect.',
72
+ },
73
+ {
74
+ v1Name: 'WORKER_ENABLED',
75
+ v2Equivalent: null,
76
+ migrationAction: 'remove',
77
+ guidance: 'V2 has no merchant-worker resolver. Remove this variable; it has no effect.',
78
+ },
79
+ {
80
+ v1Name: 'WORKER_DOMAINS',
81
+ v2Equivalent: null,
82
+ migrationAction: 'remove',
83
+ guidance: 'V2 has no merchant-worker resolver. Remove this variable; it has no effect.',
84
+ },
85
+ {
86
+ v1Name: 'WORKER_MAX_LOAD',
87
+ v2Equivalent: null,
88
+ migrationAction: 'remove',
89
+ guidance: 'V2 has no merchant-worker resolver. Remove this variable; it has no effect.',
90
+ },
91
+ {
92
+ v1Name: 'EVOLVER_MEMORY_GRAPH_AUTO_ROTATE',
93
+ v2Equivalent: null,
94
+ migrationAction: 'remove',
95
+ guidance: 'V2 LocalMemoryGraph always rotates and compacts automatically at its bounded defaults. ' +
96
+ 'There is no environment override; remove this variable.',
97
+ },
98
+ {
99
+ v1Name: 'EVOLVER_MEMORY_GRAPH_MAX_SIZE_MB',
100
+ v2Equivalent: null,
101
+ migrationAction: 'remove',
102
+ guidance: 'V2 LocalMemoryGraph uses a fixed 4 MiB active-file limit before rotation and compaction. ' +
103
+ 'There is no environment override; remove this variable.',
104
+ },
105
+ {
106
+ v1Name: 'EVOLVER_MEMORY_GRAPH_RETENTION_COUNT',
107
+ v2Equivalent: null,
108
+ migrationAction: 'remove',
109
+ guidance: 'V2 LocalMemoryGraph retains three archives by default and prunes older archives automatically. ' +
110
+ 'There is no environment override; remove this variable.',
111
+ },
112
+ ];
113
+ /**
114
+ * Scan the provided environment for deprecated V1 env vars and return
115
+ * structured results. This function is purely read-only and never mutates
116
+ * the `env` object.
117
+ *
118
+ * @param env The environment to scan (defaults to `process.env`).
119
+ * @returns Structured scan results including detected deprecations.
120
+ */
121
+ export function scanV1EnvCompat(env = typeof process !== 'undefined' ? process.env : {}) {
122
+ const detected = [];
123
+ for (const entry of V1_DEPRECATION_TABLE) {
124
+ const value = env[entry.v1Name];
125
+ if (value !== undefined && value.trim() !== '') {
126
+ detected.push(entry);
127
+ }
128
+ }
129
+ const githubTokenPresent = (env['GITHUB_TOKEN'] !== undefined && env['GITHUB_TOKEN'].trim() !== '') ||
130
+ (env['GH_TOKEN'] !== undefined && env['GH_TOKEN'].trim() !== '');
131
+ return { detected, githubTokenPresent };
132
+ }
133
+ /** Resolve the migration action while preserving compatibility with pre-action table entries. */
134
+ export function resolveV1EnvMigrationAction(entry) {
135
+ return entry.migrationAction ?? (entry.v2Equivalent ? 'map' : 'remove');
136
+ }
137
+ /**
138
+ * Emit deprecation warnings to the provided logger for all detected
139
+ * deprecated V1 env vars. This is the primary integration point for
140
+ * entrypoints that want human-readable console output.
141
+ *
142
+ * @param result The scan result from `scanV1EnvCompat`.
143
+ * @param warn Logger function (defaults to `console.warn`).
144
+ */
145
+ export function emitV1DeprecationWarnings(result, warn = (msg) => console.warn(msg)) {
146
+ for (const dep of result.detected) {
147
+ const action = resolveV1EnvMigrationAction(dep);
148
+ const v2Hint = action === 'map' && dep.v2Equivalent
149
+ ? ` Use ${dep.v2Equivalent} instead.`
150
+ : '';
151
+ const lead = action === 'manual'
152
+ ? `Deprecated env var ${dep.v1Name} is set and requires manual migration.`
153
+ : action === 'map'
154
+ ? `Deprecated env var ${dep.v1Name} is set.`
155
+ : `Deprecated env var ${dep.v1Name} is set but has no V2 resolver.`;
156
+ warn(`[evolver:v1-compat] ${lead}${v2Hint} ${dep.guidance}`);
157
+ }
158
+ // githubTokenPresent is intentionally not warned: GITHUB_TOKEN/GH_TOKEN remain
159
+ // supported by V2 issue reporter / PR tooling. Flag retained for callers/tests.
160
+ }
161
+ /**
162
+ * Convenience function: scan + emit in one call. Intended for early
163
+ * bootstrap in CLI/proxy/MCP entrypoints.
164
+ *
165
+ * @param env The environment to scan.
166
+ * @param warn Logger function.
167
+ * @returns The scan result (useful for programmatic inspection).
168
+ */
169
+ export function checkV1EnvCompat(env = typeof process !== 'undefined' ? process.env : {}, warn) {
170
+ const result = scanV1EnvCompat(env);
171
+ if (result.detected.length > 0) {
172
+ emitV1DeprecationWarnings(result, warn);
173
+ }
174
+ return result;
175
+ }
176
+ /**
177
+ * Build an offline V1→V2 env translation report.
178
+ * Does not write files; callers decide how to present or apply suggestions.
179
+ * Raw values are retained only as v2Value for mappable non-secret keys.
180
+ * GITHUB_TOKEN and GH_TOKEN are reported as keep without retaining their values.
181
+ */
182
+ export function translateV1Env(env = typeof process !== 'undefined' ? process.env : {}) {
183
+ const suggestions = [];
184
+ for (const entry of V1_DEPRECATION_TABLE) {
185
+ const raw = env[entry.v1Name];
186
+ if (raw === undefined || raw.trim() === '')
187
+ continue;
188
+ const action = resolveV1EnvMigrationAction(entry);
189
+ if (action === 'map') {
190
+ if (!entry.v2Equivalent)
191
+ throw new Error(`missing V2 equivalent for ${entry.v1Name}`);
192
+ suggestions.push({
193
+ v1Name: entry.v1Name,
194
+ action: 'map',
195
+ v2Name: entry.v2Equivalent,
196
+ v2Value: raw,
197
+ guidance: entry.guidance,
198
+ });
199
+ }
200
+ else {
201
+ suggestions.push({
202
+ v1Name: entry.v1Name,
203
+ action,
204
+ guidance: entry.guidance,
205
+ });
206
+ }
207
+ }
208
+ const githubTokenPresent = (env['GITHUB_TOKEN'] !== undefined && env['GITHUB_TOKEN'].trim() !== '') ||
209
+ (env['GH_TOKEN'] !== undefined && env['GH_TOKEN'].trim() !== '');
210
+ if (githubTokenPresent) {
211
+ const key = env['GITHUB_TOKEN']?.trim() ? 'GITHUB_TOKEN' : 'GH_TOKEN';
212
+ suggestions.push({
213
+ v1Name: key,
214
+ action: 'keep',
215
+ v2Name: key,
216
+ guidance: 'GITHUB_TOKEN/GH_TOKEN remain valid for issue submit and gh-based PR tooling. ' +
217
+ 'Prefer GH_TOKEN when using the GitHub CLI.',
218
+ });
219
+ }
220
+ return {
221
+ suggestions,
222
+ detectedCount: suggestions.filter((s) => s.action !== 'keep').length,
223
+ mappableCount: suggestions.filter((s) => s.action === 'map').length,
224
+ manualCount: suggestions.filter((s) => s.action === 'manual').length,
225
+ removableCount: suggestions.filter((s) => s.action === 'remove').length,
226
+ githubTokenPresent,
227
+ };
228
+ }
229
+ /** Human-readable report (never prints secret-looking values; only key names + actions). */
230
+ export function formatV1EnvTranslationReport(report) {
231
+ const lines = [
232
+ 'V1 → V2 env translation (offline, no writes):',
233
+ ` detected_deprecated=${report.detectedCount} map=${report.mappableCount} manual=${report.manualCount} remove=${report.removableCount} github_token=${report.githubTokenPresent ? 'present' : 'absent'}`,
234
+ ];
235
+ if (report.suggestions.length === 0) {
236
+ lines.push(' (no V1-only knobs detected)');
237
+ return `${lines.join('\n')}\n`;
238
+ }
239
+ for (const s of report.suggestions) {
240
+ if (s.action === 'map') {
241
+ lines.push(` map ${s.v1Name} → ${s.v2Name}`);
242
+ }
243
+ else if (s.action === 'manual') {
244
+ lines.push(` review ${s.v1Name} (manual migration required)`);
245
+ }
246
+ else if (s.action === 'remove') {
247
+ lines.push(` drop ${s.v1Name} (no V2 resolver)`);
248
+ }
249
+ else {
250
+ lines.push(` keep ${s.v1Name} (still valid in V2)`);
251
+ }
252
+ lines.push(` ${s.guidance}`);
253
+ }
254
+ lines.push(' See docs/migration-guide.md and docs/config-env.md § V1 变量不再是 V2 的有效配置.');
255
+ return `${lines.join('\n')}\n`;
256
+ }
@@ -10,7 +10,7 @@ export type { RootEventArchiveOptions, RootEventArchivePlan, RootEventArchiveRes
10
10
  export { rootEventsPath, evomapHome, mvDir, personalityStatePath, assetsDir, materialDir, materialStorePath, materialWatermarkPath, tracesDir, assetCallLogPath, learningTraceDir } from './paths.js';
11
11
  export { EVENT_SCHEMA_VERSION } from './eventSchema.js';
12
12
  export type { RootEvent, RawEvent, HumanNarrative, Actor, Replayability } from './eventSchema.js';
13
- export { readEvents, statusReport, listCycles, showCycle, listTriggers, dailySummary, buildNarrativeSnapshot, NARRATIVE_DEFAULT_LIMIT, NARRATIVE_MAX_LIMIT, } from './reports.js';
13
+ export { readEvents, statusReport, listCycles, showCycle, listTriggers, dailySummary, dailyCapsuleCount, buildNarrativeSnapshot, NARRATIVE_DEFAULT_LIMIT, NARRATIVE_MAX_LIMIT, } from './reports.js';
14
14
  export { buildRetentionReport, defaultMaterialCursorPath, defaultMaterialCursorPaths, RETENTION_DEFAULT_MAX_MATERIAL_BYTES, RETENTION_DEFAULT_MAX_MATERIAL_RECORDS, RETENTION_DEFAULT_MAX_ROOT_BYTES, RETENTION_DEFAULT_MAX_ROOT_EVENTS, RETENTION_DEFAULT_ROOT_TAIL_EVENTS, RETENTION_DEFAULT_WATCH_RATIO, } from './retention.js';
15
15
  export type { ReportEvent, StatusReport, CycleSummary, CycleTimeline, TriggerRow, DailySummary, NarrativeEntry, NarrativeOutcome, NarrativeSnapshot, NarrativeSnapshotOptions, } from './reports.js';
16
16
  export type { MaterialRetentionSnapshot, RetentionReport, RetentionReportOptions, RetentionState, RetentionThresholds, RootRetentionSnapshot, } from './retention.js';
@@ -5,5 +5,5 @@ export { LineTooLargeError, MAX_LINE_BYTES } from './eventStore.js';
5
5
  export { ArchiveSegmentConflictError, InvalidRootEventArchiveError, InvalidRootEventLogError, RootEventHistoryGapError, archiveRootEvents, inspectRootEventArchive, planRootEventArchive, readRootEventHistory, rootEventArchiveDir, rootEventArchiveSegmentName, validateRootEventHistory, ROOT_EVENT_ARCHIVE_DEFAULT_KEEP_EVENTS, } from './eventArchive.js';
6
6
  export { rootEventsPath, evomapHome, mvDir, personalityStatePath, assetsDir, materialDir, materialStorePath, materialWatermarkPath, tracesDir, assetCallLogPath, learningTraceDir } from './paths.js';
7
7
  export { EVENT_SCHEMA_VERSION } from './eventSchema.js';
8
- export { readEvents, statusReport, listCycles, showCycle, listTriggers, dailySummary, buildNarrativeSnapshot, NARRATIVE_DEFAULT_LIMIT, NARRATIVE_MAX_LIMIT, } from './reports.js';
8
+ export { readEvents, statusReport, listCycles, showCycle, listTriggers, dailySummary, dailyCapsuleCount, buildNarrativeSnapshot, NARRATIVE_DEFAULT_LIMIT, NARRATIVE_MAX_LIMIT, } from './reports.js';
9
9
  export { buildRetentionReport, defaultMaterialCursorPath, defaultMaterialCursorPaths, RETENTION_DEFAULT_MAX_MATERIAL_BYTES, RETENTION_DEFAULT_MAX_MATERIAL_RECORDS, RETENTION_DEFAULT_MAX_ROOT_BYTES, RETENTION_DEFAULT_MAX_ROOT_EVENTS, RETENTION_DEFAULT_ROOT_TAIL_EVENTS, RETENTION_DEFAULT_WATCH_RATIO, } from './retention.js';
@@ -61,6 +61,8 @@ export interface DailySummary {
61
61
  suppressedFailures: number;
62
62
  }
63
63
  export declare function dailySummary(evts: readonly ReportEvent[], dayPrefix: string): DailySummary;
64
+ /** 统计指定日期前缀的 capsule.produced 事件数. */
65
+ export declare function dailyCapsuleCount(evts: readonly ReportEvent[], dayPrefix: string): number;
64
66
  export type NarrativeOutcome = 'success' | 'failed' | 'inert' | 'unknown';
65
67
  export interface NarrativeEntry {
66
68
  seq: number;
@@ -85,6 +85,10 @@ export function dailySummary(evts, dayPrefix) {
85
85
  suppressedFailures,
86
86
  };
87
87
  }
88
+ /** 统计指定日期前缀的 capsule.produced 事件数. */
89
+ export function dailyCapsuleCount(evts, dayPrefix) {
90
+ return evts.filter((e) => e.type === 'capsule.produced' && e.ts.startsWith(dayPrefix)).length;
91
+ }
88
92
  export const NARRATIVE_DEFAULT_LIMIT = 30;
89
93
  export const NARRATIVE_MAX_LIMIT = 200;
90
94
  const NARRATIVE_EVENTS = new Set([
@@ -4,13 +4,14 @@ import type { ReviewLedger } from '../assetstore/reviewLedger.js';
4
4
  import type { GeneCandidateInput } from '../algo/geneSelection.js';
5
5
  import type { CycleEngine, ExecutionFailureKind, SolidifyPermitGate } from '../algo/cycleEngine.js';
6
6
  import { type AutonomousSafety } from './autonomousCycle.js';
7
- import type { GitRunner, ValidateHook } from './claudeBridge.js';
7
+ import { type GitRunner, type ValidateHook } from './claudeBridge.js';
8
8
  import type { AgentRunner } from './runnerRegistry.js';
9
9
  import { type OpenPrLister } from './openPrRegistry.js';
10
10
  import type { ReuseOutcomeSummary, ReuseOutcomeEvent } from '../ops/reuseOutcomes.js';
11
11
  import type { PersonalityStore } from '../personality/store.js';
12
12
  import type { MemoryGraphProvider } from '../algo/memoryGraph.js';
13
13
  import { type LearningPacketSink, type TraceSink } from '../trace/learningTrace.js';
14
+ import type { TraceReadOptions } from '../trace/trajectoryExport.js';
14
15
  export interface AutoExecTask {
15
16
  id: string;
16
17
  repo: string;
@@ -124,6 +125,22 @@ export interface AutoExecDeps {
124
125
  traceSink?: TraceSink;
125
126
  /** Hub packet sourceRepo column; default 'evolver-v2'. */
126
127
  sourceRepo?: string;
128
+ /**
129
+ * Proxy llm_turn fold (Learning Ops slice 5): when set, after the cycle (and BEFORE run.completed, so
130
+ * sequence order holds) the run's wall-clock window of proxy trace records is read from `dir`
131
+ * (llm-trace-*.jsonl day-files) and folded into the recorder via recordLlmTurn — real per-request
132
+ * model.called + tool.called/tool.failed detail instead of only the bridge's coarse spawn event.
133
+ * Correlation is the time window + session-first-turn heuristic (see trace/proxyTurns.ts). Best-effort:
134
+ * a missing dir / unreadable file / no proxy degrades to zero folded turns, never a verdict change.
135
+ */
136
+ proxyTraces?: {
137
+ /** Proxy trace day-file dir (events/paths.ts tracesDir()). */
138
+ dir: string;
139
+ /** Decryption material for encrypted trace envelopes (allowPartial is always forced on). */
140
+ readOptions?: TraceReadOptions;
141
+ /** Injected clock for deterministic tests. Default Date.now. */
142
+ now?: () => number;
143
+ };
127
144
  };
128
145
  }
129
146
  export interface ForcedGeneFields {
@@ -1,21 +1,16 @@
1
- // Autonomous-exec task runner — the productized per-task kernel a resident daemon polls (the deployment form
2
- // of the scratch autoexec daemon). One task → one fully-hardened evolution cycle, with a deny-by-default
3
- // allowlist pre-check that yields a clean "refused" verdict WITHOUT running anything when the repo is not
4
- // allowlisted. Secure by construction: the execute is built by makeSafeExecute (all six controls); callers
5
- // cannot bypass the safety composition.
6
- import { resolve as resolvePath, sep } from 'node:path';
7
1
  import { normalizeForPut } from '../assetstore/provider.js';
8
2
  import { ingestUntrusted } from '../assetstore/provenance.js';
9
3
  import { mergePendingSignalsForStore } from '../assetstore/pendingSignals.js';
10
4
  import { intakeGene } from '../algo/geneIntake.js';
11
5
  import { runEvolutionCycle } from '../algo/orchestrator.js';
12
6
  import { makeSafeExecute, makeTrustedGeneResolver } from './autonomousCycle.js';
7
+ import { isWithinRoot } from './claudeBridge.js';
13
8
  import { findSignalHints } from './openPrRegistry.js';
14
9
  import { AgentRunTraceRecorder, buildLearningPacketDraft } from '../trace/learningTrace.js';
10
+ import { collectRunLlmTurns } from '../trace/proxyTurns.js';
15
11
  /** Same path-containment as the bridge guard — used here to refuse before running anything (clean verdict). */
16
12
  function withinAllowlist(repo, roots) {
17
- const c = resolvePath(repo);
18
- return roots.some((root) => { const r = resolvePath(root); return c === r || c.startsWith(r.endsWith(sep) ? r : r + sep); });
13
+ return roots.some((root) => isWithinRoot(repo, root));
19
14
  }
20
15
  function cleanForcedGeneId(value) {
21
16
  if (typeof value !== 'string')
@@ -147,6 +142,10 @@ export async function runAutoExecTask(deps, rawTask, safety) {
147
142
  ...(deps.learningTrace.traceSink ? { sink: deps.learningTrace.traceSink } : {}),
148
143
  })
149
144
  : undefined;
145
+ // Wall-clock window of this run, used to correlate the proxy's llm_turn records (slice 5). Captured
146
+ // unconditionally-cheaply only when the fold is configured.
147
+ const proxyTraceClock = deps.learningTrace?.proxyTraces?.now ?? Date.now;
148
+ let runStartMs = 0;
150
149
  try {
151
150
  traceRecorder?.runStarted({ taskSummary: task.expectedEffect, signals: cycleSignals, metadata: { repo: task.repo, target: task.target } });
152
151
  }
@@ -159,6 +158,7 @@ export async function runAutoExecTask(deps, rawTask, safety) {
159
158
  hubCandidates = [];
160
159
  }
161
160
  }
161
+ runStartMs = deps.learningTrace?.proxyTraces ? proxyTraceClock() : 0;
162
162
  // evaluation fill-in (slice 6): the validate hook is the run's external verifier (sandboxed validation
163
163
  // commands), so its result — when it actually RAN — becomes the packet's evaluation.verification
164
164
  // (verifier 'automated_test'). Observation is a pass-through wrapper: the hook's result reaches the
@@ -224,6 +224,18 @@ export async function runAutoExecTask(deps, rawTask, safety) {
224
224
  const status = res.finalStage === 'solidified' ? 'solidified' : res.finalStage === 'failed' ? 'failed' : 'innovated';
225
225
  const cap = res.capsule;
226
226
  if (traceRecorder && deps.learningTrace) {
227
+ // Proxy llm_turn fold (slice 5): fold the run window's per-request turns BEFORE run.completed so the
228
+ // trajectory stays sequence-ordered (model/tool detail inside the run, completion last). Own try — a
229
+ // throwing sink mid-fold must not cost the run its completion event or packet draft.
230
+ try {
231
+ const proxyTraces = deps.learningTrace.proxyTraces;
232
+ if (proxyTraces) {
233
+ const turns = collectRunLlmTurns(proxyTraces.dir, { startMs: runStartMs, endMs: proxyTraceClock() }, proxyTraces.readOptions ? { readOptions: proxyTraces.readOptions } : {});
234
+ for (const turn of turns)
235
+ traceRecorder.recordLlmTurn(turn);
236
+ }
237
+ }
238
+ catch { /* observability only */ }
227
239
  try {
228
240
  traceRecorder.runCompleted({
229
241
  status: res.finalStage === 'solidified' ? 'success' : 'failed',
@@ -236,7 +248,10 @@ export async function runAutoExecTask(deps, rawTask, safety) {
236
248
  sourceRepo: deps.learningTrace.sourceRepo ?? 'evolver-v2',
237
249
  taskSummary: task.expectedEffect,
238
250
  signals: cycleSignals,
239
- environment: { repo: task.repo, runner: safety.runner ?? 'claude' },
251
+ environment: {
252
+ repo: task.repo,
253
+ runner: safety.runner ?? (deps.agent ? 'claude' : 'codex'),
254
+ },
240
255
  ...(observedVerification !== undefined ? { verification: observedVerification } : {}),
241
256
  }));
242
257
  }
@@ -4,7 +4,7 @@ import type { ReviewLedger } from '../assetstore/reviewLedger.js';
4
4
  import type { Mutation } from '../wire/index.js';
5
5
  import type { GeneDecision } from '../algo/geneSelection.js';
6
6
  import type { ExecutionResult } from '../algo/cycleEngine.js';
7
- import { type GeneResolver, type ValidateHook, type AgentRunnerOptions, type RunnerName, type AgentRunner, type GitRunner } from './claudeBridge.js';
7
+ import { type GeneResolver, type ValidateHook, type AgentRunnerOptions, type AgentSessionResume, type RunnerName, type AgentRunner, type GitRunner } from './claudeBridge.js';
8
8
  import type { PersonalityStore } from '../personality/store.js';
9
9
  import type { AgentRunTraceRecorder } from '../trace/learningTrace.js';
10
10
  /**
@@ -19,9 +19,11 @@ export declare function makeTrustedGeneResolver(store: AssetStoreProvider, prove
19
19
  export interface AutonomousSafety {
20
20
  /** Deny-by-default repo allowlist (#41). Required — an empty array refuses everything. */
21
21
  allowedRoots: readonly string[];
22
- /** Which built-in runner (#66). Default 'claude'. The same safety controls wrap whichever runner. cursor is a scaffold (unverified). */
22
+ /** Which built-in runner (#66). Default 'codex'. The same safety controls wrap whichever runner. cursor is a scaffold (unverified). */
23
23
  runner?: RunnerName;
24
- /** Agent runner options (#38/#40). Default: bounded skip-permissions (Read/Edit/Write). */
24
+ /** Explicit opt-in to continue a native session owned by the selected runner. */
25
+ resume?: AgentSessionResume;
26
+ /** Agent runner options (#38/#40). Default: no pre-authorized host access. */
25
27
  agentOptions?: AgentRunnerOptions;
26
28
  /** Strip evolver/hub secrets from the agent env (#42). Default true. */
27
29
  scrubEnv?: boolean;
@@ -33,10 +35,23 @@ export interface AutonomousSafety {
33
35
  /** Cooperative cancellation propagated to the spawned runner process tree. */
34
36
  signal?: AbortSignal;
35
37
  }
38
+ export declare class UnsupportedCursorAutonomousIsolationError extends Error {
39
+ constructor();
40
+ }
41
+ export declare class UnsupportedAutonomousHostAccessError extends Error {
42
+ constructor();
43
+ }
44
+ export declare class UnsupportedAutonomousClaudeRunnerError extends Error {
45
+ constructor();
46
+ }
47
+ export declare class UnsupportedAutonomousCodexRunnerError extends Error {
48
+ constructor();
49
+ }
50
+ export declare function resolveAutonomousAgentOptions(runner: RunnerName | undefined, isolation: AutonomousSafety['isolation'], overrides?: AgentRunnerOptions): AgentRunnerOptions;
36
51
  /**
37
52
  * Build the fully-hardened `execute` for an autonomous run against `repo`. Composes every exec-bridge control
38
53
  * with secure defaults so they can't be forgotten piecemeal: deny-by-default allowedRoots (#41) + worktree
39
- * isolation (#43) + env scrub (#42) + bounded skip-permissions agent (#38/#40) + trusted-gene gate fed by
54
+ * isolation (#43) + env scrub (#42) + non-bypassing agent permissions (#38/#40) + trusted-gene gate fed by
40
55
  * provenance (#45/#30). Pass the result as runEvolutionCycle's `execute`.
41
56
  */
42
57
  export declare function makeSafeExecute(repo: string, store: AssetStoreProvider, safety: AutonomousSafety, opts?: {
@@ -1,4 +1,4 @@
1
- import { makeClaudeExecBridge } from './claudeBridge.js';
1
+ import { CLAUDE_SAFE_AUTONOMOUS_TOOLS, hasBoundedClaudeFileAccess, makeClaudeExecBridge, validateAgentSessionResume } from './claudeBridge.js';
2
2
  const asStrings = (v) => (Array.isArray(v) ? v.filter((x) => typeof x === 'string') : []);
3
3
  /**
4
4
  * Resolve a gene's strategy from the store and whether it is safe to EMBED into an autonomous agent's prompt —
@@ -33,19 +33,19 @@ export function makeTrustedGeneResolver(store, provenance, review, includeProbat
33
33
  return info;
34
34
  };
35
35
  }
36
- // Claude's safe default: bypass the permission prompts but BOUND the agent to file edits (#38/#40).
37
- const CLAUDE_DEFAULT_AGENT_OPTIONS = { skipPermissions: true, allowedTools: ['Read', 'Edit', 'Write'] };
38
- // Codex's safe default is SANDBOXED (no skip): the autonomous-bypass flag is gated until verified (#66), so
39
- // skipPermissions would throw at construction. `codex exec` still runs non-interactively under its own sandbox.
36
+ // Keep Claude's project path checks enabled and expose only file/search tools.
37
+ // Bash, web, task, and MCP tools remain unavailable.
38
+ const CLAUDE_DEFAULT_AGENT_OPTIONS = {
39
+ permissionMode: 'acceptEdits',
40
+ tools: CLAUDE_SAFE_AUTONOMOUS_TOOLS,
41
+ };
42
+ // Codex workspace-write prevents host writes but does not contain host reads. It therefore remains explicit.
40
43
  const CODEX_DEFAULT_AGENT_OPTIONS = {};
41
- // Cursor's safe default keeps skip OFF (#66 SCAFFOLD). The runner is flag-confirmed via `cursor-agent --help`
42
- // but not yet run-verified end to end (needs an authed cursor-agent). The `-p --force --trust` bypass would
43
- // auto-approve shell+write, but Cursor has no verified per-run allowlist/sandbox mapping yet, so skipPermissions
44
- // is refused outright. Use default cursor with worktree isolation until the CLI is run-verified.
44
+ // Cursor has no verified host filesystem/network sandbox. Worktree isolation cannot safely authorize `--trust`.
45
45
  const CURSOR_DEFAULT_AGENT_OPTIONS = {};
46
46
  // Gemini's verified safe default is `--approval-mode auto_edit`; shell remains gated and --yolo is refused.
47
47
  const GEMINI_DEFAULT_AGENT_OPTIONS = {};
48
- /** Per-runner safe default agent options claude bypasses-with-bounds; codex + cursor stay non-bypassing (codex sandboxed; cursor skip refused, #66). */
48
+ /** Per-runner safe default agent options. Host permission bypass is never enabled by default. */
49
49
  function defaultAgentOptions(runner) {
50
50
  if (runner === 'codex')
51
51
  return CODEX_DEFAULT_AGENT_OPTIONS;
@@ -55,13 +55,62 @@ function defaultAgentOptions(runner) {
55
55
  return GEMINI_DEFAULT_AGENT_OPTIONS;
56
56
  return CLAUDE_DEFAULT_AGENT_OPTIONS;
57
57
  }
58
+ export class UnsupportedCursorAutonomousIsolationError extends Error {
59
+ constructor() {
60
+ super('Autonomous Cursor cannot use worktree isolation because --trust grants host filesystem and network access');
61
+ this.name = 'UnsupportedCursorAutonomousIsolationError';
62
+ }
63
+ }
64
+ export class UnsupportedAutonomousHostAccessError extends Error {
65
+ constructor() {
66
+ super('Autonomous Claude requires project-scoped acceptEdits with bounded file/search tools; host access bypass or unsupported tools are forbidden because a worktree is not a security boundary');
67
+ this.name = 'UnsupportedAutonomousHostAccessError';
68
+ }
69
+ }
70
+ export class UnsupportedAutonomousClaudeRunnerError extends Error {
71
+ constructor() {
72
+ super('Autonomous claude is disabled because its headless edit mode has no verified host filesystem sandbox; inject an externally sandboxed agent or explicitly select a runner after reviewing its host-access boundary');
73
+ this.name = 'UnsupportedAutonomousClaudeRunnerError';
74
+ }
75
+ }
76
+ export class UnsupportedAutonomousCodexRunnerError extends Error {
77
+ constructor() {
78
+ super('Autonomous codex is disabled because workspace-write does not restrict host filesystem reads; inject an externally sandboxed agent');
79
+ this.name = 'UnsupportedAutonomousCodexRunnerError';
80
+ }
81
+ }
82
+ export function resolveAutonomousAgentOptions(runner, isolation, overrides) {
83
+ const options = {
84
+ ...defaultAgentOptions(runner),
85
+ ...(isolation !== 'none' ? { workspaceTrust: 'isolated-worktree' } : {}),
86
+ ...(overrides ?? {}),
87
+ };
88
+ if (runner === 'cursor' && isolation !== 'none') {
89
+ throw new UnsupportedCursorAutonomousIsolationError();
90
+ }
91
+ const effectiveRunner = runner ?? 'claude';
92
+ const unsafeClaudeOptions = effectiveRunner === 'claude' && !hasBoundedClaudeFileAccess(options);
93
+ if (unsafeClaudeOptions) {
94
+ throw new UnsupportedAutonomousHostAccessError();
95
+ }
96
+ return options;
97
+ }
58
98
  /**
59
99
  * Build the fully-hardened `execute` for an autonomous run against `repo`. Composes every exec-bridge control
60
100
  * with secure defaults so they can't be forgotten piecemeal: deny-by-default allowedRoots (#41) + worktree
61
- * isolation (#43) + env scrub (#42) + bounded skip-permissions agent (#38/#40) + trusted-gene gate fed by
101
+ * isolation (#43) + env scrub (#42) + non-bypassing agent permissions (#38/#40) + trusted-gene gate fed by
62
102
  * provenance (#45/#30). Pass the result as runEvolutionCycle's `execute`.
63
103
  */
64
104
  export function makeSafeExecute(repo, store, safety, opts = {}) {
105
+ const runner = safety.runner ?? (opts.agent ? undefined : 'claude');
106
+ if (safety.resume && runner)
107
+ validateAgentSessionResume(safety.resume, runner);
108
+ if (!opts.agent && runner === 'claude') {
109
+ throw new UnsupportedAutonomousClaudeRunnerError();
110
+ }
111
+ if (!opts.agent && runner === 'codex') {
112
+ throw new UnsupportedAutonomousCodexRunnerError();
113
+ }
65
114
  return makeClaudeExecBridge({
66
115
  cwd: repo,
67
116
  enabled: true,
@@ -69,11 +118,12 @@ export function makeSafeExecute(repo, store, safety, opts = {}) {
69
118
  ...(opts.git ? { git: opts.git } : {}),
70
119
  ...(opts.traceRecorder ? { traceRecorder: opts.traceRecorder } : {}),
71
120
  allowedRoots: safety.allowedRoots,
72
- ...(safety.runner ? { runner: safety.runner } : {}),
121
+ ...(runner ? { runner } : {}),
122
+ ...(safety.resume ? { resume: safety.resume } : {}),
73
123
  ...(safety.isolation === 'none' ? {} : { isolation: 'worktree' }),
74
124
  scrubEnv: safety.scrubEnv ?? true,
75
125
  requireTrustedGene: safety.requireTrustedGene ?? true,
76
- agentOptions: safety.agentOptions ?? defaultAgentOptions(safety.runner),
126
+ agentOptions: resolveAutonomousAgentOptions(runner, safety.isolation, safety.agentOptions),
77
127
  ...(safety.timeoutMs !== undefined ? { timeoutMs: safety.timeoutMs } : {}),
78
128
  ...(safety.signal ? { signal: safety.signal } : {}),
79
129
  resolveGene: makeTrustedGeneResolver(store, opts.provenance, opts.review, opts.includeProbation ?? false),
@@ -4,9 +4,9 @@ import type { ExecutionResult } from '../algo/cycleEngine.js';
4
4
  import { type GeneStrategyInfo } from './prompt.js';
5
5
  import type { PersonalityStore } from '../personality/store.js';
6
6
  import type { AgentRunTraceRecorder } from '../trace/learningTrace.js';
7
- import { type AgentRunner, type AgentRunnerOptions, type RunnerName } from './runnerRegistry.js';
8
- export { resolveSpawnCommand, spawnCapture, DEFAULT_MAX_CAPTURE_BYTES, UnboundedSkipPermissionsError, UnsupportedCursorSkipPermissionsError, UnsupportedGeminiPermissionOptionsError, claudeRunnerArgs, makeClaudeHeadlessRunner, claudeHeadlessRunner, codexRunnerArgs, makeCodexHeadlessRunner, cursorRunnerArgs, makeCursorHeadlessRunner, getRunnerSpec, geminiRunnerArgs, makeGeminiHeadlessRunner, classifyGeminiRunnerResult, } from './runnerRegistry.js';
9
- export type { AgentRunContext, AgentRunResult, AgentRunner, RunnerName, AgentRunnerOptions, ClaudeRunnerOptions, CodexRunnerOptions, AgentRunnerSpec, } from './runnerRegistry.js';
7
+ import { type AgentRunner, type AgentRunnerOptions, type AgentSessionResume, type RunnerName } from './runnerRegistry.js';
8
+ export { resolveSpawnCommand, spawnCapture, DEFAULT_MAX_CAPTURE_BYTES, MAX_AGENT_SESSION_ID_CHARS, AgentSessionResumeError, validateAgentSessionResume, UnboundedSkipPermissionsError, UnsupportedCodexPermissionOptionsError, UnsupportedCursorAllowedToolsError, UnsupportedCursorSkipPermissionsError, UnsupportedCursorWorkspaceTrustError, UnsupportedGeminiPermissionOptionsError, claudeRunnerArgs, makeClaudeHeadlessRunner, claudeHeadlessRunner, codexRunnerArgs, makeCodexHeadlessRunner, cursorRunnerArgs, makeCursorHeadlessRunner, getRunnerSpec, hasBoundedClaudeFileAccess, CLAUDE_SAFE_AUTONOMOUS_TOOLS, geminiRunnerArgs, makeGeminiHeadlessRunner, classifyGeminiRunnerResult, } from './runnerRegistry.js';
9
+ export type { AgentRunContext, AgentRunResult, AgentRunner, RunnerName, AgentRunnerOptions, ClaudeRunnerOptions, CodexRunnerOptions, AgentRunnerSpec, AgentSessionResume, } from './runnerRegistry.js';
10
10
  export interface GitRunnerOptions {
11
11
  processSignalMode?: 'cancel' | 'ignore';
12
12
  }
@@ -32,6 +32,8 @@ export interface ExecBridgeOptions {
32
32
  agent?: AgentRunner;
33
33
  /** Which built-in runner to use when `agent` is not injected (#66). Default 'claude' (byte-identical). cursor is a scaffold (unverified runner). */
34
34
  runner?: RunnerName;
35
+ /** Explicit native session continuation. Must name the same runner selected above. */
36
+ resume?: AgentSessionResume;
35
37
  /** When `agent` is not injected, options for the built-in headless runner (permission bypass / allowed tools / model). */
36
38
  agentOptions?: AgentRunnerOptions;
37
39
  /** Default: spawns `git`. Inject a fake in tests. */
@@ -105,19 +107,33 @@ export declare class ExecBridgeForbiddenError extends Error {
105
107
  * Thrown when a FULL-ACCESS (or unverified) agent run is requested without worktree isolation. The throwaway
106
108
  * worktree is the containment WE control — allowedRoots gates the cwd but cannot stop an auto-approved or
107
109
  * unsandboxed process writing/running outside it. Gated runners:
108
- * - codex with skipPermissions `--sandbox danger-full-access` (no inner OS sandbox). Without skip it stays
109
- * workspace-write, so only the skip path is gated.
110
+ * - codex permission overrides are refused by the runner because it has no enforceable per-tool allowlist.
110
111
  * - cursor default → gated UNCONDITIONALLY. cursor-agent base `-p` already documents write+shell access, cursor
111
112
  * skipPermissions is rejected by the runner layer, and the scaffold remains unverified (#66/#181) — so default
112
113
  * cursor still needs the wrapper worktree rather than risk mutating the real tree.
113
- * claude is exempt: its skip is bounded by --allowedTools (finding #80).
114
+ * Claude is fail-closed below because a tool-name allowlist does not constrain absolute filesystem paths.
114
115
  */
115
116
  export declare class UnsandboxedFullAccessRequiresIsolationError extends Error {
116
117
  constructor();
117
118
  }
119
+ export declare class WorkspaceTrustRequiresIsolationError extends Error {
120
+ readonly code = "WORKSPACE_TRUST_REQUIRES_ISOLATION";
121
+ constructor();
122
+ }
123
+ export declare class UnsupportedCursorBuiltInRunnerError extends Error {
124
+ constructor();
125
+ }
126
+ export declare class UnsupportedClaudeBuiltInRunnerError extends Error {
127
+ constructor();
128
+ }
129
+ export declare class UnsupportedCodexBuiltInRunnerError extends Error {
130
+ constructor();
131
+ }
118
132
  export declare class UnsafeWorktreePathError extends Error {
119
133
  constructor(reason: string);
120
134
  }
135
+ /** Whether `child` is the same as, or nested under, `root`, including filesystem symlink resolution. */
136
+ export declare function isWithinRoot(child: string, root: string): boolean;
121
137
  /**
122
138
  * Whitelist-filter `env` for a spawned agent/tool: keep ONLY the minimal runtime env + the caller-declared
123
139
  * extras (the runner's own auth via allowPrefixes/allowKeys); drop everything else. Fail-safe by construction —
@@ -134,4 +150,6 @@ export declare const defaultGitPatchWriter: GitPatchWriter;
134
150
  * Build the `execute` function CycleEngine/runEvolutionCycle consume. Default-off: throws
135
151
  * ExecBridgeDisabledError on first call unless enabled.
136
152
  */
137
- export declare function makeClaudeExecBridge(opts: ExecBridgeOptions): (mutation: Mutation, decision: GeneDecision) => Promise<ExecutionResult>;
153
+ export declare function makeClaudeExecBridge(opts: ExecBridgeOptions, internal?: {
154
+ cursorWorktreeRoot?: string;
155
+ }): (mutation: Mutation, decision: GeneDecision) => Promise<ExecutionResult>;