@evo-dev/evodev 0.0.1-alpha.12 → 0.0.1-alpha.13
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/.claude-plugin/marketplace.json +2 -2
- package/dist/index.js +942 -116
- package/dist/plugins/evodev/.claude-plugin/plugin.json +1 -1
- package/dist/plugins/evodev/.codex-plugin/plugin.json +1 -1
- package/dist/plugins/evodev/package.json +1 -1
- package/dist/ui/app.js +9 -9
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -223,7 +223,7 @@ var require_lib = __commonJS((exports, module) => {
|
|
|
223
223
|
|
|
224
224
|
// packages/cli/src/index.ts
|
|
225
225
|
import { realpathSync } from "node:fs";
|
|
226
|
-
import { resolve as
|
|
226
|
+
import { resolve as resolve14 } from "node:path";
|
|
227
227
|
import { fileURLToPath as fileURLToPath5 } from "node:url";
|
|
228
228
|
|
|
229
229
|
// packages/core/src/agents/index.ts
|
|
@@ -21252,7 +21252,9 @@ function resolveDefaultAssetsRootDir(importMetaUrl = import.meta.url) {
|
|
|
21252
21252
|
}
|
|
21253
21253
|
|
|
21254
21254
|
// packages/cli/src/init.ts
|
|
21255
|
-
import { stat as stat16 } from "node:fs/promises";
|
|
21255
|
+
import { realpath as realpath2, stat as stat16 } from "node:fs/promises";
|
|
21256
|
+
import { tmpdir } from "node:os";
|
|
21257
|
+
import { isAbsolute as isAbsolute9, parse as parse2, relative as relative10, resolve as resolve9, sep as sep3 } from "node:path";
|
|
21256
21258
|
|
|
21257
21259
|
// node_modules/.bun/@inquirer+core@11.2.1+6983e0b160ab4824/node_modules/@inquirer/core/dist/lib/key.js
|
|
21258
21260
|
var keybindings = ["emacs", "vim"];
|
|
@@ -24847,9 +24849,12 @@ var DEFAULT_INIT_ANSWERS = {
|
|
|
24847
24849
|
initializeConfig: true,
|
|
24848
24850
|
registerSkills: true,
|
|
24849
24851
|
registerAgents: true,
|
|
24852
|
+
registerRecommendedProjects: false,
|
|
24850
24853
|
runDoctor: true
|
|
24851
24854
|
};
|
|
24852
|
-
var INIT_TOTAL_STEPS =
|
|
24855
|
+
var INIT_TOTAL_STEPS = 7;
|
|
24856
|
+
var INIT_RECOMMENDED_PROJECT_MAX_AGE_DAYS = 90;
|
|
24857
|
+
var MILLISECONDS_PER_DAY = 24 * 60 * 60 * 1000;
|
|
24853
24858
|
async function runInit(options = {}) {
|
|
24854
24859
|
const homeDir = resolveHomeDir3(options.homeDir);
|
|
24855
24860
|
const assetsRootDir = options.assetsRootDir ?? resolveDefaultAssetsRootDir();
|
|
@@ -24920,6 +24925,7 @@ async function runInit(options = {}) {
|
|
|
24920
24925
|
pluginInstallResults: [],
|
|
24921
24926
|
hookInstallResults: [],
|
|
24922
24927
|
syncResult: null,
|
|
24928
|
+
projectRegistrationResults: [],
|
|
24923
24929
|
doctorRan: true,
|
|
24924
24930
|
warnings: warnings2
|
|
24925
24931
|
};
|
|
@@ -24998,6 +25004,7 @@ async function runInit(options = {}) {
|
|
|
24998
25004
|
pluginInstallResults,
|
|
24999
25005
|
hookInstallResults: hookInstallResults2,
|
|
25000
25006
|
syncResult: syncResult2,
|
|
25007
|
+
projectRegistrationResults: [],
|
|
25001
25008
|
warnings: warnings2
|
|
25002
25009
|
}));
|
|
25003
25010
|
return {
|
|
@@ -25015,6 +25022,7 @@ async function runInit(options = {}) {
|
|
|
25015
25022
|
pluginInstallResults,
|
|
25016
25023
|
hookInstallResults: hookInstallResults2,
|
|
25017
25024
|
syncResult: syncResult2,
|
|
25025
|
+
projectRegistrationResults: [],
|
|
25018
25026
|
doctorRan: doctorRan2,
|
|
25019
25027
|
warnings: warnings2
|
|
25020
25028
|
};
|
|
@@ -25058,11 +25066,19 @@ async function runInit(options = {}) {
|
|
|
25058
25066
|
detail: formatSyncProgressDetail(syncResult)
|
|
25059
25067
|
});
|
|
25060
25068
|
}
|
|
25069
|
+
const projectRegistrationResults = await registerInitProjects({
|
|
25070
|
+
homeDir,
|
|
25071
|
+
now: timestamp,
|
|
25072
|
+
autoRegisterRecommended: options.answers?.registerRecommendedProjects === true,
|
|
25073
|
+
skipProjectSelection: options.answers?.registerRecommendedProjects === false,
|
|
25074
|
+
prompter: options.prompter,
|
|
25075
|
+
progress: options.progress
|
|
25076
|
+
});
|
|
25061
25077
|
const doctorRan = doctorResult !== null;
|
|
25062
|
-
const warnings = collectWarnings(syncResult, hookInstallResults, answers.runDoctor, doctorRan);
|
|
25078
|
+
const warnings = collectWarnings(syncResult, hookInstallResults, projectRegistrationResults, answers.runDoctor, doctorRan);
|
|
25063
25079
|
emitInitProgress(options.progress, {
|
|
25064
25080
|
status: "done",
|
|
25065
|
-
current:
|
|
25081
|
+
current: 7,
|
|
25066
25082
|
label: "Finish init",
|
|
25067
25083
|
detail: warnings.length === 0 ? "ready" : `${warnings.length} warning(s)`
|
|
25068
25084
|
});
|
|
@@ -25080,6 +25096,7 @@ async function runInit(options = {}) {
|
|
|
25080
25096
|
pluginInstallResults,
|
|
25081
25097
|
hookInstallResults,
|
|
25082
25098
|
syncResult,
|
|
25099
|
+
projectRegistrationResults,
|
|
25083
25100
|
warnings
|
|
25084
25101
|
}));
|
|
25085
25102
|
return {
|
|
@@ -25097,6 +25114,7 @@ async function runInit(options = {}) {
|
|
|
25097
25114
|
pluginInstallResults,
|
|
25098
25115
|
hookInstallResults,
|
|
25099
25116
|
syncResult,
|
|
25117
|
+
projectRegistrationResults,
|
|
25100
25118
|
doctorRan,
|
|
25101
25119
|
warnings
|
|
25102
25120
|
};
|
|
@@ -25139,6 +25157,7 @@ function formatInitSummary(input) {
|
|
|
25139
25157
|
...result.warnings.map((warning) => ` ⚠️ ${warning}`),
|
|
25140
25158
|
...result.errors.map((error) => ` ❌ ${error}`)
|
|
25141
25159
|
]) ?? [" - skipped"];
|
|
25160
|
+
const projectLines = formatProjectRegistrationLines(input.projectRegistrationResults);
|
|
25142
25161
|
const warningLines = formatSummaryWarningLines(input.warnings, input.syncResult);
|
|
25143
25162
|
return [
|
|
25144
25163
|
"EvoDev init result",
|
|
@@ -25146,7 +25165,7 @@ function formatInitSummary(input) {
|
|
|
25146
25165
|
...EVOHUB_BANNER,
|
|
25147
25166
|
"",
|
|
25148
25167
|
failedPluginInstall ? "❌ Status: stopped" : "✅ Status: ready",
|
|
25149
|
-
failedPluginInstall ? "Progress: stopped at Code Agent plugin install" : "Progress:
|
|
25168
|
+
failedPluginInstall ? "Progress: stopped at Code Agent plugin install" : "Progress: 7/7 complete",
|
|
25150
25169
|
`Selected Code Agents: ${input.selectedPlugins.length > 0 ? input.selectedPlugins.join(", ") : "none"}`,
|
|
25151
25170
|
`Settings: ${input.settingsPath}`,
|
|
25152
25171
|
`Registry: ${input.registryPath}`,
|
|
@@ -25164,6 +25183,8 @@ function formatInitSummary(input) {
|
|
|
25164
25183
|
...hookInstallLines,
|
|
25165
25184
|
"Sync:",
|
|
25166
25185
|
...syncLines,
|
|
25186
|
+
"Projects:",
|
|
25187
|
+
...projectLines,
|
|
25167
25188
|
"Warnings:",
|
|
25168
25189
|
...warningLines
|
|
25169
25190
|
].join(`
|
|
@@ -25191,6 +25212,8 @@ function formatInitStoppedSummary(input) {
|
|
|
25191
25212
|
" - skipped",
|
|
25192
25213
|
"Sync:",
|
|
25193
25214
|
" - skipped",
|
|
25215
|
+
"Projects:",
|
|
25216
|
+
" - skipped",
|
|
25194
25217
|
"Warnings:",
|
|
25195
25218
|
...formatSimpleWarningLines(input.warnings),
|
|
25196
25219
|
...doctorOutputLines,
|
|
@@ -25219,6 +25242,14 @@ function formatHookInstallLines(results) {
|
|
|
25219
25242
|
...result.warnings.map((warning) => ` ⚠️ ${warning}`)
|
|
25220
25243
|
]);
|
|
25221
25244
|
}
|
|
25245
|
+
function formatProjectRegistrationLines(results) {
|
|
25246
|
+
if (results.length === 0)
|
|
25247
|
+
return [" - no new projects registered"];
|
|
25248
|
+
return results.map((result) => {
|
|
25249
|
+
const icon = result.status === "failed" ? "❌" : "✅";
|
|
25250
|
+
return ` ${icon} ${result.displayName} (${result.projectKey}): ${result.status}`;
|
|
25251
|
+
});
|
|
25252
|
+
}
|
|
25222
25253
|
function formatPluginInstallIcon(status) {
|
|
25223
25254
|
switch (status) {
|
|
25224
25255
|
case "installed":
|
|
@@ -25305,6 +25336,17 @@ function createInteractivePrompter(promptApi = defaultPromptApi) {
|
|
|
25305
25336
|
required: true,
|
|
25306
25337
|
validate: (choices) => choices.length > 0 || "Select at least one Code Agent."
|
|
25307
25338
|
}));
|
|
25339
|
+
},
|
|
25340
|
+
async selectProjects(candidates2) {
|
|
25341
|
+
const selected = await promptApi.checkbox({
|
|
25342
|
+
message: "Select projects to include in EvoDev progression",
|
|
25343
|
+
choices: createInitProjectChoices(candidates2),
|
|
25344
|
+
required: false,
|
|
25345
|
+
pageSize: 15,
|
|
25346
|
+
validate: () => true
|
|
25347
|
+
});
|
|
25348
|
+
const candidateKeys = new Set(candidates2.map((candidate) => candidate.projectKey));
|
|
25349
|
+
return unique2(selected.filter((projectKey) => candidateKeys.has(projectKey)));
|
|
25308
25350
|
}
|
|
25309
25351
|
};
|
|
25310
25352
|
}
|
|
@@ -25327,6 +25369,160 @@ function createCodeAgentChoices(defaultValue) {
|
|
|
25327
25369
|
}
|
|
25328
25370
|
];
|
|
25329
25371
|
}
|
|
25372
|
+
function createInitProjectChoices(candidates2) {
|
|
25373
|
+
return candidates2.map((candidate) => ({
|
|
25374
|
+
value: candidate.projectKey,
|
|
25375
|
+
name: `${candidate.displayName} (${candidate.workspaceKind})`,
|
|
25376
|
+
description: `${candidate.workspaceRoot} · ${candidate.recommendationReason}`,
|
|
25377
|
+
checked: candidate.recommended
|
|
25378
|
+
}));
|
|
25379
|
+
}
|
|
25380
|
+
async function resolveInitProjectCandidates(input) {
|
|
25381
|
+
const now = input.now instanceof Date ? input.now.getTime() : Date.parse(input.now ?? "");
|
|
25382
|
+
const nowMs = Number.isFinite(now) ? now : Date.now();
|
|
25383
|
+
const cutoff = nowMs - INIT_RECOMMENDED_PROJECT_MAX_AGE_DAYS * MILLISECONDS_PER_DAY;
|
|
25384
|
+
const [discoveredProjects, registeredProjects, homeRoot, temporaryRoots] = await Promise.all([
|
|
25385
|
+
listDiscoveredProjects({ homeDir: input.homeDir }),
|
|
25386
|
+
listRegisteredProjects({ homeDir: input.homeDir }),
|
|
25387
|
+
canonicalExistingPath(input.homeDir),
|
|
25388
|
+
resolveTemporaryRoots()
|
|
25389
|
+
]);
|
|
25390
|
+
const registeredKeys = new Set(registeredProjects.map((project) => project.projectKey));
|
|
25391
|
+
const candidates2 = [];
|
|
25392
|
+
for (const discovered of discoveredProjects) {
|
|
25393
|
+
if (registeredKeys.has(discovered.projectKey))
|
|
25394
|
+
continue;
|
|
25395
|
+
const candidate = await resolveInitProjectCandidate({
|
|
25396
|
+
homeDir: input.homeDir,
|
|
25397
|
+
homeRoot,
|
|
25398
|
+
temporaryRoots,
|
|
25399
|
+
cutoff,
|
|
25400
|
+
discovered
|
|
25401
|
+
});
|
|
25402
|
+
if (candidate !== null)
|
|
25403
|
+
candidates2.push(candidate);
|
|
25404
|
+
}
|
|
25405
|
+
return candidates2;
|
|
25406
|
+
}
|
|
25407
|
+
async function resolveInitProjectCandidate(input) {
|
|
25408
|
+
const workspace = await resolveProjectWorkspaceFromCwd({
|
|
25409
|
+
homeDir: input.homeDir,
|
|
25410
|
+
cwd: input.discovered.workspaceRoot
|
|
25411
|
+
}).catch(() => null);
|
|
25412
|
+
if (workspace === null || workspace.projectKey !== input.discovered.projectKey || workspace.workspaceRoot !== input.discovered.workspaceRoot || workspace.workspaceKind !== input.discovered.workspaceKind) {
|
|
25413
|
+
return null;
|
|
25414
|
+
}
|
|
25415
|
+
const lastSeenAt = Date.parse(input.discovered.lastSeenAt);
|
|
25416
|
+
const recentlySeen = Number.isFinite(lastSeenAt) && lastSeenAt >= input.cutoff;
|
|
25417
|
+
const insideHome = input.homeRoot !== null && isPathWithin(input.homeRoot, workspace.workspaceRoot);
|
|
25418
|
+
const excludedLocation = workspace.workspaceRoot === parse2(workspace.workspaceRoot).root || workspace.workspaceRoot === input.homeRoot || !insideHome && input.temporaryRoots.some((root) => isPathWithin(root, workspace.workspaceRoot));
|
|
25419
|
+
const recommended = workspace.workspaceKind === "git" && recentlySeen && !excludedLocation;
|
|
25420
|
+
const recommendationReason = recommended ? `recommended: Git workspace seen within ${INIT_RECOMMENDED_PROJECT_MAX_AGE_DAYS} days` : workspace.workspaceKind === "directory" ? "not preselected: directory workspace" : excludedLocation ? "not preselected: root, HOME, or temporary location" : `not preselected: not seen within ${INIT_RECOMMENDED_PROJECT_MAX_AGE_DAYS} days`;
|
|
25421
|
+
return {
|
|
25422
|
+
projectKey: input.discovered.projectKey,
|
|
25423
|
+
displayName: input.discovered.displayName,
|
|
25424
|
+
workspaceRoot: workspace.workspaceRoot,
|
|
25425
|
+
workspaceKind: workspace.workspaceKind,
|
|
25426
|
+
lastSeenAt: input.discovered.lastSeenAt,
|
|
25427
|
+
recommended,
|
|
25428
|
+
recommendationReason
|
|
25429
|
+
};
|
|
25430
|
+
}
|
|
25431
|
+
async function registerInitProjects(input) {
|
|
25432
|
+
const canPrompt = !input.skipProjectSelection && input.prompter?.selectProjects !== undefined;
|
|
25433
|
+
if (!input.autoRegisterRecommended && !canPrompt) {
|
|
25434
|
+
emitInitProgress(input.progress, {
|
|
25435
|
+
status: "skipped",
|
|
25436
|
+
current: 6,
|
|
25437
|
+
label: "Register projects",
|
|
25438
|
+
detail: "disabled"
|
|
25439
|
+
});
|
|
25440
|
+
return [];
|
|
25441
|
+
}
|
|
25442
|
+
const candidates2 = await resolveInitProjectCandidates({
|
|
25443
|
+
homeDir: input.homeDir,
|
|
25444
|
+
now: input.now
|
|
25445
|
+
});
|
|
25446
|
+
if (candidates2.length === 0) {
|
|
25447
|
+
emitInitProgress(input.progress, {
|
|
25448
|
+
status: "skipped",
|
|
25449
|
+
current: 6,
|
|
25450
|
+
label: "Register projects",
|
|
25451
|
+
detail: "no valid discovered workspaces"
|
|
25452
|
+
});
|
|
25453
|
+
return [];
|
|
25454
|
+
}
|
|
25455
|
+
let selectedProjectKeys = [];
|
|
25456
|
+
if (input.autoRegisterRecommended) {
|
|
25457
|
+
selectedProjectKeys = candidates2.filter((candidate) => candidate.recommended).map((candidate) => candidate.projectKey);
|
|
25458
|
+
} else if (input.prompter?.selectProjects !== undefined) {
|
|
25459
|
+
await input.progress?.clear?.();
|
|
25460
|
+
selectedProjectKeys = await input.prompter.selectProjects(candidates2);
|
|
25461
|
+
}
|
|
25462
|
+
const candidatesByKey = new Map(candidates2.map((candidate) => [candidate.projectKey, candidate]));
|
|
25463
|
+
const selectedCandidates = unique2(selectedProjectKeys).map((projectKey) => candidatesByKey.get(projectKey)).filter((candidate) => candidate !== undefined);
|
|
25464
|
+
if (selectedCandidates.length === 0) {
|
|
25465
|
+
emitInitProgress(input.progress, {
|
|
25466
|
+
status: "skipped",
|
|
25467
|
+
current: 6,
|
|
25468
|
+
label: "Register projects",
|
|
25469
|
+
detail: input.autoRegisterRecommended ? "no recommended Git workspaces" : "none selected"
|
|
25470
|
+
});
|
|
25471
|
+
return [];
|
|
25472
|
+
}
|
|
25473
|
+
emitInitProgress(input.progress, {
|
|
25474
|
+
status: "doing",
|
|
25475
|
+
current: 6,
|
|
25476
|
+
label: "Register projects",
|
|
25477
|
+
detail: `${selectedCandidates.length} selected`
|
|
25478
|
+
});
|
|
25479
|
+
const results = [];
|
|
25480
|
+
for (const candidate of selectedCandidates) {
|
|
25481
|
+
try {
|
|
25482
|
+
const registration = await registerDiscoveredProject({
|
|
25483
|
+
homeDir: input.homeDir,
|
|
25484
|
+
projectKey: candidate.projectKey,
|
|
25485
|
+
now: input.now
|
|
25486
|
+
});
|
|
25487
|
+
results.push({
|
|
25488
|
+
projectKey: candidate.projectKey,
|
|
25489
|
+
displayName: candidate.displayName,
|
|
25490
|
+
status: registration.changed ? "registered" : "already-registered",
|
|
25491
|
+
path: registration.path
|
|
25492
|
+
});
|
|
25493
|
+
} catch {
|
|
25494
|
+
results.push({
|
|
25495
|
+
projectKey: candidate.projectKey,
|
|
25496
|
+
displayName: candidate.displayName,
|
|
25497
|
+
status: "failed",
|
|
25498
|
+
path: null
|
|
25499
|
+
});
|
|
25500
|
+
}
|
|
25501
|
+
}
|
|
25502
|
+
const failed = results.filter((result) => result.status === "failed").length;
|
|
25503
|
+
emitInitProgress(input.progress, {
|
|
25504
|
+
status: failed === 0 ? "done" : "failed",
|
|
25505
|
+
current: 6,
|
|
25506
|
+
label: "Register projects",
|
|
25507
|
+
detail: failed === 0 ? `${results.length} registered` : `${results.length - failed} registered, ${failed} failed`
|
|
25508
|
+
});
|
|
25509
|
+
return results;
|
|
25510
|
+
}
|
|
25511
|
+
async function resolveTemporaryRoots() {
|
|
25512
|
+
const roots = await Promise.all([tmpdir(), "/tmp", "/private/tmp", "/var/tmp"].map(canonicalExistingPath));
|
|
25513
|
+
return unique2(roots.filter((root) => root !== null));
|
|
25514
|
+
}
|
|
25515
|
+
async function canonicalExistingPath(path2) {
|
|
25516
|
+
try {
|
|
25517
|
+
return await realpath2(resolve9(path2));
|
|
25518
|
+
} catch {
|
|
25519
|
+
return null;
|
|
25520
|
+
}
|
|
25521
|
+
}
|
|
25522
|
+
function isPathWithin(root, candidate) {
|
|
25523
|
+
const path2 = relative10(root, candidate);
|
|
25524
|
+
return path2 === "" || path2 !== ".." && !path2.startsWith(`..${sep3}`) && !isAbsolute9(path2);
|
|
25525
|
+
}
|
|
25330
25526
|
function createInitSettingsInput(selectedPlugins, answers, existingSettings) {
|
|
25331
25527
|
const plugins2 = {};
|
|
25332
25528
|
if (existingSettings === null) {
|
|
@@ -25518,10 +25714,11 @@ async function maybeRunDoctor(input) {
|
|
|
25518
25714
|
const result = await input.runDoctor(input.context);
|
|
25519
25715
|
return result ?? { exitCode: 0 };
|
|
25520
25716
|
}
|
|
25521
|
-
function collectWarnings(syncResult, hookInstallResults, requestedDoctor, doctorRan) {
|
|
25717
|
+
function collectWarnings(syncResult, hookInstallResults, projectRegistrationResults, requestedDoctor, doctorRan) {
|
|
25522
25718
|
const warnings = [
|
|
25523
25719
|
...syncResult?.results.flatMap((result) => result.warnings) ?? [],
|
|
25524
|
-
...hookInstallResults.flatMap((result) => result.warnings)
|
|
25720
|
+
...hookInstallResults.flatMap((result) => result.warnings),
|
|
25721
|
+
...projectRegistrationResults.filter((result) => result.status === "failed").map((result) => `Project ${result.projectKey} could not be registered.`)
|
|
25525
25722
|
];
|
|
25526
25723
|
if (requestedDoctor && !doctorRan) {
|
|
25527
25724
|
warnings.push("Doctor command was requested, but no doctor hook was provided; run `evodev doctor` manually after init.");
|
|
@@ -26159,15 +26356,15 @@ function throwReviewWriteError(error) {
|
|
|
26159
26356
|
// packages/cli/src/session-knowledge.ts
|
|
26160
26357
|
import { spawn as spawn3 } from "node:child_process";
|
|
26161
26358
|
import { createHash as createHash5 } from "node:crypto";
|
|
26162
|
-
import { mkdtemp, readFile as readFile28, realpath as
|
|
26163
|
-
import { tmpdir } from "node:os";
|
|
26164
|
-
import { dirname as dirname23, join as join30, resolve as
|
|
26359
|
+
import { mkdtemp, readFile as readFile28, realpath as realpath4, rm as rm8, stat as stat19, writeFile as writeFile18 } from "node:fs/promises";
|
|
26360
|
+
import { tmpdir as tmpdir2 } from "node:os";
|
|
26361
|
+
import { dirname as dirname23, join as join30, resolve as resolve10 } from "node:path";
|
|
26165
26362
|
|
|
26166
26363
|
// packages/cli/src/ui/session-evidence.ts
|
|
26167
26364
|
import { createHash as createHash4 } from "node:crypto";
|
|
26168
26365
|
import { createReadStream } from "node:fs";
|
|
26169
|
-
import { readFile as readFile27, realpath as
|
|
26170
|
-
import { isAbsolute as
|
|
26366
|
+
import { readFile as readFile27, realpath as realpath3, stat as stat18 } from "node:fs/promises";
|
|
26367
|
+
import { isAbsolute as isAbsolute10, relative as relative11 } from "node:path";
|
|
26171
26368
|
import { createInterface as createInterface3 } from "node:readline";
|
|
26172
26369
|
var MAX_TRANSCRIPT_BYTES = 32 * 1024 * 1024;
|
|
26173
26370
|
var MAX_MESSAGE_BYTES = 64 * 1024;
|
|
@@ -26400,19 +26597,19 @@ function findTranscriptPointer(events) {
|
|
|
26400
26597
|
return null;
|
|
26401
26598
|
}
|
|
26402
26599
|
async function resolveTrustedTranscriptPath(input) {
|
|
26403
|
-
if (!
|
|
26600
|
+
if (!isAbsolute10(input.path) || !input.path.endsWith(".jsonl")) {
|
|
26404
26601
|
throw new Error("Native transcript path is not an absolute JSONL file.");
|
|
26405
26602
|
}
|
|
26406
26603
|
const targets = input.target === "claude" ? [{ target: "claude", root: `${input.homeDir}/.claude/projects` }] : input.target === "codex" ? [{ target: "codex", root: `${input.homeDir}/.codex/sessions` }] : [
|
|
26407
26604
|
{ target: "claude", root: `${input.homeDir}/.claude/projects` },
|
|
26408
26605
|
{ target: "codex", root: `${input.homeDir}/.codex/sessions` }
|
|
26409
26606
|
];
|
|
26410
|
-
const resolvedPath = await
|
|
26607
|
+
const resolvedPath = await realpath3(input.path);
|
|
26411
26608
|
for (const candidate of targets) {
|
|
26412
26609
|
try {
|
|
26413
|
-
const resolvedRoot = await
|
|
26414
|
-
const child =
|
|
26415
|
-
if (child !== "" && !child.startsWith("..") && !
|
|
26610
|
+
const resolvedRoot = await realpath3(candidate.root);
|
|
26611
|
+
const child = relative11(resolvedRoot, resolvedPath);
|
|
26612
|
+
if (child !== "" && !child.startsWith("..") && !isAbsolute10(child)) {
|
|
26416
26613
|
return { path: resolvedPath, target: candidate.target };
|
|
26417
26614
|
}
|
|
26418
26615
|
} catch {}
|
|
@@ -26861,7 +27058,7 @@ function createSessionKnowledgeDistiller(input = {}) {
|
|
|
26861
27058
|
}
|
|
26862
27059
|
function createCodexSessionKnowledgeAnalyzer(input = {}) {
|
|
26863
27060
|
return async (evidence) => {
|
|
26864
|
-
const workDir = await mkdtemp(join30(
|
|
27061
|
+
const workDir = await mkdtemp(join30(tmpdir2(), "evodev-session-knowledge-"));
|
|
26865
27062
|
const schemaPath = join30(workDir, "output.schema.json");
|
|
26866
27063
|
const outputPath = join30(workDir, "output.json");
|
|
26867
27064
|
try {
|
|
@@ -27204,8 +27401,8 @@ async function resolveSegmentRepoRoot(events, homeDir) {
|
|
|
27204
27401
|
for (const event of [...events].reverse()) {
|
|
27205
27402
|
if (!isRecord17(event.payload) || typeof event.payload.cwd !== "string")
|
|
27206
27403
|
continue;
|
|
27207
|
-
const cwd = await
|
|
27208
|
-
if (cwd === null || cwd ===
|
|
27404
|
+
const cwd = await realpath4(event.payload.cwd).catch(() => null);
|
|
27405
|
+
if (cwd === null || cwd === resolve10(homeDir) || !await isDirectory3(cwd))
|
|
27209
27406
|
continue;
|
|
27210
27407
|
return findRepositoryRoot(cwd, homeDir);
|
|
27211
27408
|
}
|
|
@@ -27217,15 +27414,15 @@ async function resolveHistoricalProjectRepoRoot(projectKey, homeDir) {
|
|
|
27217
27414
|
const workspaceRoot = registered?.workspaceRoot ?? discovered?.workspaceRoot;
|
|
27218
27415
|
if (workspaceRoot === undefined)
|
|
27219
27416
|
return null;
|
|
27220
|
-
const trusted = await
|
|
27221
|
-
if (trusted === null || trusted ===
|
|
27417
|
+
const trusted = await realpath4(workspaceRoot).catch(() => null);
|
|
27418
|
+
if (trusted === null || trusted === resolve10(homeDir) || !await isDirectory3(trusted)) {
|
|
27222
27419
|
return null;
|
|
27223
27420
|
}
|
|
27224
27421
|
return findRepositoryRoot(trusted, homeDir);
|
|
27225
27422
|
}
|
|
27226
27423
|
async function findRepositoryRoot(cwd, homeDir) {
|
|
27227
27424
|
let current = cwd;
|
|
27228
|
-
const stop =
|
|
27425
|
+
const stop = resolve10(homeDir);
|
|
27229
27426
|
while (current !== dirname23(current) && current !== stop) {
|
|
27230
27427
|
if (await pathExists9(join30(current, ".git")))
|
|
27231
27428
|
return current;
|
|
@@ -27455,7 +27652,7 @@ var SEMANTIC_KNOWLEDGE_SCHEMA = {
|
|
|
27455
27652
|
import { createHash as createHash7 } from "node:crypto";
|
|
27456
27653
|
import { constants as constants2 } from "node:fs";
|
|
27457
27654
|
import { lstat as lstat4, open as open4 } from "node:fs/promises";
|
|
27458
|
-
import { basename as basename6, resolve as
|
|
27655
|
+
import { basename as basename6, resolve as resolve11 } from "node:path";
|
|
27459
27656
|
|
|
27460
27657
|
// node_modules/.bun/@letta-ai+trajectory@0.2.0/node_modules/@letta-ai/trajectory/dist/types.js
|
|
27461
27658
|
class NormalizationError extends Error {
|
|
@@ -29631,7 +29828,7 @@ var TRANSCRIPT_SOURCES = [
|
|
|
29631
29828
|
];
|
|
29632
29829
|
async function runTrajectoryImportCommand(argv, options = {}) {
|
|
29633
29830
|
const flags = parseTrajectoryImportFlags(argv);
|
|
29634
|
-
const transcriptPath =
|
|
29831
|
+
const transcriptPath = resolve11(options.cwd ?? process.cwd(), flags.path);
|
|
29635
29832
|
const prepared = await prepareTrajectoryImport({
|
|
29636
29833
|
source: flags.source,
|
|
29637
29834
|
transcriptPath,
|
|
@@ -29668,7 +29865,7 @@ async function previewTrajectoryImport(input) {
|
|
|
29668
29865
|
})).plan.preview;
|
|
29669
29866
|
}
|
|
29670
29867
|
async function prepareTrajectoryImport(input) {
|
|
29671
|
-
const transcriptPath =
|
|
29868
|
+
const transcriptPath = resolve11(input.transcriptPath);
|
|
29672
29869
|
const bytes = await readStableTrajectoryImportFile(transcriptPath);
|
|
29673
29870
|
const transcript = decodeUtf8(bytes);
|
|
29674
29871
|
const normalized = normalizeCanonicalSafely(input.source, transcript);
|
|
@@ -30526,17 +30723,17 @@ async function startEvolutionReviewServer(input) {
|
|
|
30526
30723
|
response.end(error instanceof Error ? error.message : String(error));
|
|
30527
30724
|
}
|
|
30528
30725
|
});
|
|
30529
|
-
await new Promise((
|
|
30530
|
-
server.listen(input.flags.port, input.flags.host,
|
|
30726
|
+
await new Promise((resolve12) => {
|
|
30727
|
+
server.listen(input.flags.port, input.flags.host, resolve12);
|
|
30531
30728
|
});
|
|
30532
30729
|
const address = server.address();
|
|
30533
30730
|
const port = typeof address === "object" && address !== null ? address.port : input.flags.port;
|
|
30534
30731
|
input.write(`EvoDev evolution review server: http://${input.flags.host}:${port}/?token=${token}`);
|
|
30535
30732
|
input.write("Mode: local-only read snapshot. Mutation actions are not implemented in this slice.");
|
|
30536
30733
|
input.write("Press Ctrl+C to stop.");
|
|
30537
|
-
return await new Promise((
|
|
30734
|
+
return await new Promise((resolve12) => {
|
|
30538
30735
|
const close = () => {
|
|
30539
|
-
server.close(() =>
|
|
30736
|
+
server.close(() => resolve12(0));
|
|
30540
30737
|
};
|
|
30541
30738
|
process.once("SIGINT", close);
|
|
30542
30739
|
process.once("SIGTERM", close);
|
|
@@ -31828,8 +32025,8 @@ import { dirname as dirname26, join as join34 } from "node:path";
|
|
|
31828
32025
|
|
|
31829
32026
|
// packages/cli/src/improvement-eval.ts
|
|
31830
32027
|
import { spawn as spawn4 } from "node:child_process";
|
|
31831
|
-
import { mkdtemp as mkdtemp2, readFile as readFile31, realpath as
|
|
31832
|
-
import { tmpdir as
|
|
32028
|
+
import { mkdtemp as mkdtemp2, readFile as readFile31, realpath as realpath5, rm as rm9, stat as stat21, writeFile as writeFile20 } from "node:fs/promises";
|
|
32029
|
+
import { tmpdir as tmpdir3 } from "node:os";
|
|
31833
32030
|
import { join as join32 } from "node:path";
|
|
31834
32031
|
var DEFAULT_EVAL_LIMIT = 1;
|
|
31835
32032
|
var MODEL_TIMEOUT_MS2 = 2 * 60 * 1000;
|
|
@@ -31838,7 +32035,7 @@ var MAX_CANDIDATE_CHARS = 20000;
|
|
|
31838
32035
|
var MAX_PROCESS_OUTPUT_CHARS = 256000;
|
|
31839
32036
|
async function runPendingImprovementEvaluations(options) {
|
|
31840
32037
|
const snapshot = await readEvolutionReviewSnapshot({ homeDir: options.homeDir });
|
|
31841
|
-
const pending = snapshot.repoProposals.filter((proposal) => proposal.reviewState === "accepted" && proposal.improvementEval?.requested === true && (proposal.improvementEval.status === "awaiting-eval" || proposal.improvementEval.status === "evaluating")).sort((left2, right2) => (left2.improvementEval?.requestedAt ?? left2.provenance.createdAt).localeCompare(right2.improvementEval?.requestedAt ?? right2.provenance.createdAt) || left2.id.localeCompare(right2.id));
|
|
32038
|
+
const pending = snapshot.repoProposals.filter((proposal) => proposal.reviewState === "accepted" && proposal.improvementEval?.requested === true && (proposal.improvementEval.status === "awaiting-eval" || options.recoverEvaluating !== false && proposal.improvementEval.status === "evaluating") && (options.target === undefined || proposal.id === options.target.proposalId && proposal.projectKey === options.target.projectKey)).sort((left2, right2) => (left2.improvementEval?.requestedAt ?? left2.provenance.createdAt).localeCompare(right2.improvementEval?.requestedAt ?? right2.provenance.createdAt) || left2.id.localeCompare(right2.id));
|
|
31842
32039
|
const result = {
|
|
31843
32040
|
pending: pending.length,
|
|
31844
32041
|
attempted: 0,
|
|
@@ -31852,6 +32049,7 @@ async function runPendingImprovementEvaluations(options) {
|
|
|
31852
32049
|
const limit = Math.max(0, Math.min(options.limit ?? DEFAULT_EVAL_LIMIT, DEFAULT_EVAL_LIMIT));
|
|
31853
32050
|
for (const proposal of pending.slice(0, limit)) {
|
|
31854
32051
|
const startedAt = Date.now();
|
|
32052
|
+
let evaluationStarted = false;
|
|
31855
32053
|
let modelAttempted = false;
|
|
31856
32054
|
let evidenceRef = proposal.provenance.sourceRefs[0] ?? null;
|
|
31857
32055
|
result.attempted += 1;
|
|
@@ -31866,6 +32064,7 @@ async function runPendingImprovementEvaluations(options) {
|
|
|
31866
32064
|
evidenceRef,
|
|
31867
32065
|
now: now(options)
|
|
31868
32066
|
});
|
|
32067
|
+
evaluationStarted = true;
|
|
31869
32068
|
const evidence = await loadImprovementEvalEvidence(options.homeDir, proposal);
|
|
31870
32069
|
evidenceRef = evidence.evidenceRef;
|
|
31871
32070
|
if (evidence.inconclusiveReason !== null) {
|
|
@@ -31914,28 +32113,33 @@ async function runPendingImprovementEvaluations(options) {
|
|
|
31914
32113
|
} catch (error) {
|
|
31915
32114
|
const message = safeError(error);
|
|
31916
32115
|
result.warnings.push(`${proposal.id}: ${message}`);
|
|
31917
|
-
|
|
31918
|
-
|
|
31919
|
-
|
|
31920
|
-
|
|
31921
|
-
|
|
31922
|
-
|
|
31923
|
-
|
|
31924
|
-
|
|
31925
|
-
|
|
31926
|
-
|
|
31927
|
-
|
|
31928
|
-
|
|
31929
|
-
|
|
31930
|
-
|
|
32116
|
+
if (evaluationStarted) {
|
|
32117
|
+
const persisted = await persistCompletedEvaluation({
|
|
32118
|
+
options,
|
|
32119
|
+
proposal,
|
|
32120
|
+
status: "inconclusive",
|
|
32121
|
+
evidenceRef,
|
|
32122
|
+
result: {
|
|
32123
|
+
...createLocalInconclusiveResult(`The bounded replay could not complete: ${message}`, Date.now() - startedAt),
|
|
32124
|
+
evaluatedBy: modelAttempted ? "local-code-agent" : "local-eval-orchestrator",
|
|
32125
|
+
metrics: {
|
|
32126
|
+
durationMs: Date.now() - startedAt,
|
|
32127
|
+
modelCalls: modelAttempted ? 1 : 0,
|
|
32128
|
+
inputTokens: null,
|
|
32129
|
+
outputTokens: null,
|
|
32130
|
+
toolCalls: null
|
|
32131
|
+
}
|
|
31931
32132
|
}
|
|
31932
|
-
}
|
|
31933
|
-
|
|
31934
|
-
|
|
31935
|
-
|
|
31936
|
-
|
|
31937
|
-
|
|
31938
|
-
|
|
32133
|
+
}).catch((persistError) => {
|
|
32134
|
+
result.warnings.push(`${proposal.id}: ${safeError(persistError)}`);
|
|
32135
|
+
return null;
|
|
32136
|
+
});
|
|
32137
|
+
if (persisted !== null)
|
|
32138
|
+
result.inconclusive += 1;
|
|
32139
|
+
} else {
|
|
32140
|
+
result.attempted -= 1;
|
|
32141
|
+
result.proposalIds.pop();
|
|
32142
|
+
}
|
|
31939
32143
|
}
|
|
31940
32144
|
await reportProgress2(options, result, "updated");
|
|
31941
32145
|
}
|
|
@@ -31975,7 +32179,7 @@ function classifyImprovementEvalAnalysis(analysis3) {
|
|
|
31975
32179
|
}
|
|
31976
32180
|
function createCodexImprovementEvalAnalyzer(input = {}) {
|
|
31977
32181
|
return async (candidate) => {
|
|
31978
|
-
const workDir = await mkdtemp2(join32(
|
|
32182
|
+
const workDir = await mkdtemp2(join32(tmpdir3(), "evodev-improvement-eval-"));
|
|
31979
32183
|
const schemaPath = join32(workDir, "output.schema.json");
|
|
31980
32184
|
const outputPath = join32(workDir, "output.json");
|
|
31981
32185
|
try {
|
|
@@ -32033,7 +32237,7 @@ async function loadImprovementEvalEvidence(homeDir, proposal) {
|
|
|
32033
32237
|
inconclusiveReason: "The accepted proposal target repository is unavailable."
|
|
32034
32238
|
};
|
|
32035
32239
|
}
|
|
32036
|
-
const repoRoot = await
|
|
32240
|
+
const repoRoot = await realpath5(proposal.targetRepoPath).catch(() => null);
|
|
32037
32241
|
if (repoRoot === null || !(await stat21(repoRoot).catch(() => null))?.isDirectory()) {
|
|
32038
32242
|
return {
|
|
32039
32243
|
repoRoot: "",
|
|
@@ -32307,9 +32511,9 @@ var IMPROVEMENT_EVAL_SCHEMA = {
|
|
|
32307
32511
|
// packages/cli/src/session-proposals.ts
|
|
32308
32512
|
import { spawn as spawn5 } from "node:child_process";
|
|
32309
32513
|
import { createHash as createHash8, randomUUID as randomUUID4 } from "node:crypto";
|
|
32310
|
-
import { mkdir as mkdir21, mkdtemp as mkdtemp3, open as open5, readFile as readFile32, realpath as
|
|
32311
|
-
import { tmpdir as
|
|
32312
|
-
import { basename as basename7, dirname as dirname25, isAbsolute as
|
|
32514
|
+
import { mkdir as mkdir21, mkdtemp as mkdtemp3, open as open5, readFile as readFile32, realpath as realpath6, rm as rm10, stat as stat22, writeFile as writeFile21 } from "node:fs/promises";
|
|
32515
|
+
import { tmpdir as tmpdir4 } from "node:os";
|
|
32516
|
+
import { basename as basename7, dirname as dirname25, isAbsolute as isAbsolute12, join as join33, relative as relative13, resolve as resolve12 } from "node:path";
|
|
32313
32517
|
var ELIGIBLE_REASONS = new Set(["user-interruption", "intent-refinement"]);
|
|
32314
32518
|
var DEFAULT_SCAN_LIMIT = 5;
|
|
32315
32519
|
var MODEL_TIMEOUT_MS3 = 3 * 60 * 1000;
|
|
@@ -32578,7 +32782,7 @@ async function readDailySessionProposalState(homeDir) {
|
|
|
32578
32782
|
}
|
|
32579
32783
|
function createCodexSessionProposalAnalyzer(input = {}) {
|
|
32580
32784
|
return async (evidence) => {
|
|
32581
|
-
const workDir = await mkdtemp3(join33(
|
|
32785
|
+
const workDir = await mkdtemp3(join33(tmpdir4(), "evodev-session-proposal-"));
|
|
32582
32786
|
const schemaPath = join33(workDir, "output.schema.json");
|
|
32583
32787
|
const outputPath = join33(workDir, "output.json");
|
|
32584
32788
|
try {
|
|
@@ -32650,8 +32854,8 @@ async function resolveSegmentRepoRoot2(events, homeDir) {
|
|
|
32650
32854
|
for (const event of [...events].reverse()) {
|
|
32651
32855
|
if (!isRecord19(event.payload) || typeof event.payload.cwd !== "string")
|
|
32652
32856
|
continue;
|
|
32653
|
-
const cwd = await
|
|
32654
|
-
if (cwd === null || cwd ===
|
|
32857
|
+
const cwd = await realpath6(event.payload.cwd).catch(() => null);
|
|
32858
|
+
if (cwd === null || cwd === resolve12(homeDir))
|
|
32655
32859
|
continue;
|
|
32656
32860
|
if (!await isDirectory4(cwd))
|
|
32657
32861
|
continue;
|
|
@@ -32661,7 +32865,7 @@ async function resolveSegmentRepoRoot2(events, homeDir) {
|
|
|
32661
32865
|
}
|
|
32662
32866
|
async function findRepositoryRoot2(cwd, homeDir) {
|
|
32663
32867
|
let current = cwd;
|
|
32664
|
-
const stop =
|
|
32868
|
+
const stop = resolve12(homeDir);
|
|
32665
32869
|
while (current !== dirname25(current) && current !== stop) {
|
|
32666
32870
|
if (await pathExists10(join33(current, ".git")))
|
|
32667
32871
|
return current;
|
|
@@ -32673,9 +32877,9 @@ async function validatePlannedFiles(repoRoot, files) {
|
|
|
32673
32877
|
if (files.length === 0)
|
|
32674
32878
|
throw new Error("Repo proposal contains no planned files.");
|
|
32675
32879
|
for (const file of files) {
|
|
32676
|
-
const target =
|
|
32677
|
-
const child =
|
|
32678
|
-
if (child === "" || child.startsWith("..") ||
|
|
32880
|
+
const target = resolve12(repoRoot, file.relativePath);
|
|
32881
|
+
const child = relative13(repoRoot, target);
|
|
32882
|
+
if (child === "" || child.startsWith("..") || isAbsolute12(child)) {
|
|
32679
32883
|
throw new Error(`Repo proposal path escapes the target repository: ${file.relativePath}`);
|
|
32680
32884
|
}
|
|
32681
32885
|
if (!isGovernanceAssetPath(child)) {
|
|
@@ -33217,7 +33421,7 @@ class EvolutionJobService {
|
|
|
33217
33421
|
} else {
|
|
33218
33422
|
job = updateStage(job, "improvement-evals", {
|
|
33219
33423
|
status: "skipped",
|
|
33220
|
-
detail: input.source === "schedule" ? "Scheduled evolution never runs improvement evaluation." : "
|
|
33424
|
+
detail: input.source === "schedule" ? "Scheduled evolution never runs improvement evaluation." : "Repo proposal evaluation runs directly from Approve & Apply."
|
|
33221
33425
|
});
|
|
33222
33426
|
}
|
|
33223
33427
|
await persist();
|
|
@@ -33309,7 +33513,7 @@ function createEvolutionJob(input, now2) {
|
|
|
33309
33513
|
stages: [
|
|
33310
33514
|
createStage("knowledge", "Semantic knowledge distillation"),
|
|
33311
33515
|
createStage("recommendations", "Skill and rule recommendations"),
|
|
33312
|
-
createStage("improvement-evals", "
|
|
33516
|
+
createStage("improvement-evals", "Repo proposal application"),
|
|
33313
33517
|
createStage("finalize", "Evolution refresh")
|
|
33314
33518
|
],
|
|
33315
33519
|
warnings: [],
|
|
@@ -33477,7 +33681,7 @@ import {
|
|
|
33477
33681
|
access as access2,
|
|
33478
33682
|
chmod as chmod3,
|
|
33479
33683
|
mkdir as mkdir23,
|
|
33480
|
-
realpath as
|
|
33684
|
+
realpath as realpath7,
|
|
33481
33685
|
rename as rename5,
|
|
33482
33686
|
rm as rm12,
|
|
33483
33687
|
stat as stat24,
|
|
@@ -33564,7 +33768,7 @@ class OsEvolutionScheduler {
|
|
|
33564
33768
|
if (candidate === undefined || candidate.trim() === "") {
|
|
33565
33769
|
throw new Error("Cannot resolve the evodev executable for scheduler installation.");
|
|
33566
33770
|
}
|
|
33567
|
-
const resolved = await
|
|
33771
|
+
const resolved = await realpath7(candidate).catch(() => candidate);
|
|
33568
33772
|
if (!await isFile3(resolved)) {
|
|
33569
33773
|
throw new Error(`EvoDev scheduler executable is not a file: ${resolved}`);
|
|
33570
33774
|
}
|
|
@@ -34401,10 +34605,10 @@ function shouldAttachAfterStart(flags, options) {
|
|
|
34401
34605
|
|
|
34402
34606
|
class TmuxAttachRunner {
|
|
34403
34607
|
async attachSession(session) {
|
|
34404
|
-
return new Promise((
|
|
34608
|
+
return new Promise((resolve13, reject) => {
|
|
34405
34609
|
const child = spawn7("tmux", ["-u", "attach-session", "-t", session], { stdio: "inherit" });
|
|
34406
34610
|
child.on("error", reject);
|
|
34407
|
-
child.on("close", (code) =>
|
|
34611
|
+
child.on("close", (code) => resolve13(code ?? 0));
|
|
34408
34612
|
});
|
|
34409
34613
|
}
|
|
34410
34614
|
}
|
|
@@ -34832,8 +35036,8 @@ import { createServer as createServer3 } from "node:http";
|
|
|
34832
35036
|
// packages/cli/src/proposal-execution.ts
|
|
34833
35037
|
import { spawn as spawn8 } from "node:child_process";
|
|
34834
35038
|
import { createHash as createHash9, randomBytes as randomBytes3 } from "node:crypto";
|
|
34835
|
-
import { mkdir as mkdir24, readFile as readFile35, readdir as readdir18, realpath as
|
|
34836
|
-
import { dirname as dirname28, isAbsolute as
|
|
35039
|
+
import { mkdir as mkdir24, readFile as readFile35, readdir as readdir18, realpath as realpath8, rename as rename6, rm as rm13, stat as stat25, writeFile as writeFile24 } from "node:fs/promises";
|
|
35040
|
+
import { dirname as dirname28, isAbsolute as isAbsolute13, join as join36, relative as relative14, resolve as resolve13 } from "node:path";
|
|
34837
35041
|
var MAX_COMMAND_OUTPUT_BYTES = 1024 * 1024;
|
|
34838
35042
|
var MAX_DIRTY_FILES_IN_RESPONSE = 20;
|
|
34839
35043
|
var MAX_CHANGED_FILES_IN_RECORD = 100;
|
|
@@ -34886,7 +35090,7 @@ class RepoProposalExecutionService {
|
|
|
34886
35090
|
}).catch(() => null);
|
|
34887
35091
|
return {
|
|
34888
35092
|
proposal: summarizeProposal(proposal),
|
|
34889
|
-
repository: repository === null ? null : summarizeRepositoryState(repository),
|
|
35093
|
+
repository: repository === null ? null : summarizeRepositoryState(proposal, repository),
|
|
34890
35094
|
repositoryError,
|
|
34891
35095
|
agents: agents2,
|
|
34892
35096
|
latestExecution,
|
|
@@ -34903,8 +35107,8 @@ class RepoProposalExecutionService {
|
|
|
34903
35107
|
if (repository.fingerprint !== input.expectedFingerprint) {
|
|
34904
35108
|
throw new ProposalExecutionError("conflict", "Repository state changed after confirmation. Refresh and confirm the latest state.");
|
|
34905
35109
|
}
|
|
34906
|
-
if (repository
|
|
34907
|
-
throw new ProposalExecutionError("confirmation-required", "Uncommitted
|
|
35110
|
+
if (requiresDirtyConfirmation(proposal, repository) && input.confirmDirty !== true) {
|
|
35111
|
+
throw new ProposalExecutionError("confirmation-required", "Uncommitted changes overlap the reviewed proposal files and require explicit confirmation.");
|
|
34908
35112
|
}
|
|
34909
35113
|
const agents2 = await discoverExecutionAgents(this.commandRunner);
|
|
34910
35114
|
if (!agents2.some((agent) => agent.id === input.agent && agent.installed)) {
|
|
@@ -34914,8 +35118,8 @@ class RepoProposalExecutionService {
|
|
|
34914
35118
|
if (repository.fingerprint !== input.expectedFingerprint) {
|
|
34915
35119
|
throw new ProposalExecutionError("conflict", "Repository state changed while the Code Agent was being checked. Refresh and confirm the latest state.");
|
|
34916
35120
|
}
|
|
34917
|
-
if (repository
|
|
34918
|
-
throw new ProposalExecutionError("confirmation-required", "Uncommitted
|
|
35121
|
+
if (requiresDirtyConfirmation(proposal, repository) && input.confirmDirty !== true) {
|
|
35122
|
+
throw new ProposalExecutionError("confirmation-required", "Uncommitted changes overlap the reviewed proposal files and require explicit confirmation.");
|
|
34919
35123
|
}
|
|
34920
35124
|
const latestExecution = await readLatestRepoProposalExecution({
|
|
34921
35125
|
homeDir: input.homeDir,
|
|
@@ -34939,7 +35143,7 @@ class RepoProposalExecutionService {
|
|
|
34939
35143
|
head: repository.head,
|
|
34940
35144
|
dirtyAtStart: repository.dirty,
|
|
34941
35145
|
dirtyFileCountAtStart: repository.entries.length,
|
|
34942
|
-
dirtyConfirmation: repository.dirty ? "confirmed" : "
|
|
35146
|
+
dirtyConfirmation: !repository.dirty ? "not-required" : requiresDirtyConfirmation(proposal, repository) ? "confirmed" : "approval-covered",
|
|
34943
35147
|
changedFiles: [],
|
|
34944
35148
|
changedFilesTruncated: false,
|
|
34945
35149
|
startedAt: this.now(),
|
|
@@ -35218,7 +35422,7 @@ async function readRepositoryState(proposal, runner) {
|
|
|
35218
35422
|
}
|
|
35219
35423
|
let repoRoot;
|
|
35220
35424
|
try {
|
|
35221
|
-
repoRoot = await
|
|
35425
|
+
repoRoot = await realpath8(proposal.targetRepoPath);
|
|
35222
35426
|
if (!(await stat25(repoRoot)).isDirectory())
|
|
35223
35427
|
throw new Error("not a directory");
|
|
35224
35428
|
} catch {
|
|
@@ -35228,7 +35432,7 @@ async function readRepositoryState(proposal, runner) {
|
|
|
35228
35432
|
if (topLevel.exitCode !== 0) {
|
|
35229
35433
|
throw new ProposalExecutionError("invalid", "Target path is not a git repository.");
|
|
35230
35434
|
}
|
|
35231
|
-
const resolvedTopLevel = await
|
|
35435
|
+
const resolvedTopLevel = await realpath8(topLevel.stdout.trim());
|
|
35232
35436
|
if (resolvedTopLevel !== repoRoot) {
|
|
35233
35437
|
throw new ProposalExecutionError("invalid", "Proposal target must be the git repository root.");
|
|
35234
35438
|
}
|
|
@@ -35303,9 +35507,9 @@ function sanitizeGitPath(path2) {
|
|
|
35303
35507
|
return normalized;
|
|
35304
35508
|
}
|
|
35305
35509
|
async function pathMetadataSignature(repoRoot, path2) {
|
|
35306
|
-
const target =
|
|
35307
|
-
const child =
|
|
35308
|
-
if (child === "" || child.startsWith("..") ||
|
|
35510
|
+
const target = resolve13(repoRoot, path2);
|
|
35511
|
+
const child = relative14(repoRoot, target);
|
|
35512
|
+
if (child === "" || child.startsWith("..") || isAbsolute13(child))
|
|
35309
35513
|
return "outside";
|
|
35310
35514
|
return await fileMetadataSignature(target);
|
|
35311
35515
|
}
|
|
@@ -35319,8 +35523,10 @@ async function fileMetadataSignature(path2) {
|
|
|
35319
35523
|
throw error;
|
|
35320
35524
|
}
|
|
35321
35525
|
}
|
|
35322
|
-
function summarizeRepositoryState(repository) {
|
|
35526
|
+
function summarizeRepositoryState(proposal, repository) {
|
|
35323
35527
|
const dirtyFiles = repository.entries.slice(0, MAX_DIRTY_FILES_IN_RESPONSE).map((entry) => entry.path);
|
|
35528
|
+
const conflicts = dirtyPlannedFileConflicts(proposal, repository);
|
|
35529
|
+
const dirtyConflictFiles = conflicts.slice(0, MAX_DIRTY_FILES_IN_RESPONSE);
|
|
35324
35530
|
return {
|
|
35325
35531
|
branch: repository.branch,
|
|
35326
35532
|
head: repository.head,
|
|
@@ -35328,9 +35534,19 @@ function summarizeRepositoryState(repository) {
|
|
|
35328
35534
|
dirtyFileCount: repository.entries.length,
|
|
35329
35535
|
dirtyFiles,
|
|
35330
35536
|
dirtyFilesTruncated: repository.entries.length > dirtyFiles.length,
|
|
35537
|
+
requiresDirtyConfirmation: conflicts.length > 0,
|
|
35538
|
+
dirtyConflictFiles,
|
|
35539
|
+
dirtyConflictFilesTruncated: conflicts.length > dirtyConflictFiles.length,
|
|
35331
35540
|
fingerprint: repository.fingerprint
|
|
35332
35541
|
};
|
|
35333
35542
|
}
|
|
35543
|
+
function requiresDirtyConfirmation(proposal, repository) {
|
|
35544
|
+
return dirtyPlannedFileConflicts(proposal, repository).length > 0;
|
|
35545
|
+
}
|
|
35546
|
+
function dirtyPlannedFileConflicts(proposal, repository) {
|
|
35547
|
+
const plannedFiles = new Set(proposal.plannedFiles.map((file) => file.relativePath.replaceAll("\\", "/")));
|
|
35548
|
+
return [...new Set(repository.entries.map((entry) => entry.path))].filter((path2) => plannedFiles.has(path2)).sort();
|
|
35549
|
+
}
|
|
35334
35550
|
function summarizeProposal(proposal) {
|
|
35335
35551
|
if (proposal.targetRepoPath === null)
|
|
35336
35552
|
throw new ProposalExecutionError("invalid", "Missing target.");
|
|
@@ -35384,7 +35600,7 @@ function resolveRepoProposalExecutionPolicy(proposal, confirmInconclusiveEval =
|
|
|
35384
35600
|
return {
|
|
35385
35601
|
canExecute: false,
|
|
35386
35602
|
requiresInconclusiveConfirmation: false,
|
|
35387
|
-
message: evaluation.status === "evaluating" ? "The improvement evaluation is still running." : "This proposal is awaiting improvement evaluation.
|
|
35603
|
+
message: evaluation.status === "evaluating" ? "The improvement evaluation is still running." : "This proposal is awaiting improvement evaluation. Use Evaluate & Apply to continue it in this checkout."
|
|
35388
35604
|
};
|
|
35389
35605
|
}
|
|
35390
35606
|
function buildRepoProposalExecutionArgs(agent, repoRoot) {
|
|
@@ -35412,7 +35628,8 @@ function buildRepoProposalExecutionArgs(agent, repoRoot) {
|
|
|
35412
35628
|
];
|
|
35413
35629
|
}
|
|
35414
35630
|
function buildRepoProposalExecutionPrompt(input) {
|
|
35415
|
-
const
|
|
35631
|
+
const conflicts = dirtyPlannedFileConflicts(input.proposal, input.repository);
|
|
35632
|
+
const dirtyAuthorization = !input.repository.dirty ? "The server detected a clean working tree; no dirty-worktree confirmation was required." : conflicts.length > 0 ? `The server detected ${input.repository.entries.length} uncommitted path(s), including ${conflicts.length} reviewed target-file conflict(s), and the user explicitly confirmed execution on that dirty working tree.` : `The server detected ${input.repository.entries.length} non-overlapping uncommitted path(s). Approval authorizes execution while preserving those existing changes; the repository fingerprint must remain unchanged before launch.`;
|
|
35416
35633
|
const changes = input.proposal.plannedFiles.map((file, index) => `${index + 1}. ${file.action} ${file.relativePath}
|
|
35417
35634
|
Reason: ${file.reason}
|
|
35418
35635
|
Requested change:
|
|
@@ -35563,7 +35780,7 @@ function parseExecutionRecord(value) {
|
|
|
35563
35780
|
"spawn-failed",
|
|
35564
35781
|
"state-conflict"
|
|
35565
35782
|
]);
|
|
35566
|
-
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 !== "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) {
|
|
35783
|
+
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) {
|
|
35567
35784
|
throw new Error("Execution record is invalid.");
|
|
35568
35785
|
}
|
|
35569
35786
|
return value;
|
|
@@ -35617,6 +35834,115 @@ function isAlreadyExistsError3(error) {
|
|
|
35617
35834
|
return error instanceof Error && "code" in error && error.code === "EEXIST";
|
|
35618
35835
|
}
|
|
35619
35836
|
|
|
35837
|
+
// packages/cli/src/proposal-application.ts
|
|
35838
|
+
async function runRepoProposalApplication(input, dependencies) {
|
|
35839
|
+
const initial = await dependencies.proposalExecution.prepare({
|
|
35840
|
+
homeDir: input.homeDir,
|
|
35841
|
+
proposalId: input.proposalId,
|
|
35842
|
+
projectKey: input.projectKey
|
|
35843
|
+
});
|
|
35844
|
+
assertApplicationPreflight(initial, input);
|
|
35845
|
+
let reviewChanged = false;
|
|
35846
|
+
if (initial.proposal.reviewState !== "accepted") {
|
|
35847
|
+
const decision = await (dependencies.applyDecision ?? applyReviewDecision)({
|
|
35848
|
+
homeDir: input.homeDir,
|
|
35849
|
+
kind: "repo-proposal",
|
|
35850
|
+
id: input.proposalId,
|
|
35851
|
+
projectKey: input.projectKey,
|
|
35852
|
+
decision: "accepted",
|
|
35853
|
+
expectedState: input.expectedReviewState,
|
|
35854
|
+
improvementEvalRequested: input.improvementEvalRequested
|
|
35855
|
+
});
|
|
35856
|
+
reviewChanged = decision.changed;
|
|
35857
|
+
} else if (input.improvementEvalRequested !== undefined) {
|
|
35858
|
+
throw new ProposalExecutionError("invalid", "An accepted proposal cannot change its improvement-evaluation selection.");
|
|
35859
|
+
}
|
|
35860
|
+
let evaluation = null;
|
|
35861
|
+
let current = await dependencies.proposalExecution.prepare({
|
|
35862
|
+
homeDir: input.homeDir,
|
|
35863
|
+
proposalId: input.proposalId,
|
|
35864
|
+
projectKey: input.projectKey
|
|
35865
|
+
});
|
|
35866
|
+
const evaluationStatus = current.proposal.improvementEval?.status;
|
|
35867
|
+
if (evaluationStatus === "evaluating") {
|
|
35868
|
+
return blockedResult(reviewChanged, null, "This proposal is already being evaluated. Its application will remain paused until the evaluation finishes.");
|
|
35869
|
+
}
|
|
35870
|
+
if (evaluationStatus === "awaiting-eval") {
|
|
35871
|
+
try {
|
|
35872
|
+
evaluation = await (dependencies.evaluateImprovements ?? runPendingImprovementEvaluations)({
|
|
35873
|
+
homeDir: input.homeDir,
|
|
35874
|
+
limit: 1,
|
|
35875
|
+
target: {
|
|
35876
|
+
proposalId: current.proposal.id,
|
|
35877
|
+
projectKey: current.proposal.projectKey
|
|
35878
|
+
},
|
|
35879
|
+
recoverEvaluating: false
|
|
35880
|
+
});
|
|
35881
|
+
} catch (error) {
|
|
35882
|
+
return blockedResult(reviewChanged, null, `The proposal was approved, but its improvement evaluation could not start: ${safeError4(error)}`);
|
|
35883
|
+
}
|
|
35884
|
+
current = await dependencies.proposalExecution.prepare({
|
|
35885
|
+
homeDir: input.homeDir,
|
|
35886
|
+
proposalId: input.proposalId,
|
|
35887
|
+
projectKey: input.projectKey
|
|
35888
|
+
});
|
|
35889
|
+
}
|
|
35890
|
+
if (!current.executionPolicy.canExecute) {
|
|
35891
|
+
return blockedResult(reviewChanged, evaluation, current.executionPolicy.message ?? "The approved proposal is not ready to apply.");
|
|
35892
|
+
}
|
|
35893
|
+
try {
|
|
35894
|
+
const execution = await dependencies.proposalExecution.start({
|
|
35895
|
+
homeDir: input.homeDir,
|
|
35896
|
+
proposalId: input.proposalId,
|
|
35897
|
+
projectKey: input.projectKey,
|
|
35898
|
+
agent: input.agent,
|
|
35899
|
+
expectedFingerprint: input.expectedFingerprint,
|
|
35900
|
+
confirmDirty: input.confirmDirty,
|
|
35901
|
+
confirmInconclusiveEval: false
|
|
35902
|
+
});
|
|
35903
|
+
return {
|
|
35904
|
+
status: "executing",
|
|
35905
|
+
reviewChanged,
|
|
35906
|
+
evaluation,
|
|
35907
|
+
execution,
|
|
35908
|
+
message: null
|
|
35909
|
+
};
|
|
35910
|
+
} catch (error) {
|
|
35911
|
+
return blockedResult(reviewChanged, evaluation, error instanceof ProposalExecutionError ? error.message : `The proposal was approved, but repository execution could not start: ${safeError4(error)}`);
|
|
35912
|
+
}
|
|
35913
|
+
}
|
|
35914
|
+
function assertApplicationPreflight(preparation, input) {
|
|
35915
|
+
if (preparation.proposal.reviewState !== input.expectedReviewState) {
|
|
35916
|
+
throw new ProposalExecutionError("conflict", "Review state changed after the page loaded. Refresh and review the latest state.");
|
|
35917
|
+
}
|
|
35918
|
+
const repository = preparation.repository;
|
|
35919
|
+
if (repository === null) {
|
|
35920
|
+
throw new ProposalExecutionError("unavailable", preparation.repositoryError ?? "The target repository is unavailable.");
|
|
35921
|
+
}
|
|
35922
|
+
if (repository.fingerprint !== input.expectedFingerprint) {
|
|
35923
|
+
throw new ProposalExecutionError("conflict", "Repository state changed after confirmation. Refresh and confirm the latest state.");
|
|
35924
|
+
}
|
|
35925
|
+
if (repository.requiresDirtyConfirmation && input.confirmDirty !== true) {
|
|
35926
|
+
throw new ProposalExecutionError("confirmation-required", "Uncommitted changes overlap the reviewed proposal files and require explicit confirmation.");
|
|
35927
|
+
}
|
|
35928
|
+
if (!preparation.agents.some((agent) => agent.id === input.agent && agent.installed)) {
|
|
35929
|
+
throw new ProposalExecutionError("unavailable", `${input.agent === "codex" ? "Codex" : "Claude Code"} is not available on PATH.`);
|
|
35930
|
+
}
|
|
35931
|
+
}
|
|
35932
|
+
function blockedResult(reviewChanged, evaluation, message) {
|
|
35933
|
+
return {
|
|
35934
|
+
status: "blocked",
|
|
35935
|
+
reviewChanged,
|
|
35936
|
+
evaluation,
|
|
35937
|
+
execution: null,
|
|
35938
|
+
message
|
|
35939
|
+
};
|
|
35940
|
+
}
|
|
35941
|
+
function safeError4(error) {
|
|
35942
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
35943
|
+
return message.replaceAll(/\s+/gu, " ").trim().slice(0, 300) || "unknown error";
|
|
35944
|
+
}
|
|
35945
|
+
|
|
35620
35946
|
// packages/cli/src/ui/assets.ts
|
|
35621
35947
|
import { readFile as readFile36 } from "node:fs/promises";
|
|
35622
35948
|
import { dirname as dirname29, join as join37 } from "node:path";
|
|
@@ -35679,6 +36005,215 @@ async function fileExists2(path2) {
|
|
|
35679
36005
|
}
|
|
35680
36006
|
}
|
|
35681
36007
|
|
|
36008
|
+
// packages/cli/src/ui/process-runtime.ts
|
|
36009
|
+
import { execFile as execFile2 } from "node:child_process";
|
|
36010
|
+
import { createConnection } from "node:net";
|
|
36011
|
+
import { promisify as promisify2 } from "node:util";
|
|
36012
|
+
var execFileAsync2 = promisify2(execFile2);
|
|
36013
|
+
var MAX_PROCESS_LIST_BYTES = 2 * 1024 * 1024;
|
|
36014
|
+
var PORT_PROBE_TIMEOUT_MS = 500;
|
|
36015
|
+
|
|
36016
|
+
class NodeUiProcessController {
|
|
36017
|
+
currentUid = typeof process.getuid === "function" && Number.isInteger(process.getuid()) ? process.getuid() : null;
|
|
36018
|
+
async listProcesses() {
|
|
36019
|
+
if (this.currentUid === null || process.platform === "win32")
|
|
36020
|
+
return [];
|
|
36021
|
+
try {
|
|
36022
|
+
const result = await execFileAsync2("ps", ["-axo", "pid=,uid=,command="], {
|
|
36023
|
+
encoding: "utf8",
|
|
36024
|
+
maxBuffer: MAX_PROCESS_LIST_BYTES
|
|
36025
|
+
});
|
|
36026
|
+
return parseProcessList(result.stdout);
|
|
36027
|
+
} catch {
|
|
36028
|
+
return [];
|
|
36029
|
+
}
|
|
36030
|
+
}
|
|
36031
|
+
async signal(pid, signal) {
|
|
36032
|
+
process.kill(pid, signal);
|
|
36033
|
+
}
|
|
36034
|
+
async isAlive(pid) {
|
|
36035
|
+
try {
|
|
36036
|
+
process.kill(pid, 0);
|
|
36037
|
+
return true;
|
|
36038
|
+
} catch (error) {
|
|
36039
|
+
if (isNodeError3(error) && error.code === "ESRCH")
|
|
36040
|
+
return false;
|
|
36041
|
+
throw error;
|
|
36042
|
+
}
|
|
36043
|
+
}
|
|
36044
|
+
async ownsListeningPort(pid, port) {
|
|
36045
|
+
if (process.platform === "win32")
|
|
36046
|
+
return null;
|
|
36047
|
+
try {
|
|
36048
|
+
const result = await execFileAsync2("lsof", ["-nP", "-a", "-p", String(pid), `-iTCP:${port}`, "-sTCP:LISTEN", "-Fp"], {
|
|
36049
|
+
encoding: "utf8",
|
|
36050
|
+
maxBuffer: 64 * 1024
|
|
36051
|
+
});
|
|
36052
|
+
return result.stdout.split(`
|
|
36053
|
+
`).some((line) => line === `p${pid}`);
|
|
36054
|
+
} catch (error) {
|
|
36055
|
+
if (isNodeError3(error) && error.code === "ENOENT")
|
|
36056
|
+
return null;
|
|
36057
|
+
if (isExecFileExitError(error) && error.code === 1)
|
|
36058
|
+
return false;
|
|
36059
|
+
return null;
|
|
36060
|
+
}
|
|
36061
|
+
}
|
|
36062
|
+
async isPortOccupied(host, port) {
|
|
36063
|
+
return await new Promise((resolve14) => {
|
|
36064
|
+
let settled = false;
|
|
36065
|
+
const socket = createConnection({ host, port });
|
|
36066
|
+
const finish = (occupied) => {
|
|
36067
|
+
if (settled)
|
|
36068
|
+
return;
|
|
36069
|
+
settled = true;
|
|
36070
|
+
socket.destroy();
|
|
36071
|
+
resolve14(occupied);
|
|
36072
|
+
};
|
|
36073
|
+
socket.setTimeout(PORT_PROBE_TIMEOUT_MS);
|
|
36074
|
+
socket.once("connect", () => finish(true));
|
|
36075
|
+
socket.once("timeout", () => finish(true));
|
|
36076
|
+
socket.once("error", (error) => {
|
|
36077
|
+
finish(error.code !== "ECONNREFUSED");
|
|
36078
|
+
});
|
|
36079
|
+
});
|
|
36080
|
+
}
|
|
36081
|
+
}
|
|
36082
|
+
function parseUiServerProcess(input) {
|
|
36083
|
+
if (input.currentUid === null || input.snapshot.uid !== input.currentUid || input.snapshot.pid === (input.currentPid ?? process.pid)) {
|
|
36084
|
+
return null;
|
|
36085
|
+
}
|
|
36086
|
+
const tokens = splitCommandLine(input.snapshot.command);
|
|
36087
|
+
if (tokens === null)
|
|
36088
|
+
return null;
|
|
36089
|
+
const uiIndex = tokens.indexOf("ui");
|
|
36090
|
+
if (uiIndex < 1 || !isEvoDevInvocation(tokens, uiIndex))
|
|
36091
|
+
return null;
|
|
36092
|
+
let host = "127.0.0.1";
|
|
36093
|
+
let port = input.defaultPort;
|
|
36094
|
+
let hostSeen = false;
|
|
36095
|
+
let portSeen = false;
|
|
36096
|
+
for (let index = uiIndex + 1;index < tokens.length; index += 1) {
|
|
36097
|
+
const token = tokens[index];
|
|
36098
|
+
if (token === "--open" || token === "--no-open" || token === "--no-proposal-scan") {
|
|
36099
|
+
continue;
|
|
36100
|
+
}
|
|
36101
|
+
if (token === "--host") {
|
|
36102
|
+
if (hostSeen)
|
|
36103
|
+
return null;
|
|
36104
|
+
const value = tokens[index + 1];
|
|
36105
|
+
if (value !== "127.0.0.1" && value !== "localhost")
|
|
36106
|
+
return null;
|
|
36107
|
+
host = value;
|
|
36108
|
+
hostSeen = true;
|
|
36109
|
+
index += 1;
|
|
36110
|
+
continue;
|
|
36111
|
+
}
|
|
36112
|
+
if (token === "--port") {
|
|
36113
|
+
if (portSeen)
|
|
36114
|
+
return null;
|
|
36115
|
+
const value = Number(tokens[index + 1]);
|
|
36116
|
+
if (!Number.isInteger(value) || value < 1 || value > 65535)
|
|
36117
|
+
return null;
|
|
36118
|
+
port = value;
|
|
36119
|
+
portSeen = true;
|
|
36120
|
+
index += 1;
|
|
36121
|
+
continue;
|
|
36122
|
+
}
|
|
36123
|
+
return null;
|
|
36124
|
+
}
|
|
36125
|
+
return { pid: input.snapshot.pid, host, port };
|
|
36126
|
+
}
|
|
36127
|
+
function parseProcessList(value) {
|
|
36128
|
+
const processes = [];
|
|
36129
|
+
for (const line of value.split(`
|
|
36130
|
+
`)) {
|
|
36131
|
+
const match = /^\s*(\d+)\s+(\d+)\s+(.+?)\s*$/u.exec(line);
|
|
36132
|
+
if (match === null)
|
|
36133
|
+
continue;
|
|
36134
|
+
const pid = Number(match[1]);
|
|
36135
|
+
const uid = Number(match[2]);
|
|
36136
|
+
const command = match[3] ?? "";
|
|
36137
|
+
if (!Number.isSafeInteger(pid) || pid < 1 || !Number.isSafeInteger(uid) || uid < 0)
|
|
36138
|
+
continue;
|
|
36139
|
+
processes.push({ pid, uid, command });
|
|
36140
|
+
}
|
|
36141
|
+
return processes;
|
|
36142
|
+
}
|
|
36143
|
+
function isEvoDevInvocation(tokens, uiIndex) {
|
|
36144
|
+
if (uiIndex === 1)
|
|
36145
|
+
return isEvoDevEntrypoint(tokens[0] ?? "");
|
|
36146
|
+
if (uiIndex !== 2 || !isNodeLauncher(tokens[0] ?? ""))
|
|
36147
|
+
return false;
|
|
36148
|
+
return isEvoDevEntrypoint(tokens[1] ?? "");
|
|
36149
|
+
}
|
|
36150
|
+
function isNodeLauncher(value) {
|
|
36151
|
+
const name = portableBasename(value);
|
|
36152
|
+
return name === "node" || name === "node.exe" || name === "bun" || name === "bun.exe";
|
|
36153
|
+
}
|
|
36154
|
+
function isEvoDevEntrypoint(value) {
|
|
36155
|
+
const normalized = value.replaceAll("\\", "/");
|
|
36156
|
+
const name = portableBasename(normalized);
|
|
36157
|
+
if (name === "evodev" || name === "evodev.js")
|
|
36158
|
+
return true;
|
|
36159
|
+
if (normalized.includes("/@evo-dev/evodev/") && (normalized.endsWith("/dist/index.js") || normalized.endsWith("/bin/index.js"))) {
|
|
36160
|
+
return true;
|
|
36161
|
+
}
|
|
36162
|
+
return normalized.includes("/evodev/") && (normalized.endsWith("/packages/cli/src/index.ts") || normalized.endsWith("/packages/cli/dist/index.js"));
|
|
36163
|
+
}
|
|
36164
|
+
function portableBasename(value) {
|
|
36165
|
+
return value.replaceAll("\\", "/").split("/").at(-1)?.toLowerCase() ?? "";
|
|
36166
|
+
}
|
|
36167
|
+
function splitCommandLine(value) {
|
|
36168
|
+
const tokens = [];
|
|
36169
|
+
let current = "";
|
|
36170
|
+
let quote = null;
|
|
36171
|
+
let escaped = false;
|
|
36172
|
+
const pushCurrent = () => {
|
|
36173
|
+
if (current === "")
|
|
36174
|
+
return;
|
|
36175
|
+
tokens.push(current);
|
|
36176
|
+
current = "";
|
|
36177
|
+
};
|
|
36178
|
+
for (const character of value.trim()) {
|
|
36179
|
+
if (escaped) {
|
|
36180
|
+
current += character;
|
|
36181
|
+
escaped = false;
|
|
36182
|
+
continue;
|
|
36183
|
+
}
|
|
36184
|
+
if (character === "\\" && quote !== "'") {
|
|
36185
|
+
escaped = true;
|
|
36186
|
+
continue;
|
|
36187
|
+
}
|
|
36188
|
+
if (quote !== null) {
|
|
36189
|
+
if (character === quote)
|
|
36190
|
+
quote = null;
|
|
36191
|
+
else
|
|
36192
|
+
current += character;
|
|
36193
|
+
continue;
|
|
36194
|
+
}
|
|
36195
|
+
if (character === "'" || character === '"') {
|
|
36196
|
+
quote = character;
|
|
36197
|
+
continue;
|
|
36198
|
+
}
|
|
36199
|
+
if (/\s/u.test(character)) {
|
|
36200
|
+
pushCurrent();
|
|
36201
|
+
continue;
|
|
36202
|
+
}
|
|
36203
|
+
current += character;
|
|
36204
|
+
}
|
|
36205
|
+
if (quote !== null || escaped)
|
|
36206
|
+
return null;
|
|
36207
|
+
pushCurrent();
|
|
36208
|
+
return tokens;
|
|
36209
|
+
}
|
|
36210
|
+
function isNodeError3(error) {
|
|
36211
|
+
return error instanceof Error && "code" in error;
|
|
36212
|
+
}
|
|
36213
|
+
function isExecFileExitError(error) {
|
|
36214
|
+
return error instanceof Error && "code" in error && error.code === 1;
|
|
36215
|
+
}
|
|
36216
|
+
|
|
35682
36217
|
// packages/cli/src/ui/runtime.ts
|
|
35683
36218
|
import { mkdir as mkdir25, readFile as readFile37, rm as rm14, writeFile as writeFile25 } from "node:fs/promises";
|
|
35684
36219
|
import { join as join38 } from "node:path";
|
|
@@ -35795,12 +36330,12 @@ async function readOptionalFile(path2) {
|
|
|
35795
36330
|
try {
|
|
35796
36331
|
return await readFile37(path2, "utf8");
|
|
35797
36332
|
} catch (error) {
|
|
35798
|
-
if (
|
|
36333
|
+
if (isNodeError4(error) && error.code === "ENOENT")
|
|
35799
36334
|
return null;
|
|
35800
36335
|
throw error;
|
|
35801
36336
|
}
|
|
35802
36337
|
}
|
|
35803
|
-
function
|
|
36338
|
+
function isNodeError4(error) {
|
|
35804
36339
|
return error instanceof Error && "code" in error;
|
|
35805
36340
|
}
|
|
35806
36341
|
|
|
@@ -36966,6 +37501,8 @@ function sanitizeText4(value) {
|
|
|
36966
37501
|
// packages/cli/src/ui/server.ts
|
|
36967
37502
|
var DEFAULT_UI_PORT = 37646;
|
|
36968
37503
|
var UI_RUNTIME_REQUEST_TIMEOUT_MS = 2000;
|
|
37504
|
+
var MAX_ORPHAN_UI_CANDIDATES = 20;
|
|
37505
|
+
var MAX_LEGACY_UI_HTML_BYTES = 128 * 1024;
|
|
36969
37506
|
var MAX_UI_REQUEST_BODY_BYTES = 8 * 1024;
|
|
36970
37507
|
var UI_RESPONSE_HEADERS = {
|
|
36971
37508
|
"cache-control": "no-store",
|
|
@@ -36993,13 +37530,13 @@ function getUiHelpText() {
|
|
|
36993
37530
|
" evodev ui [--host 127.0.0.1|localhost] [--port <port>] [--no-open]",
|
|
36994
37531
|
" evodev ui status",
|
|
36995
37532
|
" evodev ui open",
|
|
36996
|
-
" evodev ui stop",
|
|
37533
|
+
" evodev ui stop [--host 127.0.0.1|localhost] [--port <port>]",
|
|
36997
37534
|
" evodev ui snapshot",
|
|
36998
37535
|
"",
|
|
36999
37536
|
"Commands:",
|
|
37000
37537
|
" status Show the managed dashboard process status",
|
|
37001
37538
|
" open Open the authenticated URL for the running dashboard",
|
|
37002
|
-
" stop Gracefully stop the
|
|
37539
|
+
" stop Gracefully stop the managed or safely verified orphaned dashboard",
|
|
37003
37540
|
" snapshot Print the local metadata dashboard snapshot as JSON",
|
|
37004
37541
|
"",
|
|
37005
37542
|
"Options:",
|
|
@@ -37036,7 +37573,8 @@ async function runUiCommand(argv, options = {}) {
|
|
|
37036
37573
|
return await runUiStatusCommand({
|
|
37037
37574
|
homeDir,
|
|
37038
37575
|
write,
|
|
37039
|
-
fetchImpl: options.fetchImpl ?? globalThis.fetch
|
|
37576
|
+
fetchImpl: options.fetchImpl ?? globalThis.fetch,
|
|
37577
|
+
processController: options.processController ?? new NodeUiProcessController
|
|
37040
37578
|
});
|
|
37041
37579
|
}
|
|
37042
37580
|
if (command === "open") {
|
|
@@ -37049,11 +37587,12 @@ async function runUiCommand(argv, options = {}) {
|
|
|
37049
37587
|
});
|
|
37050
37588
|
}
|
|
37051
37589
|
if (command === "stop") {
|
|
37052
|
-
requireNoUiSubcommandArgs(command, argv.slice(1));
|
|
37053
37590
|
return await runUiStopCommand({
|
|
37054
37591
|
homeDir,
|
|
37055
37592
|
write,
|
|
37056
|
-
fetchImpl: options.fetchImpl ?? globalThis.fetch
|
|
37593
|
+
fetchImpl: options.fetchImpl ?? globalThis.fetch,
|
|
37594
|
+
processController: options.processController ?? new NodeUiProcessController,
|
|
37595
|
+
selector: parseUiStopSelector(argv.slice(1))
|
|
37057
37596
|
});
|
|
37058
37597
|
}
|
|
37059
37598
|
const flags = parseServeFlags(argv);
|
|
@@ -37066,12 +37605,30 @@ async function runUiCommand(argv, options = {}) {
|
|
|
37066
37605
|
fetchImpl: options.fetchImpl ?? globalThis.fetch,
|
|
37067
37606
|
openBrowser: options.openBrowser ?? openUiBrowser,
|
|
37068
37607
|
evolutionJobService: options.evolutionJobService,
|
|
37069
|
-
proposalExecutionService: options.proposalExecutionService
|
|
37608
|
+
proposalExecutionService: options.proposalExecutionService,
|
|
37609
|
+
proposalApplicationRunner: options.proposalApplicationRunner
|
|
37070
37610
|
});
|
|
37071
37611
|
}
|
|
37072
37612
|
async function runUiStatusCommand(input) {
|
|
37073
37613
|
const runtime = await readUiRuntimeBundle(input.homeDir);
|
|
37074
37614
|
if (runtime.state === null) {
|
|
37615
|
+
const orphaned = await inspectOrphanUiProcesses({
|
|
37616
|
+
fetchImpl: input.fetchImpl,
|
|
37617
|
+
processController: input.processController
|
|
37618
|
+
});
|
|
37619
|
+
if (orphaned.verified.length > 0) {
|
|
37620
|
+
const runtimes = orphaned.verified.slice(0, 3).map((process5) => `pid=${process5.pid} url=${buildUiServerProcessUrl(process5)}`).join(", ");
|
|
37621
|
+
input.write(`EvoDev UI: orphaned ${runtimes} (managed state missing${runtime.token === null ? "" : "; stale authentication state present"}; run evodev ui stop)`);
|
|
37622
|
+
return 0;
|
|
37623
|
+
}
|
|
37624
|
+
if (orphaned.unverified.length > 0) {
|
|
37625
|
+
input.write(`EvoDev UI: unavailable; ${orphaned.unverified.length} same-user EvoDev UI process(es) could not be verified`);
|
|
37626
|
+
return 0;
|
|
37627
|
+
}
|
|
37628
|
+
if (await input.processController.isPortOccupied("127.0.0.1", DEFAULT_UI_PORT)) {
|
|
37629
|
+
input.write(`EvoDev UI: unavailable; 127.0.0.1:${DEFAULT_UI_PORT} is occupied by an unmanaged process`);
|
|
37630
|
+
return 0;
|
|
37631
|
+
}
|
|
37075
37632
|
input.write(runtime.token === null ? "EvoDev UI: stopped" : "EvoDev UI: stopped (stale authentication state present)");
|
|
37076
37633
|
return 0;
|
|
37077
37634
|
}
|
|
@@ -37126,19 +37683,44 @@ async function runUiStopCommand(input) {
|
|
|
37126
37683
|
homeDir: input.homeDir,
|
|
37127
37684
|
expectedToken: runtime.token
|
|
37128
37685
|
});
|
|
37129
|
-
input.write("EvoDev UI: stopped; removed stale authentication state.");
|
|
37130
|
-
} else {
|
|
37131
|
-
input.write("EvoDev UI: already stopped.");
|
|
37132
37686
|
}
|
|
37687
|
+
const stopped2 = await stopVerifiedOrphanUiProcess({
|
|
37688
|
+
fetchImpl: input.fetchImpl,
|
|
37689
|
+
processController: input.processController,
|
|
37690
|
+
selector: input.selector
|
|
37691
|
+
});
|
|
37692
|
+
if (stopped2 !== null) {
|
|
37693
|
+
input.write(`EvoDev UI: stopped orphaned runtime pid=${stopped2.pid} url=${buildUiServerProcessUrl(stopped2)}.`);
|
|
37694
|
+
return 0;
|
|
37695
|
+
}
|
|
37696
|
+
input.write(runtime.token === null ? "EvoDev UI: already stopped." : "EvoDev UI: stopped; removed stale authentication state.");
|
|
37133
37697
|
return 0;
|
|
37134
37698
|
}
|
|
37699
|
+
assertUiStopSelectorMatchesRuntime(input.selector, runtime.state);
|
|
37135
37700
|
if (runtime.token === null) {
|
|
37136
37701
|
const probe = await probeUiRuntime({
|
|
37137
37702
|
state: runtime.state,
|
|
37138
37703
|
fetchImpl: input.fetchImpl
|
|
37139
37704
|
});
|
|
37140
37705
|
if (probe.kind === "running") {
|
|
37141
|
-
|
|
37706
|
+
const stopped2 = await stopVerifiedOrphanUiProcess({
|
|
37707
|
+
fetchImpl: input.fetchImpl,
|
|
37708
|
+
processController: input.processController,
|
|
37709
|
+
selector: {
|
|
37710
|
+
host: runtime.state.host,
|
|
37711
|
+
port: runtime.state.port
|
|
37712
|
+
},
|
|
37713
|
+
expectedPid: runtime.state.pid
|
|
37714
|
+
});
|
|
37715
|
+
if (stopped2 === null) {
|
|
37716
|
+
throw new Error("Cannot safely stop EvoDev UI because its authentication state is missing and the owning process could not be verified.");
|
|
37717
|
+
}
|
|
37718
|
+
await removeUiRuntimeState({
|
|
37719
|
+
homeDir: input.homeDir,
|
|
37720
|
+
expectedInstanceId: runtime.state.instanceId
|
|
37721
|
+
});
|
|
37722
|
+
input.write("EvoDev UI: stopped; recovered missing authentication state.");
|
|
37723
|
+
return 0;
|
|
37142
37724
|
}
|
|
37143
37725
|
if (probe.kind === "unavailable" && probe.timedOut) {
|
|
37144
37726
|
throw new Error("Cannot safely stop EvoDev UI because its health check timed out.");
|
|
@@ -37265,18 +37847,165 @@ async function waitForUiRuntimeRemoval(input) {
|
|
|
37265
37847
|
}
|
|
37266
37848
|
return false;
|
|
37267
37849
|
}
|
|
37850
|
+
async function stopVerifiedOrphanUiProcess(input) {
|
|
37851
|
+
const inspection = await inspectOrphanUiProcesses(input);
|
|
37852
|
+
const verified = inspection.verified.filter((candidate2) => input.expectedPid === undefined || candidate2.pid === input.expectedPid);
|
|
37853
|
+
const unverified = inspection.unverified.filter((candidate2) => input.expectedPid === undefined || candidate2.pid === input.expectedPid);
|
|
37854
|
+
if (verified.length > 1) {
|
|
37855
|
+
throw new Error(`Cannot choose between ${verified.length} orphaned EvoDev UI processes. Re-run with --port <port>.`);
|
|
37856
|
+
}
|
|
37857
|
+
const candidate = verified[0];
|
|
37858
|
+
if (candidate !== undefined) {
|
|
37859
|
+
if (!await verifyUiServerProcess(candidate, input.fetchImpl, input.processController)) {
|
|
37860
|
+
throw new Error("Cannot safely stop EvoDev UI because the orphaned runtime changed during verification.");
|
|
37861
|
+
}
|
|
37862
|
+
try {
|
|
37863
|
+
await input.processController.signal(candidate.pid, "SIGTERM");
|
|
37864
|
+
} catch (error) {
|
|
37865
|
+
if (await input.processController.isAlive(candidate.pid))
|
|
37866
|
+
throw error;
|
|
37867
|
+
}
|
|
37868
|
+
if (!await waitForUiProcessExit(candidate.pid, input.processController)) {
|
|
37869
|
+
throw new Error(`EvoDev UI process ${candidate.pid} did not stop within ${UI_RUNTIME_REQUEST_TIMEOUT_MS / 1000} seconds; no SIGKILL was sent.`);
|
|
37870
|
+
}
|
|
37871
|
+
return candidate;
|
|
37872
|
+
}
|
|
37873
|
+
if (unverified.length > 0) {
|
|
37874
|
+
throw new Error("Cannot safely stop EvoDev UI because a same-user EvoDev UI process did not pass the loopback identity check; no signal was sent.");
|
|
37875
|
+
}
|
|
37876
|
+
const host = input.selector.host ?? "127.0.0.1";
|
|
37877
|
+
const port = input.selector.port ?? DEFAULT_UI_PORT;
|
|
37878
|
+
if (await input.processController.isPortOccupied(host, port)) {
|
|
37879
|
+
throw new Error(`Cannot safely stop EvoDev UI: ${host}:${port} is occupied by an unmanaged process; no signal was sent.`);
|
|
37880
|
+
}
|
|
37881
|
+
return null;
|
|
37882
|
+
}
|
|
37883
|
+
async function inspectOrphanUiProcesses(input) {
|
|
37884
|
+
const snapshots = await input.processController.listProcesses();
|
|
37885
|
+
const candidates2 = snapshots.map((snapshot) => parseUiServerProcess({
|
|
37886
|
+
snapshot,
|
|
37887
|
+
currentUid: input.processController.currentUid,
|
|
37888
|
+
defaultPort: DEFAULT_UI_PORT
|
|
37889
|
+
})).filter((candidate) => candidate !== null).filter((candidate) => matchesUiStopSelector(candidate, input.selector));
|
|
37890
|
+
if (candidates2.length > MAX_ORPHAN_UI_CANDIDATES) {
|
|
37891
|
+
throw new Error("Too many EvoDev UI process candidates; no process was signaled.");
|
|
37892
|
+
}
|
|
37893
|
+
const verified = [];
|
|
37894
|
+
const unverified = [];
|
|
37895
|
+
const inspections = await Promise.all(candidates2.map(async (candidate) => ({
|
|
37896
|
+
candidate,
|
|
37897
|
+
verified: await verifyUiServerProcess(candidate, input.fetchImpl, input.processController)
|
|
37898
|
+
})));
|
|
37899
|
+
for (const inspection of inspections) {
|
|
37900
|
+
if (inspection.verified)
|
|
37901
|
+
verified.push(inspection.candidate);
|
|
37902
|
+
else
|
|
37903
|
+
unverified.push(inspection.candidate);
|
|
37904
|
+
}
|
|
37905
|
+
return { verified, unverified };
|
|
37906
|
+
}
|
|
37907
|
+
async function verifyUiServerProcess(candidate, fetchImpl, processController) {
|
|
37908
|
+
const health = await fetchUiIdentityResponse(new URL("/api/health", buildUiServerProcessUrl(candidate)), fetchImpl);
|
|
37909
|
+
if (health?.ok) {
|
|
37910
|
+
try {
|
|
37911
|
+
const body = await health.json();
|
|
37912
|
+
const state = parseUiRuntimeState(JSON.stringify(body.data));
|
|
37913
|
+
return state.component === "evodev-ui" && state.pid === candidate.pid && state.host === candidate.host && state.port === candidate.port;
|
|
37914
|
+
} catch {
|
|
37915
|
+
return false;
|
|
37916
|
+
}
|
|
37917
|
+
}
|
|
37918
|
+
if (health === null || health.status !== 404)
|
|
37919
|
+
return false;
|
|
37920
|
+
if (!await processController.ownsListeningPort(candidate.pid, candidate.port))
|
|
37921
|
+
return false;
|
|
37922
|
+
const root = await fetchUiIdentityResponse(new URL("/", buildUiServerProcessUrl(candidate)), fetchImpl);
|
|
37923
|
+
if (root === null || !root.ok || !(root.headers.get("content-type") ?? "").startsWith("text/html") || root.headers.get("cache-control") !== "no-store" || root.headers.get("x-content-type-options") !== "nosniff" || root.headers.get("x-frame-options") !== "DENY") {
|
|
37924
|
+
return false;
|
|
37925
|
+
}
|
|
37926
|
+
const html = await readResponseTextBounded(root, MAX_LEGACY_UI_HTML_BYTES);
|
|
37927
|
+
if (html === null)
|
|
37928
|
+
return false;
|
|
37929
|
+
return html.includes("<title>EvoDev UI</title>") && html.includes('<div id="root">') && html.includes("/assets/app.js");
|
|
37930
|
+
}
|
|
37931
|
+
async function fetchUiIdentityResponse(url, fetchImpl) {
|
|
37932
|
+
const controller = new AbortController;
|
|
37933
|
+
const timeout = setTimeout(() => controller.abort(), UI_RUNTIME_REQUEST_TIMEOUT_MS);
|
|
37934
|
+
try {
|
|
37935
|
+
return await fetchImpl(url, {
|
|
37936
|
+
headers: { accept: "application/json, text/html" },
|
|
37937
|
+
signal: controller.signal
|
|
37938
|
+
});
|
|
37939
|
+
} catch {
|
|
37940
|
+
return null;
|
|
37941
|
+
} finally {
|
|
37942
|
+
clearTimeout(timeout);
|
|
37943
|
+
}
|
|
37944
|
+
}
|
|
37945
|
+
async function readResponseTextBounded(response, maxBytes) {
|
|
37946
|
+
const contentLength = Number(response.headers.get("content-length"));
|
|
37947
|
+
if (Number.isFinite(contentLength) && contentLength > maxBytes)
|
|
37948
|
+
return null;
|
|
37949
|
+
if (response.body === null)
|
|
37950
|
+
return "";
|
|
37951
|
+
const reader = response.body.getReader();
|
|
37952
|
+
const decoder = new TextDecoder;
|
|
37953
|
+
let totalBytes = 0;
|
|
37954
|
+
let text2 = "";
|
|
37955
|
+
try {
|
|
37956
|
+
while (true) {
|
|
37957
|
+
const chunk = await reader.read();
|
|
37958
|
+
if (chunk.done)
|
|
37959
|
+
return text2 + decoder.decode();
|
|
37960
|
+
totalBytes += chunk.value.byteLength;
|
|
37961
|
+
if (totalBytes > maxBytes) {
|
|
37962
|
+
await reader.cancel().catch(() => {
|
|
37963
|
+
return;
|
|
37964
|
+
});
|
|
37965
|
+
return null;
|
|
37966
|
+
}
|
|
37967
|
+
text2 += decoder.decode(chunk.value, { stream: true });
|
|
37968
|
+
}
|
|
37969
|
+
} catch {
|
|
37970
|
+
return null;
|
|
37971
|
+
} finally {
|
|
37972
|
+
reader.releaseLock();
|
|
37973
|
+
}
|
|
37974
|
+
}
|
|
37975
|
+
async function waitForUiProcessExit(pid, processController) {
|
|
37976
|
+
const deadline = Date.now() + UI_RUNTIME_REQUEST_TIMEOUT_MS;
|
|
37977
|
+
while (Date.now() < deadline) {
|
|
37978
|
+
if (!await processController.isAlive(pid))
|
|
37979
|
+
return true;
|
|
37980
|
+
await delay(25);
|
|
37981
|
+
}
|
|
37982
|
+
return !await processController.isAlive(pid);
|
|
37983
|
+
}
|
|
37984
|
+
function buildUiServerProcessUrl(process5) {
|
|
37985
|
+
return `http://${process5.host}:${process5.port}`;
|
|
37986
|
+
}
|
|
37987
|
+
function matchesUiStopSelector(candidate, selector) {
|
|
37988
|
+
if (selector === undefined)
|
|
37989
|
+
return true;
|
|
37990
|
+
return (selector.host === null || selector.host === candidate.host) && (selector.port === null || selector.port === candidate.port);
|
|
37991
|
+
}
|
|
37992
|
+
function assertUiStopSelectorMatchesRuntime(selector, state) {
|
|
37993
|
+
if (selector.host !== null && selector.host !== state.host || selector.port !== null && selector.port !== state.port) {
|
|
37994
|
+
throw new Error(`Managed EvoDev UI is running at ${buildUiRuntimeBaseUrl(state)}; the stop selector does not match.`);
|
|
37995
|
+
}
|
|
37996
|
+
}
|
|
37268
37997
|
function requireNoUiSubcommandArgs(command, argv) {
|
|
37269
37998
|
if (argv.length > 0)
|
|
37270
37999
|
throw new Error(`Unknown ui ${command} option: ${argv[0] ?? ""}`.trim());
|
|
37271
38000
|
}
|
|
37272
38001
|
async function delay(milliseconds) {
|
|
37273
|
-
await new Promise((
|
|
38002
|
+
await new Promise((resolve14) => setTimeout(resolve14, milliseconds));
|
|
37274
38003
|
}
|
|
37275
38004
|
async function writeProcessStdout(value) {
|
|
37276
|
-
await new Promise((
|
|
38005
|
+
await new Promise((resolve14, reject) => {
|
|
37277
38006
|
process.stdout.write(value, (error) => {
|
|
37278
38007
|
if (error === null || error === undefined)
|
|
37279
|
-
|
|
38008
|
+
resolve14();
|
|
37280
38009
|
else
|
|
37281
38010
|
reject(error);
|
|
37282
38011
|
});
|
|
@@ -37301,6 +38030,9 @@ async function startUiServer(input) {
|
|
|
37301
38030
|
const html = renderUiDashboardHtml();
|
|
37302
38031
|
const webAssets = await loadUiWebAssets();
|
|
37303
38032
|
const proposalExecution = input.proposalExecutionService ?? new RepoProposalExecutionService;
|
|
38033
|
+
const applyProposal = input.proposalApplicationRunner ?? ((request) => runRepoProposalApplication(request, {
|
|
38034
|
+
proposalExecution
|
|
38035
|
+
}));
|
|
37304
38036
|
const evolutionJobs = input.evolutionJobService ?? new EvolutionJobService({ homeDir: input.homeDir });
|
|
37305
38037
|
let runtimeState = null;
|
|
37306
38038
|
let requestClose = null;
|
|
@@ -37417,7 +38149,7 @@ async function startUiServer(input) {
|
|
|
37417
38149
|
knowledge: true,
|
|
37418
38150
|
semanticKnowledge: true,
|
|
37419
38151
|
recommendations: true,
|
|
37420
|
-
improvementEvals:
|
|
38152
|
+
improvementEvals: false,
|
|
37421
38153
|
forceRecommendations: true
|
|
37422
38154
|
});
|
|
37423
38155
|
writeJsonResponse(response, 202, { ok: true, data: result });
|
|
@@ -37525,6 +38257,24 @@ async function startUiServer(input) {
|
|
|
37525
38257
|
writeJsonResponse(response, 202, { ok: true, data: result });
|
|
37526
38258
|
return;
|
|
37527
38259
|
}
|
|
38260
|
+
if (url2.pathname === "/api/proposals/application/start") {
|
|
38261
|
+
await requireAuthenticatedJsonPost({ request, response, token });
|
|
38262
|
+
if (response.writableEnded)
|
|
38263
|
+
return;
|
|
38264
|
+
const application = parseUiProposalApplicationRequest(await readRequestJson(request));
|
|
38265
|
+
const result = await applyProposal({
|
|
38266
|
+
homeDir: input.homeDir,
|
|
38267
|
+
proposalId: application.id,
|
|
38268
|
+
projectKey: application.projectKey,
|
|
38269
|
+
expectedReviewState: application.expectedReviewState,
|
|
38270
|
+
improvementEvalRequested: application.improvementEvalRequested,
|
|
38271
|
+
agent: application.agent,
|
|
38272
|
+
expectedFingerprint: application.expectedFingerprint,
|
|
38273
|
+
confirmDirty: application.confirmDirty
|
|
38274
|
+
});
|
|
38275
|
+
writeJsonResponse(response, 202, { ok: true, data: result });
|
|
38276
|
+
return;
|
|
38277
|
+
}
|
|
37528
38278
|
writeJsonResponse(response, 404, { ok: false, error: "not found" });
|
|
37529
38279
|
} catch (error) {
|
|
37530
38280
|
if (error instanceof UiRequestError) {
|
|
@@ -37561,14 +38311,14 @@ async function startUiServer(input) {
|
|
|
37561
38311
|
writeJsonResponse(response, 500, { ok: false, error: "dashboard request unavailable" });
|
|
37562
38312
|
}
|
|
37563
38313
|
});
|
|
37564
|
-
await new Promise((
|
|
38314
|
+
await new Promise((resolve14, reject) => {
|
|
37565
38315
|
const onError = (error) => {
|
|
37566
38316
|
server.off("listening", onListening);
|
|
37567
|
-
reject(error.code === "EADDRINUSE" ? new Error(`Cannot start EvoDev UI: ${input.flags.host}:${input.flags.port} is already in use and no running managed UI matched it.`) : error);
|
|
38317
|
+
reject(error.code === "EADDRINUSE" ? new Error(`Cannot start EvoDev UI: ${input.flags.host}:${input.flags.port} is already in use and no running managed UI matched it. Run \`evodev ui stop --host ${input.flags.host} --port ${input.flags.port}\` to recover a safely verified orphaned EvoDev UI process.`) : error);
|
|
37568
38318
|
};
|
|
37569
38319
|
const onListening = () => {
|
|
37570
38320
|
server.off("error", onError);
|
|
37571
|
-
|
|
38321
|
+
resolve14();
|
|
37572
38322
|
};
|
|
37573
38323
|
server.once("error", onError);
|
|
37574
38324
|
server.once("listening", onListening);
|
|
@@ -37598,7 +38348,7 @@ async function startUiServer(input) {
|
|
|
37598
38348
|
await Promise.all([proposalExecution.shutdown(), evolutionJobs.shutdown()]).catch(() => {
|
|
37599
38349
|
return;
|
|
37600
38350
|
});
|
|
37601
|
-
await new Promise((
|
|
38351
|
+
await new Promise((resolve14) => server.close(() => resolve14()));
|
|
37602
38352
|
throw error;
|
|
37603
38353
|
}
|
|
37604
38354
|
const url = `http://${input.flags.host}:${port}/#token=${token}`;
|
|
@@ -37606,7 +38356,7 @@ async function startUiServer(input) {
|
|
|
37606
38356
|
input.write("Mode: local-only dashboard; only concrete repo proposals require review. Press Ctrl+C to stop.");
|
|
37607
38357
|
if (input.flags.open)
|
|
37608
38358
|
input.openBrowser(url, input.write);
|
|
37609
|
-
return await new Promise((
|
|
38359
|
+
return await new Promise((resolve14) => {
|
|
37610
38360
|
let closing = false;
|
|
37611
38361
|
const finish = () => {
|
|
37612
38362
|
process.off("SIGINT", close);
|
|
@@ -37618,7 +38368,7 @@ async function startUiServer(input) {
|
|
|
37618
38368
|
expectedToken: token
|
|
37619
38369
|
}).catch((error) => {
|
|
37620
38370
|
input.write(`Warning: dashboard stopped, but runtime state cleanup failed: ${describeError10(error)}`);
|
|
37621
|
-
}).finally(() =>
|
|
38371
|
+
}).finally(() => resolve14(0));
|
|
37622
38372
|
};
|
|
37623
38373
|
const close = () => {
|
|
37624
38374
|
if (closing)
|
|
@@ -37665,6 +38415,33 @@ function parseServeFlags(argv) {
|
|
|
37665
38415
|
}
|
|
37666
38416
|
return { host, port, open: open6 };
|
|
37667
38417
|
}
|
|
38418
|
+
function parseUiStopSelector(argv) {
|
|
38419
|
+
let host = null;
|
|
38420
|
+
let port = null;
|
|
38421
|
+
for (let index = 0;index < argv.length; index += 1) {
|
|
38422
|
+
const option = argv[index];
|
|
38423
|
+
const value = argv[index + 1];
|
|
38424
|
+
if (option !== "--host" && option !== "--port") {
|
|
38425
|
+
throw new Error(`Unknown ui stop option: ${option ?? ""}`.trim());
|
|
38426
|
+
}
|
|
38427
|
+
if (value === undefined || value.startsWith("--")) {
|
|
38428
|
+
throw new Error(`Missing value for ${option}`);
|
|
38429
|
+
}
|
|
38430
|
+
if (option === "--host") {
|
|
38431
|
+
if (host !== null)
|
|
38432
|
+
throw new Error("Duplicate ui stop option: --host");
|
|
38433
|
+
host = validateUiBindHost(value);
|
|
38434
|
+
} else {
|
|
38435
|
+
if (port !== null)
|
|
38436
|
+
throw new Error("Duplicate ui stop option: --port");
|
|
38437
|
+
port = validateUiPort(Number(value));
|
|
38438
|
+
if (port === 0)
|
|
38439
|
+
throw new Error("UI stop port must be between 1 and 65535.");
|
|
38440
|
+
}
|
|
38441
|
+
index += 1;
|
|
38442
|
+
}
|
|
38443
|
+
return { host, port };
|
|
38444
|
+
}
|
|
37668
38445
|
function validateUiBindHost(host) {
|
|
37669
38446
|
if (host === "127.0.0.1" || host === "localhost")
|
|
37670
38447
|
return host;
|
|
@@ -37866,6 +38643,51 @@ function parseUiProposalExecutionRequest(value) {
|
|
|
37866
38643
|
confirmInconclusiveEval: record.confirmInconclusiveEval === true
|
|
37867
38644
|
};
|
|
37868
38645
|
}
|
|
38646
|
+
function parseUiProposalApplicationRequest(value) {
|
|
38647
|
+
const identity = parseUiProposalIdentitySubset(value);
|
|
38648
|
+
const record = value;
|
|
38649
|
+
const allowedKeys = new Set([
|
|
38650
|
+
"id",
|
|
38651
|
+
"projectKey",
|
|
38652
|
+
"expectedReviewState",
|
|
38653
|
+
"improvementEvalRequested",
|
|
38654
|
+
"agent",
|
|
38655
|
+
"expectedFingerprint",
|
|
38656
|
+
"confirmDirty"
|
|
38657
|
+
]);
|
|
38658
|
+
if (Object.keys(record).some((key) => !allowedKeys.has(key))) {
|
|
38659
|
+
throw new UiRequestError(400, "repo proposal application request contains unsupported fields");
|
|
38660
|
+
}
|
|
38661
|
+
if (record.expectedReviewState !== "pending" && record.expectedReviewState !== "accepted" && record.expectedReviewState !== "rejected" && record.expectedReviewState !== "deferred") {
|
|
38662
|
+
throw new UiRequestError(400, "expected repo proposal review state is invalid");
|
|
38663
|
+
}
|
|
38664
|
+
if (record.improvementEvalRequested !== undefined && typeof record.improvementEvalRequested !== "boolean") {
|
|
38665
|
+
throw new UiRequestError(400, "improvement-evaluation selection is invalid");
|
|
38666
|
+
}
|
|
38667
|
+
if (record.expectedReviewState !== "accepted" && typeof record.improvementEvalRequested !== "boolean") {
|
|
38668
|
+
throw new UiRequestError(400, "improvement-evaluation selection is required when approving a proposal");
|
|
38669
|
+
}
|
|
38670
|
+
if (record.expectedReviewState === "accepted" && record.improvementEvalRequested !== undefined) {
|
|
38671
|
+
throw new UiRequestError(400, "an accepted proposal cannot change its improvement-evaluation selection");
|
|
38672
|
+
}
|
|
38673
|
+
if (record.agent !== "codex" && record.agent !== "claude") {
|
|
38674
|
+
throw new UiRequestError(400, "code agent selection is invalid");
|
|
38675
|
+
}
|
|
38676
|
+
if (typeof record.expectedFingerprint !== "string" || !/^[a-f0-9]{64}$/u.test(record.expectedFingerprint)) {
|
|
38677
|
+
throw new UiRequestError(400, "repository confirmation fingerprint is invalid");
|
|
38678
|
+
}
|
|
38679
|
+
if (record.confirmDirty !== undefined && typeof record.confirmDirty !== "boolean") {
|
|
38680
|
+
throw new UiRequestError(400, "dirty-worktree confirmation is invalid");
|
|
38681
|
+
}
|
|
38682
|
+
return {
|
|
38683
|
+
...identity,
|
|
38684
|
+
expectedReviewState: record.expectedReviewState,
|
|
38685
|
+
improvementEvalRequested: typeof record.improvementEvalRequested === "boolean" ? record.improvementEvalRequested : undefined,
|
|
38686
|
+
agent: record.agent,
|
|
38687
|
+
expectedFingerprint: record.expectedFingerprint,
|
|
38688
|
+
confirmDirty: record.confirmDirty === true
|
|
38689
|
+
};
|
|
38690
|
+
}
|
|
37869
38691
|
function parseUiProposalIdentitySubset(value) {
|
|
37870
38692
|
if (typeof value !== "object" || value === null || Array.isArray(value)) {
|
|
37871
38693
|
throw new UiRequestError(400, "repo proposal execution request is invalid");
|
|
@@ -38004,7 +38826,7 @@ function getHelpText() {
|
|
|
38004
38826
|
"",
|
|
38005
38827
|
"Commands:",
|
|
38006
38828
|
" init [claude|codex] Initialize EvoDev for selected Code Agents",
|
|
38007
|
-
" init --yes Initialize both
|
|
38829
|
+
" init --yes Initialize both Agents and recommended Git projects",
|
|
38008
38830
|
" doctor Check system, config, plugins, and built-in assets",
|
|
38009
38831
|
" sync Sync enabled built-in assets to Code Agent user dirs",
|
|
38010
38832
|
" config get Print ~/.evodev/settings.json without creating files",
|
|
@@ -38026,7 +38848,7 @@ function getHelpText() {
|
|
|
38026
38848
|
" ui Start the local-only EvoDev web dashboard",
|
|
38027
38849
|
" ui status Show the managed dashboard process status",
|
|
38028
38850
|
" ui open Open the authenticated URL for the running dashboard",
|
|
38029
|
-
" ui stop
|
|
38851
|
+
" ui stop Stop the managed or safely verified orphaned dashboard",
|
|
38030
38852
|
" ui snapshot Print dashboard metadata without starting a server",
|
|
38031
38853
|
" learn review Preview learning candidates without writing memory",
|
|
38032
38854
|
" learn lint Validate learning candidate privacy/provenance fields",
|
|
@@ -38073,7 +38895,7 @@ function getHelpText() {
|
|
|
38073
38895
|
].join(`
|
|
38074
38896
|
`);
|
|
38075
38897
|
}
|
|
38076
|
-
var CLI_VERSION = "0.0.1-alpha.
|
|
38898
|
+
var CLI_VERSION = "0.0.1-alpha.13";
|
|
38077
38899
|
function getVersionText() {
|
|
38078
38900
|
return `evodev ${CLI_VERSION}`;
|
|
38079
38901
|
}
|
|
@@ -38723,6 +39545,7 @@ function parseInitFlags(argv) {
|
|
|
38723
39545
|
answers.initializeConfig = true;
|
|
38724
39546
|
answers.registerSkills = true;
|
|
38725
39547
|
answers.registerAgents = true;
|
|
39548
|
+
answers.registerRecommendedProjects = true;
|
|
38726
39549
|
answers.runDoctor = true;
|
|
38727
39550
|
continue;
|
|
38728
39551
|
}
|
|
@@ -38758,7 +39581,7 @@ if (isMainModule()) {
|
|
|
38758
39581
|
process.exitCode = await run();
|
|
38759
39582
|
}
|
|
38760
39583
|
function isMainModule() {
|
|
38761
|
-
return process.argv[1] !== undefined && realpathSync(fileURLToPath5(import.meta.url)) === realpathSync(
|
|
39584
|
+
return process.argv[1] !== undefined && realpathSync(fileURLToPath5(import.meta.url)) === realpathSync(resolve14(process.argv[1]));
|
|
38762
39585
|
}
|
|
38763
39586
|
export {
|
|
38764
39587
|
shouldRunPostCommandMaintenance,
|
|
@@ -38771,6 +39594,7 @@ export {
|
|
|
38771
39594
|
runTeamCommand,
|
|
38772
39595
|
runSyncCommand,
|
|
38773
39596
|
runScheduleCommand,
|
|
39597
|
+
runRepoProposalApplication,
|
|
38774
39598
|
runPluginCommand,
|
|
38775
39599
|
runPendingImprovementEvaluations,
|
|
38776
39600
|
runPackCommand,
|
|
@@ -38794,6 +39618,7 @@ export {
|
|
|
38794
39618
|
resolveLazyEvolutionStatePath,
|
|
38795
39619
|
resolveLaunchAgentPath,
|
|
38796
39620
|
resolveLatestEvolutionJobPath,
|
|
39621
|
+
resolveInitProjectCandidates,
|
|
38797
39622
|
resolveEvolutionJobPath,
|
|
38798
39623
|
resolveDefaultCodeAgentSelections,
|
|
38799
39624
|
renderUiDashboardHtml,
|
|
@@ -38825,6 +39650,7 @@ export {
|
|
|
38825
39650
|
formatDoctorOutput,
|
|
38826
39651
|
createSessionKnowledgeDistiller,
|
|
38827
39652
|
createInteractivePrompter,
|
|
39653
|
+
createInitProjectChoices,
|
|
38828
39654
|
createDefaultInitPluginRegistry,
|
|
38829
39655
|
createCodexSessionProposalAnalyzer,
|
|
38830
39656
|
createCodexSessionKnowledgeAnalyzer,
|