@evo-dev/evodev 0.0.1-alpha.16 → 0.0.1-alpha.18

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 CHANGED
@@ -6789,6 +6789,9 @@ function buildCodexArgs(role, startupPrompt) {
6789
6789
  const args = ["--no-alt-screen"];
6790
6790
  if (role.model !== null)
6791
6791
  args.push("--model", role.model);
6792
+ if (role.thinkingLevel !== null) {
6793
+ args.push("--config", `model_reasoning_effort=${JSON.stringify(role.thinkingLevel)}`);
6794
+ }
6792
6795
  if (shouldPassVisibleStartupPrompt(role))
6793
6796
  args.push(startupPrompt);
6794
6797
  return args;
@@ -6797,6 +6800,9 @@ function buildCodexResumeArgs(role, startupPrompt, mode) {
6797
6800
  const args = ["--no-alt-screen"];
6798
6801
  if (role.model !== null)
6799
6802
  args.push("--model", role.model);
6803
+ if (role.thinkingLevel !== null) {
6804
+ args.push("--config", `model_reasoning_effort=${JSON.stringify(role.thinkingLevel)}`);
6805
+ }
6800
6806
  args.push("resume");
6801
6807
  if (mode.type === "resume") {
6802
6808
  args.push(mode.sessionId);
@@ -8380,6 +8386,9 @@ function createDefaultMemorySettings() {
8380
8386
  }
8381
8387
  function createDefaultEvolutionSettings() {
8382
8388
  return {
8389
+ schedule: {
8390
+ dailyTime: "02:00"
8391
+ },
8383
8392
  automation: {
8384
8393
  knowledge: true,
8385
8394
  semanticKnowledge: false,
@@ -8431,6 +8440,10 @@ function mergeSettings(existing, defaults = createDefaultSettings()) {
8431
8440
  evolution: {
8432
8441
  ...defaults.evolution,
8433
8442
  ...existing.evolution,
8443
+ schedule: {
8444
+ ...defaults.evolution.schedule,
8445
+ ...existing.evolution?.schedule
8446
+ },
8434
8447
  automation: {
8435
8448
  ...defaults.evolution.automation,
8436
8449
  ...existing.evolution?.automation
@@ -8489,8 +8502,12 @@ function parseSettings(value) {
8489
8502
  function parseEvolutionSettings(value, path) {
8490
8503
  const input = expectRecord(value, path);
8491
8504
  const defaults = createDefaultEvolutionSettings();
8505
+ const schedule = expectRecord(input.schedule ?? defaults.schedule, `${path}.schedule`);
8492
8506
  const automation = expectRecord(input.automation ?? defaults.automation, `${path}.automation`);
8493
8507
  return {
8508
+ schedule: {
8509
+ dailyTime: parseDailyTime(schedule.dailyTime ?? defaults.schedule.dailyTime, `${path}.schedule.dailyTime`)
8510
+ },
8494
8511
  automation: {
8495
8512
  knowledge: automation.knowledge === undefined ? defaults.automation.knowledge : expectBoolean(automation.knowledge, `${path}.automation.knowledge`),
8496
8513
  semanticKnowledge: automation.semanticKnowledge === undefined ? defaults.automation.semanticKnowledge : expectBoolean(automation.semanticKnowledge, `${path}.automation.semanticKnowledge`),
@@ -8498,6 +8515,14 @@ function parseEvolutionSettings(value, path) {
8498
8515
  }
8499
8516
  };
8500
8517
  }
8518
+ function parseDailyTime(value, path) {
8519
+ const dailyTime = expectString(value, path);
8520
+ const match = /^(\d{2}):(\d{2})$/u.exec(dailyTime);
8521
+ if (match === null || Number(match[1]) > 23 || Number(match[2]) > 59) {
8522
+ throw new EvoDevConfigError(`Invalid ${path}; expected a 24-hour HH:MM time`);
8523
+ }
8524
+ return dailyTime;
8525
+ }
8501
8526
  function parseMemorySettings(value, path) {
8502
8527
  const input = expectRecord(value, path);
8503
8528
  const defaults = createDefaultMemorySettings();
@@ -15215,6 +15240,7 @@ async function processEvolutionTriggers(input) {
15215
15240
  pending: 0,
15216
15241
  triggerIds: [],
15217
15242
  batchIds: [],
15243
+ pendingChangeIds: [],
15218
15244
  warnings,
15219
15245
  dryRun
15220
15246
  };
@@ -15250,6 +15276,7 @@ async function processEvolutionTriggers(input) {
15250
15276
  ...pending.map((trigger) => trigger.id)
15251
15277
  ],
15252
15278
  batchIds: [],
15279
+ pendingChangeIds: [],
15253
15280
  warnings,
15254
15281
  dryRun
15255
15282
  };
@@ -15355,6 +15382,7 @@ async function processEvolutionTriggers(input) {
15355
15382
  });
15356
15383
  result.consumed += 1;
15357
15384
  result.batchIds.push(batch.id);
15385
+ result.pendingChangeIds.push(...curated.activation.okf.pendingChangeIds);
15358
15386
  await updateSegmentTriggers(input.homeDir, [attemptedTrigger], {
15359
15387
  status: "consumed",
15360
15388
  updatedAt: now,
@@ -15438,13 +15466,14 @@ async function processEvolutionTriggers(input) {
15438
15466
  });
15439
15467
  if (lock !== null)
15440
15468
  await assertEvolutionProcessLockOwned(lock);
15441
- await activateEvolutionDistillationBatch({
15469
+ const activation = await activateEvolutionDistillationBatch({
15442
15470
  homeDir: input.homeDir,
15443
15471
  batch,
15444
15472
  overwrite: true
15445
15473
  });
15446
15474
  result.consumed += triggers.length;
15447
15475
  result.batchIds.push(batch.id);
15476
+ result.pendingChangeIds.push(...activation.okf.pendingChangeIds);
15448
15477
  await updateTriggers(input.homeDir, attemptedTriggers, {
15449
15478
  status: "consumed",
15450
15479
  updatedAt: now,
@@ -22886,6 +22915,7 @@ import { createHash as createHash6, randomUUID as randomUUID5 } from "node:crypt
22886
22915
  import { mkdir as mkdir17, open as open4, rename as rename5, rm as rm8, stat as stat13, writeFile as writeFile15 } from "node:fs/promises";
22887
22916
  import { dirname as dirname20, join as join29 } from "node:path";
22888
22917
  var EVO_DEV_SETTINGS_KEYS = [
22918
+ "evolution.schedule.dailyTime",
22889
22919
  "evolution.automation.knowledge",
22890
22920
  "evolution.automation.semanticKnowledge",
22891
22921
  "evolution.automation.recommendations",
@@ -22934,7 +22964,17 @@ async function updateEvoDevSetting(input) {
22934
22964
  }
22935
22965
  function applyEvoDevSettingsMutation(current, key, value) {
22936
22966
  let next;
22937
- if (key.startsWith("evolution.automation.")) {
22967
+ if (key === "evolution.schedule.dailyTime") {
22968
+ next = {
22969
+ ...current,
22970
+ evolution: {
22971
+ ...current.evolution,
22972
+ schedule: {
22973
+ dailyTime: expectDailyTimeValue(key, value)
22974
+ }
22975
+ }
22976
+ };
22977
+ } else if (key.startsWith("evolution.automation.")) {
22938
22978
  const field = key.slice("evolution.automation.".length);
22939
22979
  next = {
22940
22980
  ...current,
@@ -22994,6 +23034,9 @@ function applyEvoDevSettingsMutation(current, key, value) {
22994
23034
  return parseSettings(next);
22995
23035
  }
22996
23036
  function parseEvoDevSettingsCliValue(key, value) {
23037
+ if (key === "evolution.schedule.dailyTime") {
23038
+ return expectDailyTimeValue(key, value);
23039
+ }
22997
23040
  if (key.startsWith("evolution.automation.") || key.startsWith("memory.") || key === "teamRuntime.recordTranscript") {
22998
23041
  if (value === "true")
22999
23042
  return true;
@@ -23018,6 +23061,16 @@ function parseEvoDevSettingsCliValue(key, value) {
23018
23061
  }
23019
23062
  throw new EvoDevSettingsMutationError("invalid", `Unsupported config key: ${key}`);
23020
23063
  }
23064
+ function expectDailyTimeValue(key, value) {
23065
+ if (typeof value !== "string")
23066
+ throw invalidValue(key, "a 24-hour HH:MM time");
23067
+ const normalized = value.trim();
23068
+ const match = /^(\d{2}):(\d{2})$/u.exec(normalized);
23069
+ if (match === null || Number(match[1]) > 23 || Number(match[2]) > 59) {
23070
+ throw invalidValue(key, "a 24-hour HH:MM time");
23071
+ }
23072
+ return normalized;
23073
+ }
23021
23074
  function formatEvoDevSettingsValue(value) {
23022
23075
  return value === null ? "default" : String(value);
23023
23076
  }
@@ -34506,16 +34559,176 @@ function resolveHomeDir11(homeDir) {
34506
34559
  return envHome;
34507
34560
  }
34508
34561
 
34562
+ // packages/cli/src/daily-schedule.ts
34563
+ import { randomUUID as randomUUID6 } from "node:crypto";
34564
+ import { mkdir as mkdir23, readFile as readFile32, rename as rename6, rm as rm11, writeFile as writeFile22 } from "node:fs/promises";
34565
+ import { dirname as dirname27, join as join34 } from "node:path";
34566
+ async function prepareDailyEvolutionSchedule(input) {
34567
+ const now = asDate(input.now);
34568
+ const current = await readDailyEvolutionScheduleState(input.homeDir);
34569
+ const state = {
34570
+ schemaVersion: 1,
34571
+ kind: "daily-evolution-schedule-state",
34572
+ configuredTime: parseDailyTime2(input.dailyTime).value,
34573
+ configuredAt: now.toISOString(),
34574
+ notBefore: nextOccurrence(now, input.dailyTime).toISOString(),
34575
+ lastRunDay: current?.lastRunDay ?? null,
34576
+ lastRunAt: current?.lastRunAt ?? null,
34577
+ lastJobId: current?.lastJobId ?? null,
34578
+ lastJobStatus: current?.lastJobStatus ?? null
34579
+ };
34580
+ await writeState(input.homeDir, state);
34581
+ return state;
34582
+ }
34583
+ async function decideDailyEvolutionTick(input) {
34584
+ const now = asDate(input.now);
34585
+ const time2 = parseDailyTime2(input.dailyTime);
34586
+ const state = await readDailyEvolutionScheduleState(input.homeDir);
34587
+ if (state !== null && Date.parse(state.notBefore) > now.getTime()) {
34588
+ return { due: false, reason: "before-effective-time" };
34589
+ }
34590
+ const day = localDay(now);
34591
+ if (state?.lastRunDay === day)
34592
+ return { due: false, reason: "already-ran" };
34593
+ if (now.getHours() * 60 + now.getMinutes() < time2.hour * 60 + time2.minute) {
34594
+ return { due: false, reason: "before-trigger" };
34595
+ }
34596
+ return { due: true, day };
34597
+ }
34598
+ async function recordDailyEvolutionRun(input) {
34599
+ const now = asDate(input.now);
34600
+ const current = await readDailyEvolutionScheduleState(input.homeDir);
34601
+ const state = {
34602
+ schemaVersion: 1,
34603
+ kind: "daily-evolution-schedule-state",
34604
+ configuredTime: parseDailyTime2(input.dailyTime).value,
34605
+ configuredAt: current?.configuredAt ?? now.toISOString(),
34606
+ notBefore: current?.notBefore ?? now.toISOString(),
34607
+ lastRunDay: localDay(now),
34608
+ lastRunAt: now.toISOString(),
34609
+ lastJobId: input.jobId,
34610
+ lastJobStatus: input.status
34611
+ };
34612
+ await writeState(input.homeDir, state);
34613
+ return state;
34614
+ }
34615
+ async function readDailyEvolutionScheduleStatus(input) {
34616
+ const now = asDate(input.now);
34617
+ const state = await readDailyEvolutionScheduleState(input.homeDir);
34618
+ const next = resolveNextRun(now, input.dailyTime, state);
34619
+ return {
34620
+ dailyTime: parseDailyTime2(input.dailyTime).value,
34621
+ timeZone: Intl.DateTimeFormat().resolvedOptions().timeZone || "system-local",
34622
+ nextRunAt: next.toISOString(),
34623
+ lastRunAt: state?.lastRunAt ?? null,
34624
+ lastJobId: state?.lastJobId ?? null,
34625
+ lastJobStatus: state?.lastJobStatus ?? null
34626
+ };
34627
+ }
34628
+ async function readDailyEvolutionScheduleState(homeDir) {
34629
+ try {
34630
+ const value = JSON.parse(await readFile32(resolveDailyScheduleStatePath(homeDir), "utf8"));
34631
+ if (value.schemaVersion !== 1 || value.kind !== "daily-evolution-schedule-state" || typeof value.configuredTime !== "string" || typeof value.configuredAt !== "string" || typeof value.notBefore !== "string" || !isIsoTimestamp(value.configuredAt) || !isIsoTimestamp(value.notBefore) || !isNullableLocalDay(value.lastRunDay) || !isNullableTimestamp(value.lastRunAt) || !isNullableString(value.lastJobId) || !isEvolutionJobStatus(value.lastJobStatus)) {
34632
+ return null;
34633
+ }
34634
+ parseDailyTime2(value.configuredTime);
34635
+ return value;
34636
+ } catch {
34637
+ return null;
34638
+ }
34639
+ }
34640
+ function isIsoTimestamp(value) {
34641
+ return Number.isFinite(Date.parse(value));
34642
+ }
34643
+ function isNullableTimestamp(value) {
34644
+ return value === null || typeof value === "string" && isIsoTimestamp(value);
34645
+ }
34646
+ function isNullableLocalDay(value) {
34647
+ return value === null || typeof value === "string" && /^\d{4}-\d{2}-\d{2}$/u.test(value);
34648
+ }
34649
+ function isNullableString(value) {
34650
+ return value === null || typeof value === "string";
34651
+ }
34652
+ function isEvolutionJobStatus(value) {
34653
+ return value === null || value === "queued" || value === "running" || value === "completed" || value === "completed-with-warnings" || value === "failed";
34654
+ }
34655
+ function resolveDailyScheduleStatePath(homeDir) {
34656
+ return join34(resolveEvoDevPaths(homeDir).stateDir, "schedule", "daily-evolution.json");
34657
+ }
34658
+ function resolveNextRun(now, dailyTime, state) {
34659
+ const candidate = occurrenceOnDay(now, dailyTime);
34660
+ const alreadyRan = state?.lastRunDay === localDay(now);
34661
+ const notBefore = state === null ? Number.NEGATIVE_INFINITY : Date.parse(state.notBefore);
34662
+ if (!alreadyRan && candidate.getTime() >= now.getTime() && candidate.getTime() >= notBefore) {
34663
+ return candidate;
34664
+ }
34665
+ if (!alreadyRan && candidate.getTime() < now.getTime() && now.getTime() >= notBefore) {
34666
+ return now;
34667
+ }
34668
+ if (!alreadyRan && notBefore > now.getTime())
34669
+ return new Date(notBefore);
34670
+ const tomorrow = new Date(now);
34671
+ tomorrow.setDate(tomorrow.getDate() + 1);
34672
+ return occurrenceOnDay(tomorrow, dailyTime);
34673
+ }
34674
+ function nextOccurrence(now, dailyTime) {
34675
+ const today = occurrenceOnDay(now, dailyTime);
34676
+ if (today.getTime() > now.getTime())
34677
+ return today;
34678
+ const tomorrow = new Date(now);
34679
+ tomorrow.setDate(tomorrow.getDate() + 1);
34680
+ return occurrenceOnDay(tomorrow, dailyTime);
34681
+ }
34682
+ function occurrenceOnDay(day, dailyTime) {
34683
+ const time2 = parseDailyTime2(dailyTime);
34684
+ return new Date(day.getFullYear(), day.getMonth(), day.getDate(), time2.hour, time2.minute, 0, 0);
34685
+ }
34686
+ function parseDailyTime2(value) {
34687
+ const match = /^(\d{2}):(\d{2})$/u.exec(value);
34688
+ const hour = Number(match?.[1]);
34689
+ const minute = Number(match?.[2]);
34690
+ if (match === null || hour > 23 || minute > 59) {
34691
+ throw new Error("Evolution schedule time must use 24-hour HH:MM format.");
34692
+ }
34693
+ return { value, hour, minute };
34694
+ }
34695
+ function localDay(value) {
34696
+ return [
34697
+ String(value.getFullYear()).padStart(4, "0"),
34698
+ String(value.getMonth() + 1).padStart(2, "0"),
34699
+ String(value.getDate()).padStart(2, "0")
34700
+ ].join("-");
34701
+ }
34702
+ function asDate(value) {
34703
+ const date = value === undefined ? new Date : new Date(value);
34704
+ if (!Number.isFinite(date.getTime()))
34705
+ throw new Error("Invalid daily evolution timestamp.");
34706
+ return date;
34707
+ }
34708
+ async function writeState(homeDir, state) {
34709
+ const path2 = resolveDailyScheduleStatePath(homeDir);
34710
+ await mkdir23(dirname27(path2), { recursive: true });
34711
+ const temporary = `${path2}.${process.pid}.${randomUUID6()}.tmp`;
34712
+ await writeFile22(temporary, `${JSON.stringify(state, null, 2)}
34713
+ `, "utf8");
34714
+ try {
34715
+ await rename6(temporary, path2);
34716
+ } catch (error) {
34717
+ await rm11(temporary, { force: true });
34718
+ throw error;
34719
+ }
34720
+ }
34721
+
34509
34722
  // packages/cli/src/evolution-job.ts
34510
- import { randomUUID as randomUUID7 } from "node:crypto";
34511
- import { mkdir as mkdir25, readFile as readFile35, rename as rename7, rm as rm14, stat as stat26, utimes, writeFile as writeFile25 } from "node:fs/promises";
34512
- import { dirname as dirname29, join as join37 } from "node:path";
34723
+ import { randomUUID as randomUUID8 } from "node:crypto";
34724
+ import { mkdir as mkdir26, readFile as readFile36, readdir as readdir21, rename as rename8, rm as rm15, stat as stat26, utimes, writeFile as writeFile26 } from "node:fs/promises";
34725
+ import { dirname as dirname30, join as join38 } from "node:path";
34513
34726
 
34514
34727
  // packages/cli/src/improvement-eval.ts
34515
34728
  import { spawn as spawn4 } from "node:child_process";
34516
- import { mkdtemp as mkdtemp2, readFile as readFile32, realpath as realpath5, rm as rm11, stat as stat23, writeFile as writeFile22 } from "node:fs/promises";
34729
+ import { mkdtemp as mkdtemp2, readFile as readFile33, realpath as realpath5, rm as rm12, stat as stat23, writeFile as writeFile23 } from "node:fs/promises";
34517
34730
  import { tmpdir as tmpdir3 } from "node:os";
34518
- import { join as join34 } from "node:path";
34731
+ import { join as join35 } from "node:path";
34519
34732
  var DEFAULT_EVAL_LIMIT = 1;
34520
34733
  var MODEL_TIMEOUT_MS2 = 2 * 60 * 1000;
34521
34734
  var MAX_BASELINE_CHARS = 12000;
@@ -34667,11 +34880,11 @@ function classifyImprovementEvalAnalysis(analysis3) {
34667
34880
  }
34668
34881
  function createCodexImprovementEvalAnalyzer(input = {}) {
34669
34882
  return async (candidate) => {
34670
- const workDir = await mkdtemp2(join34(tmpdir3(), "evodev-improvement-eval-"));
34671
- const schemaPath = join34(workDir, "output.schema.json");
34672
- const outputPath = join34(workDir, "output.json");
34883
+ const workDir = await mkdtemp2(join35(tmpdir3(), "evodev-improvement-eval-"));
34884
+ const schemaPath = join35(workDir, "output.schema.json");
34885
+ const outputPath = join35(workDir, "output.json");
34673
34886
  try {
34674
- await writeFile22(schemaPath, `${JSON.stringify(IMPROVEMENT_EVAL_SCHEMA, null, 2)}
34887
+ await writeFile23(schemaPath, `${JSON.stringify(IMPROVEMENT_EVAL_SCHEMA, null, 2)}
34675
34888
  `, "utf8");
34676
34889
  const metrics = await runCodexReplay({
34677
34890
  command: input.command ?? "codex",
@@ -34682,11 +34895,11 @@ function createCodexImprovementEvalAnalyzer(input = {}) {
34682
34895
  timeoutMs: input.timeoutMs ?? MODEL_TIMEOUT_MS2
34683
34896
  });
34684
34897
  return {
34685
- ...parseImprovementEvalReplayAnalysis(JSON.parse(await readFile32(outputPath, "utf8"))),
34898
+ ...parseImprovementEvalReplayAnalysis(JSON.parse(await readFile33(outputPath, "utf8"))),
34686
34899
  metrics
34687
34900
  };
34688
34901
  } finally {
34689
- await rm11(workDir, { recursive: true, force: true });
34902
+ await rm12(workDir, { recursive: true, force: true });
34690
34903
  }
34691
34904
  };
34692
34905
  }
@@ -34998,26 +35211,26 @@ var IMPROVEMENT_EVAL_SCHEMA = {
34998
35211
 
34999
35212
  // packages/cli/src/session-proposals.ts
35000
35213
  import { spawn as spawn6 } from "node:child_process";
35001
- import { createHash as createHash12, randomUUID as randomUUID6 } from "node:crypto";
35214
+ import { createHash as createHash12, randomUUID as randomUUID7 } from "node:crypto";
35002
35215
  import {
35003
- mkdir as mkdir24,
35216
+ mkdir as mkdir25,
35004
35217
  mkdtemp as mkdtemp3,
35005
35218
  open as open6,
35006
- readFile as readFile34,
35219
+ readFile as readFile35,
35007
35220
  readdir as readdir20,
35008
35221
  realpath as realpath7,
35009
- rm as rm13,
35222
+ rm as rm14,
35010
35223
  stat as stat25,
35011
- writeFile as writeFile24
35224
+ writeFile as writeFile25
35012
35225
  } from "node:fs/promises";
35013
35226
  import { tmpdir as tmpdir4 } from "node:os";
35014
- import { basename as basename8, dirname as dirname28, isAbsolute as isAbsolute13, join as join36, relative as relative14, resolve as resolve14 } from "node:path";
35227
+ import { basename as basename8, dirname as dirname29, isAbsolute as isAbsolute13, join as join37, relative as relative14, resolve as resolve14 } from "node:path";
35015
35228
 
35016
35229
  // packages/cli/src/proposal-execution.ts
35017
35230
  import { spawn as spawn5 } from "node:child_process";
35018
35231
  import { createHash as createHash11, randomBytes as randomBytes3 } from "node:crypto";
35019
- import { mkdir as mkdir23, readFile as readFile33, readdir as readdir19, realpath as realpath6, rename as rename6, rm as rm12, stat as stat24, writeFile as writeFile23 } from "node:fs/promises";
35020
- import { dirname as dirname27, isAbsolute as isAbsolute12, join as join35, relative as relative13, resolve as resolve13 } from "node:path";
35232
+ import { mkdir as mkdir24, readFile as readFile34, readdir as readdir19, realpath as realpath6, rename as rename7, rm as rm13, stat as stat24, writeFile as writeFile24 } from "node:fs/promises";
35233
+ import { dirname as dirname28, isAbsolute as isAbsolute12, join as join36, relative as relative13, resolve as resolve13 } from "node:path";
35021
35234
  var MAX_COMMAND_OUTPUT_BYTES = 1024 * 1024;
35022
35235
  var MAX_DIRTY_FILES_IN_RESPONSE = 20;
35023
35236
  var MAX_CHANGED_FILES_IN_RECORD = 100;
@@ -35146,7 +35359,7 @@ class RepoProposalExecutionService {
35146
35359
  try {
35147
35360
  await writeExecutionRecord(input.homeDir, proposal, record);
35148
35361
  } catch (error) {
35149
- await rm12(lockPath, { force: true });
35362
+ await rm13(lockPath, { force: true });
35150
35363
  throw error;
35151
35364
  }
35152
35365
  const prompt = buildRepoProposalExecutionPrompt({ proposal, repository });
@@ -35172,7 +35385,7 @@ class RepoProposalExecutionService {
35172
35385
  changedFiles: []
35173
35386
  });
35174
35387
  await writeExecutionRecord(input.homeDir, proposal, failed);
35175
- await rm12(lockPath, { force: true });
35388
+ await rm13(lockPath, { force: true });
35176
35389
  throw new ProposalExecutionError("unavailable", `${agentLabel(input.agent)} could not start.`);
35177
35390
  }
35178
35391
  record.pid = child.pid ?? null;
@@ -35195,7 +35408,7 @@ class RepoProposalExecutionService {
35195
35408
  exitCode,
35196
35409
  signal,
35197
35410
  launchError
35198
- }).finally(() => rm12(lockPath, { force: true }));
35411
+ }).finally(() => rm13(lockPath, { force: true }));
35199
35412
  };
35200
35413
  child.once("error", () => {
35201
35414
  finish(null, null, true).catch(() => {
@@ -35344,7 +35557,7 @@ async function listRepoProposalExecutionRecords(input) {
35344
35557
  const records = [];
35345
35558
  for (const file of files) {
35346
35559
  try {
35347
- records.push(parseExecutionRecord(JSON.parse(await readFile33(join35(dir, file), "utf8"))));
35560
+ records.push(parseExecutionRecord(JSON.parse(await readFile34(join36(dir, file), "utf8"))));
35348
35561
  } catch {}
35349
35562
  }
35350
35563
  return records.sort((left2, right2) => left2.startedAt.localeCompare(right2.startedAt));
@@ -35696,15 +35909,15 @@ function resolveExecutionRecordsDir(homeDir, proposal) {
35696
35909
  projectKey: proposal.projectKey,
35697
35910
  runId: proposal.provenance.runId
35698
35911
  });
35699
- return join35(paths4.runStateDir, "proposal-executions", proposal.id);
35912
+ return join36(paths4.runStateDir, "proposal-executions", proposal.id);
35700
35913
  }
35701
35914
  function resolveExecutionLockPath(homeDir, proposal) {
35702
- return join35(resolveExecutionRecordsDir(homeDir, proposal), ".execution.lock");
35915
+ return join36(resolveExecutionRecordsDir(homeDir, proposal), ".execution.lock");
35703
35916
  }
35704
35917
  async function acquireExecutionLock(path2, latest) {
35705
- await mkdir23(dirname27(path2), { recursive: true });
35918
+ await mkdir24(dirname28(path2), { recursive: true });
35706
35919
  try {
35707
- await writeFile23(path2, `${JSON.stringify({ version: 1, pid: process.pid })}
35920
+ await writeFile24(path2, `${JSON.stringify({ version: 1, pid: process.pid })}
35708
35921
  `, {
35709
35922
  encoding: "utf8",
35710
35923
  flag: "wx"
@@ -35718,8 +35931,8 @@ async function acquireExecutionLock(path2, latest) {
35718
35931
  if (latest?.status === "running" && isProcessAlive2(latest.pid)) {
35719
35932
  throw new ProposalExecutionError("conflict", "This repo proposal is already executing.");
35720
35933
  }
35721
- await rm12(path2, { force: true });
35722
- await writeFile23(path2, `${JSON.stringify({ version: 1, pid: process.pid })}
35934
+ await rm13(path2, { force: true });
35935
+ await writeFile24(path2, `${JSON.stringify({ version: 1, pid: process.pid })}
35723
35936
  `, {
35724
35937
  encoding: "utf8",
35725
35938
  flag: "wx"
@@ -35728,7 +35941,7 @@ async function acquireExecutionLock(path2, latest) {
35728
35941
  }
35729
35942
  async function isExecutionLockOwnerAlive(path2) {
35730
35943
  try {
35731
- const value = JSON.parse(await readFile33(path2, "utf8"));
35944
+ const value = JSON.parse(await readFile34(path2, "utf8"));
35732
35945
  return isRecord20(value) && typeof value.pid === "number" && isProcessAlive2(value.pid);
35733
35946
  } catch {
35734
35947
  return false;
@@ -35736,12 +35949,12 @@ async function isExecutionLockOwnerAlive(path2) {
35736
35949
  }
35737
35950
  async function writeExecutionRecord(homeDir, proposal, record) {
35738
35951
  const dir = resolveExecutionRecordsDir(homeDir, proposal);
35739
- const path2 = join35(dir, `${record.id}.json`);
35952
+ const path2 = join36(dir, `${record.id}.json`);
35740
35953
  const temporary = `${path2}.${process.pid}.${randomBytes3(4).toString("hex")}.tmp`;
35741
- await mkdir23(dir, { recursive: true });
35742
- await writeFile23(temporary, `${JSON.stringify(record, null, 2)}
35954
+ await mkdir24(dir, { recursive: true });
35955
+ await writeFile24(temporary, `${JSON.stringify(record, null, 2)}
35743
35956
  `, "utf8");
35744
- await rename6(temporary, path2);
35957
+ await rename7(temporary, path2);
35745
35958
  }
35746
35959
  function parseExecutionRecord(value) {
35747
35960
  if (!isRecord20(value))
@@ -35782,7 +35995,7 @@ function parseExecutionRecord(value) {
35782
35995
  "spawn-failed",
35783
35996
  "state-conflict"
35784
35997
  ]);
35785
- if (Object.keys(value).some((key) => !allowedKeys.has(key)) || value.schemaVersion !== 1 || value.kind !== "repo-proposal-execution" || !isSafeRecordIdentity(value.id) || !isSafeRecordIdentity(value.proposalId) || !isSafeRecordIdentity(value.projectKey) || !isSafeRecordIdentity(value.runId) || value.agent !== "codex" && value.agent !== "claude" || !["running", "succeeded", "failed"].includes(String(value.status)) || value.branch !== null && typeof value.branch !== "string" || typeof value.head !== "string" || !/^[a-f0-9]{40,64}$/u.test(value.head) || typeof value.dirtyAtStart !== "boolean" || !Number.isInteger(value.dirtyFileCountAtStart) || Number(value.dirtyFileCountAtStart) < 0 || value.dirtyConfirmation !== "not-required" && value.dirtyConfirmation !== "approval-covered" && value.dirtyConfirmation !== "confirmed" || !Array.isArray(value.changedFiles) || value.changedFiles.length > MAX_CHANGED_FILES_IN_RECORD || !value.changedFiles.every((path2) => typeof path2 === "string") || typeof value.changedFilesTruncated !== "boolean" || typeof value.startedAt !== "string" || !isIsoTimestamp(value.startedAt) || value.finishedAt !== null && (typeof value.finishedAt !== "string" || !isIsoTimestamp(value.finishedAt)) || value.pid !== null && (!Number.isInteger(value.pid) || Number(value.pid) <= 0) || value.exitCode !== null && !Number.isInteger(value.exitCode) || value.signal !== null && typeof value.signal !== "string" || value.errorCode !== null && (typeof value.errorCode !== "string" || !errorCodes.has(value.errorCode)) || value.metadataOnly !== true || value.rawPromptStored !== false || value.sourceContentStored !== false || value.rawCommandOutputStored !== false) {
35998
+ if (Object.keys(value).some((key) => !allowedKeys.has(key)) || value.schemaVersion !== 1 || value.kind !== "repo-proposal-execution" || !isSafeRecordIdentity(value.id) || !isSafeRecordIdentity(value.proposalId) || !isSafeRecordIdentity(value.projectKey) || !isSafeRecordIdentity(value.runId) || value.agent !== "codex" && value.agent !== "claude" || !["running", "succeeded", "failed"].includes(String(value.status)) || value.branch !== null && typeof value.branch !== "string" || typeof value.head !== "string" || !/^[a-f0-9]{40,64}$/u.test(value.head) || typeof value.dirtyAtStart !== "boolean" || !Number.isInteger(value.dirtyFileCountAtStart) || Number(value.dirtyFileCountAtStart) < 0 || value.dirtyConfirmation !== "not-required" && value.dirtyConfirmation !== "approval-covered" && value.dirtyConfirmation !== "confirmed" || !Array.isArray(value.changedFiles) || value.changedFiles.length > MAX_CHANGED_FILES_IN_RECORD || !value.changedFiles.every((path2) => typeof path2 === "string") || typeof value.changedFilesTruncated !== "boolean" || typeof value.startedAt !== "string" || !isIsoTimestamp2(value.startedAt) || value.finishedAt !== null && (typeof value.finishedAt !== "string" || !isIsoTimestamp2(value.finishedAt)) || value.pid !== null && (!Number.isInteger(value.pid) || Number(value.pid) <= 0) || value.exitCode !== null && !Number.isInteger(value.exitCode) || value.signal !== null && typeof value.signal !== "string" || value.errorCode !== null && (typeof value.errorCode !== "string" || !errorCodes.has(value.errorCode)) || value.metadataOnly !== true || value.rawPromptStored !== false || value.sourceContentStored !== false || value.rawCommandOutputStored !== false) {
35786
35999
  throw new Error("Execution record is invalid.");
35787
36000
  }
35788
36001
  return value;
@@ -35790,7 +36003,7 @@ function parseExecutionRecord(value) {
35790
36003
  function isSafeRecordIdentity(value) {
35791
36004
  return typeof value === "string" && /^[A-Za-z0-9][A-Za-z0-9._:-]{0,199}$/u.test(value);
35792
36005
  }
35793
- function isIsoTimestamp(value) {
36006
+ function isIsoTimestamp2(value) {
35794
36007
  return /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d{3})?Z$/u.test(value);
35795
36008
  }
35796
36009
  function sanitizeVersion(value) {
@@ -36123,7 +36336,7 @@ async function readSessionProposalBacklogStatus(input) {
36123
36336
  }
36124
36337
  async function runDailySessionProposalScan(options) {
36125
36338
  const now2 = options.now ?? new Date().toISOString();
36126
- const day = localDay(now2);
36339
+ const day = localDay2(now2);
36127
36340
  const statePath = resolveDailyStatePath(options.homeDir);
36128
36341
  const state = await readDailyState(statePath);
36129
36342
  if (!options.force && state?.day === day) {
@@ -36177,11 +36390,11 @@ async function readDailySessionProposalState(homeDir) {
36177
36390
  }
36178
36391
  function createCodexSessionProposalAnalyzer(input = {}) {
36179
36392
  return async (evidence) => {
36180
- const workDir = await mkdtemp3(join36(tmpdir4(), "evodev-session-proposal-"));
36181
- const schemaPath = join36(workDir, "output.schema.json");
36182
- const outputPath = join36(workDir, "output.json");
36393
+ const workDir = await mkdtemp3(join37(tmpdir4(), "evodev-session-proposal-"));
36394
+ const schemaPath = join37(workDir, "output.schema.json");
36395
+ const outputPath = join37(workDir, "output.json");
36183
36396
  try {
36184
- await writeFile24(schemaPath, `${JSON.stringify(SESSION_PROPOSAL_SCHEMA, null, 2)}
36397
+ await writeFile25(schemaPath, `${JSON.stringify(SESSION_PROPOSAL_SCHEMA, null, 2)}
36185
36398
  `, "utf8");
36186
36399
  await runCodex2({
36187
36400
  command: input.command ?? "codex",
@@ -36191,9 +36404,9 @@ function createCodexSessionProposalAnalyzer(input = {}) {
36191
36404
  prompt: redactSessionMemoryCredentialText(createAnalyzerPrompt2(evidence)).value,
36192
36405
  timeoutMs: input.timeoutMs ?? MODEL_TIMEOUT_MS3
36193
36406
  });
36194
- return parseSessionProposalAnalysis(JSON.parse(await readFile34(outputPath, "utf8")));
36407
+ return parseSessionProposalAnalysis(JSON.parse(await readFile35(outputPath, "utf8")));
36195
36408
  } finally {
36196
- await rm13(workDir, { recursive: true, force: true });
36409
+ await rm14(workDir, { recursive: true, force: true });
36197
36410
  }
36198
36411
  };
36199
36412
  }
@@ -36271,10 +36484,10 @@ async function resolveSegmentRepoRoot2(events, homeDir) {
36271
36484
  async function findRepositoryRoot2(cwd, homeDir) {
36272
36485
  let current = cwd;
36273
36486
  const stop = resolve14(homeDir);
36274
- while (current !== dirname28(current) && current !== stop) {
36275
- if (await pathExists10(join36(current, ".git")))
36487
+ while (current !== dirname29(current) && current !== stop) {
36488
+ if (await pathExists10(join37(current, ".git")))
36276
36489
  return current;
36277
- current = dirname28(current);
36490
+ current = dirname29(current);
36278
36491
  }
36279
36492
  return cwd;
36280
36493
  }
@@ -36495,20 +36708,20 @@ function createReceipt(segment, analyzedAt, status, proposalId, reason, candidat
36495
36708
  };
36496
36709
  }
36497
36710
  function resolveReceiptPath(homeDir, segmentId) {
36498
- return join36(resolveReceiptsDir(homeDir), `${segmentId}.json`);
36711
+ return join37(resolveReceiptsDir(homeDir), `${segmentId}.json`);
36499
36712
  }
36500
36713
  function resolveReceiptsDir(homeDir) {
36501
- return join36(resolveEvoDevPaths(homeDir).stateDir, "evolution", "session-proposal-scans");
36714
+ return join37(resolveEvoDevPaths(homeDir).stateDir, "evolution", "session-proposal-scans");
36502
36715
  }
36503
36716
  function resolveDailyStatePath(homeDir) {
36504
- return join36(resolveEvoDevPaths(homeDir).stateDir, "schedule", "repo-proposals.json");
36717
+ return join37(resolveEvoDevPaths(homeDir).stateDir, "schedule", "repo-proposals.json");
36505
36718
  }
36506
36719
  async function acquireScanLock(homeDir) {
36507
- const lockPath = join36(resolveEvoDevPaths(homeDir).stateDir, "evolution", ".proposal-scan.lock");
36508
- await mkdir24(dirname28(lockPath), { recursive: true });
36720
+ const lockPath = join37(resolveEvoDevPaths(homeDir).stateDir, "evolution", ".proposal-scan.lock");
36721
+ await mkdir25(dirname29(lockPath), { recursive: true });
36509
36722
  try {
36510
36723
  const handle = await open6(lockPath, "wx");
36511
- const ownerId = randomUUID6();
36724
+ const ownerId = randomUUID7();
36512
36725
  try {
36513
36726
  await handle.writeFile(`${JSON.stringify({ ownerId, pid: process.pid, createdAt: new Date().toISOString() })}
36514
36727
  `, "utf8");
@@ -36516,7 +36729,7 @@ async function acquireScanLock(homeDir) {
36516
36729
  await handle.close().catch(() => {
36517
36730
  return;
36518
36731
  });
36519
- await rm13(lockPath, { force: true }).catch(() => {
36732
+ await rm14(lockPath, { force: true }).catch(() => {
36520
36733
  return;
36521
36734
  });
36522
36735
  throw error;
@@ -36534,7 +36747,7 @@ async function acquireScanLock(homeDir) {
36534
36747
  throw error;
36535
36748
  const info = await stat25(lockPath).catch(() => null);
36536
36749
  if (info !== null && Date.now() - info.mtimeMs > SCAN_LOCK_STALE_MS) {
36537
- await rm13(lockPath, { force: true });
36750
+ await rm14(lockPath, { force: true });
36538
36751
  return acquireScanLock(homeDir);
36539
36752
  }
36540
36753
  return null;
@@ -36547,11 +36760,11 @@ async function releaseScanLock(lock) {
36547
36760
  });
36548
36761
  const currentOwner = await readScanLockOwner(lock.path);
36549
36762
  if (currentOwner === lock.ownerId)
36550
- await rm13(lock.path, { force: true });
36763
+ await rm14(lock.path, { force: true });
36551
36764
  }
36552
36765
  async function readScanLockOwner(path2) {
36553
36766
  try {
36554
- const value = JSON.parse(await readFile34(path2, "utf8"));
36767
+ const value = JSON.parse(await readFile35(path2, "utf8"));
36555
36768
  return isRecord21(value) && typeof value.ownerId === "string" ? value.ownerId : null;
36556
36769
  } catch {
36557
36770
  return null;
@@ -36613,7 +36826,7 @@ async function listAllCandidateReceipts(homeDir) {
36613
36826
  throw error;
36614
36827
  });
36615
36828
  const receipts = await Promise.all(names.filter((name) => name.endsWith(".json")).map(async (name) => {
36616
- const path2 = join36(dir, name);
36829
+ const path2 = join37(dir, name);
36617
36830
  const receipt = await readReceipt(path2);
36618
36831
  return receipt === null ? null : { path: path2, receipt };
36619
36832
  }));
@@ -36630,7 +36843,7 @@ function selectIndependentCandidateReceipts(candidates2) {
36630
36843
  }
36631
36844
  async function readReceipt(path2) {
36632
36845
  try {
36633
- const value = JSON.parse(await readFile34(path2, "utf8"));
36846
+ const value = JSON.parse(await readFile35(path2, "utf8"));
36634
36847
  return isRecord21(value) && value.kind === "session-repo-proposal-receipt" ? value : null;
36635
36848
  } catch {
36636
36849
  return null;
@@ -36638,7 +36851,7 @@ async function readReceipt(path2) {
36638
36851
  }
36639
36852
  async function readDailyState(path2) {
36640
36853
  try {
36641
- const value = JSON.parse(await readFile34(path2, "utf8"));
36854
+ const value = JSON.parse(await readFile35(path2, "utf8"));
36642
36855
  return isRecord21(value) && value.kind === "session-repo-proposal-daily-state" ? value : null;
36643
36856
  } catch {
36644
36857
  return null;
@@ -36648,8 +36861,8 @@ async function writeReceipt(path2, receipt) {
36648
36861
  await writeJson4(path2, receipt);
36649
36862
  }
36650
36863
  async function writeJson4(path2, value) {
36651
- await mkdir24(dirname28(path2), { recursive: true });
36652
- await writeFile24(path2, `${JSON.stringify(value, null, 2)}
36864
+ await mkdir25(dirname29(path2), { recursive: true });
36865
+ await writeFile25(path2, `${JSON.stringify(value, null, 2)}
36653
36866
  `, "utf8");
36654
36867
  }
36655
36868
  async function pathExists10(path2) {
@@ -36671,7 +36884,7 @@ function startOfLocalDay(value) {
36671
36884
  const date = new Date(value);
36672
36885
  return new Date(date.getFullYear(), date.getMonth(), date.getDate());
36673
36886
  }
36674
- function localDay(value) {
36887
+ function localDay2(value) {
36675
36888
  const date = new Date(value);
36676
36889
  const month = String(date.getMonth() + 1).padStart(2, "0");
36677
36890
  const day = String(date.getDate()).padStart(2, "0");
@@ -36889,7 +37102,8 @@ class EvolutionJobService {
36889
37102
  });
36890
37103
  await persist();
36891
37104
  try {
36892
- const result = await (this.#options.processKnowledge ?? defaultKnowledgeProcessor)({
37105
+ const processKnowledge = this.#options.processKnowledge ?? defaultKnowledgeProcessor;
37106
+ const knowledgeInput = {
36893
37107
  homeDir: this.#options.homeDir,
36894
37108
  limit: EVOLUTION_JOB_KNOWLEDGE_LIMIT,
36895
37109
  distillSessionKnowledge: createSessionKnowledgeDistiller({
@@ -36910,7 +37124,22 @@ class EvolutionJobService {
36910
37124
  });
36911
37125
  await persist();
36912
37126
  }
36913
- });
37127
+ };
37128
+ let retryAttempts = 0;
37129
+ let result;
37130
+ try {
37131
+ result = await processKnowledge(knowledgeInput);
37132
+ } catch (error) {
37133
+ if (!isTransientEvolutionFailure(error))
37134
+ throw error;
37135
+ retryAttempts = 1;
37136
+ result = await processKnowledge(knowledgeInput);
37137
+ }
37138
+ if (retryAttempts === 0 && result.pending > 0 && result.warnings.some(isTransientEvolutionFailure)) {
37139
+ retryAttempts = 1;
37140
+ const retried = await processKnowledge(knowledgeInput);
37141
+ result = mergeKnowledgeRetryResults(result, retried);
37142
+ }
36914
37143
  job = updateStage(job, "knowledge", {
36915
37144
  status: "completed",
36916
37145
  detail: `Knowledge pass finished with ${result.consumed} consumed trigger(s).`,
@@ -36920,9 +37149,14 @@ class EvolutionJobService {
36920
37149
  consumed: result.consumed,
36921
37150
  skipped: result.skipped,
36922
37151
  failed: result.failed,
36923
- pending: result.pending
37152
+ pending: result.pending,
37153
+ retryAttempts
36924
37154
  }
36925
37155
  });
37156
+ job = addJobOutputs(job, {
37157
+ distillationBatchIds: result.batchIds,
37158
+ knowledgeChangeIds: result.pendingChangeIds
37159
+ });
36926
37160
  if (result.failed > 0)
36927
37161
  job = addJobWarning(job, `${result.failed} knowledge trigger(s) failed.`);
36928
37162
  for (const warning of result.warnings)
@@ -36944,7 +37178,8 @@ class EvolutionJobService {
36944
37178
  });
36945
37179
  await persist();
36946
37180
  try {
36947
- const result = await (this.#options.scanRecommendations ?? defaultRecommendationScanner)({
37181
+ const scanRecommendations = this.#options.scanRecommendations ?? defaultRecommendationScanner;
37182
+ const recommendationInput = {
36948
37183
  homeDir: this.#options.homeDir,
36949
37184
  limit: EVOLUTION_JOB_RECOMMENDATION_LIMIT,
36950
37185
  force: input.forceRecommendations === true,
@@ -36965,7 +37200,22 @@ class EvolutionJobService {
36965
37200
  });
36966
37201
  await persist();
36967
37202
  }
36968
- });
37203
+ };
37204
+ let retryAttempts = 0;
37205
+ let result;
37206
+ try {
37207
+ result = await scanRecommendations(recommendationInput);
37208
+ } catch (error) {
37209
+ if (!isTransientEvolutionFailure(error))
37210
+ throw error;
37211
+ retryAttempts = 1;
37212
+ result = await scanRecommendations({ ...recommendationInput, force: true });
37213
+ }
37214
+ if (retryAttempts === 0 && result.failed > 0 && result.warnings.some(isTransientEvolutionFailure)) {
37215
+ retryAttempts = 1;
37216
+ const retried = await scanRecommendations({ ...recommendationInput, force: true });
37217
+ result = mergeRecommendationRetryResults(result, retried);
37218
+ }
36969
37219
  job = updateStage(job, "recommendations", {
36970
37220
  status: "completed",
36971
37221
  detail: result.ran ? `Recommendation pass produced ${result.proposed} review proposal(s).` : result.skippedReason === "scan-already-running" ? "Another recommendation scan is already running; the daily slot remains available." : "The daily recommendation pass already ran today.",
@@ -36976,9 +37226,11 @@ class EvolutionJobService {
36976
37226
  noChange: result.noChange,
36977
37227
  failed: result.failed,
36978
37228
  skipped: result.skipped,
36979
- superseded: result.superseded
37229
+ superseded: result.superseded,
37230
+ retryAttempts
36980
37231
  }
36981
37232
  });
37233
+ job = addJobOutputs(job, { proposalIds: result.proposalIds });
36982
37234
  if (result.failed > 0) {
36983
37235
  job = addJobWarning(job, `${result.failed} recommendation analysis item(s) failed.`);
36984
37236
  }
@@ -37105,6 +37357,17 @@ async function readEvolutionJob(homeDir, jobId) {
37105
37357
  async function readLatestEvolutionJob(homeDir) {
37106
37358
  return readJobFile(resolveLatestEvolutionJobPath(homeDir));
37107
37359
  }
37360
+ async function listEvolutionJobs(homeDir, limit = 30) {
37361
+ const directory = resolveEvolutionJobsDir(homeDir);
37362
+ let names;
37363
+ try {
37364
+ names = await readdir21(directory);
37365
+ } catch {
37366
+ return [];
37367
+ }
37368
+ const jobs = await Promise.all(names.filter((name) => name.startsWith("evolution-job-") && name.endsWith(".json")).map((name) => readJobFile(join38(directory, name))));
37369
+ return jobs.filter((job) => job !== null).sort((left2, right2) => right2.createdAt.localeCompare(left2.createdAt)).slice(0, Math.max(0, limit));
37370
+ }
37108
37371
  async function reconcileLatestEvolutionJob(homeDir, now2 = new Date().toISOString()) {
37109
37372
  const job = await readLatestEvolutionJob(homeDir);
37110
37373
  if (job === null || job.status !== "queued" && job.status !== "running")
@@ -37112,16 +37375,16 @@ async function reconcileLatestEvolutionJob(homeDir, now2 = new Date().toISOStrin
37112
37375
  return reconcileEvolutionJob(homeDir, job, now2);
37113
37376
  }
37114
37377
  function resolveEvolutionJobPath(homeDir, jobId) {
37115
- return join37(resolveEvolutionJobsDir(homeDir), `${jobId}.json`);
37378
+ return join38(resolveEvolutionJobsDir(homeDir), `${jobId}.json`);
37116
37379
  }
37117
37380
  function resolveLatestEvolutionJobPath(homeDir) {
37118
- return join37(resolveEvolutionJobsDir(homeDir), "latest.json");
37381
+ return join38(resolveEvolutionJobsDir(homeDir), "latest.json");
37119
37382
  }
37120
37383
  function createEvolutionJob(input, now2) {
37121
37384
  return {
37122
37385
  schemaVersion: 1,
37123
37386
  kind: "evolution-job",
37124
- id: `evolution-job-${randomUUID7()}`,
37387
+ id: `evolution-job-${randomUUID8()}`,
37125
37388
  source: input.source,
37126
37389
  status: "queued",
37127
37390
  createdAt: now2,
@@ -37134,6 +37397,11 @@ function createEvolutionJob(input, now2) {
37134
37397
  createStage("improvement-evals", "Repo proposal application"),
37135
37398
  createStage("finalize", "Evolution refresh")
37136
37399
  ],
37400
+ outputs: {
37401
+ distillationBatchIds: [],
37402
+ proposalIds: [],
37403
+ knowledgeChangeIds: []
37404
+ },
37137
37405
  warnings: [],
37138
37406
  metadataOnly: true,
37139
37407
  rawContentStored: false
@@ -37161,6 +37429,70 @@ function addJobWarning(job, warning) {
37161
37429
  return job;
37162
37430
  return { ...job, warnings: [...job.warnings, bounded].slice(-MAX_JOB_WARNINGS) };
37163
37431
  }
37432
+ function addJobOutputs(job, output) {
37433
+ const current = job.outputs ?? {
37434
+ distillationBatchIds: [],
37435
+ proposalIds: [],
37436
+ knowledgeChangeIds: []
37437
+ };
37438
+ return {
37439
+ ...job,
37440
+ outputs: {
37441
+ distillationBatchIds: unique3([
37442
+ ...current.distillationBatchIds,
37443
+ ...output.distillationBatchIds ?? []
37444
+ ]),
37445
+ proposalIds: unique3([...current.proposalIds, ...output.proposalIds ?? []]),
37446
+ knowledgeChangeIds: unique3([
37447
+ ...current.knowledgeChangeIds,
37448
+ ...output.knowledgeChangeIds ?? []
37449
+ ])
37450
+ }
37451
+ };
37452
+ }
37453
+ function unique3(values) {
37454
+ return [...new Set(values)].slice(0, 100);
37455
+ }
37456
+ function mergeKnowledgeRetryResults(first, retry) {
37457
+ return {
37458
+ processed: first.processed + retry.processed,
37459
+ consumed: first.consumed + retry.consumed,
37460
+ skipped: first.skipped + retry.skipped,
37461
+ failed: retry.failed,
37462
+ pending: retry.pending,
37463
+ triggerIds: unique3([...first.triggerIds, ...retry.triggerIds]),
37464
+ batchIds: unique3([...first.batchIds, ...retry.batchIds]),
37465
+ pendingChangeIds: unique3([...first.pendingChangeIds, ...retry.pendingChangeIds]),
37466
+ warnings: unique3([
37467
+ ...first.warnings.filter((warning) => !isTransientEvolutionFailure(warning)),
37468
+ ...retry.warnings
37469
+ ]),
37470
+ dryRun: false
37471
+ };
37472
+ }
37473
+ function mergeRecommendationRetryResults(first, retry) {
37474
+ return {
37475
+ ...retry,
37476
+ ran: first.ran || retry.ran,
37477
+ skippedReason: retry.ran ? null : retry.skippedReason,
37478
+ scanned: first.scanned + retry.scanned,
37479
+ proposed: first.proposed + retry.proposed,
37480
+ candidates: first.candidates + retry.candidates,
37481
+ noChange: first.noChange + retry.noChange,
37482
+ failed: retry.failed,
37483
+ skipped: first.skipped + retry.skipped,
37484
+ superseded: first.superseded + retry.superseded,
37485
+ proposalIds: unique3([...first.proposalIds, ...retry.proposalIds]),
37486
+ warnings: unique3([
37487
+ ...first.warnings.filter((warning) => !isTransientEvolutionFailure(warning)),
37488
+ ...retry.warnings
37489
+ ])
37490
+ };
37491
+ }
37492
+ function isTransientEvolutionFailure(error) {
37493
+ const message = (error instanceof Error ? `${error.name}: ${error.message}` : String(error)).toLowerCase().trim();
37494
+ return /(?:timed?\s*out|timeout|rate[\s-]*limit|too many requests|\b429\b|network|econn(?:reset|refused|aborted)|eai_again|etimedout|socket|temporar|service unavailable|\b50[0234]\b|fetch failed)/u.test(message);
37495
+ }
37164
37496
  async function defaultKnowledgeProcessor(input) {
37165
37497
  return processEvolutionTriggers(input);
37166
37498
  }
@@ -37177,33 +37509,52 @@ async function writeEvolutionJob(homeDir, job) {
37177
37509
  ]);
37178
37510
  }
37179
37511
  async function writeAtomicJson(path2, value) {
37180
- await mkdir25(dirname29(path2), { recursive: true });
37181
- const temporary = `${path2}.${process.pid}.${randomUUID7()}.tmp`;
37182
- await writeFile25(temporary, `${JSON.stringify(value, null, 2)}
37512
+ await mkdir26(dirname30(path2), { recursive: true });
37513
+ const temporary = `${path2}.${process.pid}.${randomUUID8()}.tmp`;
37514
+ await writeFile26(temporary, `${JSON.stringify(value, null, 2)}
37183
37515
  `, "utf8");
37184
37516
  try {
37185
- await rename7(temporary, path2);
37517
+ await rename8(temporary, path2);
37186
37518
  } catch (error) {
37187
- await rm14(temporary, { force: true });
37519
+ await rm15(temporary, { force: true });
37188
37520
  throw error;
37189
37521
  }
37190
37522
  }
37191
37523
  async function readJobFile(path2) {
37192
37524
  try {
37193
- const value = JSON.parse(await readFile35(path2, "utf8"));
37194
- return isEvolutionJob(value) ? value : null;
37525
+ const value = JSON.parse(await readFile36(path2, "utf8"));
37526
+ if (!isEvolutionJob(value))
37527
+ return null;
37528
+ return {
37529
+ ...value,
37530
+ outputs: normalizeJobOutputs(value.outputs)
37531
+ };
37195
37532
  } catch {
37196
37533
  return null;
37197
37534
  }
37198
37535
  }
37536
+ function normalizeJobOutputs(value) {
37537
+ if (typeof value !== "object" || value === null || Array.isArray(value)) {
37538
+ return { distillationBatchIds: [], proposalIds: [], knowledgeChangeIds: [] };
37539
+ }
37540
+ const outputs = value;
37541
+ return {
37542
+ distillationBatchIds: normalizeOutputIds(outputs.distillationBatchIds),
37543
+ proposalIds: normalizeOutputIds(outputs.proposalIds),
37544
+ knowledgeChangeIds: normalizeOutputIds(outputs.knowledgeChangeIds)
37545
+ };
37546
+ }
37547
+ function normalizeOutputIds(value) {
37548
+ return Array.isArray(value) ? unique3(value.filter((item) => typeof item === "string")) : [];
37549
+ }
37199
37550
  function isEvolutionJob(value) {
37200
37551
  return typeof value === "object" && value !== null && value.schemaVersion === 1 && value.kind === "evolution-job" && typeof value.id === "string" && Array.isArray(value.stages);
37201
37552
  }
37202
37553
  async function acquireEvolutionJobLock(homeDir, jobId, now2) {
37203
- const path2 = join37(resolveEvoDevPaths(homeDir).stateDir, "evolution", ".job.lock");
37204
- await mkdir25(dirname29(path2), { recursive: true });
37554
+ const path2 = join38(resolveEvoDevPaths(homeDir).stateDir, "evolution", ".job.lock");
37555
+ await mkdir26(dirname30(path2), { recursive: true });
37205
37556
  try {
37206
- await writeFile25(path2, `${JSON.stringify({ schemaVersion: 1, kind: "evolution-job-lock", jobId, pid: process.pid, createdAt: now2 })}
37557
+ await writeFile26(path2, `${JSON.stringify({ schemaVersion: 1, kind: "evolution-job-lock", jobId, pid: process.pid, createdAt: now2 })}
37207
37558
  `, { encoding: "utf8", flag: "wx" });
37208
37559
  return { path: path2, jobId };
37209
37560
  } catch (error) {
@@ -37211,7 +37562,7 @@ async function acquireEvolutionJobLock(homeDir, jobId, now2) {
37211
37562
  throw error;
37212
37563
  const info = await stat26(path2).catch(() => null);
37213
37564
  if (info !== null && Date.now() - info.mtimeMs > EVOLUTION_JOB_LOCK_STALE_MS) {
37214
- await rm14(path2, { force: true });
37565
+ await rm15(path2, { force: true });
37215
37566
  return acquireEvolutionJobLock(homeDir, jobId, now2);
37216
37567
  }
37217
37568
  return null;
@@ -37225,12 +37576,12 @@ async function refreshEvolutionJobLock(lock) {
37225
37576
  }
37226
37577
  async function releaseEvolutionJobLock(lock) {
37227
37578
  if (await evolutionJobLockIsOwned(lock)) {
37228
- await rm14(lock.path, { force: true });
37579
+ await rm15(lock.path, { force: true });
37229
37580
  }
37230
37581
  }
37231
37582
  async function evolutionJobLockIsOwned(lock) {
37232
37583
  try {
37233
- const value = JSON.parse(await readFile35(lock.path, "utf8"));
37584
+ const value = JSON.parse(await readFile36(lock.path, "utf8"));
37234
37585
  return value.jobId === lock.jobId;
37235
37586
  } catch {
37236
37587
  return false;
@@ -37272,9 +37623,9 @@ async function reconcileEvolutionJob(homeDir, job, now2) {
37272
37623
  return reconciled;
37273
37624
  }
37274
37625
  async function hasLiveEvolutionJobLock(homeDir, jobId) {
37275
- const path2 = join37(resolveEvoDevPaths(homeDir).stateDir, "evolution", ".job.lock");
37626
+ const path2 = join38(resolveEvoDevPaths(homeDir).stateDir, "evolution", ".job.lock");
37276
37627
  try {
37277
- const [value, info] = await Promise.all([readFile35(path2, "utf8"), stat26(path2)]);
37628
+ const [value, info] = await Promise.all([readFile36(path2, "utf8"), stat26(path2)]);
37278
37629
  const lock = JSON.parse(value);
37279
37630
  return lock.jobId === jobId && Date.now() - info.mtimeMs <= EVOLUTION_JOB_LOCK_STALE_MS;
37280
37631
  } catch {
@@ -37282,7 +37633,7 @@ async function hasLiveEvolutionJobLock(homeDir, jobId) {
37282
37633
  }
37283
37634
  }
37284
37635
  function resolveEvolutionJobsDir(homeDir) {
37285
- return join37(resolveEvoDevPaths(homeDir).stateDir, "schedule", "evolution-jobs");
37636
+ return join38(resolveEvoDevPaths(homeDir).stateDir, "schedule", "evolution-jobs");
37286
37637
  }
37287
37638
  function isFileExistsError3(error) {
37288
37639
  return error instanceof Error && "code" in error && error.code === "EEXIST";
@@ -37293,21 +37644,23 @@ function safeError3(error) {
37293
37644
 
37294
37645
  // packages/cli/src/os-scheduler.ts
37295
37646
  import { spawn as spawn7 } from "node:child_process";
37296
- import { randomUUID as randomUUID8 } from "node:crypto";
37647
+ import { randomUUID as randomUUID9 } from "node:crypto";
37297
37648
  import { constants as constants3 } from "node:fs";
37298
37649
  import {
37299
37650
  access as access2,
37300
37651
  chmod as chmod3,
37301
- mkdir as mkdir26,
37652
+ mkdir as mkdir27,
37653
+ readFile as readFile37,
37302
37654
  realpath as realpath8,
37303
- rename as rename8,
37304
- rm as rm15,
37655
+ rename as rename9,
37656
+ rm as rm16,
37305
37657
  stat as stat27,
37306
- writeFile as writeFile26
37658
+ writeFile as writeFile27
37307
37659
  } from "node:fs/promises";
37308
- import { dirname as dirname30, join as join38 } from "node:path";
37660
+ import { dirname as dirname31, join as join39 } from "node:path";
37309
37661
  var EVOLUTION_SCHEDULE_LABEL = "com.evodev.evolution";
37310
37662
  var EVOLUTION_SCHEDULE_INTERVAL_SECONDS = 15 * 60;
37663
+ var DEFAULT_EVOLUTION_SCHEDULE_DAILY_TIME = "02:00";
37311
37664
 
37312
37665
  class OsEvolutionScheduler {
37313
37666
  #options;
@@ -37321,23 +37674,26 @@ class OsEvolutionScheduler {
37321
37674
  }
37322
37675
  if (!await isFile3(path2))
37323
37676
  return createStatus("not-installed", path2);
37677
+ const schedule = await readInstalledSchedule(path2);
37324
37678
  const result = await this.#runLaunchctl(["print", this.#serviceTarget()]);
37325
- return createStatus(result.exitCode === 0 ? "installed-loaded" : "installed-not-loaded", path2);
37679
+ return createStatus(result.exitCode === 0 ? "installed-loaded" : "installed-not-loaded", path2, schedule);
37326
37680
  }
37327
- async install() {
37681
+ async install(dailyTime = DEFAULT_EVOLUTION_SCHEDULE_DAILY_TIME) {
37328
37682
  this.#assertDarwin();
37683
+ const parsedTime = parseDailyTime3(dailyTime);
37329
37684
  const commandPath = await this.#resolveCommandPath();
37330
37685
  const path2 = resolveLaunchAgentPath(this.#options.homeDir);
37331
37686
  const paths4 = resolveEvoDevPaths(this.#options.homeDir);
37332
- const logDir = join38(paths4.logsDir, "schedule");
37333
- await mkdir26(dirname30(path2), { recursive: true });
37334
- await mkdir26(logDir, { recursive: true });
37687
+ const logDir = join39(paths4.logsDir, "schedule");
37688
+ await mkdir27(dirname31(path2), { recursive: true });
37689
+ await mkdir27(logDir, { recursive: true });
37335
37690
  const plist = renderLaunchAgentPlist({
37336
37691
  commandPath,
37337
37692
  homeDir: this.#options.homeDir,
37338
37693
  pathValue: this.#options.pathValue ?? process.env.PATH ?? "/usr/local/bin:/usr/bin:/bin",
37339
- stdoutPath: join38(logDir, "evolution.stdout.log"),
37340
- stderrPath: join38(logDir, "evolution.stderr.log")
37694
+ stdoutPath: join39(logDir, "evolution.stdout.log"),
37695
+ stderrPath: join39(logDir, "evolution.stderr.log"),
37696
+ dailyTime: parsedTime.value
37341
37697
  });
37342
37698
  const bootout = await this.#runLaunchctl(["bootout", this.#serviceTarget()]);
37343
37699
  if (bootout.exitCode !== 0 && !isLaunchctlServiceNotLoaded(bootout)) {
@@ -37373,7 +37729,7 @@ class OsEvolutionScheduler {
37373
37729
  if (!isLaunchctlServiceNotLoaded(verification)) {
37374
37730
  throw new Error(`Could not verify EvoDev evolution schedule shutdown; its LaunchAgent file was preserved: ${safeCommandError(verification)}`);
37375
37731
  }
37376
- await rm15(path2, { force: true });
37732
+ await rm16(path2, { force: true });
37377
37733
  return createStatus("not-installed", path2);
37378
37734
  }
37379
37735
  #assertDarwin() {
@@ -37417,9 +37773,10 @@ function isLaunchctlServiceNotLoaded(result) {
37417
37773
  ${result.stderr}`);
37418
37774
  }
37419
37775
  function resolveLaunchAgentPath(homeDir) {
37420
- return join38(homeDir, "Library", "LaunchAgents", `${EVOLUTION_SCHEDULE_LABEL}.plist`);
37776
+ return join39(homeDir, "Library", "LaunchAgents", `${EVOLUTION_SCHEDULE_LABEL}.plist`);
37421
37777
  }
37422
37778
  function renderLaunchAgentPlist(input) {
37779
+ const dailyTime = parseDailyTime3(input.dailyTime);
37423
37780
  return [
37424
37781
  '<?xml version="1.0" encoding="UTF-8"?>',
37425
37782
  '<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">',
@@ -37442,8 +37799,13 @@ function renderLaunchAgentPlist(input) {
37442
37799
  " </dict>",
37443
37800
  " <key>RunAtLoad</key>",
37444
37801
  " <true/>",
37445
- " <key>StartInterval</key>",
37446
- ` <integer>${EVOLUTION_SCHEDULE_INTERVAL_SECONDS}</integer>`,
37802
+ " <key>StartCalendarInterval</key>",
37803
+ " <dict>",
37804
+ " <key>Hour</key>",
37805
+ ` <integer>${dailyTime.hour}</integer>`,
37806
+ " <key>Minute</key>",
37807
+ ` <integer>${dailyTime.minute}</integer>`,
37808
+ " </dict>",
37447
37809
  " <key>ProcessType</key>",
37448
37810
  " <string>Background</string>",
37449
37811
  " <key>StandardOutPath</key>",
@@ -37476,12 +37838,12 @@ async function runSchedulerCommand(command, args) {
37476
37838
  });
37477
37839
  }
37478
37840
  async function writeAtomic(path2, value) {
37479
- const temporary = `${path2}.${process.pid}.${randomUUID8()}.tmp`;
37480
- await writeFile26(temporary, value, "utf8");
37841
+ const temporary = `${path2}.${process.pid}.${randomUUID9()}.tmp`;
37842
+ await writeFile27(temporary, value, "utf8");
37481
37843
  try {
37482
- await rename8(temporary, path2);
37844
+ await rename9(temporary, path2);
37483
37845
  } catch (error) {
37484
- await rm15(temporary, { force: true });
37846
+ await rm16(temporary, { force: true });
37485
37847
  throw error;
37486
37848
  }
37487
37849
  }
@@ -37492,14 +37854,47 @@ async function isFile3(path2) {
37492
37854
  return false;
37493
37855
  }
37494
37856
  }
37495
- function createStatus(state, path2) {
37857
+ function createStatus(state, path2, schedule = {
37858
+ scheduleKind: "none",
37859
+ dailyTime: null,
37860
+ intervalSeconds: null
37861
+ }) {
37496
37862
  return {
37497
37863
  state,
37498
37864
  label: EVOLUTION_SCHEDULE_LABEL,
37499
37865
  path: path2,
37500
- intervalSeconds: EVOLUTION_SCHEDULE_INTERVAL_SECONDS
37866
+ ...schedule
37501
37867
  };
37502
37868
  }
37869
+ async function readInstalledSchedule(path2) {
37870
+ try {
37871
+ const plist = await readFile37(path2, "utf8");
37872
+ const hour = /<key>Hour<\/key>\s*<integer>(\d{1,2})<\/integer>/u.exec(plist)?.[1];
37873
+ const minute = /<key>Minute<\/key>\s*<integer>(\d{1,2})<\/integer>/u.exec(plist)?.[1];
37874
+ if (plist.includes("<key>StartCalendarInterval</key>") && hour !== undefined) {
37875
+ const normalized = parseDailyTime3(`${hour.padStart(2, "0")}:${(minute ?? "0").padStart(2, "0")}`);
37876
+ return { scheduleKind: "daily", dailyTime: normalized.value, intervalSeconds: null };
37877
+ }
37878
+ const interval = /<key>StartInterval<\/key>\s*<integer>(\d+)<\/integer>/u.exec(plist)?.[1];
37879
+ if (interval !== undefined) {
37880
+ return {
37881
+ scheduleKind: "legacy-interval",
37882
+ dailyTime: null,
37883
+ intervalSeconds: Number.parseInt(interval, 10)
37884
+ };
37885
+ }
37886
+ } catch {}
37887
+ return { scheduleKind: "unknown", dailyTime: null, intervalSeconds: null };
37888
+ }
37889
+ function parseDailyTime3(value) {
37890
+ const match = /^(\d{2}):(\d{2})$/u.exec(value);
37891
+ const hour = Number(match?.[1]);
37892
+ const minute = Number(match?.[2]);
37893
+ if (match === null || hour > 23 || minute > 59) {
37894
+ throw new Error("Evolution schedule time must use 24-hour HH:MM format.");
37895
+ }
37896
+ return { value, hour, minute };
37897
+ }
37503
37898
  function xmlEscape(value) {
37504
37899
  return value.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;").replaceAll('"', "&quot;").replaceAll("'", "&apos;");
37505
37900
  }
@@ -37519,6 +37914,7 @@ async function runScheduleCommand(argv, options = {}) {
37519
37914
  }
37520
37915
  if (subcommand === "status") {
37521
37916
  const flags = parseScheduleStatusFlags(argv.slice(1));
37917
+ const settingsPromise = readEvolutionSettings(homeDir);
37522
37918
  const [
37523
37919
  status,
37524
37920
  segmentTriggers,
@@ -37527,7 +37923,8 @@ async function runScheduleCommand(argv, options = {}) {
37527
37923
  proposalDaily,
37528
37924
  osStatus,
37529
37925
  settings,
37530
- latestEvolutionJob
37926
+ latestEvolutionJob,
37927
+ dailySchedule
37531
37928
  ] = await Promise.all([
37532
37929
  readLazyEvolutionStatus({
37533
37930
  homeDir,
@@ -37554,8 +37951,13 @@ async function runScheduleCommand(argv, options = {}) {
37554
37951
  }),
37555
37952
  readDailySessionProposalState(homeDir),
37556
37953
  osScheduler.status(),
37557
- readEvolutionAutomationSettings2(homeDir),
37558
- readLatestEvolutionJob(homeDir)
37954
+ settingsPromise,
37955
+ readLatestEvolutionJob(homeDir),
37956
+ settingsPromise.then((value) => readDailyEvolutionScheduleStatus({
37957
+ homeDir,
37958
+ dailyTime: value.schedule.dailyTime,
37959
+ now: options.now?.()
37960
+ }))
37559
37961
  ]);
37560
37962
  write(formatKnowledgeScheduleStatus({
37561
37963
  projectKey: flags.projectKey,
@@ -37569,7 +37971,8 @@ async function runScheduleCommand(argv, options = {}) {
37569
37971
  proposalBacklog,
37570
37972
  proposalLastRunAt: proposalDaily?.lastRunAt ?? null,
37571
37973
  osStatus,
37572
- automation: settings,
37974
+ automation: settings.automation,
37975
+ dailySchedule,
37573
37976
  latestEvolutionJob
37574
37977
  }));
37575
37978
  return 0;
@@ -37577,7 +37980,13 @@ async function runScheduleCommand(argv, options = {}) {
37577
37980
  if (subcommand === "install") {
37578
37981
  if (argv.length !== 1)
37579
37982
  throw new Error(`Unknown schedule install option: ${argv[1]}`);
37580
- const status = await osScheduler.install();
37983
+ const settings = await readEvolutionSettings(homeDir);
37984
+ await prepareDailyEvolutionSchedule({
37985
+ homeDir,
37986
+ dailyTime: settings.schedule.dailyTime,
37987
+ now: options.now?.()
37988
+ });
37989
+ const status = await osScheduler.install(settings.schedule.dailyTime);
37581
37990
  write(formatOsScheduleMutation("installed", status));
37582
37991
  return 0;
37583
37992
  }
@@ -37591,7 +38000,16 @@ async function runScheduleCommand(argv, options = {}) {
37591
38000
  if (subcommand === "tick") {
37592
38001
  if (argv.length !== 1)
37593
38002
  throw new Error(`Unknown schedule tick option: ${argv[1]}`);
37594
- const settings = await readEvolutionAutomationSettings2(homeDir);
38003
+ const settings = await readEvolutionSettings(homeDir);
38004
+ const decision = await decideDailyEvolutionTick({
38005
+ homeDir,
38006
+ dailyTime: settings.schedule.dailyTime,
38007
+ now: options.now?.()
38008
+ });
38009
+ if (!decision.due) {
38010
+ write(formatScheduleTickSkipped(decision.reason));
38011
+ return 0;
38012
+ }
37595
38013
  const service = options.evolutionJobService ?? new EvolutionJobService({
37596
38014
  homeDir,
37597
38015
  now: options.now,
@@ -37600,13 +38018,33 @@ async function runScheduleCommand(argv, options = {}) {
37600
38018
  });
37601
38019
  const started = await service.start({
37602
38020
  source: "schedule",
37603
- knowledge: settings.knowledge,
37604
- semanticKnowledge: settings.semanticKnowledge,
37605
- recommendations: settings.recommendations,
38021
+ knowledge: settings.automation.knowledge,
38022
+ semanticKnowledge: settings.automation.semanticKnowledge,
38023
+ recommendations: settings.automation.recommendations,
37606
38024
  improvementEvals: false,
37607
38025
  forceRecommendations: false
37608
38026
  });
38027
+ if (!started.started && started.job.source === "manual-ui") {
38028
+ write("Scheduled evolution remains due because an independent manual evolution batch is running.");
38029
+ return 0;
38030
+ }
38031
+ await recordDailyEvolutionRun({
38032
+ homeDir,
38033
+ dailyTime: settings.schedule.dailyTime,
38034
+ jobId: started.job.id,
38035
+ status: started.job.status,
38036
+ now: options.now?.()
38037
+ });
37609
38038
  const job = started.started ? await service.wait(started.job.id) : started.job;
38039
+ if (job.status !== started.job.status) {
38040
+ await recordDailyEvolutionRun({
38041
+ homeDir,
38042
+ dailyTime: settings.schedule.dailyTime,
38043
+ jobId: job.id,
38044
+ status: job.status,
38045
+ now: options.now?.()
38046
+ });
38047
+ }
37610
38048
  write(formatScheduleTick(job.status, job.id, started.started));
37611
38049
  return job.status === "failed" ? 1 : 0;
37612
38050
  }
@@ -37684,7 +38122,9 @@ function getScheduleHelpText() {
37684
38122
  "",
37685
38123
  "Notes:",
37686
38124
  " schedule status is read-only.",
37687
- " schedule install registers a macOS user LaunchAgent that runs schedule tick every 15 minutes.",
38125
+ " schedule install registers a macOS user LaunchAgent at evolution.schedule.dailyTime.",
38126
+ " schedule tick runs at most once per local day and catches up once after sleep or restart.",
38127
+ " changing a past daily time starts with the next occurrence; use Evolution Run now for immediate work.",
37688
38128
  " schedule tick reads evolution.automation settings; it does not modify repository files.",
37689
38129
  " model-backed knowledge and recommendations send bounded, credential-redacted Session Evidence to the configured local Code Agent; that Agent may use its configured model provider.",
37690
38130
  " EvoDev does not send telemetry or upload evidence directly.",
@@ -37765,7 +38205,12 @@ function formatKnowledgeScheduleStatus(input) {
37765
38205
  `Project: ${input.projectKey ?? "all"}`,
37766
38206
  `Run: ${input.runId ?? "all"}`,
37767
38207
  `OS schedule: ${input.osStatus.state}`,
37768
- `OS schedule interval: ${Math.round(input.osStatus.intervalSeconds / 60)} minutes`,
38208
+ `OS schedule kind: ${input.osStatus.scheduleKind}`,
38209
+ `Daily trigger: ${input.dailySchedule.dailyTime} (${input.dailySchedule.timeZone})`,
38210
+ `Installed daily trigger: ${input.osStatus.dailyTime ?? "not configured"}`,
38211
+ `Next daily run: ${input.dailySchedule.nextRunAt}`,
38212
+ `Last daily run: ${input.dailySchedule.lastRunAt ?? "never"}`,
38213
+ `Last daily status: ${input.dailySchedule.lastJobStatus ?? "never"}`,
37769
38214
  `Automatic knowledge: ${input.automation.knowledge ? "enabled" : "disabled"}`,
37770
38215
  `Semantic knowledge model: ${input.automation.semanticKnowledge ? "enabled" : "disabled"}`,
37771
38216
  `Automatic recommendations: ${input.automation.recommendations ? "enabled" : "disabled"}`,
@@ -37794,12 +38239,16 @@ function formatOsScheduleMutation(action, status) {
37794
38239
  `EvoDev evolution schedule ${action}`,
37795
38240
  "",
37796
38241
  `State: ${status.state}`,
37797
- `Interval: ${Math.round(status.intervalSeconds / 60)} minutes`,
38242
+ action === "installed" ? `Schedule: daily at ${status.dailyTime ?? "unknown"} local time` : "Schedule: disabled",
37798
38243
  `LaunchAgent: ${status.path}`,
37799
38244
  "Repository files are never modified by scheduled evolution."
37800
38245
  ].join(`
37801
38246
  `);
37802
38247
  }
38248
+ function formatScheduleTickSkipped(reason) {
38249
+ return ["EvoDev evolution schedule tick", "", "Started: no", `Reason: ${reason}`].join(`
38250
+ `);
38251
+ }
37803
38252
  function formatScheduleTick(status, jobId, started) {
37804
38253
  return [
37805
38254
  "EvoDev evolution schedule tick",
@@ -37841,12 +38290,12 @@ function resolveHomeDir12(homeDir) {
37841
38290
  }
37842
38291
  return envHome;
37843
38292
  }
37844
- async function readEvolutionAutomationSettings2(homeDir) {
38293
+ async function readEvolutionSettings(homeDir) {
37845
38294
  try {
37846
- return (await createCoreConfigStore(homeDir).readSettings()).evolution.automation;
38295
+ return (await createCoreConfigStore(homeDir).readSettings()).evolution;
37847
38296
  } catch (error) {
37848
38297
  if (error instanceof Error && error.message.includes("ENOENT")) {
37849
- return createDefaultSettings().evolution.automation;
38298
+ return createDefaultSettings().evolution;
37850
38299
  }
37851
38300
  throw error;
37852
38301
  }
@@ -38649,7 +39098,7 @@ function resolveHomeDir14(homeDir) {
38649
39098
  }
38650
39099
 
38651
39100
  // packages/cli/src/ui/server.ts
38652
- import { spawn as spawn10 } from "node:child_process";
39101
+ import { spawn as spawn11 } from "node:child_process";
38653
39102
  import { randomBytes as randomBytes4, timingSafeEqual } from "node:crypto";
38654
39103
  import { createServer as createServer3 } from "node:http";
38655
39104
 
@@ -38766,8 +39215,8 @@ function safeError4(error) {
38766
39215
  }
38767
39216
 
38768
39217
  // packages/cli/src/ui/assets.ts
38769
- import { readFile as readFile37 } from "node:fs/promises";
38770
- import { dirname as dirname31, join as join39 } from "node:path";
39218
+ import { readFile as readFile38 } from "node:fs/promises";
39219
+ import { dirname as dirname32, join as join40 } from "node:path";
38771
39220
  import { fileURLToPath as fileURLToPath4 } from "node:url";
38772
39221
  var cachedAssets = null;
38773
39222
  function loadUiWebAssets() {
@@ -38775,9 +39224,9 @@ function loadUiWebAssets() {
38775
39224
  return cachedAssets;
38776
39225
  }
38777
39226
  async function loadUiWebAssetsUncached() {
38778
- const moduleDir = dirname31(fileURLToPath4(import.meta.url));
38779
- const sourceEntry = join39(moduleDir, "web", "App.tsx");
38780
- const sourceStyles = join39(moduleDir, "web", "styles.css");
39227
+ const moduleDir = dirname32(fileURLToPath4(import.meta.url));
39228
+ const sourceEntry = join40(moduleDir, "web", "App.tsx");
39229
+ const sourceStyles = join40(moduleDir, "web", "styles.css");
38781
39230
  if (await fileExists2(sourceEntry)) {
38782
39231
  if (typeof Bun === "undefined") {
38783
39232
  throw new Error("Source UI assets require Bun; run bun run build:cli for Node execution.");
@@ -38797,21 +39246,21 @@ async function loadUiWebAssetsUncached() {
38797
39246
  throw new Error("EvoDev UI browser bundle produced no entry point.");
38798
39247
  return { script: await entry.text(), styles: await compileUiStyles(sourceStyles) };
38799
39248
  }
38800
- const distAssetsDir = join39(moduleDir, "ui");
39249
+ const distAssetsDir = join40(moduleDir, "ui");
38801
39250
  return {
38802
- script: await readFile37(join39(distAssetsDir, "app.js"), "utf8"),
38803
- styles: await readFile37(join39(distAssetsDir, "styles.css"), "utf8")
39251
+ script: await readFile38(join40(distAssetsDir, "app.js"), "utf8"),
39252
+ styles: await readFile38(join40(distAssetsDir, "styles.css"), "utf8")
38804
39253
  };
38805
39254
  }
38806
39255
  async function compileUiStyles(inputPath, options = {}) {
38807
39256
  const [{ default: postcss }, { default: tailwindcss }, source] = await Promise.all([
38808
39257
  import("postcss"),
38809
39258
  import("@tailwindcss/postcss"),
38810
- readFile37(inputPath, "utf8")
39259
+ readFile38(inputPath, "utf8")
38811
39260
  ]);
38812
39261
  const result = await postcss([
38813
39262
  tailwindcss({
38814
- base: dirname31(inputPath),
39263
+ base: dirname32(inputPath),
38815
39264
  optimize: options.optimize ?? false,
38816
39265
  transformAssetUrls: false
38817
39266
  })
@@ -38820,7 +39269,7 @@ async function compileUiStyles(inputPath, options = {}) {
38820
39269
  }
38821
39270
  async function fileExists2(path2) {
38822
39271
  try {
38823
- await readFile37(path2, "utf8");
39272
+ await readFile38(path2, "utf8");
38824
39273
  return true;
38825
39274
  } catch {
38826
39275
  return false;
@@ -38828,7 +39277,7 @@ async function fileExists2(path2) {
38828
39277
  }
38829
39278
 
38830
39279
  // packages/cli/src/ui/knowledge-detail.ts
38831
- import { lstat as lstat5, readFile as readFile38, realpath as realpath9 } from "node:fs/promises";
39280
+ import { lstat as lstat5, readFile as readFile39, realpath as realpath9 } from "node:fs/promises";
38832
39281
  import { isAbsolute as isAbsolute14, relative as relative15 } from "node:path";
38833
39282
  class UiKnowledgeDetailError extends Error {
38834
39283
  code;
@@ -38853,7 +39302,7 @@ async function readUiKnowledgeConceptDetail(input) {
38853
39302
  homeDir: input.homeDir,
38854
39303
  path: concept.path
38855
39304
  });
38856
- const markdown = await readFile38(path2, "utf8");
39305
+ const markdown = await readFile39(path2, "utf8");
38857
39306
  if (detectSessionMemorySensitivity(markdown).classification === "credential") {
38858
39307
  throw new UiKnowledgeDetailError("unsafe", "Knowledge detail is unavailable because the local file contains credential-like content.");
38859
39308
  }
@@ -39198,14 +39647,14 @@ function isExecFileExitError(error) {
39198
39647
  }
39199
39648
 
39200
39649
  // packages/cli/src/ui/runtime.ts
39201
- import { mkdir as mkdir27, readFile as readFile39, rm as rm16, writeFile as writeFile27 } from "node:fs/promises";
39202
- import { join as join40 } from "node:path";
39650
+ import { mkdir as mkdir28, readFile as readFile40, rm as rm17, writeFile as writeFile28 } from "node:fs/promises";
39651
+ import { join as join41 } from "node:path";
39203
39652
  function resolveUiRuntimePaths(homeDir) {
39204
- const rootDir = join40(resolveEvoDevPaths(homeDir).stateDir, "ui");
39653
+ const rootDir = join41(resolveEvoDevPaths(homeDir).stateDir, "ui");
39205
39654
  return {
39206
39655
  rootDir,
39207
- runtimePath: join40(rootDir, "runtime.json"),
39208
- tokenPath: join40(rootDir, "token")
39656
+ runtimePath: join41(rootDir, "runtime.json"),
39657
+ tokenPath: join41(rootDir, "token")
39209
39658
  };
39210
39659
  }
39211
39660
  async function readUiRuntimeBundle(homeDir) {
@@ -39225,16 +39674,16 @@ async function writeUiRuntimeState(input) {
39225
39674
  const paths4 = resolveUiRuntimePaths(input.homeDir);
39226
39675
  let tokenCreated = false;
39227
39676
  let runtimeCreated = false;
39228
- await mkdir27(paths4.rootDir, { recursive: true });
39677
+ await mkdir28(paths4.rootDir, { recursive: true });
39229
39678
  try {
39230
- await writeFile27(paths4.tokenPath, `${token}
39679
+ await writeFile28(paths4.tokenPath, `${token}
39231
39680
  `, {
39232
39681
  encoding: "utf8",
39233
39682
  flag: "wx",
39234
39683
  mode: 384
39235
39684
  });
39236
39685
  tokenCreated = true;
39237
- await writeFile27(paths4.runtimePath, `${JSON.stringify(state, null, 2)}
39686
+ await writeFile28(paths4.runtimePath, `${JSON.stringify(state, null, 2)}
39238
39687
  `, {
39239
39688
  encoding: "utf8",
39240
39689
  flag: "wx",
@@ -39244,11 +39693,11 @@ async function writeUiRuntimeState(input) {
39244
39693
  return paths4;
39245
39694
  } catch (error) {
39246
39695
  if (runtimeCreated)
39247
- await rm16(paths4.runtimePath, { force: true }).catch(() => {
39696
+ await rm17(paths4.runtimePath, { force: true }).catch(() => {
39248
39697
  return;
39249
39698
  });
39250
39699
  if (tokenCreated)
39251
- await rm16(paths4.tokenPath, { force: true }).catch(() => {
39700
+ await rm17(paths4.tokenPath, { force: true }).catch(() => {
39252
39701
  return;
39253
39702
  });
39254
39703
  throw error;
@@ -39264,11 +39713,11 @@ async function removeUiRuntimeState(input) {
39264
39713
  return [];
39265
39714
  const removed = [];
39266
39715
  if (current.state !== null) {
39267
- await rm16(paths4.runtimePath, { force: true });
39716
+ await rm17(paths4.runtimePath, { force: true });
39268
39717
  removed.push(paths4.runtimePath);
39269
39718
  }
39270
39719
  if (current.token !== null) {
39271
- await rm16(paths4.tokenPath, { force: true });
39720
+ await rm17(paths4.tokenPath, { force: true });
39272
39721
  removed.push(paths4.tokenPath);
39273
39722
  }
39274
39723
  return removed;
@@ -39311,7 +39760,7 @@ function parseUiRuntimeToken(value) {
39311
39760
  }
39312
39761
  async function readOptionalFile(path2) {
39313
39762
  try {
39314
- return await readFile39(path2, "utf8");
39763
+ return await readFile40(path2, "utf8");
39315
39764
  } catch (error) {
39316
39765
  if (isNodeError5(error) && error.code === "ENOENT")
39317
39766
  return null;
@@ -39322,6 +39771,154 @@ function isNodeError5(error) {
39322
39771
  return error instanceof Error && "code" in error;
39323
39772
  }
39324
39773
 
39774
+ // packages/cli/src/runtime-discovery.ts
39775
+ import { spawn as spawn10 } from "node:child_process";
39776
+ import { readFile as readFile41, stat as stat28 } from "node:fs/promises";
39777
+ import { join as join42 } from "node:path";
39778
+ var MAX_CODEX_CONFIG_BYTES = 1024 * 1024;
39779
+ var MAX_CODEX_CATALOG_BYTES = 8 * 1024 * 1024;
39780
+ var CODEX_DISCOVERY_TIMEOUT_MS = 1e4;
39781
+ var MAX_CODEX_MODELS = 100;
39782
+ var MAX_REASONING_EFFORTS = 20;
39783
+ async function discoverCodexRuntime(input) {
39784
+ const defaults = await readAllowlistedCodexDefaults(input.codexHome ?? process.env.CODEX_HOME ?? join42(input.homeDir, ".codex"));
39785
+ try {
39786
+ const result = await (input.commandRunner ?? runCommand)(input.command ?? "codex", [
39787
+ "debug",
39788
+ "models"
39789
+ ]);
39790
+ if (result.exitCode !== 0)
39791
+ throw new Error("Codex model discovery failed.");
39792
+ const models = parseCodexModelCatalog(result.stdout);
39793
+ if (models.length === 0)
39794
+ throw new Error("Codex returned an empty model catalog.");
39795
+ const selected = models.find((model) => model.id === defaults.model) ?? models[0] ?? null;
39796
+ return {
39797
+ available: true,
39798
+ defaultModel: defaults.model ?? selected?.id ?? null,
39799
+ defaultReasoningEffort: defaults.reasoningEffort ?? selected?.defaultReasoningEffort ?? null,
39800
+ models,
39801
+ warning: null
39802
+ };
39803
+ } catch {
39804
+ return {
39805
+ available: false,
39806
+ defaultModel: defaults.model,
39807
+ defaultReasoningEffort: defaults.reasoningEffort,
39808
+ models: [],
39809
+ warning: "Codex model catalog is unavailable; runtime defaults will still be inherited."
39810
+ };
39811
+ }
39812
+ }
39813
+ function parseCodexModelCatalog(value) {
39814
+ let parsed;
39815
+ try {
39816
+ parsed = JSON.parse(value);
39817
+ } catch {
39818
+ return [];
39819
+ }
39820
+ if (!isRecord22(parsed) || !Array.isArray(parsed.models))
39821
+ return [];
39822
+ const models = [];
39823
+ const seenModels = new Set;
39824
+ for (const entry of parsed.models) {
39825
+ if (models.length >= MAX_CODEX_MODELS)
39826
+ break;
39827
+ if (!isRecord22(entry))
39828
+ continue;
39829
+ const id = printableText(entry.slug, 120);
39830
+ if (id === null || seenModels.has(id))
39831
+ continue;
39832
+ const label = printableText(entry.display_name, 120) ?? id;
39833
+ const defaultReasoningEffort = printableText(entry.default_reasoning_level, 40) ?? "medium";
39834
+ const supportedReasoningEfforts = Array.isArray(entry.supported_reasoning_levels) ? [
39835
+ ...new Set(entry.supported_reasoning_levels.map((option) => isRecord22(option) ? printableText(option.effort, 40) : null).filter((effort) => effort !== null))
39836
+ ].slice(0, MAX_REASONING_EFFORTS) : [];
39837
+ seenModels.add(id);
39838
+ models.push({ id, label, defaultReasoningEffort, supportedReasoningEfforts });
39839
+ }
39840
+ return models;
39841
+ }
39842
+ async function readAllowlistedCodexDefaults(codexHome) {
39843
+ const path2 = join42(codexHome, "config.toml");
39844
+ try {
39845
+ const metadata = await stat28(path2);
39846
+ if (!metadata.isFile() || metadata.size > MAX_CODEX_CONFIG_BYTES) {
39847
+ return { model: null, reasoningEffort: null };
39848
+ }
39849
+ const config2 = await readFile41(path2, "utf8");
39850
+ if (Buffer.byteLength(config2, "utf8") > MAX_CODEX_CONFIG_BYTES) {
39851
+ return { model: null, reasoningEffort: null };
39852
+ }
39853
+ let inTopLevel = true;
39854
+ let model = null;
39855
+ let reasoningEffort = null;
39856
+ for (const line of config2.split(/\r?\n/u)) {
39857
+ const trimmed = line.trim();
39858
+ if (trimmed.startsWith("["))
39859
+ inTopLevel = false;
39860
+ if (!inTopLevel || trimmed === "" || trimmed.startsWith("#"))
39861
+ continue;
39862
+ const match = /^(model|model_reasoning_effort)\s*=\s*(["'])([^"']+)\2\s*(?:#.*)?$/u.exec(trimmed);
39863
+ if (match === null)
39864
+ continue;
39865
+ const parsed = printableText(match[3], 120);
39866
+ if (match[1] === "model")
39867
+ model = parsed;
39868
+ else
39869
+ reasoningEffort = parsed;
39870
+ }
39871
+ return { model, reasoningEffort };
39872
+ } catch {
39873
+ return { model: null, reasoningEffort: null };
39874
+ }
39875
+ }
39876
+ async function runCommand(command, args) {
39877
+ return await new Promise((resolvePromise, reject) => {
39878
+ const child = spawn10(command, args, { stdio: ["ignore", "pipe", "ignore"] });
39879
+ let stdout = "";
39880
+ let settled = false;
39881
+ const finish = (result) => {
39882
+ if (settled)
39883
+ return;
39884
+ settled = true;
39885
+ clearTimeout(timeout);
39886
+ if (result instanceof Error)
39887
+ reject(result);
39888
+ else
39889
+ resolvePromise(result);
39890
+ };
39891
+ const timeout = setTimeout(() => {
39892
+ child.kill("SIGTERM");
39893
+ finish(new Error("Codex model discovery timed out."));
39894
+ }, CODEX_DISCOVERY_TIMEOUT_MS);
39895
+ child.stdout?.on("data", (chunk) => {
39896
+ stdout += String(chunk);
39897
+ if (Buffer.byteLength(stdout, "utf8") > MAX_CODEX_CATALOG_BYTES) {
39898
+ child.kill("SIGTERM");
39899
+ finish(new Error("Codex model catalog exceeded the local size limit."));
39900
+ }
39901
+ });
39902
+ child.on("error", finish);
39903
+ child.on("close", (code) => finish({ exitCode: code ?? 1, stdout }));
39904
+ });
39905
+ }
39906
+ function printableText(value, maxLength) {
39907
+ if (typeof value !== "string")
39908
+ return null;
39909
+ const normalized = value.trim();
39910
+ if (normalized === "" || normalized.length > maxLength || [...normalized].some((character) => {
39911
+ const code = character.charCodeAt(0);
39912
+ return code < 32 || code === 127;
39913
+ })) {
39914
+ return null;
39915
+ }
39916
+ return normalized;
39917
+ }
39918
+ function isRecord22(value) {
39919
+ return typeof value === "object" && value !== null && !Array.isArray(value);
39920
+ }
39921
+
39325
39922
  // packages/cli/src/ui/settings.ts
39326
39923
  class UiSettingsError extends Error {
39327
39924
  code;
@@ -39333,11 +39930,17 @@ class UiSettingsError extends Error {
39333
39930
  }
39334
39931
  async function readUiSettingsSnapshot(input) {
39335
39932
  const scheduler = input.osScheduler ?? new OsEvolutionScheduler({ homeDir: input.homeDir });
39336
- const [settings, scheduleStatus] = await Promise.all([
39337
- readEvoDevSettingsSnapshot(input.homeDir),
39338
- scheduler.status()
39933
+ const settings = await readEvoDevSettingsSnapshot(input.homeDir);
39934
+ const [scheduleStatus, dailySchedule, codex] = await Promise.all([
39935
+ scheduler.status(),
39936
+ readDailyEvolutionScheduleStatus({
39937
+ homeDir: input.homeDir,
39938
+ dailyTime: settings.settings.evolution.schedule.dailyTime,
39939
+ now: input.now
39940
+ }),
39941
+ (input.discoverCodex ?? (() => discoverCodexRuntime({ homeDir: input.homeDir })))()
39339
39942
  ]);
39340
- return createUiSettingsSnapshot(settings.settings, settings.revision, scheduleStatus);
39943
+ return createUiSettingsSnapshot(settings.settings, settings.revision, scheduleStatus, dailySchedule, codex);
39341
39944
  }
39342
39945
  async function updateUiSetting(input) {
39343
39946
  const scheduler = input.osScheduler ?? new OsEvolutionScheduler({ homeDir: input.homeDir });
@@ -39348,7 +39951,23 @@ async function updateUiSetting(input) {
39348
39951
  value: input.value,
39349
39952
  expectedRevision: input.expectedRevision
39350
39953
  });
39351
- return createUiSettingsSnapshot(settings.settings, settings.revision, scheduleStatus);
39954
+ let installedStatus = scheduleStatus;
39955
+ if (input.key === "evolution.schedule.dailyTime") {
39956
+ const dailyTime = settings.settings.evolution.schedule.dailyTime;
39957
+ await prepareDailyEvolutionSchedule({ homeDir: input.homeDir, dailyTime, now: input.now });
39958
+ if (scheduleStatus.state === "installed-loaded" || scheduleStatus.state === "installed-not-loaded") {
39959
+ installedStatus = await scheduler.install(dailyTime);
39960
+ }
39961
+ }
39962
+ const [dailySchedule, codex] = await Promise.all([
39963
+ readDailyEvolutionScheduleStatus({
39964
+ homeDir: input.homeDir,
39965
+ dailyTime: settings.settings.evolution.schedule.dailyTime,
39966
+ now: input.now
39967
+ }),
39968
+ (input.discoverCodex ?? (() => discoverCodexRuntime({ homeDir: input.homeDir })))()
39969
+ ]);
39970
+ return createUiSettingsSnapshot(settings.settings, settings.revision, installedStatus, dailySchedule, codex);
39352
39971
  }
39353
39972
  async function updateUiEvolutionScheduler(input) {
39354
39973
  const scheduler = input.osScheduler ?? new OsEvolutionScheduler({ homeDir: input.homeDir });
@@ -39360,21 +39979,41 @@ async function updateUiEvolutionScheduler(input) {
39360
39979
  throw new UiSettingsError("unsupported", "OS evolution scheduling is not supported on this platform.");
39361
39980
  }
39362
39981
  let after;
39982
+ const settings = await readEvoDevSettingsSnapshot(input.homeDir);
39363
39983
  if (input.enabled) {
39364
- after = before.state === "installed-loaded" ? before : await scheduler.install();
39984
+ await prepareDailyEvolutionSchedule({
39985
+ homeDir: input.homeDir,
39986
+ dailyTime: settings.settings.evolution.schedule.dailyTime,
39987
+ now: input.now
39988
+ });
39989
+ after = before.state === "installed-loaded" && before.scheduleKind === "daily" && before.dailyTime === settings.settings.evolution.schedule.dailyTime ? before : await scheduler.install(settings.settings.evolution.schedule.dailyTime);
39365
39990
  } else {
39366
39991
  after = before.state === "not-installed" ? before : await scheduler.uninstall();
39367
39992
  }
39368
- const settings = await readEvoDevSettingsSnapshot(input.homeDir);
39369
- return createUiSettingsSnapshot(settings.settings, settings.revision, after);
39993
+ const [dailySchedule, codex] = await Promise.all([
39994
+ readDailyEvolutionScheduleStatus({
39995
+ homeDir: input.homeDir,
39996
+ dailyTime: settings.settings.evolution.schedule.dailyTime,
39997
+ now: input.now
39998
+ }),
39999
+ (input.discoverCodex ?? (() => discoverCodexRuntime({ homeDir: input.homeDir })))()
40000
+ ]);
40001
+ return createUiSettingsSnapshot(settings.settings, settings.revision, after, dailySchedule, codex);
39370
40002
  }
39371
- function createUiSettingsSnapshot(settings, revision, scheduler) {
40003
+ function createUiSettingsSnapshot(settings, revision, scheduler, dailySchedule, codex) {
39372
40004
  return {
39373
40005
  schemaVersion: 1,
39374
40006
  revision,
39375
40007
  scheduler: {
39376
40008
  state: scheduler.state,
39377
- intervalSeconds: scheduler.intervalSeconds
40009
+ scheduleKind: scheduler.scheduleKind,
40010
+ configuredDailyTime: settings.evolution.schedule.dailyTime,
40011
+ installedDailyTime: scheduler.dailyTime,
40012
+ legacyIntervalSeconds: scheduler.intervalSeconds,
40013
+ timeZone: dailySchedule.timeZone,
40014
+ nextRunAt: dailySchedule.nextRunAt,
40015
+ lastRunAt: dailySchedule.lastRunAt,
40016
+ lastJobStatus: dailySchedule.lastJobStatus
39378
40017
  },
39379
40018
  evolution: settings.evolution.automation,
39380
40019
  knowledge: {
@@ -39382,7 +40021,13 @@ function createUiSettingsSnapshot(settings, revision, scheduler) {
39382
40021
  runtimeInjection: settings.memory.runtimeInjection,
39383
40022
  staleReview: settings.memory.staleReview
39384
40023
  },
39385
- teamRuntime: settings.teamRuntime
40024
+ teamRuntime: settings.teamRuntime,
40025
+ runtimeDiscovery: {
40026
+ codex,
40027
+ claude: {
40028
+ reasoningEfforts: ["low", "medium", "high", "xhigh", "max"]
40029
+ }
40030
+ }
39386
40031
  };
39387
40032
  }
39388
40033
 
@@ -39755,7 +40400,8 @@ async function readUiDashboardSnapshot(input) {
39755
40400
  okfKnowledge,
39756
40401
  knowledgeChanges,
39757
40402
  staleReviewRemindersEnabled,
39758
- segmentTriggers
40403
+ segmentTriggers,
40404
+ evolutionJobs
39759
40405
  ] = await Promise.all([
39760
40406
  readSessionBundles(input.homeDir, warnings),
39761
40407
  listSessionEvidenceSegments({ homeDir: input.homeDir }).catch(() => {
@@ -39813,6 +40459,10 @@ async function readUiDashboardSnapshot(input) {
39813
40459
  listSegmentEvolutionTriggers({ homeDir: input.homeDir }).catch(() => {
39814
40460
  warnings.push("Segment trigger index unavailable.");
39815
40461
  return [];
40462
+ }),
40463
+ listEvolutionJobs(input.homeDir).catch(() => {
40464
+ warnings.push("Evolution job history unavailable.");
40465
+ return [];
39816
40466
  })
39817
40467
  ]);
39818
40468
  const roles = await buildRoleSummaries({
@@ -39852,6 +40502,11 @@ async function readUiDashboardSnapshot(input) {
39852
40502
  const pending = [
39853
40503
  ...evolutionSnapshot.repoProposals.filter((proposal) => (proposal.reviewState === "pending" || proposal.reviewState === "deferred") && hasConcreteRepoProposalChanges(proposal)).map(summarizePendingProposal)
39854
40504
  ].sort(compareReviewItemsByCreatedAt);
40505
+ const reviewBatches = summarizeReviewBatches({
40506
+ jobs: evolutionJobs,
40507
+ proposals: evolutionSnapshot.repoProposals,
40508
+ knowledgeChanges
40509
+ });
39855
40510
  const progressionEvents = buildProgressionEvents({
39856
40511
  homeDir: input.homeDir,
39857
40512
  repoRoot,
@@ -39918,7 +40573,8 @@ async function readUiDashboardSnapshot(input) {
39918
40573
  evolution: {
39919
40574
  artifacts,
39920
40575
  knowledge: knowledge2,
39921
- knowledgeChanges: summarizedKnowledgeChanges
40576
+ knowledgeChanges: summarizedKnowledgeChanges,
40577
+ reviewBatches
39922
40578
  },
39923
40579
  reviews: {
39924
40580
  pending,
@@ -39934,6 +40590,40 @@ async function readUiDashboardSnapshot(input) {
39934
40590
  warnings
39935
40591
  };
39936
40592
  }
40593
+ function summarizeReviewBatches(input) {
40594
+ const proposals = new Map(input.proposals.map((proposal) => [proposal.id, proposal]));
40595
+ const changes2 = new Map(input.knowledgeChanges.map((change) => [change.id, change]));
40596
+ return input.jobs.map((job) => {
40597
+ const proposalIds = (job.outputs?.proposalIds ?? []).filter((id) => {
40598
+ const proposal = proposals.get(id);
40599
+ return proposal !== undefined && hasConcreteRepoProposalChanges(proposal);
40600
+ });
40601
+ const knowledgeChangeIds = (job.outputs?.knowledgeChangeIds ?? []).filter((id) => changes2.has(id));
40602
+ if (proposalIds.length === 0 && knowledgeChangeIds.length === 0)
40603
+ return null;
40604
+ const pendingProposalIds = proposalIds.filter((id) => {
40605
+ const state = proposals.get(id)?.reviewState;
40606
+ return state === "pending" || state === "deferred";
40607
+ });
40608
+ const pendingKnowledgeChangeIds = knowledgeChangeIds.filter((id) => {
40609
+ const state = changes2.get(id)?.state;
40610
+ return state === "pending" || state === "deferred" || state === "accepted";
40611
+ });
40612
+ return {
40613
+ id: job.id,
40614
+ source: job.source === "schedule" ? "daily" : "manual",
40615
+ state: pendingProposalIds.length + pendingKnowledgeChangeIds.length > 0 ? "pending" : "reviewed",
40616
+ jobStatus: job.status,
40617
+ createdAt: job.createdAt,
40618
+ finishedAt: job.finishedAt,
40619
+ proposalIds,
40620
+ knowledgeChangeIds,
40621
+ pendingProposalIds,
40622
+ pendingKnowledgeChangeIds,
40623
+ warningCount: job.warnings.length
40624
+ };
40625
+ }).filter((batch) => batch !== null);
40626
+ }
39937
40627
  async function buildRoleSummaries(input) {
39938
40628
  const roleIds = new Set(["main"]);
39939
40629
  const agentsByRole = new Map;
@@ -40787,6 +41477,7 @@ async function runUiCommand(argv, options = {}) {
40787
41477
  proposalApplicationRunner: options.proposalApplicationRunner,
40788
41478
  knowledgeFileLauncher: options.knowledgeFileLauncher,
40789
41479
  osScheduler: options.osScheduler,
41480
+ discoverCodex: options.discoverCodex,
40790
41481
  platform: options.platform
40791
41482
  });
40792
41483
  }
@@ -41377,7 +42068,8 @@ async function startUiServer(input) {
41377
42068
  return;
41378
42069
  const settings = await readUiSettingsSnapshot({
41379
42070
  homeDir: input.homeDir,
41380
- osScheduler: input.osScheduler
42071
+ osScheduler: input.osScheduler,
42072
+ discoverCodex: input.discoverCodex
41381
42073
  });
41382
42074
  writeJsonResponse(response, 200, { ok: true, data: settings });
41383
42075
  return;
@@ -41390,6 +42082,7 @@ async function startUiServer(input) {
41390
42082
  const settings = await updateUiSetting({
41391
42083
  homeDir: input.homeDir,
41392
42084
  osScheduler: input.osScheduler,
42085
+ discoverCodex: input.discoverCodex,
41393
42086
  ...mutation
41394
42087
  });
41395
42088
  writeJsonResponse(response, 200, { ok: true, data: settings });
@@ -41403,6 +42096,7 @@ async function startUiServer(input) {
41403
42096
  const settings = await updateUiEvolutionScheduler({
41404
42097
  homeDir: input.homeDir,
41405
42098
  osScheduler: input.osScheduler,
42099
+ discoverCodex: input.discoverCodex,
41406
42100
  ...mutation
41407
42101
  });
41408
42102
  writeJsonResponse(response, 200, { ok: true, data: settings });
@@ -42134,7 +42828,7 @@ function isAllowedLocalOrigin2(origin) {
42134
42828
  return false;
42135
42829
  }
42136
42830
  }
42137
- function openUiBrowser(url, write, spawnProcess = spawn10) {
42831
+ function openUiBrowser(url, write, spawnProcess = spawn11) {
42138
42832
  const platform = process.platform;
42139
42833
  const command = platform === "darwin" ? "open" : platform === "win32" ? "cmd" : "xdg-open";
42140
42834
  const args = platform === "win32" ? ["/c", "start", "", url] : [url];
@@ -42157,7 +42851,7 @@ function resolveHomeDir15(homeDir) {
42157
42851
  return envHome;
42158
42852
  }
42159
42853
  // packages/cli/src/workflow.ts
42160
- import { join as join42 } from "node:path";
42854
+ import { join as join44 } from "node:path";
42161
42855
  function getWorkflowHelpText() {
42162
42856
  return [
42163
42857
  "EvoDev workflow",
@@ -42220,7 +42914,7 @@ function parseWorkflowDryRunFlags(argv) {
42220
42914
  return { workflowId };
42221
42915
  }
42222
42916
  function resolveDefaultWorkflowsDir() {
42223
- return join42(resolveDefaultAssetsRootDir(), "workflows");
42917
+ return join44(resolveDefaultAssetsRootDir(), "workflows");
42224
42918
  }
42225
42919
  function isHelpArg3(arg) {
42226
42920
  return arg === undefined || arg === "help" || arg === "--help" || arg === "-h";
@@ -42280,13 +42974,14 @@ function getHelpText() {
42280
42974
  " evo process --once Consume queued evolution triggers",
42281
42975
  " evo import --source <source> --path <file> [--project <key>] (--dry-run [--json] | --apply <preview-id> --yes) Preview or apply bounded historical evidence",
42282
42976
  " schedule status Show OS schedule, automation settings, and pending work",
42283
- " schedule install|uninstall Manage the macOS user evolution LaunchAgent",
42284
- " schedule tick Run one configured knowledge/recommendation job",
42977
+ " schedule install|uninstall Manage the daily macOS user evolution LaunchAgent",
42978
+ " schedule tick Run the due daily knowledge/recommendation job",
42285
42979
  " schedule run knowledge --dry-run|--activate Run the local knowledge processor once",
42286
42980
  " schedule run proposals --dry-run|--activate Analyze prior session corrections into repo proposals",
42287
42981
  " config set evolution.automation.knowledge true|false Toggle scheduled local knowledge",
42288
42982
  " config set evolution.automation.semanticKnowledge true|false Toggle scheduled model-backed semantic knowledge",
42289
42983
  " config set evolution.automation.recommendations true|false Toggle scheduled model-backed recommendations",
42984
+ " config set evolution.schedule.dailyTime HH:MM Set the system-local daily trigger time",
42290
42985
  " config set memory.reviewKnowledgeUpdates true|false Review runtime-affecting knowledge updates",
42291
42986
  " config set memory.runtimeInjection true|false Toggle verified knowledge runtime delivery",
42292
42987
  " config set memory.staleReview true|false Toggle review-due reminders",
@@ -42315,7 +43010,7 @@ function getHelpText() {
42315
43010
  ].join(`
42316
43011
  `);
42317
43012
  }
42318
- var CLI_VERSION = "0.0.1-alpha.16";
43013
+ var CLI_VERSION = "0.0.1-alpha.18";
42319
43014
  function getVersionText() {
42320
43015
  return `evodev ${CLI_VERSION}`;
42321
43016
  }
@@ -42351,7 +43046,7 @@ async function runWithCliLogging(argv, options) {
42351
43046
  });
42352
43047
  }
42353
43048
  try {
42354
- const exitCode = await runCommand(commandArgv, options);
43049
+ const exitCode = await runCommand2(commandArgv, options);
42355
43050
  if (!suppressRuntimeSideEffects && exitCode === 0) {
42356
43051
  await runLazyEvolutionFallbackSafely(commandArgv, options, debug);
42357
43052
  await runPostCommandMaintenanceSafely(commandArgv, options, debug);
@@ -42408,7 +43103,7 @@ function isReadOnlyCommand(argv) {
42408
43103
  function isBackgroundCommand(argv) {
42409
43104
  return argv[0] === "schedule" && argv[1] === "tick";
42410
43105
  }
42411
- async function runCommand(argv, options) {
43106
+ async function runCommand2(argv, options) {
42412
43107
  const command = argv[0];
42413
43108
  if (command === undefined || command === "help" || command === "--help" || command === "-h") {
42414
43109
  console.log(getHelpText());
@@ -43044,8 +43739,10 @@ export {
43044
43739
  resolveInitProjectCandidates,
43045
43740
  resolveEvolutionJobPath,
43046
43741
  resolveDefaultCodeAgentSelections,
43742
+ resolveDailyScheduleStatePath,
43047
43743
  renderUiDashboardHtml,
43048
43744
  renderLaunchAgentPlist,
43745
+ recordDailyEvolutionRun,
43049
43746
  readUiSettingsSnapshot,
43050
43747
  readUiDashboardSnapshot,
43051
43748
  readSessionProposalBacklogStatus,
@@ -43054,13 +43751,18 @@ export {
43054
43751
  readEvolutionJob,
43055
43752
  readEvoDevSettingsSnapshot,
43056
43753
  readDailySessionProposalState,
43754
+ readDailyEvolutionScheduleStatus,
43755
+ readDailyEvolutionScheduleState,
43057
43756
  previewTrajectoryImport,
43058
43757
  prepareTrajectoryImport,
43758
+ prepareDailyEvolutionSchedule,
43059
43759
  parseSyncFlags,
43060
43760
  parseSessionProposalAnalysis,
43061
43761
  parseSemanticKnowledgeAnalysis,
43062
43762
  parseInitFlags,
43063
43763
  parseImprovementEvalReplayAnalysis,
43764
+ parseCodexModelCatalog,
43765
+ listEvolutionJobs,
43064
43766
  getWorkflowHelpText,
43065
43767
  getVersionText,
43066
43768
  getUiHelpText,
@@ -43073,6 +43775,8 @@ export {
43073
43775
  formatTrajectoryImportApplyResult,
43074
43776
  formatSyncOutput,
43075
43777
  formatDoctorOutput,
43778
+ discoverCodexRuntime,
43779
+ decideDailyEvolutionTick,
43076
43780
  createSessionKnowledgeDistiller,
43077
43781
  createInteractivePrompter,
43078
43782
  createInitProjectChoices,
@@ -43098,6 +43802,7 @@ export {
43098
43802
  EVOLUTION_SCHEDULE_INTERVAL_SECONDS,
43099
43803
  DEFAULT_LAZY_EVOLUTION_LIMIT,
43100
43804
  DEFAULT_LAZY_EVOLUTION_INTERVAL_MS,
43805
+ DEFAULT_EVOLUTION_SCHEDULE_DAILY_TIME,
43101
43806
  DEFAULT_CODE_AGENT_MARKETPLACE_SOURCE,
43102
43807
  DEFAULT_CODEX_MARKETPLACE_SOURCE,
43103
43808
  DEFAULT_CODEX_MARKETPLACE_FALLBACK_SOURCE,