@ordewell/core 0.5.0 → 0.5.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{ITerminalRunner-nupxhtPb.d.ts → ITerminalRunner-Bd-vAJnw.d.ts} +24 -2
- package/dist/{ITerminalRunner-DXlJop_f.d.mts → ITerminalRunner-ByeoLF57.d.mts} +24 -2
- package/dist/{ModeResolver-D1-2xJsE.d.mts → ModeResolver-DVJ7HV3k.d.mts} +1 -1
- package/dist/{ModeResolver-BBVktyiP.d.ts → ModeResolver-Dkig8ghQ.d.ts} +1 -1
- package/dist/{Task-CfTjRQeS.d.mts → Task-BxQkPlXO.d.mts} +88 -10
- package/dist/{Task-CfTjRQeS.d.ts → Task-BxQkPlXO.d.ts} +88 -10
- package/dist/{chunk-HQICT7HQ.mjs → chunk-T2S5O36I.mjs} +17 -4
- package/dist/chunk-T2S5O36I.mjs.map +1 -0
- package/dist/index.d.mts +151 -57
- package/dist/index.d.ts +151 -57
- package/dist/index.js +1179 -500
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +1162 -498
- package/dist/index.mjs.map +1 -1
- package/dist/order-labels.d.mts +1 -1
- package/dist/order-labels.d.ts +1 -1
- package/dist/{parsing-Bv3g2Tu_.d.ts → parsing-CF_grC29.d.ts} +2 -2
- package/dist/{parsing-BJECTMOD.d.mts → parsing-DRp4dPC0.d.mts} +2 -2
- package/dist/parsing.d.mts +3 -3
- package/dist/parsing.d.ts +3 -3
- package/dist/{plan-utils-Cy-RsBSV.d.ts → plan-utils-CkNbqAmS.d.ts} +36 -3
- package/dist/{plan-utils-B5oIZkuI.d.mts → plan-utils-CtB3_Ovf.d.mts} +36 -3
- package/dist/plan-utils.d.mts +3 -3
- package/dist/plan-utils.d.ts +3 -3
- package/dist/plan-utils.js +7 -0
- package/dist/plan-utils.js.map +1 -1
- package/dist/plan-utils.mjs +4 -0
- package/dist/testing.d.mts +20 -8
- package/dist/testing.d.ts +20 -8
- package/dist/testing.js +52 -4
- package/dist/testing.js.map +1 -1
- package/dist/testing.mjs +47 -4
- package/dist/testing.mjs.map +1 -1
- package/package.json +1 -1
- package/dist/chunk-HQICT7HQ.mjs.map +0 -1
package/dist/index.js
CHANGED
|
@@ -56,6 +56,7 @@ __export(index_exports, {
|
|
|
56
56
|
ConversationEditError: () => ConversationEditError,
|
|
57
57
|
DAEMON_SUBPROTOCOL: () => DAEMON_SUBPROTOCOL,
|
|
58
58
|
DAEMON_TOKEN_SUBPROTOCOL_PREFIX: () => DAEMON_TOKEN_SUBPROTOCOL_PREFIX,
|
|
59
|
+
DEFAULT_MAX_PARALLEL: () => DEFAULT_MAX_PARALLEL,
|
|
59
60
|
DENY_ALL: () => DENY_ALL,
|
|
60
61
|
EmbeddedNewlineError: () => EmbeddedNewlineError,
|
|
61
62
|
EnvConfig: () => EnvConfig,
|
|
@@ -129,6 +130,7 @@ __export(index_exports, {
|
|
|
129
130
|
augmentPromptWithPriorOutputs: () => augmentPromptWithPriorOutputs,
|
|
130
131
|
augmentedPath: () => augmentedPath,
|
|
131
132
|
bearerHeaderValue: () => bearerHeaderValue,
|
|
133
|
+
buildConflictRepairPrompt: () => buildConflictRepairPrompt,
|
|
132
134
|
buildConflictResolutionPrompt: () => buildConflictResolutionPrompt,
|
|
133
135
|
buildConversationSystemPrompt: () => buildConversationSystemPrompt,
|
|
134
136
|
buildFallbackGrepArgs: () => buildFallbackGrepArgs,
|
|
@@ -148,6 +150,7 @@ __export(index_exports, {
|
|
|
148
150
|
canMergeTasks: () => canMergeTasks,
|
|
149
151
|
canSetDependencies: () => canSetDependencies,
|
|
150
152
|
canSplitTask: () => canSplitTask,
|
|
153
|
+
capConflictFiles: () => capConflictFiles,
|
|
151
154
|
checkDepsResolve: () => checkDepsResolve,
|
|
152
155
|
checkImmutableLog: () => checkImmutableLog,
|
|
153
156
|
checkInProgress: () => checkInProgress,
|
|
@@ -236,6 +239,7 @@ __export(index_exports, {
|
|
|
236
239
|
modifyValidationFeedback: () => modifyValidationFeedback,
|
|
237
240
|
normalizeAgentArgs: () => normalizeAgentArgs,
|
|
238
241
|
normalizeGeminiModel: () => normalizeGeminiModel,
|
|
242
|
+
parseMaxParallel: () => parseMaxParallel,
|
|
239
243
|
parsePartialPlan: () => parsePartialPlan,
|
|
240
244
|
parsePlanJson: () => parsePlanJson,
|
|
241
245
|
parseTaskOpsJson: () => parseTaskOpsJson,
|
|
@@ -3189,33 +3193,33 @@ function grepOptions(args) {
|
|
|
3189
3193
|
};
|
|
3190
3194
|
}
|
|
3191
3195
|
var STOPPED_TOOL_RESULT = "Stopped by the user \u2014 this call was not executed, and no further tool calls will run this turn.";
|
|
3192
|
-
async function executeTool(name, args,
|
|
3193
|
-
const outcome = await runTool(name, args,
|
|
3196
|
+
async function executeTool(name, args, fs15, fetcher, signal) {
|
|
3197
|
+
const outcome = await runTool(name, args, fs15, fetcher, signal);
|
|
3194
3198
|
return { ...outcome, output: redactSecrets(outcome.output) };
|
|
3195
3199
|
}
|
|
3196
|
-
async function runTool(name, args,
|
|
3200
|
+
async function runTool(name, args, fs15, fetcher, signal) {
|
|
3197
3201
|
if (signal?.aborted) return { success: false, output: STOPPED_TOOL_RESULT, truncated: false };
|
|
3198
3202
|
switch (name) {
|
|
3199
3203
|
case "read_file": {
|
|
3200
3204
|
const offset = num(args.offset);
|
|
3201
3205
|
const limit = num(args.limit);
|
|
3202
3206
|
const maxBytes = num(args.maxBytes);
|
|
3203
|
-
return
|
|
3207
|
+
return fs15.readFile(String(args.path ?? ""), { offset, limit, maxBytes });
|
|
3204
3208
|
}
|
|
3205
3209
|
case "read_files": {
|
|
3206
3210
|
const paths = Array.isArray(args.paths) ? args.paths.map(String) : [];
|
|
3207
|
-
return
|
|
3211
|
+
return fs15.readFiles(paths);
|
|
3208
3212
|
}
|
|
3209
3213
|
case "glob":
|
|
3210
|
-
return
|
|
3214
|
+
return fs15.glob(String(args.pattern ?? ""), { path: str(args.path), headLimit: num(args.head_limit ?? args.headLimit) });
|
|
3211
3215
|
case "grep":
|
|
3212
|
-
return
|
|
3216
|
+
return fs15.grep(String(args.pattern ?? ""), grepOptions(args));
|
|
3213
3217
|
case "find_symbol":
|
|
3214
|
-
return
|
|
3218
|
+
return fs15.findSymbol(String(args.symbol ?? ""), { language: str(args.language), path: str(args.path) });
|
|
3215
3219
|
case "list_dir":
|
|
3216
|
-
return
|
|
3220
|
+
return fs15.listDir(String(args.path ?? ""), num(args.depth));
|
|
3217
3221
|
case "bash":
|
|
3218
|
-
return
|
|
3222
|
+
return fs15.bash(String(args.command ?? ""), signal);
|
|
3219
3223
|
case "fetch": {
|
|
3220
3224
|
if (!fetcher) return { success: false, output: "Web fetching is not available in this environment.", truncated: false };
|
|
3221
3225
|
const url = String(args.url ?? "");
|
|
@@ -3323,14 +3327,14 @@ var PendingApprovals = class {
|
|
|
3323
3327
|
ask(request) {
|
|
3324
3328
|
const id = `ap-${Date.now()}-${counter++}`;
|
|
3325
3329
|
const pending = { id, request, createdAt: (/* @__PURE__ */ new Date()).toISOString() };
|
|
3326
|
-
return new Promise((
|
|
3330
|
+
return new Promise((resolve7) => {
|
|
3327
3331
|
const settle = (granted) => {
|
|
3328
3332
|
const entry = this.entries.get(id);
|
|
3329
3333
|
if (!entry) return;
|
|
3330
3334
|
clearTimeout(entry.timer);
|
|
3331
3335
|
this.entries.delete(id);
|
|
3332
3336
|
this.opts.onSettled?.(id, granted);
|
|
3333
|
-
|
|
3337
|
+
resolve7(granted);
|
|
3334
3338
|
};
|
|
3335
3339
|
const timer = setTimeout(() => settle(false), this.opts.timeoutMs ?? DEFAULT_TIMEOUT_MS);
|
|
3336
3340
|
timer.unref?.();
|
|
@@ -3428,9 +3432,9 @@ function applyHeadLimit(stdout, headLimit) {
|
|
|
3428
3432
|
}
|
|
3429
3433
|
function formatSearchOutput(capped, root, opts) {
|
|
3430
3434
|
if (capped.rows.length === 0) return opts.emptyMessage;
|
|
3431
|
-
const
|
|
3432
|
-
const prefix = root ===
|
|
3433
|
-
const body = prefix ===
|
|
3435
|
+
const sep2 = opts.sep ?? import_node_path.default.sep;
|
|
3436
|
+
const prefix = root === sep2 || root.endsWith(sep2) ? root : `${root}${sep2}`;
|
|
3437
|
+
const body = prefix === sep2 ? capped.rows.join("\n") : capped.rows.map((row) => row.startsWith(prefix) ? row.slice(prefix.length) : row).join("\n");
|
|
3434
3438
|
if (!capped.truncated) return body;
|
|
3435
3439
|
const hint = opts.hint ? ` ${opts.hint}` : "";
|
|
3436
3440
|
return `${body}
|
|
@@ -4021,6 +4025,15 @@ function configuredProviders(config) {
|
|
|
4021
4025
|
return out;
|
|
4022
4026
|
}
|
|
4023
4027
|
|
|
4028
|
+
// src/utils/maxParallel.ts
|
|
4029
|
+
var DEFAULT_MAX_PARALLEL = 3;
|
|
4030
|
+
function parseMaxParallel(value) {
|
|
4031
|
+
const text = String(value ?? "").trim();
|
|
4032
|
+
const n = typeof value === "number" ? value : Number(text);
|
|
4033
|
+
if (text === "" || !Number.isInteger(n) || n < 1) return null;
|
|
4034
|
+
return n;
|
|
4035
|
+
}
|
|
4036
|
+
|
|
4024
4037
|
// src/interfaces/BaseConfig.ts
|
|
4025
4038
|
function normalizeGeminiModel(id) {
|
|
4026
4039
|
return id.replace(/^gemini:/, "").replace(/^google\//, "");
|
|
@@ -4028,6 +4041,10 @@ function normalizeGeminiModel(id) {
|
|
|
4028
4041
|
function commaList(raw) {
|
|
4029
4042
|
return (raw || "").split(",").map((s) => s.trim()).filter(Boolean);
|
|
4030
4043
|
}
|
|
4044
|
+
function nonNegativeInt(raw, fallback) {
|
|
4045
|
+
const trimmed = (raw ?? "").trim();
|
|
4046
|
+
return /^\d+$/.test(trimmed) ? Number(trimmed) : fallback;
|
|
4047
|
+
}
|
|
4031
4048
|
var BaseConfig = class {
|
|
4032
4049
|
// --- Legacy named getters (backward compat) ---
|
|
4033
4050
|
get openAiBaseUrl() {
|
|
@@ -4221,7 +4238,7 @@ var BaseConfig = class {
|
|
|
4221
4238
|
return "";
|
|
4222
4239
|
}
|
|
4223
4240
|
get maxParallelSessions() {
|
|
4224
|
-
return
|
|
4241
|
+
return parseMaxParallel(process.env.ORDEWELL_MAX_PARALLEL) ?? DEFAULT_MAX_PARALLEL;
|
|
4225
4242
|
}
|
|
4226
4243
|
get researchEnabled() {
|
|
4227
4244
|
return process.env.ORDEWELL_RESEARCH_ENABLED !== "false";
|
|
@@ -4250,6 +4267,9 @@ var BaseConfig = class {
|
|
|
4250
4267
|
get worktreeLinks() {
|
|
4251
4268
|
return commaList(process.env.ORDEWELL_WORKTREE_LINKS);
|
|
4252
4269
|
}
|
|
4270
|
+
get conflictRepairAttempts() {
|
|
4271
|
+
return nonNegativeInt(process.env.ORDEWELL_CONFLICT_REPAIR_ATTEMPTS, 2);
|
|
4272
|
+
}
|
|
4253
4273
|
get approvalMode() {
|
|
4254
4274
|
const raw = (process.env.ORDEWELL_APPROVAL_MODE || "").trim().toLowerCase();
|
|
4255
4275
|
return raw === "allow" || raw === "deny" ? raw : "ask";
|
|
@@ -4309,21 +4329,33 @@ var ISOLATION_STATE = {
|
|
|
4309
4329
|
active: "active",
|
|
4310
4330
|
merged: "integrated",
|
|
4311
4331
|
conflict: "conflict",
|
|
4332
|
+
repairing: "repairing",
|
|
4312
4333
|
kept: "kept",
|
|
4313
4334
|
failed: "kept"
|
|
4314
4335
|
};
|
|
4315
|
-
function taskIsolationOf(record) {
|
|
4336
|
+
function taskIsolationOf(record, repairLimit) {
|
|
4316
4337
|
return {
|
|
4317
4338
|
state: ISOLATION_STATE[record.status],
|
|
4318
4339
|
branch: record.branch,
|
|
4319
4340
|
worktree: record.workspace,
|
|
4320
4341
|
repos: Object.entries(record.repos).filter(([, r]) => r.changed).map(([repoPath]) => repoPath),
|
|
4321
|
-
...record.conflictRepo ? { conflictRepo: record.conflictRepo } : {}
|
|
4342
|
+
...record.conflictRepo ? { conflictRepo: record.conflictRepo } : {},
|
|
4343
|
+
...record.conflictFiles?.length ? { conflictFiles: record.conflictFiles } : {},
|
|
4344
|
+
...record.repairs ? { repair: { attempt: record.repairs, limit: repairLimit } } : {},
|
|
4345
|
+
...record.repairedFiles?.length ? { repairedFiles: record.repairedFiles } : {}
|
|
4322
4346
|
};
|
|
4323
4347
|
}
|
|
4348
|
+
function capConflictFiles(files, max = 5) {
|
|
4349
|
+
return files.length <= max ? files.join(", ") : `${files.slice(0, max).join(", ")}, +${files.length - max} more`;
|
|
4350
|
+
}
|
|
4324
4351
|
function handoffOf(run) {
|
|
4325
4352
|
const merged = Object.values(run.tasks).filter((r) => r.status === "merged").sort((a, b) => a.order - b.order);
|
|
4326
|
-
const entry = (r) => ({
|
|
4353
|
+
const entry = (r) => ({
|
|
4354
|
+
taskId: r.taskId,
|
|
4355
|
+
order: r.order,
|
|
4356
|
+
title: r.title,
|
|
4357
|
+
...r.repairedFiles?.length ? { repairedFiles: r.repairedFiles } : {}
|
|
4358
|
+
});
|
|
4327
4359
|
return {
|
|
4328
4360
|
repos: run.repos.map((repo) => ({
|
|
4329
4361
|
path: repo.path,
|
|
@@ -4387,13 +4419,17 @@ function landedNotice(landed) {
|
|
|
4387
4419
|
if (landed.length === 0) return "Nothing was merged.";
|
|
4388
4420
|
return landed.length === 1 ? `${landed[0]} was merged already and stays merged.` : `${landed.join(", ")} were merged already and stay merged.`;
|
|
4389
4421
|
}
|
|
4390
|
-
function
|
|
4422
|
+
function repairedNotice(repaired) {
|
|
4423
|
+
const named = repaired.map((t) => `${t.title} (${capConflictFiles(t.repairedFiles ?? [])})`);
|
|
4424
|
+
const list = named.length === 1 ? named[0] : `${named.slice(0, -1).join(", ")} and ${named[named.length - 1]}`;
|
|
4425
|
+
return `${list} landed through ${repaired.length === 1 ? "a conflict repair" : "conflict repairs"}.`;
|
|
4426
|
+
}
|
|
4427
|
+
function describeMergeResult(result, branch, group, repaired = []) {
|
|
4391
4428
|
switch (result.outcome) {
|
|
4392
|
-
case "merged":
|
|
4393
|
-
|
|
4394
|
-
|
|
4395
|
-
|
|
4396
|
-
};
|
|
4429
|
+
case "merged": {
|
|
4430
|
+
const base = group ? `Merged ${branch} into the checked-out branch of every repository.` : `Merged ${branch} into your checked-out branch.`;
|
|
4431
|
+
return { level: "info", message: repaired.length > 0 ? `${base} ${repairedNotice(repaired)}` : base };
|
|
4432
|
+
}
|
|
4397
4433
|
case "blocked":
|
|
4398
4434
|
return { level: "warn", message: `Merged nothing, so every tree is as it was: ${result.blocked.map(blockNotice).join("; ")}.` };
|
|
4399
4435
|
case "conflict":
|
|
@@ -4414,7 +4450,7 @@ var import_child_process2 = require("child_process");
|
|
|
4414
4450
|
var import_util2 = require("util");
|
|
4415
4451
|
var import_crypto = require("crypto");
|
|
4416
4452
|
var fs4 = __toESM(require("fs"));
|
|
4417
|
-
var
|
|
4453
|
+
var path9 = __toESM(require("path"));
|
|
4418
4454
|
|
|
4419
4455
|
// src/utils/shellPath.ts
|
|
4420
4456
|
var import_child_process = require("child_process");
|
|
@@ -4443,28 +4479,28 @@ function posixBinDirs() {
|
|
|
4443
4479
|
];
|
|
4444
4480
|
}
|
|
4445
4481
|
function windowsBinDirs(env, home) {
|
|
4446
|
-
const
|
|
4447
|
-
const under = (base, ...parts) => base ? [
|
|
4482
|
+
const join24 = (...parts) => path5.win32.join(...parts);
|
|
4483
|
+
const under = (base, ...parts) => base ? [join24(base, ...parts)] : [];
|
|
4448
4484
|
const appData = env.APPDATA;
|
|
4449
4485
|
const localAppData = env.LOCALAPPDATA;
|
|
4450
4486
|
const programData = env.ProgramData;
|
|
4451
4487
|
return [
|
|
4452
4488
|
// Native per-user installers, PowerShell-driven and otherwise.
|
|
4453
|
-
|
|
4454
|
-
|
|
4455
|
-
|
|
4456
|
-
|
|
4489
|
+
join24(home, ".local", "bin"),
|
|
4490
|
+
join24(home, ".opencode", "bin"),
|
|
4491
|
+
join24(home, ".claude", "bin"),
|
|
4492
|
+
join24(home, ".codex", "bin"),
|
|
4457
4493
|
...under(localAppData, "Programs", "claude"),
|
|
4458
4494
|
// Node package managers. npm's global prefix is `%APPDATA%\npm`; the
|
|
4459
4495
|
// literal spelling is kept as a fallback for a host that does not export
|
|
4460
4496
|
// APPDATA (a service account, a stripped CI container).
|
|
4461
4497
|
...under(appData, "npm"),
|
|
4462
|
-
|
|
4498
|
+
join24(home, "AppData", "Roaming", "npm"),
|
|
4463
4499
|
...under(localAppData, "pnpm"),
|
|
4464
4500
|
...under(localAppData, "Yarn", "bin"),
|
|
4465
|
-
|
|
4501
|
+
join24(home, ".bun", "bin"),
|
|
4466
4502
|
// Windows package managers.
|
|
4467
|
-
|
|
4503
|
+
join24(home, "scoop", "shims"),
|
|
4468
4504
|
...under(env.SCOOP, "shims"),
|
|
4469
4505
|
...under(env.SCOOP_GLOBAL ?? programData, "scoop", "shims"),
|
|
4470
4506
|
...under(programData, "chocolatey", "bin"),
|
|
@@ -4474,7 +4510,7 @@ function windowsBinDirs(env, home) {
|
|
|
4474
4510
|
...under(localAppData, "Microsoft", "WindowsApps"),
|
|
4475
4511
|
// Version managers. Volta's Windows home is not the POSIX `~/.volta`.
|
|
4476
4512
|
...under(localAppData, "Volta", "bin"),
|
|
4477
|
-
|
|
4513
|
+
join24(home, ".volta", "bin")
|
|
4478
4514
|
];
|
|
4479
4515
|
}
|
|
4480
4516
|
function wellKnownBinDirs(deps = {}) {
|
|
@@ -4578,6 +4614,7 @@ function savePrdMarkdown(workspace, slug2, markdown) {
|
|
|
4578
4614
|
|
|
4579
4615
|
// src/services/worktreeLink.ts
|
|
4580
4616
|
var fs3 = __toESM(require("fs"));
|
|
4617
|
+
var path8 = __toESM(require("path"));
|
|
4581
4618
|
function linkPath(source, target, platform, hardLink = fs3.linkSync) {
|
|
4582
4619
|
const isDir = fs3.statSync(source).isDirectory();
|
|
4583
4620
|
if (platform !== "win32") {
|
|
@@ -4596,6 +4633,55 @@ function linkPath(source, target, platform, hardLink = fs3.linkSync) {
|
|
|
4596
4633
|
return "copy";
|
|
4597
4634
|
}
|
|
4598
4635
|
}
|
|
4636
|
+
function mirrorDir(source, target, opts) {
|
|
4637
|
+
const copied = [];
|
|
4638
|
+
const links = [];
|
|
4639
|
+
const mirror = (from, to, prefix, top) => {
|
|
4640
|
+
fs3.mkdirSync(to, { recursive: true });
|
|
4641
|
+
for (const entry of fs3.readdirSync(from, { withFileTypes: true })) {
|
|
4642
|
+
const name = path8.join(prefix, entry.name);
|
|
4643
|
+
const at = { from: path8.join(from, entry.name), to: path8.join(to, entry.name), name };
|
|
4644
|
+
if (entry.isSymbolicLink()) links.push(at);
|
|
4645
|
+
else if (top && entry.isDirectory() && (entry.name.startsWith("@") || entry.name === ".bin")) mirror(at.from, at.to, name, false);
|
|
4646
|
+
else if (linkPath(at.from, at.to, opts.platform, opts.hardLink) === "copy") copied.push(name);
|
|
4647
|
+
}
|
|
4648
|
+
};
|
|
4649
|
+
mirror(source, target, "", true);
|
|
4650
|
+
let pending = links;
|
|
4651
|
+
for (let before = Infinity; pending.length > 0 && pending.length < before; ) {
|
|
4652
|
+
before = pending.length;
|
|
4653
|
+
pending = pending.filter((link) => {
|
|
4654
|
+
const kind = relink(link.from, link.to, opts);
|
|
4655
|
+
if (kind === "copy") copied.push(link.name);
|
|
4656
|
+
return kind === null;
|
|
4657
|
+
});
|
|
4658
|
+
}
|
|
4659
|
+
return copied;
|
|
4660
|
+
}
|
|
4661
|
+
function relink(from, to, opts) {
|
|
4662
|
+
const text = fs3.readlinkSync(from);
|
|
4663
|
+
const pointee = path8.isAbsolute(text) ? rebased(text, opts) : text;
|
|
4664
|
+
if (opts.platform !== "win32") {
|
|
4665
|
+
if (!fs3.existsSync(path8.resolve(path8.dirname(to), pointee)) && fs3.existsSync(path8.resolve(path8.dirname(from), pointee))) {
|
|
4666
|
+
fs3.symlinkSync(rebased(path8.resolve(path8.dirname(from), pointee), opts), to);
|
|
4667
|
+
return "symlink";
|
|
4668
|
+
}
|
|
4669
|
+
fs3.symlinkSync(pointee, to);
|
|
4670
|
+
return "symlink";
|
|
4671
|
+
}
|
|
4672
|
+
const resolved = fs3.existsSync(path8.resolve(path8.dirname(to), pointee)) ? path8.resolve(path8.dirname(to), pointee) : rebased(path8.resolve(path8.dirname(from), pointee), opts);
|
|
4673
|
+
try {
|
|
4674
|
+
fs3.statSync(resolved);
|
|
4675
|
+
} catch {
|
|
4676
|
+
return null;
|
|
4677
|
+
}
|
|
4678
|
+
return linkPath(resolved, to, opts.platform, opts.hardLink);
|
|
4679
|
+
}
|
|
4680
|
+
function rebased(pointee, opts) {
|
|
4681
|
+
const rel = path8.relative(opts.from, pointee);
|
|
4682
|
+
const inside = rel !== "" && !rel.startsWith("..") && !path8.isAbsolute(rel);
|
|
4683
|
+
return inside && !rel.split(/[\\/]/).includes("node_modules") ? path8.join(opts.to, rel) : pointee;
|
|
4684
|
+
}
|
|
4599
4685
|
|
|
4600
4686
|
// src/services/GitWorktreeIsolation.ts
|
|
4601
4687
|
var execFileAsync = (0, import_util2.promisify)(import_child_process2.execFile);
|
|
@@ -4610,15 +4696,17 @@ var INHERITED_GIT_VARS = ["GIT_DIR", "GIT_WORK_TREE", "GIT_INDEX_FILE", "GIT_COM
|
|
|
4610
4696
|
var LINKED_ARTIFACTS = /* @__PURE__ */ new Set(["node_modules", "vendor", ".venv", ".claude", ".opencode", ".codegraph", ".envrc"]);
|
|
4611
4697
|
var isEnvFile = (name) => name.startsWith(".env");
|
|
4612
4698
|
var INTEGRATION_DIR = "integration";
|
|
4699
|
+
var RUN_BRANCH = /^ordewell\/([^/]+)\/[^/]+$/;
|
|
4700
|
+
var RUN_WORKTREE = /.*[\\/]\.ordewell[\\/]worktrees[\\/]([^\\/]+)[\\/]/;
|
|
4613
4701
|
var NESTED_REPO_DEPTH = 2;
|
|
4614
4702
|
var NEVER_SCANNED = /* @__PURE__ */ new Set([".git", STATE_DIR, "node_modules"]);
|
|
4615
4703
|
var GITLINK_MODE = "160000";
|
|
4616
4704
|
function isInside(parent, child) {
|
|
4617
|
-
const rel =
|
|
4618
|
-
return rel !== "" && !rel.startsWith("..") && !
|
|
4705
|
+
const rel = path9.relative(parent, child);
|
|
4706
|
+
return rel !== "" && !rel.startsWith("..") && !path9.isAbsolute(rel);
|
|
4619
4707
|
}
|
|
4620
4708
|
function hasGitEntry(dir) {
|
|
4621
|
-
return fs4.existsSync(
|
|
4709
|
+
return fs4.existsSync(path9.join(dir, ".git"));
|
|
4622
4710
|
}
|
|
4623
4711
|
function firstLine(text) {
|
|
4624
4712
|
return text.split(/\r?\n/, 1)[0].trim();
|
|
@@ -4634,12 +4722,20 @@ function statusPaths(porcelain) {
|
|
|
4634
4722
|
}
|
|
4635
4723
|
return paths;
|
|
4636
4724
|
}
|
|
4725
|
+
function leftoverMarkerFiles(check) {
|
|
4726
|
+
const files = check.split(/\r?\n/).flatMap((line) => /^(.+):\d+: leftover conflict marker$/.exec(line)?.[1] ?? []);
|
|
4727
|
+
return [...new Set(files)];
|
|
4728
|
+
}
|
|
4729
|
+
function settleStatus(record, status) {
|
|
4730
|
+
record.status = status;
|
|
4731
|
+
delete record.repairBase;
|
|
4732
|
+
}
|
|
4637
4733
|
function sameDir(a, b) {
|
|
4638
4734
|
const real = (p) => {
|
|
4639
4735
|
try {
|
|
4640
4736
|
return fs4.realpathSync.native(p);
|
|
4641
4737
|
} catch {
|
|
4642
|
-
return
|
|
4738
|
+
return path9.resolve(p);
|
|
4643
4739
|
}
|
|
4644
4740
|
};
|
|
4645
4741
|
return real(a) === real(b);
|
|
@@ -4673,8 +4769,8 @@ function inactive(reason, repos) {
|
|
|
4673
4769
|
}
|
|
4674
4770
|
function groupPathOf(listed) {
|
|
4675
4771
|
const slashed = listed.trim().replace(/\\/g, "/");
|
|
4676
|
-
if (
|
|
4677
|
-
const rel =
|
|
4772
|
+
if (path9.posix.isAbsolute(slashed) || path9.win32.isAbsolute(slashed)) return null;
|
|
4773
|
+
const rel = path9.posix.normalize(slashed).replace(/\/+$/, "");
|
|
4678
4774
|
return rel === "" || rel === "." || rel === ".." || rel.startsWith("../") ? null : rel;
|
|
4679
4775
|
}
|
|
4680
4776
|
function matchLinks(root, patterns) {
|
|
@@ -4690,9 +4786,26 @@ function matchLinks(root, patterns) {
|
|
|
4690
4786
|
}
|
|
4691
4787
|
function segmentMatches(root, base, segment) {
|
|
4692
4788
|
const under = (name) => base ? `${base}/${name}` : name;
|
|
4693
|
-
if (!/[*?]/.test(segment)) return lexists(
|
|
4789
|
+
if (!/[*?]/.test(segment)) return lexists(path9.join(root, base, segment)) ? [under(segment)] : [];
|
|
4694
4790
|
const pattern = new RegExp(`^${segment.replace(/[.+^${}()|[\]\\]/g, "\\$&").replace(/\*/g, ".*").replace(/\?/g, ".")}$`);
|
|
4695
|
-
return listDir(
|
|
4791
|
+
return listDir(path9.join(root, base)).filter((name) => !NEVER_SCANNED.has(name) && pattern.test(name)).map(under);
|
|
4792
|
+
}
|
|
4793
|
+
function installDirs(root) {
|
|
4794
|
+
const packages = matchLinks(root, workspaceGlobs(root)).map((pkg) => `${pkg}/node_modules`);
|
|
4795
|
+
return ["node_modules", ...packages].filter((dir) => lexists(path9.join(root, dir)));
|
|
4796
|
+
}
|
|
4797
|
+
function workspaceGlobs(root) {
|
|
4798
|
+
let manifest;
|
|
4799
|
+
try {
|
|
4800
|
+
manifest = JSON.parse(fs4.readFileSync(path9.join(root, "package.json"), "utf8"));
|
|
4801
|
+
} catch {
|
|
4802
|
+
return [];
|
|
4803
|
+
}
|
|
4804
|
+
if (typeof manifest !== "object" || manifest === null || !("workspaces" in manifest)) return [];
|
|
4805
|
+
const { workspaces } = manifest;
|
|
4806
|
+
const listed = typeof workspaces === "object" && workspaces !== null && "packages" in workspaces ? workspaces.packages : workspaces;
|
|
4807
|
+
if (!Array.isArray(listed)) return [];
|
|
4808
|
+
return listed.filter((glob) => typeof glob === "string" && !glob.startsWith("!"));
|
|
4696
4809
|
}
|
|
4697
4810
|
var GitWorktreeIsolation = class {
|
|
4698
4811
|
constructor(deps) {
|
|
@@ -4746,7 +4859,7 @@ var GitWorktreeIsolation = class {
|
|
|
4746
4859
|
groupPaths(workspaceRoot) {
|
|
4747
4860
|
const listed = this.deps.config.workspaceRepos;
|
|
4748
4861
|
if (listed.length === 0) return this.reposDirectlyIn(workspaceRoot);
|
|
4749
|
-
const paths = listed.map(groupPathOf).filter((rel) => rel !== null && hasGitEntry(
|
|
4862
|
+
const paths = listed.map(groupPathOf).filter((rel) => rel !== null && hasGitEntry(path9.join(workspaceRoot, rel)));
|
|
4750
4863
|
return [...new Set(paths)].sort();
|
|
4751
4864
|
}
|
|
4752
4865
|
async committedRepos(workspaceRoot, paths) {
|
|
@@ -4762,7 +4875,7 @@ var GitWorktreeIsolation = class {
|
|
|
4762
4875
|
return status.ok ? status.stdout.trim() !== "" : null;
|
|
4763
4876
|
}
|
|
4764
4877
|
reposDirectlyIn(dir) {
|
|
4765
|
-
return this.childDirs(dir).filter((name) => hasGitEntry(
|
|
4878
|
+
return this.childDirs(dir).filter((name) => hasGitEntry(path9.join(dir, name)));
|
|
4766
4879
|
}
|
|
4767
4880
|
childDirs(dir) {
|
|
4768
4881
|
try {
|
|
@@ -4779,9 +4892,9 @@ var GitWorktreeIsolation = class {
|
|
|
4779
4892
|
async nestedRepos(workspaceRoot, toplevel) {
|
|
4780
4893
|
const found = [];
|
|
4781
4894
|
const walk = (rel, depth) => {
|
|
4782
|
-
for (const name of this.childDirs(
|
|
4895
|
+
for (const name of this.childDirs(path9.join(workspaceRoot, rel))) {
|
|
4783
4896
|
const child = rel ? `${rel}/${name}` : name;
|
|
4784
|
-
if (hasGitEntry(
|
|
4897
|
+
if (hasGitEntry(path9.join(workspaceRoot, child))) found.push(child);
|
|
4785
4898
|
else if (depth < NESTED_REPO_DEPTH) walk(child, depth + 1);
|
|
4786
4899
|
}
|
|
4787
4900
|
};
|
|
@@ -4802,7 +4915,7 @@ var GitWorktreeIsolation = class {
|
|
|
4802
4915
|
const [meta, file] = entry.split(" ");
|
|
4803
4916
|
if (file !== void 0 && meta.startsWith(`${GITLINK_MODE} `)) paths.add(file);
|
|
4804
4917
|
}
|
|
4805
|
-
const declared = await this.tryGit(workspaceRoot, ["config", "-z", "-f",
|
|
4918
|
+
const declared = await this.tryGit(workspaceRoot, ["config", "-z", "-f", path9.join(toplevel, ".gitmodules"), "--get-regexp", "^submodule\\..*\\.path$"]);
|
|
4806
4919
|
for (const entry of declared.stdout.split("\0")) {
|
|
4807
4920
|
const value = entry.split("\n")[1];
|
|
4808
4921
|
if (value) paths.add(value.replace(/\/+$/, ""));
|
|
@@ -4858,7 +4971,7 @@ var GitWorktreeIsolation = class {
|
|
|
4858
4971
|
async acceptsWorktree(run, repo) {
|
|
4859
4972
|
const dir = this.integrationDir(run, repo);
|
|
4860
4973
|
ensureStateDirIgnored(run.workspaceRoot);
|
|
4861
|
-
fs4.mkdirSync(
|
|
4974
|
+
fs4.mkdirSync(path9.dirname(dir), { recursive: true });
|
|
4862
4975
|
const added = await this.tryGit(repo.root, ["worktree", "add", "-q", "--no-checkout", dir, repo.integrationBranch]);
|
|
4863
4976
|
await this.removeWorktreeDir(run, repo, dir, []);
|
|
4864
4977
|
return added.ok;
|
|
@@ -4873,12 +4986,12 @@ var GitWorktreeIsolation = class {
|
|
|
4873
4986
|
if (isolated.includes(SELF_REPO)) return [];
|
|
4874
4987
|
const shared = [];
|
|
4875
4988
|
const walk = (rel) => {
|
|
4876
|
-
for (const name of listDir(
|
|
4989
|
+
for (const name of listDir(path9.join(workspaceRoot, rel))) {
|
|
4877
4990
|
if (!rel && (name === STATE_DIR || name === ".git")) continue;
|
|
4878
4991
|
const child = rel ? `${rel}/${name}` : name;
|
|
4879
4992
|
if (isolated.includes(child)) continue;
|
|
4880
4993
|
if (isolated.some((p) => p.startsWith(`${child}/`))) walk(child);
|
|
4881
|
-
else if (resolves(
|
|
4994
|
+
else if (resolves(path9.join(workspaceRoot, child))) shared.push(child);
|
|
4882
4995
|
}
|
|
4883
4996
|
};
|
|
4884
4997
|
walk("");
|
|
@@ -4895,17 +5008,17 @@ var GitWorktreeIsolation = class {
|
|
|
4895
5008
|
const copied = [];
|
|
4896
5009
|
try {
|
|
4897
5010
|
for (const repo of run.repos) {
|
|
4898
|
-
const worktree =
|
|
5011
|
+
const worktree = path9.join(dir, repo.path);
|
|
4899
5012
|
const tip = (await this.git(repo.root, ["rev-parse", repo.integrationBranch])).trim();
|
|
4900
|
-
fs4.mkdirSync(
|
|
5013
|
+
fs4.mkdirSync(path9.dirname(worktree), { recursive: true });
|
|
4901
5014
|
await this.git(repo.root, ["worktree", "add", "-q", "-B", branch, worktree, tip]);
|
|
4902
5015
|
record.repos[repo.path] = { worktree, linked: [] };
|
|
4903
5016
|
}
|
|
4904
5017
|
run.shared = this.sharedPaths(run.workspaceRoot, run.repos.map((r) => r.path));
|
|
4905
5018
|
for (const rel of run.shared) {
|
|
4906
|
-
const target =
|
|
4907
|
-
fs4.mkdirSync(
|
|
4908
|
-
if (linkPath(
|
|
5019
|
+
const target = path9.join(dir, rel);
|
|
5020
|
+
fs4.mkdirSync(path9.dirname(target), { recursive: true });
|
|
5021
|
+
if (linkPath(path9.join(run.workspaceRoot, rel), target, this.platform) === "copy") copied.push(rel);
|
|
4909
5022
|
}
|
|
4910
5023
|
for (const repo of run.repos) {
|
|
4911
5024
|
const entry = record.repos[repo.path];
|
|
@@ -4913,7 +5026,7 @@ var GitWorktreeIsolation = class {
|
|
|
4913
5026
|
if (repo.path === SELF_REPO) cwd = inRepo;
|
|
4914
5027
|
const boot = await this.bootstrap(repo, inRepo);
|
|
4915
5028
|
entry.linked = boot.linked;
|
|
4916
|
-
copied.push(...boot.copied.map((name) =>
|
|
5029
|
+
copied.push(...boot.copied.map((name) => path9.posix.join(repo.path, name)));
|
|
4917
5030
|
}
|
|
4918
5031
|
} catch (err) {
|
|
4919
5032
|
record.status = "failed";
|
|
@@ -4924,6 +5037,53 @@ var GitWorktreeIsolation = class {
|
|
|
4924
5037
|
return { cwd, branch, copied };
|
|
4925
5038
|
});
|
|
4926
5039
|
}
|
|
5040
|
+
reopen(task, run) {
|
|
5041
|
+
return this.admin(run.workspaceRoot, async () => {
|
|
5042
|
+
const record = run.tasks[task.id];
|
|
5043
|
+
if (record?.status !== "conflict") throw new Error(`Task ${task.order} has no conflict to repair`);
|
|
5044
|
+
let cwd = record.workspace;
|
|
5045
|
+
const repairBase = {};
|
|
5046
|
+
for (const repo of run.repos) {
|
|
5047
|
+
const entry = record.repos[repo.path];
|
|
5048
|
+
if (!entry) continue;
|
|
5049
|
+
if (!fs4.existsSync(entry.worktree)) throw new Error(`The worktree of task ${task.order} is gone: ${entry.worktree}`);
|
|
5050
|
+
if (repo.path === SELF_REPO) cwd = await this.inWorkspacePlace(repo, entry.worktree);
|
|
5051
|
+
if (entry.changed || repo.path === record.conflictRepo) {
|
|
5052
|
+
repairBase[repo.path] = (await this.git(repo.root, ["rev-parse", "--verify", repo.integrationBranch])).trim();
|
|
5053
|
+
}
|
|
5054
|
+
}
|
|
5055
|
+
const files = (record.conflictFiles ?? []).map((file) => record.conflictRepo && record.conflictRepo !== SELF_REPO ? `${record.conflictRepo}/${file}` : file);
|
|
5056
|
+
record.repairs = (record.repairs ?? 0) + 1;
|
|
5057
|
+
record.repairBase = repairBase;
|
|
5058
|
+
record.repairedFiles = [.../* @__PURE__ */ new Set([...record.repairedFiles ?? [], ...files])];
|
|
5059
|
+
record.status = "repairing";
|
|
5060
|
+
return { cwd, branch: record.branch, copied: [] };
|
|
5061
|
+
});
|
|
5062
|
+
}
|
|
5063
|
+
async verifyRepair(task, run) {
|
|
5064
|
+
const record = run.tasks[task.id];
|
|
5065
|
+
if (!record?.repairBase) return { ok: false, reason: "failed", repo: record?.conflictRepo ?? SELF_REPO };
|
|
5066
|
+
for (const repo of run.repos) {
|
|
5067
|
+
const entry = record.repos[repo.path];
|
|
5068
|
+
if (!entry) continue;
|
|
5069
|
+
try {
|
|
5070
|
+
await this.commitWorktree(repo, record, entry);
|
|
5071
|
+
} catch {
|
|
5072
|
+
return { ok: false, reason: "failed", repo: repo.path };
|
|
5073
|
+
}
|
|
5074
|
+
}
|
|
5075
|
+
for (const repo of run.repos) {
|
|
5076
|
+
const base = record.repairBase[repo.path];
|
|
5077
|
+
if (base === void 0) continue;
|
|
5078
|
+
const contains = await this.tryGit(repo.root, ["merge-base", "--is-ancestor", base, record.branch]);
|
|
5079
|
+
if (!contains.ok) return { ok: false, reason: contains.code === 1 ? "not-merged" : "failed", repo: repo.path };
|
|
5080
|
+
const check = await this.tryGit(repo.root, ["-c", "core.quotePath=false", "diff", "--check", base, record.branch]);
|
|
5081
|
+
if (!check.ok && check.code !== 2) return { ok: false, reason: "failed", repo: repo.path };
|
|
5082
|
+
const files = leftoverMarkerFiles(check.stdout);
|
|
5083
|
+
if (files.length > 0) return { ok: false, reason: "conflict-markers", repo: repo.path, files };
|
|
5084
|
+
}
|
|
5085
|
+
return { ok: true };
|
|
5086
|
+
}
|
|
4927
5087
|
/**
|
|
4928
5088
|
* The place in a repo's worktree that matches where the workspace sits in
|
|
4929
5089
|
* the real repo: the worktree itself, or a subdirectory of it when the
|
|
@@ -4931,7 +5091,7 @@ var GitWorktreeIsolation = class {
|
|
|
4931
5091
|
*/
|
|
4932
5092
|
async inWorkspacePlace(repo, worktree) {
|
|
4933
5093
|
const prefix = await this.prefixOf(repo);
|
|
4934
|
-
return prefix ?
|
|
5094
|
+
return prefix ? path9.join(worktree, prefix) : worktree;
|
|
4935
5095
|
}
|
|
4936
5096
|
async prefixOf(repo) {
|
|
4937
5097
|
return (await this.git(repo.root, ["rev-parse", "--show-prefix"])).trim();
|
|
@@ -4951,6 +5111,7 @@ var GitWorktreeIsolation = class {
|
|
|
4951
5111
|
if (!record) return;
|
|
4952
5112
|
if (opts.keep) {
|
|
4953
5113
|
if (record.status === "active") record.status = "kept";
|
|
5114
|
+
else if (record.status === "repairing") settleStatus(record, "conflict");
|
|
4954
5115
|
return;
|
|
4955
5116
|
}
|
|
4956
5117
|
await this.removeTask(run, record, { dropRecord: true });
|
|
@@ -4970,6 +5131,7 @@ var GitWorktreeIsolation = class {
|
|
|
4970
5131
|
for (const record of Object.values(run.tasks)) {
|
|
4971
5132
|
if (record.status === "active") await this.removeTask(run, record, { dropRecord: true });
|
|
4972
5133
|
else if (record.status === "merged") await this.removeTask(run, record, { dropRecord: false });
|
|
5134
|
+
else if (record.status === "repairing") settleStatus(record, "conflict");
|
|
4973
5135
|
}
|
|
4974
5136
|
await this.removeUnowned(run);
|
|
4975
5137
|
for (const repo of run.repos) await this.tryGit(repo.root, ["worktree", "prune"]);
|
|
@@ -5064,14 +5226,59 @@ var GitWorktreeIsolation = class {
|
|
|
5064
5226
|
await this.removeTask(run, record, { dropRecord: record.status !== "merged" });
|
|
5065
5227
|
}
|
|
5066
5228
|
await this.removeUnowned(run);
|
|
5067
|
-
if (
|
|
5068
|
-
for (const repo of run.repos)
|
|
5229
|
+
if (opts.integration !== "keep") {
|
|
5230
|
+
for (const repo of run.repos) {
|
|
5231
|
+
if (opts.integration === "delete") await this.tryGit(repo.root, ["branch", "-D", repo.integrationBranch]);
|
|
5232
|
+
else await this.deleteIfMerged(repo.root, repo.integrationBranch);
|
|
5233
|
+
}
|
|
5069
5234
|
run.tasks = {};
|
|
5070
5235
|
this.removeIfEmpty(this.runRoot(run));
|
|
5071
5236
|
}
|
|
5072
5237
|
for (const repo of run.repos) await this.tryGit(repo.root, ["worktree", "prune"]);
|
|
5073
5238
|
});
|
|
5074
5239
|
}
|
|
5240
|
+
sweep(run) {
|
|
5241
|
+
return this.admin(run.workspaceRoot, async () => {
|
|
5242
|
+
const failed = [];
|
|
5243
|
+
for (const repo of run.repos) {
|
|
5244
|
+
if (!await this.sweepRepo(run, repo)) failed.push(repo.path);
|
|
5245
|
+
}
|
|
5246
|
+
if (failed.length > 0) throw new Error(`git failed clearing merged branches of earlier runs in ${failed.join(", ")}`);
|
|
5247
|
+
});
|
|
5248
|
+
}
|
|
5249
|
+
/** False when git failed anywhere; the other branches are still tried. */
|
|
5250
|
+
async sweepRepo(run, repo) {
|
|
5251
|
+
await this.tryGit(repo.root, ["worktree", "prune"]);
|
|
5252
|
+
const worktrees = await this.tryGit(repo.root, ["worktree", "list", "--porcelain"]);
|
|
5253
|
+
const refs = await this.tryGit(repo.root, ["for-each-ref", "--format=%(refname)", "refs/heads/ordewell/"]);
|
|
5254
|
+
if (!worktrees.ok || !refs.ok) return false;
|
|
5255
|
+
const checkedOut = /* @__PURE__ */ new Set();
|
|
5256
|
+
const liveRuns = /* @__PURE__ */ new Set();
|
|
5257
|
+
for (const line of worktrees.stdout.split(/\r?\n/)) {
|
|
5258
|
+
if (line.startsWith("branch refs/heads/")) checkedOut.add(line.slice("branch refs/heads/".length));
|
|
5259
|
+
const runId = line.startsWith("worktree ") ? RUN_WORKTREE.exec(line)?.[1] : void 0;
|
|
5260
|
+
if (runId) liveRuns.add(runId);
|
|
5261
|
+
}
|
|
5262
|
+
let ok = true;
|
|
5263
|
+
for (const ref of refs.stdout.split(/\r?\n/).filter(Boolean)) {
|
|
5264
|
+
const branch = ref.slice("refs/heads/".length);
|
|
5265
|
+
const runId = RUN_BRANCH.exec(branch)?.[1];
|
|
5266
|
+
if (!runId || runId === run.id || liveRuns.has(runId) || checkedOut.has(branch)) continue;
|
|
5267
|
+
if (await this.deleteIfMerged(repo.root, branch) === "failed") ok = false;
|
|
5268
|
+
}
|
|
5269
|
+
return ok;
|
|
5270
|
+
}
|
|
5271
|
+
/**
|
|
5272
|
+
* Delete `branch` only if the repo's checked-out HEAD already contains it —
|
|
5273
|
+
* HEAD, not an upstream: what the user has checked out is what they merged
|
|
5274
|
+
* into. `-d` rather than `-D`, so git also refuses a branch a worktree has
|
|
5275
|
+
* checked out.
|
|
5276
|
+
*/
|
|
5277
|
+
async deleteIfMerged(root, branch) {
|
|
5278
|
+
const merged = await this.tryGit(root, ["merge-base", "--is-ancestor", branch, "HEAD"]);
|
|
5279
|
+
if (!merged.ok) return merged.code === 1 ? "kept" : "failed";
|
|
5280
|
+
return (await this.tryGit(root, ["branch", "-d", branch])).ok ? "deleted" : "failed";
|
|
5281
|
+
}
|
|
5075
5282
|
async drain() {
|
|
5076
5283
|
try {
|
|
5077
5284
|
while (this.waiting.length > 0) {
|
|
@@ -5124,21 +5331,24 @@ var GitWorktreeIsolation = class {
|
|
|
5124
5331
|
return this.stopLanding(record, "failed");
|
|
5125
5332
|
}
|
|
5126
5333
|
for (const repo of changed) {
|
|
5127
|
-
const outcome = await this.mergeTask(run, repo, record);
|
|
5334
|
+
const { outcome, files } = await this.mergeTask(run, repo, record);
|
|
5128
5335
|
if (outcome === "merged") continue;
|
|
5129
|
-
return (await this.settleLanding(run)).length === 0 ? this.stopLanding(record, outcome, repo) : this.stopLanding(record, "failed", repo);
|
|
5336
|
+
return (await this.settleLanding(run)).length === 0 ? this.stopLanding(record, outcome, repo, files) : this.stopLanding(record, "failed", repo);
|
|
5130
5337
|
}
|
|
5131
5338
|
}
|
|
5132
5339
|
delete run.landing;
|
|
5133
|
-
record
|
|
5340
|
+
settleStatus(record, "merged");
|
|
5134
5341
|
delete record.conflictRepo;
|
|
5342
|
+
delete record.conflictFiles;
|
|
5135
5343
|
await this.admin(run.workspaceRoot, () => this.removeTask(run, record, { dropRecord: false })).catch(() => void 0);
|
|
5136
5344
|
return "merged";
|
|
5137
5345
|
}
|
|
5138
|
-
stopLanding(record, outcome, repo) {
|
|
5139
|
-
record
|
|
5346
|
+
stopLanding(record, outcome, repo, files) {
|
|
5347
|
+
settleStatus(record, outcome);
|
|
5140
5348
|
if (repo) record.conflictRepo = repo.path;
|
|
5141
5349
|
else delete record.conflictRepo;
|
|
5350
|
+
if (files && files.length > 0) record.conflictFiles = files;
|
|
5351
|
+
else delete record.conflictFiles;
|
|
5142
5352
|
return outcome;
|
|
5143
5353
|
}
|
|
5144
5354
|
/** Merge the task branch into one repo's integration branch. A merge that does not complete is aborted: it is Ordewell's own. */
|
|
@@ -5147,13 +5357,13 @@ var GitWorktreeIsolation = class {
|
|
|
5147
5357
|
try {
|
|
5148
5358
|
dir = await this.ensureIntegrationWorktree(run, repo);
|
|
5149
5359
|
} catch {
|
|
5150
|
-
return "failed";
|
|
5360
|
+
return { outcome: "failed", files: [] };
|
|
5151
5361
|
}
|
|
5152
5362
|
const merge = await this.tryGit(dir, ["merge", "--no-ff", "--no-edit", "-m", `Merge task ${record.order}: ${firstLine(record.title)}`, record.branch]);
|
|
5153
|
-
if (merge.ok) return "merged";
|
|
5154
|
-
const
|
|
5363
|
+
if (merge.ok) return { outcome: "merged", files: [] };
|
|
5364
|
+
const files = await this.unmergedPaths(dir);
|
|
5155
5365
|
if (await this.mergeInProgress(dir)) await this.abortMerge(dir);
|
|
5156
|
-
return
|
|
5366
|
+
return { outcome: files.length > 0 ? "conflict" : "failed", files };
|
|
5157
5367
|
}
|
|
5158
5368
|
/**
|
|
5159
5369
|
* Return every repo of the landing in flight to its recorded tip, then
|
|
@@ -5221,15 +5431,15 @@ var GitWorktreeIsolation = class {
|
|
|
5221
5431
|
await this.git(entry.worktree, ["commit", "-q", "-m", `ordewell: task ${record.order} ${firstLine(record.title)}`]);
|
|
5222
5432
|
}
|
|
5223
5433
|
integrationDir(run, repo) {
|
|
5224
|
-
return
|
|
5434
|
+
return path9.join(this.runRoot(run), INTEGRATION_DIR, repo.path);
|
|
5225
5435
|
}
|
|
5226
5436
|
async ensureIntegrationWorktree(run, repo) {
|
|
5227
5437
|
return this.admin(run.workspaceRoot, async () => {
|
|
5228
5438
|
const dir = this.integrationDir(run, repo);
|
|
5229
|
-
if (fs4.existsSync(
|
|
5439
|
+
if (fs4.existsSync(path9.join(dir, ".git"))) return dir;
|
|
5230
5440
|
await this.tryGit(repo.root, ["worktree", "prune"]);
|
|
5231
5441
|
ensureStateDirIgnored(run.workspaceRoot);
|
|
5232
|
-
fs4.mkdirSync(
|
|
5442
|
+
fs4.mkdirSync(path9.dirname(dir), { recursive: true });
|
|
5233
5443
|
await this.git(repo.root, ["worktree", "add", "-q", dir, repo.integrationBranch]);
|
|
5234
5444
|
return dir;
|
|
5235
5445
|
});
|
|
@@ -5264,20 +5474,20 @@ var GitWorktreeIsolation = class {
|
|
|
5264
5474
|
return;
|
|
5265
5475
|
}
|
|
5266
5476
|
for (const entry of entries) {
|
|
5267
|
-
const target =
|
|
5477
|
+
const target = path9.join(dir, entry.name);
|
|
5268
5478
|
if (entry.isSymbolicLink()) this.unlinkIfLink(target);
|
|
5269
5479
|
else if (entry.isDirectory()) this.unlinkLinksUnder(target);
|
|
5270
5480
|
}
|
|
5271
5481
|
}
|
|
5272
5482
|
/** Task workspaces and branches under this run that no record accounts for. */
|
|
5273
5483
|
async removeUnowned(run) {
|
|
5274
|
-
const owned = new Set(Object.values(run.tasks).map((r) =>
|
|
5484
|
+
const owned = new Set(Object.values(run.tasks).map((r) => path9.resolve(r.workspace)));
|
|
5275
5485
|
const root = this.runRoot(run);
|
|
5276
5486
|
if (fs4.existsSync(root)) {
|
|
5277
5487
|
for (const entry of fs4.readdirSync(root)) {
|
|
5278
|
-
const dir =
|
|
5279
|
-
if (entry === INTEGRATION_DIR || owned.has(
|
|
5280
|
-
for (const repo of run.repos) await this.removeWorktreeDir(run, repo,
|
|
5488
|
+
const dir = path9.join(root, entry);
|
|
5489
|
+
if (entry === INTEGRATION_DIR || owned.has(path9.resolve(dir))) continue;
|
|
5490
|
+
for (const repo of run.repos) await this.removeWorktreeDir(run, repo, path9.join(dir, repo.path), []);
|
|
5281
5491
|
this.removeTaskWorkspace(run, dir);
|
|
5282
5492
|
}
|
|
5283
5493
|
}
|
|
@@ -5291,14 +5501,18 @@ var GitWorktreeIsolation = class {
|
|
|
5291
5501
|
}
|
|
5292
5502
|
}
|
|
5293
5503
|
async removeWorktreeDir(run, repo, dir, linked) {
|
|
5294
|
-
for (const name of linked
|
|
5504
|
+
for (const name of /* @__PURE__ */ new Set([...linked, ...installDirs(dir)])) {
|
|
5505
|
+
const target = path9.join(dir, name);
|
|
5506
|
+
this.unlinkIfLink(target);
|
|
5507
|
+
this.unlinkLinksUnder(target);
|
|
5508
|
+
}
|
|
5295
5509
|
for (const name of fs4.existsSync(dir) ? fs4.readdirSync(dir) : []) {
|
|
5296
|
-
if (LINKED_ARTIFACTS.has(name) || isEnvFile(name)) this.unlinkIfLink(
|
|
5510
|
+
if (LINKED_ARTIFACTS.has(name) || isEnvFile(name)) this.unlinkIfLink(path9.join(dir, name));
|
|
5297
5511
|
}
|
|
5298
5512
|
await this.tryGit(repo.root, ["worktree", "remove", "--force", dir]);
|
|
5299
5513
|
if (fs4.existsSync(dir) && isInside(this.runRoot(run), dir)) fs4.rmSync(dir, { recursive: true, force: true });
|
|
5300
5514
|
await this.tryGit(repo.root, ["worktree", "prune"]);
|
|
5301
|
-
for (let parent =
|
|
5515
|
+
for (let parent = path9.dirname(dir); isInside(this.runRoot(run), parent); parent = path9.dirname(parent)) this.removeIfEmpty(parent);
|
|
5302
5516
|
}
|
|
5303
5517
|
unlinkIfLink(target) {
|
|
5304
5518
|
try {
|
|
@@ -5313,14 +5527,14 @@ var GitWorktreeIsolation = class {
|
|
|
5313
5527
|
}
|
|
5314
5528
|
}
|
|
5315
5529
|
runRoot(run) {
|
|
5316
|
-
return
|
|
5530
|
+
return path9.join(run.workspaceRoot, STATE_DIR, "worktrees", run.id);
|
|
5317
5531
|
}
|
|
5318
5532
|
namesFor(run, task) {
|
|
5319
5533
|
const slug2 = sanitizeSlug(task.title).slice(0, 40).replace(/-+$/, "") || "task";
|
|
5320
5534
|
let name = `${task.order}-${slug2}`;
|
|
5321
5535
|
const clash = Object.values(run.tasks).some((r) => r.taskId !== task.id && r.branch === `ordewell/${run.id}/${name}`);
|
|
5322
5536
|
if (clash) name = `${name}-${sanitizeSlug(task.id) || "x"}`;
|
|
5323
|
-
return { branch: `ordewell/${run.id}/${name}`, dir:
|
|
5537
|
+
return { branch: `ordewell/${run.id}/${name}`, dir: path9.join(this.runRoot(run), name) };
|
|
5324
5538
|
}
|
|
5325
5539
|
/**
|
|
5326
5540
|
* Make one repo's worktree runnable: the default artifacts, unless a setup
|
|
@@ -5333,16 +5547,26 @@ var GitWorktreeIsolation = class {
|
|
|
5333
5547
|
const linked = [];
|
|
5334
5548
|
const copied = [];
|
|
5335
5549
|
const link = (name) => {
|
|
5336
|
-
const target =
|
|
5550
|
+
const target = path9.join(cwd, name);
|
|
5337
5551
|
if (lexists(target)) return;
|
|
5338
|
-
fs4.mkdirSync(
|
|
5339
|
-
if (linkPath(
|
|
5552
|
+
fs4.mkdirSync(path9.dirname(target), { recursive: true });
|
|
5553
|
+
if (linkPath(path9.join(repo.root, name), target, this.platform) === "copy") copied.push(name);
|
|
5554
|
+
linked.push(name);
|
|
5555
|
+
};
|
|
5556
|
+
const mirror = (name) => {
|
|
5557
|
+
const target = path9.join(cwd, name);
|
|
5558
|
+
if (lexists(target) || !fs4.existsSync(path9.dirname(target))) return;
|
|
5559
|
+
const source = path9.join(repo.root, name);
|
|
5560
|
+
if (!fs4.lstatSync(source).isDirectory()) return link(name);
|
|
5561
|
+
const mirrored = mirrorDir(source, target, { platform: this.platform, from: repo.root, to: cwd });
|
|
5562
|
+
copied.push(...mirrored.map((entry) => path9.join(name, entry)));
|
|
5340
5563
|
linked.push(name);
|
|
5341
5564
|
};
|
|
5342
5565
|
if (!setup) {
|
|
5343
5566
|
for (const name of fs4.readdirSync(repo.root)) {
|
|
5344
|
-
if (name !== STATE_DIR && (LINKED_ARTIFACTS.has(name) || isEnvFile(name))) link(name);
|
|
5567
|
+
if (name !== STATE_DIR && name !== "node_modules" && (LINKED_ARTIFACTS.has(name) || isEnvFile(name))) link(name);
|
|
5345
5568
|
}
|
|
5569
|
+
for (const dir of installDirs(repo.root)) mirror(dir);
|
|
5346
5570
|
}
|
|
5347
5571
|
for (const name of matchLinks(repo.root, this.deps.config.worktreeLinks)) link(name);
|
|
5348
5572
|
if (setup) await this.runSetup(setup, repo, cwd);
|
|
@@ -6004,6 +6228,7 @@ function researchPhaseBlock(harnessMode) {
|
|
|
6004
6228
|
}
|
|
6005
6229
|
var ISOLATED_PARALLELISM_RULE = "- Each AI task runs in its own git worktree, and its work is merged in afterwards, so tasks that edit the same files can still run in parallel. Never add a dependency just because two tasks touch the same file \u2014 keep dependencies for genuine logical ordering.";
|
|
6006
6230
|
var OVERLAP_AVOIDANCE_RULE = "- For parallel tasks, specify different target files to avoid merge conflicts.";
|
|
6231
|
+
var SHARED_APPEND_FILE_RULE = "- Tasks that run in parallel must not each append to the same shared file (changelogs, registries, index lists) \u2014 give that edit to the final task, or to one task the others depend on.";
|
|
6007
6232
|
function repoGroupSection(isolated) {
|
|
6008
6233
|
if (!isolated || isolated.repos.length === 1 && isolated.repos[0] === SELF_REPO) return [];
|
|
6009
6234
|
return [
|
|
@@ -6095,6 +6320,7 @@ function buildConversationBody(goal, context, modelsJson, runners, modeGuide, mo
|
|
|
6095
6320
|
"DEPENDENCY & PARALLELISM:",
|
|
6096
6321
|
"- Independent slices should have NO dependencies \u2014 they run in parallel.",
|
|
6097
6322
|
"- Only add dependencies when a slice truly depends on artifacts another slice creates.",
|
|
6323
|
+
SHARED_APPEND_FILE_RULE,
|
|
6098
6324
|
...variant.isolatedExecution ? [ISOLATED_PARALLELISM_RULE] : [],
|
|
6099
6325
|
...repoGroupSection(variant.isolatedExecution),
|
|
6100
6326
|
"",
|
|
@@ -6196,6 +6422,7 @@ function corePlannerPrompt(isolatedExecution) {
|
|
|
6196
6422
|
isolatedExecution ? "- Independent vertical slices should have NO dependencies between them \u2014 they can run in parallel." : "- Independent vertical slices (no shared files/modules) should have NO dependencies between them \u2014 they can run in parallel.",
|
|
6197
6423
|
"- Only add dependencies when the second slice truly depends on artifacts (files, APIs) that the first slice creates.",
|
|
6198
6424
|
"- Prefer parallelism over serial chains. A plan with 3 independent slices running in parallel is better than 3 sequential tasks.",
|
|
6425
|
+
SHARED_APPEND_FILE_RULE,
|
|
6199
6426
|
isolatedExecution ? ISOLATED_PARALLELISM_RULE : "- Slices that touch different areas of the codebase are naturally parallel.",
|
|
6200
6427
|
...repoGroupSection(isolatedExecution),
|
|
6201
6428
|
"",
|
|
@@ -6473,6 +6700,31 @@ ${task.prompt ?? task.description}`;
|
|
|
6473
6700
|
asked
|
|
6474
6701
|
].join("\n");
|
|
6475
6702
|
}
|
|
6703
|
+
function buildConflictRepairPrompt(task, conflict, integrationBranch) {
|
|
6704
|
+
const { branch } = conflict;
|
|
6705
|
+
const files = conflict.conflictFiles ?? [];
|
|
6706
|
+
const asked = `What the task was asked to do:
|
|
6707
|
+
${task.prompt ?? task.description}`;
|
|
6708
|
+
const resolve7 = "resolve every conflict so that both sides' intent survives: keep the work already integrated and add what the task contributed.";
|
|
6709
|
+
const repos = conflict.repos.filter((repo) => repo !== SELF_REPO);
|
|
6710
|
+
if (repos.length === 0) {
|
|
6711
|
+
return [
|
|
6712
|
+
`Task #${task.order} "${task.title}" passed, but merging its branch \`${branch}\` into \`${integrationBranch}\` conflicted${files.length > 0 ? ` in ${files.join(", ")}` : ""}. None of its work has landed yet.`,
|
|
6713
|
+
`This is the task's own worktree, on \`${branch}\`, with its work committed. Run \`git merge --no-edit ${integrationBranch}\` here and ${resolve7} Never drop either side wholesale, and never abort the merge or reset it away.`,
|
|
6714
|
+
"Build and test the result the way this project does, commit the merge, and only then print the completion marker.",
|
|
6715
|
+
"",
|
|
6716
|
+
asked
|
|
6717
|
+
].join("\n");
|
|
6718
|
+
}
|
|
6719
|
+
const stoppedIn = conflict.conflictRepo ?? repos[0];
|
|
6720
|
+
return [
|
|
6721
|
+
`Task #${task.order} "${task.title}" passed, but landing its branch \`${branch}\` on \`${integrationBranch}\` conflicted in ${stoppedIn}${files.length > 0 ? ` (${files.join(", ")})` : ""}. A task lands in every repository it changed or in none, so none of its work has landed yet.`,
|
|
6722
|
+
`This is the task's own workspace, with each repository at its usual path on \`${branch}\` and the task's work committed. In each repository the task changed \u2014 ${repos.join(", ")} \u2014 run \`git merge --no-edit ${integrationBranch}\` inside that repository's directory, and ${resolve7} Never drop either side wholesale, and never abort a merge or reset it away.`,
|
|
6723
|
+
"Build and test the result the way this project does, commit the merge in each repository, and only then print the completion marker.",
|
|
6724
|
+
"",
|
|
6725
|
+
asked
|
|
6726
|
+
].join("\n");
|
|
6727
|
+
}
|
|
6476
6728
|
|
|
6477
6729
|
// src/services/PlanValidator.ts
|
|
6478
6730
|
function parsePlanJson(raw, runners, runnerModes, autonomousDefault = true) {
|
|
@@ -7826,8 +8078,8 @@ function compactResearchResults(resultsText, maxChars = COMPACTION_LIMITS.resear
|
|
|
7826
8078
|
|
|
7827
8079
|
// src/services/ContextCollector.ts
|
|
7828
8080
|
var ORDEWELL_CONTEXT_MAX = 8e3;
|
|
7829
|
-
async function collectResearchContext(
|
|
7830
|
-
const collector = new ContextCollector(
|
|
8081
|
+
async function collectResearchContext(fs15, runners) {
|
|
8082
|
+
const collector = new ContextCollector(fs15);
|
|
7831
8083
|
let contextStr = "";
|
|
7832
8084
|
let firstContext = null;
|
|
7833
8085
|
for (const r of runners) {
|
|
@@ -7854,8 +8106,8 @@ ${firstContext.dirStructure}
|
|
|
7854
8106
|
return contextStr;
|
|
7855
8107
|
}
|
|
7856
8108
|
var ContextCollector = class {
|
|
7857
|
-
constructor(
|
|
7858
|
-
this.fs =
|
|
8109
|
+
constructor(fs15, registry) {
|
|
8110
|
+
this.fs = fs15;
|
|
7859
8111
|
this.registry = registry;
|
|
7860
8112
|
}
|
|
7861
8113
|
fs;
|
|
@@ -7936,10 +8188,10 @@ function summarizeToolCall(tool, argsJson, toolLabel) {
|
|
|
7936
8188
|
const scope = args.include ? ` in ${args.include}` : "";
|
|
7937
8189
|
return `grep ${ellipsize(String(args.pattern), 40)}${scope}${mode}`;
|
|
7938
8190
|
}
|
|
7939
|
-
const
|
|
7940
|
-
if (
|
|
7941
|
-
const
|
|
7942
|
-
return `${display} ${
|
|
8191
|
+
const path26 = args.filePath || args.file_path || args.path;
|
|
8192
|
+
if (path26) {
|
|
8193
|
+
const basename2 = String(path26).split("/").pop() || String(path26);
|
|
8194
|
+
return `${display} ${basename2}`;
|
|
7943
8195
|
}
|
|
7944
8196
|
if (args.pattern) {
|
|
7945
8197
|
return `${display} ${args.pattern}`;
|
|
@@ -7972,22 +8224,22 @@ var ALLOWED_TOOL_NAMES = new Set(subagentToolSpecs().map((t) => t.name));
|
|
|
7972
8224
|
function refused(output) {
|
|
7973
8225
|
return { success: false, output, truncated: false };
|
|
7974
8226
|
}
|
|
7975
|
-
function nonPromptingFs(
|
|
7976
|
-
const withinWorkspace = (p) => resolveWithin(
|
|
8227
|
+
function nonPromptingFs(fs15) {
|
|
8228
|
+
const withinWorkspace = (p) => resolveWithin(fs15.getWorkspaceRoot(), p ?? ".").inside;
|
|
7977
8229
|
const outsideRefusal = (p) => refused(
|
|
7978
8230
|
`Path "${p}" is outside the workspace. Research agents are confined to the workspace root \u2014 note the gap in your digest and let the planner request it directly.`
|
|
7979
8231
|
);
|
|
7980
8232
|
return {
|
|
7981
|
-
getWorkspaceRoot: () =>
|
|
7982
|
-
readFile: (p, opts) => withinWorkspace(p) ?
|
|
8233
|
+
getWorkspaceRoot: () => fs15.getWorkspaceRoot(),
|
|
8234
|
+
readFile: (p, opts) => withinWorkspace(p) ? fs15.readFile(p, opts) : Promise.resolve(outsideRefusal(p)),
|
|
7983
8235
|
readFiles: (paths) => {
|
|
7984
8236
|
const escaping = paths.find((p) => !withinWorkspace(p));
|
|
7985
|
-
return escaping ? Promise.resolve(outsideRefusal(escaping)) :
|
|
8237
|
+
return escaping ? Promise.resolve(outsideRefusal(escaping)) : fs15.readFiles(paths);
|
|
7986
8238
|
},
|
|
7987
|
-
glob: (pattern, opts) => withinWorkspace(opts?.path) ?
|
|
7988
|
-
grep: (pattern, opts) => withinWorkspace(opts?.path) ?
|
|
7989
|
-
findSymbol: (symbol, opts) => withinWorkspace(opts?.path) ?
|
|
7990
|
-
listDir: (p, depth) => withinWorkspace(p) ?
|
|
8239
|
+
glob: (pattern, opts) => withinWorkspace(opts?.path) ? fs15.glob(pattern, opts) : Promise.resolve(outsideRefusal(opts.path)),
|
|
8240
|
+
grep: (pattern, opts) => withinWorkspace(opts?.path) ? fs15.grep(pattern, opts) : Promise.resolve(outsideRefusal(opts.path)),
|
|
8241
|
+
findSymbol: (symbol, opts) => withinWorkspace(opts?.path) ? fs15.findSymbol(symbol, opts) : Promise.resolve(outsideRefusal(opts.path)),
|
|
8242
|
+
listDir: (p, depth) => withinWorkspace(p) ? fs15.listDir(p, depth) : Promise.resolve(outsideRefusal(p)),
|
|
7991
8243
|
bash: async (command, signal) => {
|
|
7992
8244
|
const dialect = resolveResearchShell().dialect;
|
|
7993
8245
|
const { tier, reason } = classifyCommand(command, { dialect });
|
|
@@ -7998,13 +8250,13 @@ function nonPromptingFs(fs14) {
|
|
|
7998
8250
|
}
|
|
7999
8251
|
const escaping = pathLikeArgs(command, { dialect }).find((p) => !withinWorkspace(p));
|
|
8000
8252
|
if (escaping) return outsideRefusal(escaping);
|
|
8001
|
-
return
|
|
8253
|
+
return fs15.bash(command, signal);
|
|
8002
8254
|
}
|
|
8003
8255
|
};
|
|
8004
8256
|
}
|
|
8005
8257
|
async function runLoop(prompt, deps) {
|
|
8006
8258
|
if (deps.signal?.aborted) return "[research agent aborted before starting]";
|
|
8007
|
-
const
|
|
8259
|
+
const fs15 = nonPromptingFs(deps.fs);
|
|
8008
8260
|
const chat = deps.createChat((delta) => deps.onProgress?.({ type: "thinking", text: delta }));
|
|
8009
8261
|
let turn = await chat.sendMessage(prompt, deps.signal);
|
|
8010
8262
|
for (let step = 0; turn.hasToolCalls && step < SUBAGENT_LIMITS.maxSteps; step++) {
|
|
@@ -8021,7 +8273,7 @@ ${turn.text}`;
|
|
|
8021
8273
|
if (tc.name === "read_file" && !("maxBytes" in args) && !("limit" in args)) args.limit = 2e3;
|
|
8022
8274
|
const toolArgs = JSON.stringify(tc.args);
|
|
8023
8275
|
deps.onProgress?.({ type: "tool_call", tool: tc.name, toolArgs, toolCallId: tc.id });
|
|
8024
|
-
const res = await executeTool(tc.name, args,
|
|
8276
|
+
const res = await executeTool(tc.name, args, fs15, void 0, deps.signal);
|
|
8025
8277
|
const output = res.output.length > SUBAGENT_LIMITS.toolOutputMaxChars ? res.output.slice(0, SUBAGENT_LIMITS.toolOutputMaxChars) + `
|
|
8026
8278
|
[... truncated to ${SUBAGENT_LIMITS.toolOutputMaxChars} chars, total ${res.output.length}]` : res.output;
|
|
8027
8279
|
const stepEntry = {
|
|
@@ -8137,7 +8389,7 @@ var BaseAiService = class _BaseAiService {
|
|
|
8137
8389
|
* steering/failed result and the loop continues sequentially: a subagent can
|
|
8138
8390
|
* never fail a plan or a turn.
|
|
8139
8391
|
*/
|
|
8140
|
-
async executeSpawnAgent(args,
|
|
8392
|
+
async executeSpawnAgent(args, fs15, onProgress, signal, subagentId) {
|
|
8141
8393
|
const prompt = typeof args.prompt === "string" ? args.prompt.trim() : "";
|
|
8142
8394
|
if (!prompt) {
|
|
8143
8395
|
return { success: false, output: 'spawn_research_agent requires a non-empty "prompt" string: a self-contained task for the agent, including what its digest must report back.', truncated: false };
|
|
@@ -8148,20 +8400,20 @@ var BaseAiService = class _BaseAiService {
|
|
|
8148
8400
|
if (!chat) throw new Error("subagent chats are not available for this provider");
|
|
8149
8401
|
return chat;
|
|
8150
8402
|
},
|
|
8151
|
-
fs:
|
|
8403
|
+
fs: fs15,
|
|
8152
8404
|
signal,
|
|
8153
8405
|
onProgress: (progress) => onProgress({ ...progress, subagentId })
|
|
8154
8406
|
});
|
|
8155
8407
|
}
|
|
8156
8408
|
/** Collect project context for the planning phase. Shared with the harness backend. */
|
|
8157
|
-
static async collectResearchContext(
|
|
8158
|
-
return collectResearchContext(
|
|
8409
|
+
static async collectResearchContext(fs15, runners) {
|
|
8410
|
+
return collectResearchContext(fs15, runners);
|
|
8159
8411
|
}
|
|
8160
8412
|
/**
|
|
8161
8413
|
* Execute one round of tool calls and report each through onProgress.
|
|
8162
8414
|
* Returns the results to feed back plus the log entries produced.
|
|
8163
8415
|
*/
|
|
8164
|
-
async executeToolCalls(toolCalls,
|
|
8416
|
+
async executeToolCalls(toolCalls, fs15, onProgress, fetcher, stepIndex, thinking, signal) {
|
|
8165
8417
|
const toolResults = [];
|
|
8166
8418
|
const logEntries = [];
|
|
8167
8419
|
let resultsText = "";
|
|
@@ -8180,18 +8432,18 @@ var BaseAiService = class _BaseAiService {
|
|
|
8180
8432
|
const subagentId = (0, import_uuid3.v4)();
|
|
8181
8433
|
spawnSubagentIds.set(i, subagentId);
|
|
8182
8434
|
onProgress({ type: "tool_call", tool: toolCalls[i].name, toolArgs: JSON.stringify(toolCalls[i].args), subagentId, toolCallId: toolCalls[i].id });
|
|
8183
|
-
precomputed.set(i, await this.executeSpawnAgent({ ...toolCalls[i].args },
|
|
8435
|
+
precomputed.set(i, await this.executeSpawnAgent({ ...toolCalls[i].args }, fs15, onProgress, signal, subagentId));
|
|
8184
8436
|
}) : Promise.resolve(),
|
|
8185
8437
|
parallelIndexes.length > 1 ? mapWithConcurrency(parallelIndexes, TOOL_ROUND_CONCURRENCY, async (i) => {
|
|
8186
8438
|
onProgress({ type: "tool_call", tool: toolCalls[i].name, toolArgs: JSON.stringify(toolCalls[i].args), toolCallId: toolCalls[i].id });
|
|
8187
|
-
precomputed.set(i, await executeTool(toolCalls[i].name, normalizedArgs[i],
|
|
8439
|
+
precomputed.set(i, await executeTool(toolCalls[i].name, normalizedArgs[i], fs15, fetcher, signal));
|
|
8188
8440
|
}) : Promise.resolve()
|
|
8189
8441
|
]);
|
|
8190
8442
|
for (const [index, tc] of toolCalls.entries()) {
|
|
8191
8443
|
const toolArgs = normalizedArgs[index];
|
|
8192
8444
|
const preResult = precomputed.get(index);
|
|
8193
8445
|
if (!preResult) onProgress({ type: "tool_call", tool: tc.name, toolArgs: JSON.stringify(tc.args), toolCallId: tc.id });
|
|
8194
|
-
const toolResult = preResult ?? await executeTool(tc.name, toolArgs,
|
|
8446
|
+
const toolResult = preResult ?? await executeTool(tc.name, toolArgs, fs15, fetcher, signal);
|
|
8195
8447
|
const MAX_RESPONSE_CHARS = 5e4;
|
|
8196
8448
|
const truncatedResponse = toolResult.success && (toolResult.truncated || toolResult.output.length > MAX_RESPONSE_CHARS);
|
|
8197
8449
|
const llmOutput = toolResult.output.length > MAX_RESPONSE_CHARS ? toolResult.output.slice(0, MAX_RESPONSE_CHARS) + `
|
|
@@ -8377,7 +8629,7 @@ ${llmOutput}
|
|
|
8377
8629
|
* parsed tasks if a plan was emitted mid-loop, or null if the loop
|
|
8378
8630
|
* exhausted without a valid plan.
|
|
8379
8631
|
*/
|
|
8380
|
-
async runResearchLoop(chat, firstMessage,
|
|
8632
|
+
async runResearchLoop(chat, firstMessage, fs15, onProgress, runners, initialLog, fetcher, userGoal, runnerModes, autonomousDefault, signal) {
|
|
8381
8633
|
const researchLog = initialLog ? [...initialLog] : [];
|
|
8382
8634
|
if (!initialLog && userGoal) {
|
|
8383
8635
|
researchLog.push({
|
|
@@ -8404,7 +8656,7 @@ ${llmOutput}
|
|
|
8404
8656
|
if (!turn.hasToolCalls) break;
|
|
8405
8657
|
const { toolResults, logEntries, resultsText } = await this.executeToolCalls(
|
|
8406
8658
|
turn.toolCalls,
|
|
8407
|
-
|
|
8659
|
+
fs15,
|
|
8408
8660
|
onProgress,
|
|
8409
8661
|
fetcher,
|
|
8410
8662
|
step,
|
|
@@ -8637,11 +8889,11 @@ ${repairHint}` }] : parts;
|
|
|
8637
8889
|
return this.consumePlanStream(result, onToken);
|
|
8638
8890
|
}
|
|
8639
8891
|
// --- IAiService implementation ---
|
|
8640
|
-
async researchAndPlan(userDescription, runners, modelsByRunner,
|
|
8892
|
+
async researchAndPlan(userDescription, runners, modelsByRunner, fs15, onProgress, fetcher, runnerModes, modes = DEFAULT_PLANNER_MODES, signal) {
|
|
8641
8893
|
this.ensureInit();
|
|
8642
8894
|
const { autonomousDefault } = modes;
|
|
8643
8895
|
const genModel = this.getPlanningModel();
|
|
8644
|
-
const contextStr = await BaseAiService.collectResearchContext(
|
|
8896
|
+
const contextStr = await BaseAiService.collectResearchContext(fs15, runners);
|
|
8645
8897
|
const systemPrompt = buildResearchPrompt(userDescription, contextStr, modelsByRunner, runners, runnerModes, modes);
|
|
8646
8898
|
const firstMessage = `${buildResearchToolsPrompt()}
|
|
8647
8899
|
|
|
@@ -8652,17 +8904,17 @@ Explore the workspace to understand the codebase, then generate the plan.`;
|
|
|
8652
8904
|
generationConfig: { temperature: 0.3, topP: 0.95, maxOutputTokens: 16384 }
|
|
8653
8905
|
});
|
|
8654
8906
|
const researchChat = new GeminiResearchChat(chat);
|
|
8655
|
-
const result = await this.runResearchLoop(researchChat, firstMessage,
|
|
8907
|
+
const result = await this.runResearchLoop(researchChat, firstMessage, fs15, onProgress, runners, void 0, fetcher, userDescription, runnerModes, autonomousDefault, signal);
|
|
8656
8908
|
if (result.tasks) return { tasks: result.tasks, researchLog: result.researchLog, researchResults: result.researchResults };
|
|
8657
8909
|
const fallback = await this.generatePlanFallback(userDescription, contextStr, result.researchResults, modelsByRunner, runners, onProgress, result.researchLog, runnerModes, autonomousDefault, signal, modes);
|
|
8658
8910
|
return { tasks: fallback.tasks, researchLog: fallback.researchLog, researchResults: result.researchResults };
|
|
8659
8911
|
}
|
|
8660
|
-
async generatePlanDirect(userDescription, runners = ["claude-code"], modelsByRunner = {}, onToken,
|
|
8912
|
+
async generatePlanDirect(userDescription, runners = ["claude-code"], modelsByRunner = {}, onToken, fs15, _fetcher, runnerModes, modes = DEFAULT_PLANNER_MODES, _signal) {
|
|
8661
8913
|
this.ensureInit();
|
|
8662
8914
|
const { autonomousDefault } = modes;
|
|
8663
8915
|
let ctx = "";
|
|
8664
|
-
if (
|
|
8665
|
-
const collected = await new ContextCollector(
|
|
8916
|
+
if (fs15) {
|
|
8917
|
+
const collected = await new ContextCollector(fs15).collect(runners[0] ?? "claude-code");
|
|
8666
8918
|
if (collected.aiflowContext) ctx = `
|
|
8667
8919
|
<aiflow_context>
|
|
8668
8920
|
${collected.aiflowContext}
|
|
@@ -8678,12 +8930,12 @@ ${collected.aiflowContext}
|
|
|
8678
8930
|
autonomousDefault
|
|
8679
8931
|
);
|
|
8680
8932
|
}
|
|
8681
|
-
async modifyPlan(existingPlan, userRequest, modelsByRunner, onProgress,
|
|
8933
|
+
async modifyPlan(existingPlan, userRequest, modelsByRunner, onProgress, fs15, _fetcher, runnerModes, modes = DEFAULT_PLANNER_MODES, _signal) {
|
|
8682
8934
|
this.ensureInit();
|
|
8683
8935
|
const { autonomousDefault } = modes;
|
|
8684
8936
|
let aiflowContext;
|
|
8685
|
-
if (
|
|
8686
|
-
const collected = await new ContextCollector(
|
|
8937
|
+
if (fs15) {
|
|
8938
|
+
const collected = await new ContextCollector(fs15).collect(existingPlan.runners[0] ?? "claude-code");
|
|
8687
8939
|
if (collected.aiflowContext) aiflowContext = collected.aiflowContext;
|
|
8688
8940
|
}
|
|
8689
8941
|
const prompt = buildModifyPlanPrompt(existingPlan, userRequest, modelsByRunner, aiflowContext, runnerModes, autonomousDefault);
|
|
@@ -8919,11 +9171,11 @@ ${buildResearchToolsPrompt()}` }
|
|
|
8919
9171
|
}
|
|
8920
9172
|
}
|
|
8921
9173
|
// --- IAiService implementation ---
|
|
8922
|
-
async researchAndPlan(userDescription, runners, modelsByRunner,
|
|
9174
|
+
async researchAndPlan(userDescription, runners, modelsByRunner, fs15, onProgress, fetcher, runnerModes, modes = DEFAULT_PLANNER_MODES, signal) {
|
|
8923
9175
|
this.ensureInit();
|
|
8924
9176
|
const client = this.getClient();
|
|
8925
9177
|
const { autonomousDefault } = modes;
|
|
8926
|
-
const contextStr = await BaseAiService.collectResearchContext(
|
|
9178
|
+
const contextStr = await BaseAiService.collectResearchContext(fs15, runners);
|
|
8927
9179
|
const systemPrompt = buildResearchPrompt(userDescription, contextStr, modelsByRunner, runners, runnerModes, modes);
|
|
8928
9180
|
const userText = `User goal: ${userDescription}`;
|
|
8929
9181
|
const firstMessage = `${buildResearchToolsPrompt()}
|
|
@@ -8941,17 +9193,17 @@ ${userText}`;
|
|
|
8941
9193
|
toOpenAiTools(),
|
|
8942
9194
|
(delta) => onProgress({ type: "thinking", text: delta })
|
|
8943
9195
|
);
|
|
8944
|
-
const result = await this.runResearchLoop(researchChat, firstMessage,
|
|
9196
|
+
const result = await this.runResearchLoop(researchChat, firstMessage, fs15, onProgress, runners, void 0, fetcher, userDescription, runnerModes, autonomousDefault, signal);
|
|
8945
9197
|
if (result.tasks) return { tasks: result.tasks, researchLog: result.researchLog, researchResults: result.researchResults };
|
|
8946
9198
|
const fallback = await this.generatePlanFallback(userDescription, contextStr, result.researchResults, modelsByRunner, runners, onProgress, result.researchLog, runnerModes, autonomousDefault, signal, modes);
|
|
8947
9199
|
return { tasks: fallback.tasks, researchLog: fallback.researchLog, researchResults: result.researchResults };
|
|
8948
9200
|
}
|
|
8949
|
-
async generatePlanDirect(userDescription, runners = ["claude-code"], modelsByRunner = {}, onToken,
|
|
9201
|
+
async generatePlanDirect(userDescription, runners = ["claude-code"], modelsByRunner = {}, onToken, fs15, _fetcher, runnerModes, modes = DEFAULT_PLANNER_MODES, signal) {
|
|
8950
9202
|
this.ensureInit();
|
|
8951
9203
|
const { autonomousDefault } = modes;
|
|
8952
9204
|
let ctx = "";
|
|
8953
|
-
if (
|
|
8954
|
-
const collected = await new ContextCollector(
|
|
9205
|
+
if (fs15) {
|
|
9206
|
+
const collected = await new ContextCollector(fs15).collect(runners[0] ?? "claude-code");
|
|
8955
9207
|
if (collected.aiflowContext) ctx = `
|
|
8956
9208
|
<aiflow_context>
|
|
8957
9209
|
${collected.aiflowContext}
|
|
@@ -8967,12 +9219,12 @@ ${collected.aiflowContext}
|
|
|
8967
9219
|
autonomousDefault
|
|
8968
9220
|
);
|
|
8969
9221
|
}
|
|
8970
|
-
async modifyPlan(existingPlan, userRequest, modelsByRunner, onProgress,
|
|
9222
|
+
async modifyPlan(existingPlan, userRequest, modelsByRunner, onProgress, fs15, _fetcher, runnerModes, modes = DEFAULT_PLANNER_MODES, signal) {
|
|
8971
9223
|
this.ensureInit();
|
|
8972
9224
|
const { autonomousDefault } = modes;
|
|
8973
9225
|
let aiflowContext;
|
|
8974
|
-
if (
|
|
8975
|
-
const collected = await new ContextCollector(
|
|
9226
|
+
if (fs15) {
|
|
9227
|
+
const collected = await new ContextCollector(fs15).collect(existingPlan.runners[0] ?? "claude-code");
|
|
8976
9228
|
if (collected.aiflowContext) aiflowContext = collected.aiflowContext;
|
|
8977
9229
|
}
|
|
8978
9230
|
const prompt = buildModifyPlanPrompt(existingPlan, userRequest, modelsByRunner, aiflowContext, runnerModes, autonomousDefault);
|
|
@@ -8991,6 +9243,9 @@ ${collected.aiflowContext}
|
|
|
8991
9243
|
}
|
|
8992
9244
|
};
|
|
8993
9245
|
|
|
9246
|
+
// src/services/TaskOrchestrator.ts
|
|
9247
|
+
var path12 = __toESM(require("path"));
|
|
9248
|
+
|
|
8994
9249
|
// src/services/promptAugment.ts
|
|
8995
9250
|
var MAX_TAIL_CHARS = 500;
|
|
8996
9251
|
var DEFAULT_PLAN_MAP_MAX_ENTRIES = 30;
|
|
@@ -9577,17 +9832,229 @@ ${line}
|
|
|
9577
9832
|
|
|
9578
9833
|
// src/services/transcriptCapture.ts
|
|
9579
9834
|
var import_fs = require("fs");
|
|
9835
|
+
var os4 = __toESM(require("os"));
|
|
9836
|
+
var path11 = __toESM(require("path"));
|
|
9837
|
+
|
|
9838
|
+
// src/services/workspaceEnv.ts
|
|
9839
|
+
var import_child_process3 = require("child_process");
|
|
9840
|
+
var fs5 = __toESM(require("fs"));
|
|
9580
9841
|
var os3 = __toESM(require("os"));
|
|
9581
|
-
var
|
|
9842
|
+
var path10 = __toESM(require("path"));
|
|
9843
|
+
|
|
9844
|
+
// src/services/settingsEnvAllowlist.ts
|
|
9845
|
+
var ORDEWELL_SETTABLE_ENV = [
|
|
9846
|
+
"AI_PROVIDER",
|
|
9847
|
+
"ORCHESTRATOR_MODEL",
|
|
9848
|
+
"ORDEWELL_SUBAGENT_MODEL",
|
|
9849
|
+
"ORDEWELL_PLANNER_EFFORT",
|
|
9850
|
+
"ORDEWELL_RESEARCH_ENABLED",
|
|
9851
|
+
"ORDEWELL_MAX_PARALLEL",
|
|
9852
|
+
"ORDEWELL_CONFLICT_REPAIR_ATTEMPTS"
|
|
9853
|
+
];
|
|
9854
|
+
var SETTINGS_ENV_REFUSED = [
|
|
9855
|
+
// Approval posture and pre-approved scope.
|
|
9856
|
+
"ORDEWELL_AUTONOMOUS_MODE",
|
|
9857
|
+
"ORDEWELL_APPROVAL_MODE",
|
|
9858
|
+
"ORDEWELL_APPROVAL_ALLOW",
|
|
9859
|
+
// Where settings are persisted.
|
|
9860
|
+
"ORDEWELL_SETTINGS_PATH",
|
|
9861
|
+
// Runtime options and dynamic loaders.
|
|
9862
|
+
"NODE_OPTIONS",
|
|
9863
|
+
"NODE_REPL_EXTERNAL_MODULE",
|
|
9864
|
+
"NODE_PATH",
|
|
9865
|
+
"ELECTRON_RUN_AS_NODE",
|
|
9866
|
+
"LD_PRELOAD",
|
|
9867
|
+
"LD_LIBRARY_PATH",
|
|
9868
|
+
"LD_AUDIT",
|
|
9869
|
+
"DYLD_INSERT_LIBRARIES",
|
|
9870
|
+
"DYLD_LIBRARY_PATH",
|
|
9871
|
+
"DYLD_FRAMEWORK_PATH",
|
|
9872
|
+
// Executable resolution.
|
|
9873
|
+
"PATH",
|
|
9874
|
+
"PATHEXT",
|
|
9875
|
+
"NODE"
|
|
9876
|
+
];
|
|
9877
|
+
var REFUSED = new Set(SETTINGS_ENV_REFUSED);
|
|
9878
|
+
function providerEnvKeys() {
|
|
9879
|
+
const keys = [];
|
|
9880
|
+
for (const meta of Object.values(ALL_PROVIDERS)) {
|
|
9881
|
+
if (meta.apiKeyEnvVar) keys.push(meta.apiKeyEnvVar);
|
|
9882
|
+
if (meta.baseUrlEnvVar) keys.push(meta.baseUrlEnvVar);
|
|
9883
|
+
for (const detect of meta.detectEnvVars) keys.push(detect);
|
|
9884
|
+
}
|
|
9885
|
+
return keys;
|
|
9886
|
+
}
|
|
9887
|
+
var PROVIDER_CREDENTIAL_ENV = new Set(providerEnvKeys());
|
|
9888
|
+
var SETTINGS_ENV_ALLOWLIST = new Set(
|
|
9889
|
+
[...providerEnvKeys(), ...ORDEWELL_SETTABLE_ENV].filter((key) => !REFUSED.has(key))
|
|
9890
|
+
);
|
|
9891
|
+
function admitSettingsEnv(env) {
|
|
9892
|
+
const accepted = {};
|
|
9893
|
+
const rejected = [];
|
|
9894
|
+
for (const [key, value] of Object.entries(env)) {
|
|
9895
|
+
if (REFUSED.has(key) || !SETTINGS_ENV_ALLOWLIST.has(key) || typeof value !== "string") {
|
|
9896
|
+
rejected.push(key);
|
|
9897
|
+
continue;
|
|
9898
|
+
}
|
|
9899
|
+
accepted[key] = value;
|
|
9900
|
+
}
|
|
9901
|
+
return { accepted, rejected };
|
|
9902
|
+
}
|
|
9903
|
+
|
|
9904
|
+
// src/services/workspaceEnv.ts
|
|
9905
|
+
var WORKSPACE_ENV_FILE = "env";
|
|
9906
|
+
var DIRENV_TIMEOUT_MS = 5e3;
|
|
9907
|
+
var KEY = /^[A-Za-z_][A-Za-z0-9_]*$/;
|
|
9908
|
+
function defaultDeps() {
|
|
9909
|
+
return {
|
|
9910
|
+
run: (command, args, opts) => new Promise((resolve7) => {
|
|
9911
|
+
(0, import_child_process3.execFile)(command, args, { ...opts, timeout: DIRENV_TIMEOUT_MS, encoding: "utf8" }, (err, stdout, stderr) => {
|
|
9912
|
+
const code = err ? typeof err.code === "number" ? Number(err.code) : -1 : 0;
|
|
9913
|
+
resolve7({ code, stdout: String(stdout ?? ""), stderr: String(stderr ?? "") });
|
|
9914
|
+
});
|
|
9915
|
+
}),
|
|
9916
|
+
readFile: (file) => {
|
|
9917
|
+
try {
|
|
9918
|
+
return fs5.readFileSync(file, "utf8");
|
|
9919
|
+
} catch {
|
|
9920
|
+
return null;
|
|
9921
|
+
}
|
|
9922
|
+
},
|
|
9923
|
+
isDirectory: (dir) => {
|
|
9924
|
+
try {
|
|
9925
|
+
return fs5.statSync(dir).isDirectory();
|
|
9926
|
+
} catch {
|
|
9927
|
+
return false;
|
|
9928
|
+
}
|
|
9929
|
+
},
|
|
9930
|
+
baseEnv: process.env,
|
|
9931
|
+
home: os3.homedir()
|
|
9932
|
+
};
|
|
9933
|
+
}
|
|
9934
|
+
var REFUSED2 = new Set(SETTINGS_ENV_REFUSED);
|
|
9935
|
+
async function resolveWorkspaceEnv(cwd, overrides = {}) {
|
|
9936
|
+
const deps = { ...defaultDeps(), ...overrides };
|
|
9937
|
+
const result = { env: {}, blockedEnvrc: null, refused: [], trackedEnvFile: null };
|
|
9938
|
+
if (!deps.isDirectory(cwd)) return result;
|
|
9939
|
+
const collected = {};
|
|
9940
|
+
const direnv = await fromDirenv(checkoutDirOf(cwd, deps), deps);
|
|
9941
|
+
result.blockedEnvrc = direnv.blocked;
|
|
9942
|
+
Object.assign(collected, direnv.env);
|
|
9943
|
+
const file = findUp(cwd, path10.join(STATE_DIR, WORKSPACE_ENV_FILE), deps);
|
|
9944
|
+
if (file) {
|
|
9945
|
+
const content = deps.readFile(file);
|
|
9946
|
+
if (content !== null) {
|
|
9947
|
+
if (await isTracked(file, deps)) result.trackedEnvFile = file;
|
|
9948
|
+
else Object.assign(collected, parseEnvFile(content, { ...deps.baseEnv, ...collected }, deps.home));
|
|
9949
|
+
}
|
|
9950
|
+
}
|
|
9951
|
+
for (const [key, value] of Object.entries(collected)) {
|
|
9952
|
+
if (!KEY.test(key)) continue;
|
|
9953
|
+
if (REFUSED2.has(key) || key.toUpperCase() === "PATH") result.refused.push(key);
|
|
9954
|
+
else result.env[key] = value;
|
|
9955
|
+
}
|
|
9956
|
+
return result;
|
|
9957
|
+
}
|
|
9958
|
+
async function workspaceEnvOf(cwd) {
|
|
9959
|
+
return (await resolveWorkspaceEnv(cwd)).env;
|
|
9960
|
+
}
|
|
9961
|
+
function checkoutDirOf(cwd, deps) {
|
|
9962
|
+
const parts = path10.resolve(cwd).split(path10.sep);
|
|
9963
|
+
const at = parts.findIndex((part, i) => part === STATE_DIR && parts[i + 1] === "worktrees");
|
|
9964
|
+
if (at < 0 || parts.length < at + 4) return cwd;
|
|
9965
|
+
const root = parts.slice(0, at).join(path10.sep) || path10.sep;
|
|
9966
|
+
const mapped = path10.join(root, ...parts.slice(at + 4));
|
|
9967
|
+
return deps.isDirectory(mapped) ? mapped : root;
|
|
9968
|
+
}
|
|
9969
|
+
async function fromDirenv(cwd, deps) {
|
|
9970
|
+
const setting = deps.baseEnv.ORDEWELL_DIRENV;
|
|
9971
|
+
if (setting === "false" || setting === "0") return { env: {}, blocked: null };
|
|
9972
|
+
const baseEnv = Object.fromEntries(Object.entries(deps.baseEnv).filter(([key]) => !key.startsWith("DIRENV_")));
|
|
9973
|
+
let out;
|
|
9974
|
+
try {
|
|
9975
|
+
out = await deps.run("direnv", ["export", "json"], { cwd, env: baseEnv });
|
|
9976
|
+
} catch {
|
|
9977
|
+
return { env: {}, blocked: null };
|
|
9978
|
+
}
|
|
9979
|
+
const blocked = out.stderr.match(/error (\S*\.envrc) is blocked/);
|
|
9980
|
+
if (blocked) return { env: {}, blocked: blocked[1] };
|
|
9981
|
+
if (out.code !== 0 || !out.stdout.trim()) return { env: {}, blocked: null };
|
|
9982
|
+
let parsed;
|
|
9983
|
+
try {
|
|
9984
|
+
parsed = JSON.parse(out.stdout);
|
|
9985
|
+
} catch {
|
|
9986
|
+
return { env: {}, blocked: null };
|
|
9987
|
+
}
|
|
9988
|
+
if (!parsed || typeof parsed !== "object") return { env: {}, blocked: null };
|
|
9989
|
+
const env = {};
|
|
9990
|
+
for (const [key, value] of Object.entries(parsed)) {
|
|
9991
|
+
if (typeof value === "string" && !key.startsWith("DIRENV_")) env[key] = value;
|
|
9992
|
+
}
|
|
9993
|
+
return { env, blocked: null };
|
|
9994
|
+
}
|
|
9995
|
+
function findUp(start, relative5, deps) {
|
|
9996
|
+
let dir = path10.resolve(start);
|
|
9997
|
+
for (; ; ) {
|
|
9998
|
+
const candidate = path10.join(dir, relative5);
|
|
9999
|
+
if (deps.readFile(candidate) !== null) return candidate;
|
|
10000
|
+
const parent = path10.dirname(dir);
|
|
10001
|
+
if (parent === dir) return null;
|
|
10002
|
+
dir = parent;
|
|
10003
|
+
}
|
|
10004
|
+
}
|
|
10005
|
+
async function isTracked(file, deps) {
|
|
10006
|
+
try {
|
|
10007
|
+
const out = await deps.run("git", ["ls-files", "--error-unmatch", path10.basename(file)], { cwd: path10.dirname(file), env: deps.baseEnv });
|
|
10008
|
+
return out.code === 0;
|
|
10009
|
+
} catch {
|
|
10010
|
+
return false;
|
|
10011
|
+
}
|
|
10012
|
+
}
|
|
10013
|
+
function parseEnvFile(content, context, home) {
|
|
10014
|
+
const env = {};
|
|
10015
|
+
const lookup2 = (name) => env[name] ?? context[name] ?? "";
|
|
10016
|
+
const expand = (value) => {
|
|
10017
|
+
const tilde = value.startsWith("~/") ? path10.join(home, value.slice(2)) : value;
|
|
10018
|
+
return tilde.replace(/\$\{([A-Za-z_][A-Za-z0-9_]*)\}|\$([A-Za-z_][A-Za-z0-9_]*)/g, (_, braced, bare) => lookup2(braced ?? bare));
|
|
10019
|
+
};
|
|
10020
|
+
for (const raw of content.split(/\r?\n/)) {
|
|
10021
|
+
const line = raw.trim().replace(/^export\s+/, "");
|
|
10022
|
+
if (!line || line.startsWith("#")) continue;
|
|
10023
|
+
const eq = line.indexOf("=");
|
|
10024
|
+
if (eq <= 0) continue;
|
|
10025
|
+
const key = line.slice(0, eq).trim();
|
|
10026
|
+
if (!KEY.test(key)) continue;
|
|
10027
|
+
const rest = line.slice(eq + 1).trim();
|
|
10028
|
+
if (rest.startsWith("'")) {
|
|
10029
|
+
const end = rest.indexOf("'", 1);
|
|
10030
|
+
env[key] = end > 0 ? rest.slice(1, end) : rest.slice(1);
|
|
10031
|
+
} else if (rest.startsWith('"')) {
|
|
10032
|
+
const end = rest.lastIndexOf('"');
|
|
10033
|
+
env[key] = expand((end > 0 ? rest.slice(1, end) : rest.slice(1)).replace(/\\n/g, "\n").replace(/\\"/g, '"'));
|
|
10034
|
+
} else {
|
|
10035
|
+
env[key] = expand(rest.replace(/\s+#.*$/, ""));
|
|
10036
|
+
}
|
|
10037
|
+
}
|
|
10038
|
+
return env;
|
|
10039
|
+
}
|
|
10040
|
+
|
|
10041
|
+
// src/services/transcriptCapture.ts
|
|
9582
10042
|
var HomeTranscriptReader = class {
|
|
9583
10043
|
home;
|
|
10044
|
+
claudeConfigDirs;
|
|
9584
10045
|
constructor(opts = {}) {
|
|
9585
10046
|
const { homeDir } = opts;
|
|
9586
|
-
this.home = homeDir ? () => homeDir : () => process.env.HOME ||
|
|
10047
|
+
this.home = homeDir ? () => homeDir : () => process.env.HOME || os4.homedir();
|
|
10048
|
+
const workspaceEnv = opts.workspaceEnv ?? workspaceEnvOf;
|
|
10049
|
+
this.claudeConfigDirs = async (cwd) => [...new Set([
|
|
10050
|
+
(await workspaceEnv(cwd)).CLAUDE_CONFIG_DIR,
|
|
10051
|
+
homeDir ? void 0 : process.env.CLAUDE_CONFIG_DIR,
|
|
10052
|
+
path11.join(this.home(), ".claude")
|
|
10053
|
+
].filter((dir) => Boolean(dir)))];
|
|
9587
10054
|
}
|
|
9588
10055
|
async finalAssistantText(query, maxChars = 4e3) {
|
|
9589
10056
|
try {
|
|
9590
|
-
if (query.runner === "claude-code") return claudeFinal(this.
|
|
10057
|
+
if (query.runner === "claude-code") return claudeFinal(await this.claudeConfigDirs(query.cwd), query, maxChars);
|
|
9591
10058
|
if (query.runner === "opencode") return await opencodeFinal(this.home(), query, maxChars);
|
|
9592
10059
|
if (query.runner === "codex") return codexFinal(this.home(), query, maxChars);
|
|
9593
10060
|
} catch {
|
|
@@ -9596,20 +10063,20 @@ var HomeTranscriptReader = class {
|
|
|
9596
10063
|
}
|
|
9597
10064
|
};
|
|
9598
10065
|
var CLAUDE_PROJECT_NAME_MAX = 200;
|
|
9599
|
-
function claudeProjectDirs(
|
|
9600
|
-
const projects =
|
|
10066
|
+
function claudeProjectDirs(configDir2, cwd) {
|
|
10067
|
+
const projects = path11.join(configDir2, "projects");
|
|
9601
10068
|
const munged = cwd.replace(/[^a-zA-Z0-9]/g, "-");
|
|
9602
10069
|
if (munged.length <= CLAUDE_PROJECT_NAME_MAX) {
|
|
9603
|
-
const dir =
|
|
10070
|
+
const dir = path11.join(projects, munged);
|
|
9604
10071
|
return (0, import_fs.existsSync)(dir) ? [dir] : [];
|
|
9605
10072
|
}
|
|
9606
10073
|
if (!(0, import_fs.existsSync)(projects)) return [];
|
|
9607
10074
|
const prefix = `${munged.slice(0, CLAUDE_PROJECT_NAME_MAX)}-`;
|
|
9608
|
-
return (0, import_fs.readdirSync)(projects).filter((name) => name.startsWith(prefix)).map((name) =>
|
|
10075
|
+
return (0, import_fs.readdirSync)(projects).filter((name) => name.startsWith(prefix)).map((name) => path11.join(projects, name));
|
|
9609
10076
|
}
|
|
9610
|
-
function claudeFinal(
|
|
10077
|
+
function claudeFinal(configDirs, query, maxChars) {
|
|
9611
10078
|
const cutoff = query.startedAt ? Date.parse(query.startedAt) : 0;
|
|
9612
|
-
const candidates = claudeProjectDirs(
|
|
10079
|
+
const candidates = configDirs.flatMap((dir) => claudeProjectDirs(dir, query.cwd)).flatMap((dir) => (0, import_fs.readdirSync)(dir).filter((f) => f.endsWith(".jsonl")).map((f) => path11.join(dir, f))).filter((f) => cutoff ? (0, import_fs.statSync)(f).mtimeMs >= cutoff - 5e3 : true).sort((a, b) => (0, import_fs.statSync)(b).mtimeMs - (0, import_fs.statSync)(a).mtimeMs);
|
|
9613
10080
|
for (const file of candidates) {
|
|
9614
10081
|
const raw = (0, import_fs.readFileSync)(file, "utf8");
|
|
9615
10082
|
if (!raw.includes(query.marker)) continue;
|
|
@@ -9645,7 +10112,7 @@ async function opencodeFinal(home, query, maxChars) {
|
|
|
9645
10112
|
} catch {
|
|
9646
10113
|
return null;
|
|
9647
10114
|
}
|
|
9648
|
-
const dbPath =
|
|
10115
|
+
const dbPath = path11.join(home, ".local", "share", "opencode", "opencode.db");
|
|
9649
10116
|
if (!(0, import_fs.existsSync)(dbPath)) return null;
|
|
9650
10117
|
let db;
|
|
9651
10118
|
try {
|
|
@@ -9698,7 +10165,7 @@ async function opencodeFinal(home, query, maxChars) {
|
|
|
9698
10165
|
}
|
|
9699
10166
|
}
|
|
9700
10167
|
function codexFinal(home, query, maxChars) {
|
|
9701
|
-
const root =
|
|
10168
|
+
const root = path11.join(home, ".codex", "sessions");
|
|
9702
10169
|
if (!(0, import_fs.existsSync)(root)) return null;
|
|
9703
10170
|
const cutoff = query.startedAt ? Date.parse(query.startedAt) : 0;
|
|
9704
10171
|
const files = [];
|
|
@@ -9712,7 +10179,7 @@ function codexFinal(home, query, maxChars) {
|
|
|
9712
10179
|
continue;
|
|
9713
10180
|
}
|
|
9714
10181
|
for (const e of entries) {
|
|
9715
|
-
const full =
|
|
10182
|
+
const full = path11.join(dir, e);
|
|
9716
10183
|
let st;
|
|
9717
10184
|
try {
|
|
9718
10185
|
st = (0, import_fs.statSync)(full);
|
|
@@ -10163,6 +10630,43 @@ ${taskB.description}`,
|
|
|
10163
10630
|
}
|
|
10164
10631
|
};
|
|
10165
10632
|
|
|
10633
|
+
// src/utils/shell.ts
|
|
10634
|
+
var ANSI_RE = /\x1b\[[0-9;]*[A-Za-z]|\x1b\][^\x07]*\x07|\x1b[()][AB012]|\r/g;
|
|
10635
|
+
function stripAnsi(text) {
|
|
10636
|
+
return text.replace(ANSI_RE, "");
|
|
10637
|
+
}
|
|
10638
|
+
function posixShellQuote(s) {
|
|
10639
|
+
return `'${s.replace(/'/g, `'\\''`)}'`;
|
|
10640
|
+
}
|
|
10641
|
+
function buildShellInvocation(command, args) {
|
|
10642
|
+
const inner = [command, ...args].map(posixShellQuote).join(" ");
|
|
10643
|
+
return { shellPath: "/bin/bash", shellArgs: ["-lc", inner] };
|
|
10644
|
+
}
|
|
10645
|
+
function wrapWithPty(command, args, opts = {}) {
|
|
10646
|
+
const inner = [command, ...args].map(posixShellQuote).join(" ");
|
|
10647
|
+
const watcher = opts.controlChannel ? 'exec 4<&0; ( while read -r C R <&3; do stty cols "$C" rows "$R" <&4; done ) & ' : "";
|
|
10648
|
+
const setup = opts.size ? `stty cols ${opts.size.cols} rows ${opts.size.rows}; ` : "";
|
|
10649
|
+
return { command: "script", args: ["-q", "-e", "-f", "-c", `${watcher}${setup}${inner}`, "/dev/null"] };
|
|
10650
|
+
}
|
|
10651
|
+
|
|
10652
|
+
// src/services/blockingPrompts.ts
|
|
10653
|
+
var TAIL = 4096;
|
|
10654
|
+
var squash = (text) => stripAnsi(text).replace(/\s+/g, "").toLowerCase();
|
|
10655
|
+
function watchBlockingPrompts(session, prompts, onPrompt) {
|
|
10656
|
+
if (prompts.length === 0) return;
|
|
10657
|
+
const pending = prompts.map((prompt) => ({ prompt, phrase: squash(prompt.phrase) }));
|
|
10658
|
+
let tail = "";
|
|
10659
|
+
session.onOutput((text) => {
|
|
10660
|
+
if (pending.length === 0) return;
|
|
10661
|
+
tail = (tail + squash(text)).slice(-TAIL);
|
|
10662
|
+
for (let i = pending.length - 1; i >= 0; i--) {
|
|
10663
|
+
if (!tail.includes(pending[i].phrase)) continue;
|
|
10664
|
+
onPrompt(pending[i].prompt);
|
|
10665
|
+
pending.splice(i, 1);
|
|
10666
|
+
}
|
|
10667
|
+
});
|
|
10668
|
+
}
|
|
10669
|
+
|
|
10166
10670
|
// src/services/TaskOrchestrator.ts
|
|
10167
10671
|
var SHARED_ROOT_TAIL = "tasks run in the workspace root without worktree isolation.";
|
|
10168
10672
|
function sharedRootNotice(reason, repos) {
|
|
@@ -10226,6 +10730,25 @@ var TaskOrchestrator = class {
|
|
|
10226
10730
|
*/
|
|
10227
10731
|
retryCounts = /* @__PURE__ */ new Map();
|
|
10228
10732
|
spawnCounts = /* @__PURE__ */ new Map();
|
|
10733
|
+
/**
|
|
10734
|
+
* The terminal a verdict left open, by task. It stays so the user can read
|
|
10735
|
+
* the agent's output or keep talking to it — but only while its worktree
|
|
10736
|
+
* does: once that is removed the agent sits in a deleted directory, and a
|
|
10737
|
+
* newer attempt in the same worktree would share it with a second agent.
|
|
10738
|
+
* Without this, every task of every run left one agent process running
|
|
10739
|
+
* until the daemon stopped.
|
|
10740
|
+
*/
|
|
10741
|
+
lingering = /* @__PURE__ */ new Map();
|
|
10742
|
+
/**
|
|
10743
|
+
* A full-plan run a failure paused. Retrying the failed task is the explicit
|
|
10744
|
+
* resume the pause waits for — without this a retry only reset the task to
|
|
10745
|
+
* pending and nothing ran until the user also re-ran the whole plan.
|
|
10746
|
+
*/
|
|
10747
|
+
haltedByFailure = false;
|
|
10748
|
+
/** The workspace's own variables for a task's cwd (ADR-0016); swapped out in tests. */
|
|
10749
|
+
workspaceEnv = (cwd) => resolveWorkspaceEnv(cwd);
|
|
10750
|
+
/** What the workspace env has already warned about, so a run says it once, not per task. */
|
|
10751
|
+
envWarnings = /* @__PURE__ */ new Set();
|
|
10229
10752
|
/**
|
|
10230
10753
|
* Tasks pulled out of auto-scheduling (user-cancelled or failed to spawn).
|
|
10231
10754
|
* They stay 'pending' — "not executed" — but the scheduler skips them until
|
|
@@ -10269,6 +10792,32 @@ var TaskOrchestrator = class {
|
|
|
10269
10792
|
setWorkspaceRoot(fn) {
|
|
10270
10793
|
this.workspaceRootFn = fn;
|
|
10271
10794
|
}
|
|
10795
|
+
setWorkspaceEnvResolver(resolve7) {
|
|
10796
|
+
this.workspaceEnv = resolve7;
|
|
10797
|
+
}
|
|
10798
|
+
/**
|
|
10799
|
+
* The variables a task's agent gets from its workspace. What cannot be
|
|
10800
|
+
* applied is said once — silently starting without them is how agents ran
|
|
10801
|
+
* under the wrong account when an edited `.envrc` was left unallowed.
|
|
10802
|
+
*/
|
|
10803
|
+
async envForTask(cwd) {
|
|
10804
|
+
const resolved = await this.workspaceEnv(cwd);
|
|
10805
|
+
const warn = (key, message) => {
|
|
10806
|
+
if (this.envWarnings.has(key)) return;
|
|
10807
|
+
this.envWarnings.add(key);
|
|
10808
|
+
this.notifications.warn(message);
|
|
10809
|
+
};
|
|
10810
|
+
if (resolved.blockedEnvrc) {
|
|
10811
|
+
warn(`blocked:${resolved.blockedEnvrc}`, `direnv has blocked ${resolved.blockedEnvrc}, so tasks start without its variables. Run \`direnv allow\` in ${path12.dirname(resolved.blockedEnvrc)} to use them.`);
|
|
10812
|
+
}
|
|
10813
|
+
if (resolved.trackedEnvFile) {
|
|
10814
|
+
warn(`tracked:${resolved.trackedEnvFile}`, `Ignored ${resolved.trackedEnvFile}: git tracks it, and a committed file must not choose the environment agents run in. Untrack it to use it.`);
|
|
10815
|
+
}
|
|
10816
|
+
if (resolved.refused.length > 0) {
|
|
10817
|
+
warn(`refused:${resolved.refused.join(",")}`, `Ignored ${resolved.refused.join(", ")} from the workspace environment: Ordewell never passes these to agents.`);
|
|
10818
|
+
}
|
|
10819
|
+
return resolved.env;
|
|
10820
|
+
}
|
|
10272
10821
|
setRegistry(registry) {
|
|
10273
10822
|
this.registry = registry;
|
|
10274
10823
|
}
|
|
@@ -10350,7 +10899,7 @@ var TaskOrchestrator = class {
|
|
|
10350
10899
|
if (!this.isolationRun) return null;
|
|
10351
10900
|
const record = this.isolationRun.tasks[taskId];
|
|
10352
10901
|
if (!record) return { state: "none" };
|
|
10353
|
-
return taskIsolationOf(record);
|
|
10902
|
+
return taskIsolationOf(record, this.config.conflictRepairAttempts);
|
|
10354
10903
|
}
|
|
10355
10904
|
/**
|
|
10356
10905
|
* The plan's isolation as a surface shows it, for one with no stream to have
|
|
@@ -10359,7 +10908,7 @@ var TaskOrchestrator = class {
|
|
|
10359
10908
|
isolationView() {
|
|
10360
10909
|
const run = this.isolationRun;
|
|
10361
10910
|
if (!run) return null;
|
|
10362
|
-
const tasks = Object.fromEntries(Object.values(run.tasks).map((r) => [r.taskId, taskIsolationOf(r)]));
|
|
10911
|
+
const tasks = Object.fromEntries(Object.values(run.tasks).map((r) => [r.taskId, taskIsolationOf(r, this.config.conflictRepairAttempts)]));
|
|
10363
10912
|
return { tasks, handoff: handoffOf(run) };
|
|
10364
10913
|
}
|
|
10365
10914
|
getAttemptSession(taskId) {
|
|
@@ -10395,24 +10944,62 @@ var TaskOrchestrator = class {
|
|
|
10395
10944
|
async reviewRunDiff() {
|
|
10396
10945
|
return this.isolation.reviewDiff(this.requireRun());
|
|
10397
10946
|
}
|
|
10398
|
-
/**
|
|
10947
|
+
/**
|
|
10948
|
+
* "Merge all": the run's integration branches into whatever the user has
|
|
10949
|
+
* checked out, in every repo or none. Once everything merged, the run has
|
|
10950
|
+
* nothing left to hand over, so it is cleared up and forgotten; a branch
|
|
10951
|
+
* the user's HEAD somehow does not contain stays for the next run's sweep.
|
|
10952
|
+
*/
|
|
10399
10953
|
async mergeRun() {
|
|
10400
10954
|
const run = this.requireRun();
|
|
10401
10955
|
const result = await this.isolation.mergeIntoCheckedOut(run);
|
|
10402
10956
|
const branch = integrationBranchNameOf(run);
|
|
10403
10957
|
const group = run.repos.some((r) => r.path !== SELF_REPO);
|
|
10404
|
-
const
|
|
10958
|
+
const repaired = handoffOf(run).landed.filter((t) => t.repairedFiles?.length);
|
|
10959
|
+
const { level, message } = describeMergeResult(result, branch, group, repaired);
|
|
10405
10960
|
this.notifications[level](message);
|
|
10961
|
+
if (result.outcome === "merged") await this.clearMergedRun(run);
|
|
10406
10962
|
return result;
|
|
10407
10963
|
}
|
|
10964
|
+
async clearMergedRun(run) {
|
|
10965
|
+
this.closeLingeringOf(run);
|
|
10966
|
+
try {
|
|
10967
|
+
await this.isolation.discard(run, { integration: "delete-merged" });
|
|
10968
|
+
} catch (err) {
|
|
10969
|
+
this.tell("warn", `Merged, but could not clean up the run's worktrees and branches: ${err instanceof Error ? err.message : String(err)}`);
|
|
10970
|
+
return;
|
|
10971
|
+
}
|
|
10972
|
+
this.forgetRun();
|
|
10973
|
+
}
|
|
10408
10974
|
/** Worktrees and task branches go; the integration branch and the record stay for review and merge. */
|
|
10409
10975
|
async cleanupRun() {
|
|
10410
|
-
|
|
10976
|
+
this.closeLingeringOf(this.requireRun());
|
|
10977
|
+
await this.isolation.discard(this.requireRun(), { integration: "keep" });
|
|
10411
10978
|
this.emit("onIsolationChanged");
|
|
10412
10979
|
}
|
|
10413
10980
|
/** The run and everything it made go, and the plan forgets it; the next run starts afresh. */
|
|
10414
10981
|
async discardRun() {
|
|
10415
|
-
|
|
10982
|
+
this.closeLingeringOf(this.requireRun());
|
|
10983
|
+
await this.isolation.discard(this.requireRun(), { integration: "delete" });
|
|
10984
|
+
this.forgetRun();
|
|
10985
|
+
}
|
|
10986
|
+
watchBlockingPrompts(task, attempt, session) {
|
|
10987
|
+
const manifest = this.registry?.get(attempt.runner)?.manifest;
|
|
10988
|
+
watchBlockingPrompts(session, manifest?.runner.blockingPrompts ?? [], (prompt) => {
|
|
10989
|
+
if (this.attempts.get(task.id) !== attempt) return;
|
|
10990
|
+
this.notifications.warn(`Task "${task.title}" is waiting for you: ${manifest?.displayName ?? attempt.runner} is asking ${prompt.asks}. Answer it in the task's terminal.`);
|
|
10991
|
+
});
|
|
10992
|
+
}
|
|
10993
|
+
closeLingering(taskId) {
|
|
10994
|
+
const sessionId = this.lingering.get(taskId);
|
|
10995
|
+
if (sessionId === void 0) return;
|
|
10996
|
+
this.lingering.delete(taskId);
|
|
10997
|
+
this.terminalRunner.stop(sessionId);
|
|
10998
|
+
}
|
|
10999
|
+
closeLingeringOf(run) {
|
|
11000
|
+
for (const taskId of Object.keys(run.tasks)) this.closeLingering(taskId);
|
|
11001
|
+
}
|
|
11002
|
+
forgetRun() {
|
|
10416
11003
|
this.isolationRun = null;
|
|
10417
11004
|
this.resolvers = {};
|
|
10418
11005
|
this.emit("onIsolationChanged");
|
|
@@ -10448,7 +11035,8 @@ var TaskOrchestrator = class {
|
|
|
10448
11035
|
}
|
|
10449
11036
|
loadPlan(tasks, planRunners = ["claude-code"]) {
|
|
10450
11037
|
this.store.load(tasks, planRunners);
|
|
10451
|
-
this.endAllAttempts("load");
|
|
11038
|
+
const repairs = this.endAllAttempts("load").filter((a) => a.repair && a.worktree);
|
|
11039
|
+
for (const a of repairs) void this.releaseWorktree(a.taskId, { keep: true }, a.integration);
|
|
10452
11040
|
if (!this.running) this.runMode = null;
|
|
10453
11041
|
this.blockedStart = null;
|
|
10454
11042
|
this.planStatus = "approved";
|
|
@@ -10498,17 +11086,25 @@ var TaskOrchestrator = class {
|
|
|
10498
11086
|
if (!await this.openRun(() => this.start()) || this.running) return;
|
|
10499
11087
|
console.log(`[TaskOrchestrator] Starting with ${this.store.allTasks.length} tasks (${this.store.allTasks.filter((t) => t.type === "ai" && t.prompt).length} AI ready)`);
|
|
10500
11088
|
this.running = true;
|
|
11089
|
+
this.haltedByFailure = false;
|
|
10501
11090
|
this.planStatus = "running";
|
|
10502
11091
|
this.emit("onTaskChanged");
|
|
10503
11092
|
await this.tick();
|
|
10504
11093
|
}
|
|
11094
|
+
haltOnFailure() {
|
|
11095
|
+
if (this.running) this.haltedByFailure = true;
|
|
11096
|
+
this.running = false;
|
|
11097
|
+
this.planStatus = "approved";
|
|
11098
|
+
}
|
|
10505
11099
|
stop() {
|
|
11100
|
+
this.haltedByFailure = false;
|
|
10506
11101
|
this.running = false;
|
|
10507
11102
|
this.planStatus = "approved";
|
|
10508
11103
|
this.terminalRunner.stopAll();
|
|
10509
|
-
const
|
|
10510
|
-
|
|
10511
|
-
|
|
11104
|
+
for (const a of this.endAllAttempts("stop")) {
|
|
11105
|
+
if (a.repair) this.store.markAwaitingUser(a.taskId);
|
|
11106
|
+
if (a.worktree) void this.releaseWorktree(a.taskId, { keep: true }, a.integration);
|
|
11107
|
+
}
|
|
10512
11108
|
this.runMode = null;
|
|
10513
11109
|
this.blockedStart = null;
|
|
10514
11110
|
this.onHold.clear();
|
|
@@ -10524,6 +11120,7 @@ var TaskOrchestrator = class {
|
|
|
10524
11120
|
console.error(`[TaskOrchestrator] Task #${task.order} "${task.title}" verdict=${verdict.outcome}`);
|
|
10525
11121
|
console.error(`[TaskOrchestrator] Runner: ${task.assignedRunner}, Model: ${task.assignedModel?.modelId ?? "default"}`);
|
|
10526
11122
|
console.error(`[TaskOrchestrator] Prompt preview: ${(task.prompt ?? "").slice(0, 200)}`);
|
|
11123
|
+
if (attempt.repair) return this.settleRepair(task, attempt, verdict);
|
|
10527
11124
|
const doneToken = `<<<ORDEWELL_DONE_${task.completionMarker}>>>`;
|
|
10528
11125
|
const summary = await this.output.finalText({ ...attempt, completionMarker: task.completionMarker }, doneToken);
|
|
10529
11126
|
if (this.attempts.get(taskId) !== attempt) return;
|
|
@@ -10537,13 +11134,15 @@ ${summary || "(empty \u2014 no output captured)"}`);
|
|
|
10537
11134
|
await this.landPassed(task, landing);
|
|
10538
11135
|
} else {
|
|
10539
11136
|
this.store.markFailed(taskId);
|
|
10540
|
-
this.
|
|
10541
|
-
this.planStatus = "approved";
|
|
11137
|
+
this.haltOnFailure();
|
|
10542
11138
|
this.notifications.error(`Task "${task.title}" failed verification: ${verdict.reason}`);
|
|
10543
11139
|
if (attempt.worktree) await this.releaseWorktree(taskId, { keep: true });
|
|
10544
11140
|
}
|
|
10545
11141
|
this.store.setTaskOutputSummary(taskId, summarizeOutput(verdict.reason, summary));
|
|
10546
11142
|
this.logAndArchive(task, verdict);
|
|
11143
|
+
await this.afterVerdict();
|
|
11144
|
+
}
|
|
11145
|
+
async afterVerdict() {
|
|
10547
11146
|
this.emit("onTaskChanged");
|
|
10548
11147
|
if (!this.running) {
|
|
10549
11148
|
if (this.attempts.size === 0) {
|
|
@@ -10556,6 +11155,71 @@ ${summary || "(empty \u2014 no output captured)"}`);
|
|
|
10556
11155
|
}
|
|
10557
11156
|
await this.tick();
|
|
10558
11157
|
}
|
|
11158
|
+
/**
|
|
11159
|
+
* A repair's verdict decides only whether its work may try to land. It never
|
|
11160
|
+
* replaces the verdict the task's own work earned, and a repair that does not
|
|
11161
|
+
* land leaves the task waiting on the user — never a failed task, so never a
|
|
11162
|
+
* halted run.
|
|
11163
|
+
*/
|
|
11164
|
+
async settleRepair(task, attempt, verdict) {
|
|
11165
|
+
const landed = verdict.outcome === "pass" ? await this.landRepair(task, attempt) : null;
|
|
11166
|
+
if (this.attempts.get(task.id) !== attempt) return;
|
|
11167
|
+
this.endAttempt(task.id, "verdict");
|
|
11168
|
+
if (!landed) await this.unrepaired(task, `did not finish (${verdict.reason})`);
|
|
11169
|
+
else if (!landed.evidence.ok) await this.unrepaired(task, this.describeEvidence(landed.evidence));
|
|
11170
|
+
else if (landed.outcome === "merged") await this.landedRepair(task, verdict);
|
|
11171
|
+
else this.landUnmerged(task, landed.outcome);
|
|
11172
|
+
await this.afterVerdict();
|
|
11173
|
+
}
|
|
11174
|
+
/**
|
|
11175
|
+
* Evidence before the queue: the repair's work is committed and checked, and
|
|
11176
|
+
* only then merged, through the same serialized landing as any other task —
|
|
11177
|
+
* so a repair the tip has moved past again is a fresh conflict, not a pass.
|
|
11178
|
+
*/
|
|
11179
|
+
async landRepair(task, attempt) {
|
|
11180
|
+
const run = this.isolationRun;
|
|
11181
|
+
let evidence = { ok: false, reason: "failed", repo: SELF_REPO };
|
|
11182
|
+
if (!run) return { evidence, outcome: "failed" };
|
|
11183
|
+
attempt.phase = "integrating";
|
|
11184
|
+
attempt.integration = (async () => {
|
|
11185
|
+
evidence = await this.isolation.verifyRepair(task, run).catch(() => ({ ok: false, reason: "failed", repo: run.tasks[task.id]?.conflictRepo ?? SELF_REPO }));
|
|
11186
|
+
if (evidence.ok) return this.integrateWork(task);
|
|
11187
|
+
await this.releaseWorktree(task.id, { keep: true });
|
|
11188
|
+
return "conflict";
|
|
11189
|
+
})();
|
|
11190
|
+
const outcome = await attempt.integration;
|
|
11191
|
+
return { evidence, outcome };
|
|
11192
|
+
}
|
|
11193
|
+
async landedRepair(task, verdict) {
|
|
11194
|
+
const files = this.isolationRun?.tasks[task.id]?.repairedFiles ?? [];
|
|
11195
|
+
this.store.markCompleted(task.id);
|
|
11196
|
+
this.store.unblockDependents(task.id);
|
|
11197
|
+
this.logAndArchive(task, task.verdict ?? verdict);
|
|
11198
|
+
this.tell("info", `Task "${task.title}" landed after repairing a conflict${files.length > 0 ? ` in ${capConflictFiles(files)}` : ""}.`);
|
|
11199
|
+
await this.landResolved(task.id);
|
|
11200
|
+
}
|
|
11201
|
+
/** A repair that did not land leaves the task as its conflict did: waiting on the user, worktree and refs kept. */
|
|
11202
|
+
async unrepaired(task, why) {
|
|
11203
|
+
await this.releaseWorktree(task.id, { keep: true });
|
|
11204
|
+
this.store.markAwaitingUser(task.id);
|
|
11205
|
+
const group = this.isolationRun?.repos.some((r) => r.path !== SELF_REPO);
|
|
11206
|
+
this.tell("warn", `The conflict repair of task "${task.title}" ${why}, so it did not land. Its ${group ? "worktrees are" : "worktree is"} kept \u2014 resolve it by hand and mark it complete, retry it, or resolve it as a task.`);
|
|
11207
|
+
}
|
|
11208
|
+
describeEvidence(evidence) {
|
|
11209
|
+
const run = this.isolationRun;
|
|
11210
|
+
const inRepo = evidence.repo !== SELF_REPO ? ` in ${evidence.repo}` : "";
|
|
11211
|
+
const branch = run ? integrationBranchNameOf(run) : "the integration branch";
|
|
11212
|
+
switch (evidence.reason) {
|
|
11213
|
+
case "not-merged":
|
|
11214
|
+
return `finished, but its branch${inRepo} does not contain ${branch}`;
|
|
11215
|
+
case "conflict-markers": {
|
|
11216
|
+
const files = (evidence.files ?? []).map((file) => evidence.repo !== SELF_REPO ? `${evidence.repo}/${file}` : file);
|
|
11217
|
+
return `finished, but left conflict markers in ${capConflictFiles(files)}`;
|
|
11218
|
+
}
|
|
11219
|
+
case "failed":
|
|
11220
|
+
return `finished, but git could not check its work${inRepo}`;
|
|
11221
|
+
}
|
|
11222
|
+
}
|
|
10559
11223
|
/**
|
|
10560
11224
|
* Merge a passed attempt's worktree. The attempt stays live while it waits on
|
|
10561
11225
|
* the module's merge queue, so a cancel, retry or stop in that window still
|
|
@@ -10589,18 +11253,44 @@ ${summary || "(empty \u2014 no output captured)"}`);
|
|
|
10589
11253
|
}
|
|
10590
11254
|
landUnmerged(task, landing) {
|
|
10591
11255
|
const branch = this.isolationRun ? integrationBranchNameOf(this.isolationRun) : "the integration branch";
|
|
10592
|
-
const
|
|
10593
|
-
const inRepo =
|
|
11256
|
+
const record = this.isolationRun?.tasks[task.id];
|
|
11257
|
+
const inRepo = record?.conflictRepo && record.conflictRepo !== SELF_REPO ? record.conflictRepo : null;
|
|
10594
11258
|
if (landing === "conflict") {
|
|
11259
|
+
const repair = this.nextRepair(task.id);
|
|
10595
11260
|
this.store.markAwaitingUser(task.id);
|
|
10596
|
-
|
|
11261
|
+
const files = record?.conflictFiles?.length ? ` (${capConflictFiles(record.conflictFiles)})` : "";
|
|
11262
|
+
const conflicted = inRepo ? `Task "${task.title}" passed, but landing it on ${branch} conflicted in ${inRepo}${files}, so none of it landed.` : `Task "${task.title}" passed, but merging it into ${branch} conflicted${files}.`;
|
|
11263
|
+
if (repair) {
|
|
11264
|
+
this.notifications.warn(conflicted);
|
|
11265
|
+
if (this.attempts.size < this.config.maxParallelSessions) void this.startTask(task);
|
|
11266
|
+
else {
|
|
11267
|
+
this.store.markPending(task.id);
|
|
11268
|
+
this.tell("info", `Task "${task.title}" is repaired once a slot is free.`);
|
|
11269
|
+
}
|
|
11270
|
+
return;
|
|
11271
|
+
}
|
|
11272
|
+
this.notifications.warn(`${conflicted} ${inRepo ? "Its worktrees are" : "Its worktree is"} kept \u2014 resolve it by hand and mark it complete, retry it, or resolve it as a task.`);
|
|
11273
|
+
const whyNot = this.noRepairReason(task);
|
|
11274
|
+
if (whyNot) this.tell("info", whyNot);
|
|
10597
11275
|
} else {
|
|
10598
11276
|
this.store.markFailed(task.id);
|
|
10599
|
-
this.
|
|
10600
|
-
this.planStatus = "approved";
|
|
11277
|
+
this.haltOnFailure();
|
|
10601
11278
|
this.notifications.error(inRepo ? `Task "${task.title}" passed, but git could not integrate its work in ${inRepo}, so none of it landed. Its worktrees are kept for inspection.` : `Task "${task.title}" passed, but git could not integrate its work. Its worktree is kept for inspection.`);
|
|
10602
11279
|
}
|
|
10603
11280
|
}
|
|
11281
|
+
/** The repair a conflicted task is owed next; null when repair is off, used up, or there is no isolated run to repair it in. */
|
|
11282
|
+
nextRepair(taskId) {
|
|
11283
|
+
const record = this.runMode === "isolated" ? this.isolationRun?.tasks[taskId] : void 0;
|
|
11284
|
+
const limit = this.config.conflictRepairAttempts;
|
|
11285
|
+
const spent = record?.repairs ?? 0;
|
|
11286
|
+
return record?.status === "conflict" && spent < limit ? { n: spent + 1, limit } : null;
|
|
11287
|
+
}
|
|
11288
|
+
noRepairReason(task) {
|
|
11289
|
+
const limit = this.config.conflictRepairAttempts;
|
|
11290
|
+
if (limit === 0) return `Conflict repair is off (conflictRepairAttempts is 0), so task "${task.title}" waits for you.`;
|
|
11291
|
+
const spent = this.isolationRun?.tasks[task.id]?.repairs ?? 0;
|
|
11292
|
+
return spent >= limit ? `Task "${task.title}" has had ${spent} of its ${limit} conflict repairs, so its conflict waits for you.` : null;
|
|
11293
|
+
}
|
|
10604
11294
|
/**
|
|
10605
11295
|
* Mark which added task resolves which conflict. The resolver merges the
|
|
10606
11296
|
* conflicted task's branch by hand in its own worktree; once that lands, the
|
|
@@ -10635,6 +11325,7 @@ ${summary || "(empty \u2014 no output captured)"}`);
|
|
|
10635
11325
|
await integration;
|
|
10636
11326
|
const run = this.isolationRun;
|
|
10637
11327
|
if (!run?.tasks[taskId]) return;
|
|
11328
|
+
if (!opts.keep) this.closeLingering(taskId);
|
|
10638
11329
|
try {
|
|
10639
11330
|
await this.isolation.release(run, taskId, opts);
|
|
10640
11331
|
} catch (err) {
|
|
@@ -10783,7 +11474,8 @@ ${summary || "(empty \u2014 no output captured)"}`);
|
|
|
10783
11474
|
this.onHold.delete(taskId);
|
|
10784
11475
|
this.emit("onTaskChanged");
|
|
10785
11476
|
await this.releaseWorktree(taskId, { keep: false }, ended?.integration);
|
|
10786
|
-
await this.
|
|
11477
|
+
if (this.haltedByFailure && !this.running) await this.start();
|
|
11478
|
+
else await this.tick();
|
|
10787
11479
|
}
|
|
10788
11480
|
/**
|
|
10789
11481
|
* Manually start a single AI task right now, bypassing dependency/readiness
|
|
@@ -10890,6 +11582,7 @@ ${summary || "(empty \u2014 no output captured)"}`);
|
|
|
10890
11582
|
cwd: null,
|
|
10891
11583
|
worktree: false,
|
|
10892
11584
|
integration: null,
|
|
11585
|
+
repair: this.nextRepair(task.id),
|
|
10893
11586
|
startedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
10894
11587
|
};
|
|
10895
11588
|
this.spawnCounts.set(task.id, attempt.attempt);
|
|
@@ -10900,13 +11593,16 @@ ${summary || "(empty \u2014 no output captured)"}`);
|
|
|
10900
11593
|
const cwd = await this.resolveAttemptCwd(task, attempt);
|
|
10901
11594
|
attempt.cwd = cwd;
|
|
10902
11595
|
if (this.attempts.get(task.id) !== attempt) {
|
|
10903
|
-
if (attempt.worktree && !this.attempts.has(task.id)) await this.releaseWorktree(task.id, { keep:
|
|
10904
|
-
return this.abandonSpawn(task);
|
|
11596
|
+
if (attempt.worktree && !this.attempts.has(task.id)) await this.releaseWorktree(task.id, { keep: attempt.repair !== null });
|
|
11597
|
+
return this.abandonSpawn(task, attempt);
|
|
10905
11598
|
}
|
|
10906
|
-
const finalPrompt = composeAugmentedPrompt(task, this.store.planTasks, {
|
|
11599
|
+
const finalPrompt = composeAugmentedPrompt(attempt.repair ? { ...task, prompt: this.repairPrompt(task) } : task, this.store.planTasks, {
|
|
10907
11600
|
planMapEnabled: this.config.planMapEnabled,
|
|
10908
|
-
|
|
11601
|
+
// A merge to resolve is not new behaviour to drive test-first.
|
|
11602
|
+
tddEnabled: !attempt.repair && this.tddEnabled()
|
|
10909
11603
|
});
|
|
11604
|
+
this.closeLingering(task.id);
|
|
11605
|
+
const env = await this.envForTask(cwd);
|
|
10910
11606
|
const session = await this.terminalRunner.spawn({
|
|
10911
11607
|
taskId: task.id,
|
|
10912
11608
|
runner: attempt.runner,
|
|
@@ -10918,18 +11614,30 @@ ${summary || "(empty \u2014 no output captured)"}`);
|
|
|
10918
11614
|
cwd,
|
|
10919
11615
|
registry: this.registry ?? void 0,
|
|
10920
11616
|
order: task.order,
|
|
10921
|
-
title: task.title
|
|
11617
|
+
title: task.title,
|
|
11618
|
+
env
|
|
10922
11619
|
});
|
|
10923
|
-
if (this.attempts.get(task.id) !== attempt) return this.abandonSpawn(task, session);
|
|
11620
|
+
if (this.attempts.get(task.id) !== attempt) return this.abandonSpawn(task, attempt, session);
|
|
10924
11621
|
attempt.phase = "running";
|
|
10925
11622
|
attempt.session = session;
|
|
10926
11623
|
this.output.attach(task.id, session);
|
|
10927
11624
|
this.verifier.watch(task, session);
|
|
11625
|
+
this.watchBlockingPrompts(task, attempt, session);
|
|
10928
11626
|
this.emit("onTaskChanged");
|
|
10929
|
-
|
|
11627
|
+
if (attempt.repair) {
|
|
11628
|
+
const group = this.isolationRun?.repos.some((r) => r.path !== SELF_REPO);
|
|
11629
|
+
this.tell("info", `Repairing the conflict of task "${task.title}" in its own ${group ? "worktrees" : "worktree"} (repair ${attempt.repair.n} of ${attempt.repair.limit}).`);
|
|
11630
|
+
} else {
|
|
11631
|
+
this.notifications.info(`Task "${task.title}" started (${attempt.runner})`);
|
|
11632
|
+
}
|
|
10930
11633
|
} catch (err) {
|
|
10931
|
-
if (this.attempts.get(task.id) !== attempt) return this.abandonSpawn(task);
|
|
11634
|
+
if (this.attempts.get(task.id) !== attempt) return this.abandonSpawn(task, attempt);
|
|
10932
11635
|
this.endAttempt(task.id, "spawn-failed");
|
|
11636
|
+
if (attempt.repair) {
|
|
11637
|
+
await this.unrepaired(task, `could not start: ${err instanceof Error ? err.message : String(err)}`);
|
|
11638
|
+
this.emit("onTaskChanged");
|
|
11639
|
+
return this.tick();
|
|
11640
|
+
}
|
|
10933
11641
|
await this.releaseWorktree(task.id, { keep: false });
|
|
10934
11642
|
this.store.markPending(task.id);
|
|
10935
11643
|
this.onHold.add(task.id);
|
|
@@ -10943,17 +11651,35 @@ ${summary || "(empty \u2014 no output captured)"}`);
|
|
|
10943
11651
|
* and take back the claim it made. Only the claim — whatever ended the
|
|
10944
11652
|
* attempt may have decided the task since (mark complete, cancel, retry).
|
|
10945
11653
|
*/
|
|
10946
|
-
abandonSpawn(task, session) {
|
|
11654
|
+
abandonSpawn(task, attempt, session) {
|
|
10947
11655
|
session?.kill();
|
|
10948
11656
|
if (this.attempts.has(task.id) || this.store.get(task.id)?.status !== "in_progress") return;
|
|
10949
|
-
this.store.
|
|
11657
|
+
if (attempt.repair) this.store.markAwaitingUser(task.id);
|
|
11658
|
+
else this.store.markPending(task.id);
|
|
10950
11659
|
this.emit("onTaskChanged");
|
|
10951
11660
|
}
|
|
11661
|
+
repairPrompt(task) {
|
|
11662
|
+
const run = this.requireRun();
|
|
11663
|
+
const record = run.tasks[task.id];
|
|
11664
|
+
const conflict = {
|
|
11665
|
+
branch: record?.branch ?? "",
|
|
11666
|
+
repos: Object.keys(record?.repairBase ?? {}),
|
|
11667
|
+
...record?.conflictRepo ? { conflictRepo: record.conflictRepo } : {},
|
|
11668
|
+
...record?.conflictFiles ? { conflictFiles: record.conflictFiles } : {}
|
|
11669
|
+
};
|
|
11670
|
+
return buildConflictRepairPrompt(task, conflict, integrationBranchNameOf(run));
|
|
11671
|
+
}
|
|
10952
11672
|
/**
|
|
10953
11673
|
* The one place an attempt's working directory is decided. Async so a
|
|
10954
11674
|
* per-attempt workspace (worktree isolation, #12) can be prepared here.
|
|
10955
11675
|
*/
|
|
10956
11676
|
async resolveAttemptCwd(task, attempt) {
|
|
11677
|
+
if (attempt.repair && this.isolationRun) {
|
|
11678
|
+
const { cwd: cwd2 } = await this.isolation.reopen(task, this.isolationRun);
|
|
11679
|
+
attempt.worktree = true;
|
|
11680
|
+
this.emit("onIsolationChanged");
|
|
11681
|
+
return cwd2;
|
|
11682
|
+
}
|
|
10957
11683
|
if (this.runMode !== "isolated" || !this.isolationRun) {
|
|
10958
11684
|
await this.releaseWorktree(task.id, { keep: false });
|
|
10959
11685
|
return this.workspaceRootFn();
|
|
@@ -11041,8 +11767,18 @@ ${summary || "(empty \u2014 no output captured)"}`);
|
|
|
11041
11767
|
}
|
|
11042
11768
|
}
|
|
11043
11769
|
this.runMode = "isolated";
|
|
11770
|
+
await this.sweep();
|
|
11044
11771
|
return true;
|
|
11045
11772
|
}
|
|
11773
|
+
/** What earlier runs left merged in the group goes; a failure here is worth a word, never a stopped run. */
|
|
11774
|
+
async sweep() {
|
|
11775
|
+
if (!this.isolationRun) return;
|
|
11776
|
+
try {
|
|
11777
|
+
await this.isolation.sweep(this.isolationRun);
|
|
11778
|
+
} catch (err) {
|
|
11779
|
+
this.tell("warn", `Could not clear merged branches of earlier runs: ${err instanceof Error ? err.message : String(err)}`);
|
|
11780
|
+
}
|
|
11781
|
+
}
|
|
11046
11782
|
/**
|
|
11047
11783
|
* The plan's run carries on while anything has landed on its integration
|
|
11048
11784
|
* branch: a resumed plan's dependents must start from a tip that holds their
|
|
@@ -11055,14 +11791,14 @@ ${summary || "(empty \u2014 no output captured)"}`);
|
|
|
11055
11791
|
/**
|
|
11056
11792
|
* A run with nothing landed holds only superseded attempts, so it goes whole.
|
|
11057
11793
|
* One that cannot be continued for another reason — it ran from a different
|
|
11058
|
-
* workspace path — keeps its integration branch
|
|
11059
|
-
* work up.
|
|
11794
|
+
* workspace path — keeps its integration branch in each repo that has not
|
|
11795
|
+
* merged it: only the user gives landed work up.
|
|
11060
11796
|
*/
|
|
11061
11797
|
async mintRun(root) {
|
|
11062
11798
|
const previous = this.isolationRun;
|
|
11063
11799
|
if (previous) {
|
|
11064
11800
|
const landed = Object.values(previous.tasks).some((r) => r.status === "merged");
|
|
11065
|
-
await this.isolation.discard(previous, {
|
|
11801
|
+
await this.isolation.discard(previous, { integration: landed ? "delete-merged" : "delete" }).catch(() => void 0);
|
|
11066
11802
|
this.isolationRun = null;
|
|
11067
11803
|
}
|
|
11068
11804
|
this.isolationRun = await this.isolation.startRun(root);
|
|
@@ -11119,6 +11855,7 @@ ${summary || "(empty \u2014 no output captured)"}`);
|
|
|
11119
11855
|
const attempt = this.attempts.get(taskId);
|
|
11120
11856
|
this.attempts.delete(taskId);
|
|
11121
11857
|
if (attempt) this.output.detach(taskId);
|
|
11858
|
+
if (reason === "verdict" && attempt?.session) this.lingering.set(taskId, attempt.session.id);
|
|
11122
11859
|
if (reason === "cancel" || reason === "release" || reason === "complete" || reason === "retry") {
|
|
11123
11860
|
const task = this.store.get(taskId);
|
|
11124
11861
|
if (task) this.verifier.clear(task);
|
|
@@ -11127,9 +11864,11 @@ ${summary || "(empty \u2014 no output captured)"}`);
|
|
|
11127
11864
|
return attempt;
|
|
11128
11865
|
}
|
|
11129
11866
|
endAllAttempts(reason) {
|
|
11130
|
-
|
|
11867
|
+
const ended = [...this.attempts.values()];
|
|
11868
|
+
for (const { taskId } of ended) this.endAttempt(taskId, reason);
|
|
11131
11869
|
this.verifier.reset();
|
|
11132
11870
|
if (reason === "load") this.output.reset();
|
|
11871
|
+
return ended;
|
|
11133
11872
|
}
|
|
11134
11873
|
};
|
|
11135
11874
|
|
|
@@ -11259,29 +11998,29 @@ ${errors.map((e) => `- ${e}`).join("\n")}`
|
|
|
11259
11998
|
};
|
|
11260
11999
|
|
|
11261
12000
|
// src/services/ModelDiscovery.ts
|
|
11262
|
-
var
|
|
11263
|
-
var
|
|
12001
|
+
var path15 = __toESM(require("path"));
|
|
12002
|
+
var import_child_process5 = require("child_process");
|
|
11264
12003
|
var import_util3 = require("util");
|
|
11265
12004
|
var fsSync3 = __toESM(require("fs"));
|
|
11266
12005
|
var osMod = __toESM(require("os"));
|
|
11267
12006
|
|
|
11268
12007
|
// src/utils/globalDataDir.ts
|
|
11269
|
-
var
|
|
11270
|
-
var
|
|
11271
|
-
var
|
|
12008
|
+
var fs6 = __toESM(require("fs"));
|
|
12009
|
+
var path13 = __toESM(require("path"));
|
|
12010
|
+
var os5 = __toESM(require("os"));
|
|
11272
12011
|
function globalDataDir() {
|
|
11273
|
-
return
|
|
12012
|
+
return path13.join(os5.homedir(), ".ordewell");
|
|
11274
12013
|
}
|
|
11275
12014
|
function copyDirSync(src, dest) {
|
|
11276
|
-
|
|
11277
|
-
const entries =
|
|
12015
|
+
fs6.mkdirSync(dest, { recursive: true });
|
|
12016
|
+
const entries = fs6.readdirSync(src, { withFileTypes: true });
|
|
11278
12017
|
for (const entry of entries) {
|
|
11279
|
-
const srcPath =
|
|
11280
|
-
const destPath =
|
|
12018
|
+
const srcPath = path13.join(src, entry.name);
|
|
12019
|
+
const destPath = path13.join(dest, entry.name);
|
|
11281
12020
|
if (entry.isDirectory()) {
|
|
11282
12021
|
copyDirSync(srcPath, destPath);
|
|
11283
12022
|
} else {
|
|
11284
|
-
|
|
12023
|
+
fs6.copyFileSync(srcPath, destPath);
|
|
11285
12024
|
}
|
|
11286
12025
|
}
|
|
11287
12026
|
}
|
|
@@ -11289,50 +12028,29 @@ var migrated = false;
|
|
|
11289
12028
|
function migrateOldConfigDir() {
|
|
11290
12029
|
if (migrated) return;
|
|
11291
12030
|
migrated = true;
|
|
11292
|
-
const oldDir =
|
|
11293
|
-
if (!
|
|
12031
|
+
const oldDir = path13.join(os5.homedir(), ".config", "ordewell");
|
|
12032
|
+
if (!fs6.existsSync(oldDir)) return;
|
|
11294
12033
|
const newDir = globalDataDir();
|
|
11295
12034
|
const liftFile = (name) => {
|
|
11296
|
-
const oldFile =
|
|
11297
|
-
const newFile =
|
|
11298
|
-
if (
|
|
11299
|
-
|
|
11300
|
-
|
|
12035
|
+
const oldFile = path13.join(oldDir, name);
|
|
12036
|
+
const newFile = path13.join(newDir, name);
|
|
12037
|
+
if (fs6.existsSync(oldFile) && !fs6.existsSync(newFile)) {
|
|
12038
|
+
fs6.mkdirSync(newDir, { recursive: true });
|
|
12039
|
+
fs6.copyFileSync(oldFile, newFile);
|
|
11301
12040
|
}
|
|
11302
12041
|
};
|
|
11303
12042
|
liftFile("settings.json");
|
|
11304
12043
|
liftFile(".env");
|
|
11305
|
-
const oldPlugins =
|
|
11306
|
-
const newPlugins =
|
|
11307
|
-
if (
|
|
12044
|
+
const oldPlugins = path13.join(oldDir, "plugins");
|
|
12045
|
+
const newPlugins = path13.join(newDir, "plugins");
|
|
12046
|
+
if (fs6.existsSync(oldPlugins) && !fs6.existsSync(newPlugins)) {
|
|
11308
12047
|
copyDirSync(oldPlugins, newPlugins);
|
|
11309
12048
|
}
|
|
11310
12049
|
}
|
|
11311
12050
|
|
|
11312
12051
|
// src/utils/launch.ts
|
|
11313
12052
|
var fsSync2 = __toESM(require("fs"));
|
|
11314
|
-
var
|
|
11315
|
-
|
|
11316
|
-
// src/utils/shell.ts
|
|
11317
|
-
var ANSI_RE = /\x1b\[[0-9;]*[A-Za-z]|\x1b\][^\x07]*\x07|\x1b[()][AB012]|\r/g;
|
|
11318
|
-
function stripAnsi(text) {
|
|
11319
|
-
return text.replace(ANSI_RE, "");
|
|
11320
|
-
}
|
|
11321
|
-
function posixShellQuote(s) {
|
|
11322
|
-
return `'${s.replace(/'/g, `'\\''`)}'`;
|
|
11323
|
-
}
|
|
11324
|
-
function buildShellInvocation(command, args) {
|
|
11325
|
-
const inner = [command, ...args].map(posixShellQuote).join(" ");
|
|
11326
|
-
return { shellPath: "/bin/bash", shellArgs: ["-lc", inner] };
|
|
11327
|
-
}
|
|
11328
|
-
function wrapWithPty(command, args, opts = {}) {
|
|
11329
|
-
const inner = [command, ...args].map(posixShellQuote).join(" ");
|
|
11330
|
-
const watcher = opts.controlChannel ? 'exec 4<&0; ( while read -r C R <&3; do stty cols "$C" rows "$R" <&4; done ) & ' : "";
|
|
11331
|
-
const setup = opts.size ? `stty cols ${opts.size.cols} rows ${opts.size.rows}; ` : "";
|
|
11332
|
-
return { command: "script", args: ["-q", "-e", "-f", "-c", `${watcher}${setup}${inner}`, "/dev/null"] };
|
|
11333
|
-
}
|
|
11334
|
-
|
|
11335
|
-
// src/utils/launch.ts
|
|
12053
|
+
var path14 = __toESM(require("path"));
|
|
11336
12054
|
var DIRECT_EXTENSIONS = [".exe", ".com"];
|
|
11337
12055
|
var BATCH_EXTENSIONS = [".cmd", ".bat"];
|
|
11338
12056
|
var POWERSHELL_EXTENSIONS = [".ps1"];
|
|
@@ -11340,7 +12058,7 @@ var CMD_EXE_MAX_COMMAND_LINE = 8191;
|
|
|
11340
12058
|
var WINDOWS_MAX_COMMAND_LINE = 32767;
|
|
11341
12059
|
function defaultPowerShell() {
|
|
11342
12060
|
const root = process.env.SystemRoot || process.env.windir;
|
|
11343
|
-
return root ?
|
|
12061
|
+
return root ? path14.win32.join(root, "System32", "WindowsPowerShell", "v1.0", "powershell.exe") : "powershell.exe";
|
|
11344
12062
|
}
|
|
11345
12063
|
var POWERSHELL_FLAGS = ["-NoLogo", "-NoProfile", "-NonInteractive", "-ExecutionPolicy", "Bypass", "-File"];
|
|
11346
12064
|
var CommandLineTooLongError = class extends Error {
|
|
@@ -11392,7 +12110,7 @@ Searched: ${searchedPath || "(empty PATH)"}`);
|
|
|
11392
12110
|
};
|
|
11393
12111
|
function isResolvableOnPosixPath(command, PATH, exists) {
|
|
11394
12112
|
if (command.includes("/")) return exists(command);
|
|
11395
|
-
return PATH.split(
|
|
12113
|
+
return PATH.split(path14.delimiter).map((d) => d.trim()).filter(Boolean).some((dir) => exists(path14.join(dir, command)));
|
|
11396
12114
|
}
|
|
11397
12115
|
function isExecutableResolved(command, plan, PATH, deps = {}) {
|
|
11398
12116
|
if ((deps.platform ?? process.platform) === "win32") return plan.file !== command;
|
|
@@ -11432,9 +12150,9 @@ function cmdArgs(line) {
|
|
|
11432
12150
|
return ["/d", "/s", "/c", `"${line}"`];
|
|
11433
12151
|
}
|
|
11434
12152
|
function candidateExtensions(command, pathExt) {
|
|
11435
|
-
const declared = pathExt.split(
|
|
12153
|
+
const declared = pathExt.split(path14.win32.delimiter).map((e) => e.trim().toLowerCase()).filter(Boolean);
|
|
11436
12154
|
const known = /* @__PURE__ */ new Set([...DIRECT_EXTENSIONS, ...BATCH_EXTENSIONS, ...POWERSHELL_EXTENSIONS, ...declared]);
|
|
11437
|
-
const ext =
|
|
12155
|
+
const ext = path14.win32.extname(command).toLowerCase();
|
|
11438
12156
|
if (ext && known.has(ext)) {
|
|
11439
12157
|
const route = BATCH_EXTENSIONS.includes(ext) ? "cmd" : POWERSHELL_EXTENSIONS.includes(ext) ? "powershell" : "direct";
|
|
11440
12158
|
return { direct: [], cmd: [], powershell: [], [route]: [""] };
|
|
@@ -11455,13 +12173,13 @@ async function findWindowsExecutables(command, deps) {
|
|
|
11455
12173
|
const exists = deps.exists ?? defaultExists2;
|
|
11456
12174
|
const pathExt = (deps.pathExt ?? (() => process.env.PATHEXT ?? ""))();
|
|
11457
12175
|
const byRoute = candidateExtensions(command, pathExt);
|
|
11458
|
-
const dirs =
|
|
11459
|
-
const base =
|
|
12176
|
+
const dirs = path14.win32.basename(command) !== command ? [path14.win32.dirname(path14.win32.resolve(command))] : (await (deps.resolvePath ?? augmentedPath)() || "").split(path14.win32.delimiter).map((d) => d.trim().replace(/^"|"$/g, "")).filter(Boolean);
|
|
12177
|
+
const base = path14.win32.basename(command);
|
|
11460
12178
|
const found = [];
|
|
11461
12179
|
for (const route of ROUTES) {
|
|
11462
12180
|
outer: for (const ext of byRoute[route]) {
|
|
11463
12181
|
for (const dir of dirs) {
|
|
11464
|
-
const candidate =
|
|
12182
|
+
const candidate = path14.win32.join(dir, `${base}${ext}`);
|
|
11465
12183
|
if (exists(candidate)) {
|
|
11466
12184
|
found.push({ file: candidate, route });
|
|
11467
12185
|
break outer;
|
|
@@ -11507,10 +12225,10 @@ async function planShellLaunch(command, args, deps = {}) {
|
|
|
11507
12225
|
}
|
|
11508
12226
|
|
|
11509
12227
|
// src/utils/processTree.ts
|
|
11510
|
-
var
|
|
12228
|
+
var import_child_process4 = require("child_process");
|
|
11511
12229
|
var HARD_KILL_DELAY_MS = 5e3;
|
|
11512
12230
|
function defaultExecFile2(file, args, cb) {
|
|
11513
|
-
(0,
|
|
12231
|
+
(0, import_child_process4.execFile)(file, args, (err) => cb(err ?? null));
|
|
11514
12232
|
}
|
|
11515
12233
|
function killTree(proc, deps = {}) {
|
|
11516
12234
|
if (!proc || proc.killed || typeof proc.exitCode === "number") return;
|
|
@@ -11546,7 +12264,7 @@ function killTree(proc, deps = {}) {
|
|
|
11546
12264
|
}
|
|
11547
12265
|
|
|
11548
12266
|
// src/services/ModelDiscovery.ts
|
|
11549
|
-
var execAsync3 = (0, import_util3.promisify)(
|
|
12267
|
+
var execAsync3 = (0, import_util3.promisify)(import_child_process5.exec);
|
|
11550
12268
|
var defaultExec2 = async (command, options) => {
|
|
11551
12269
|
const PATH = await augmentedPath();
|
|
11552
12270
|
const { stdout } = await execAsync3(command, { ...options, maxBuffer: 32 * 1024 * 1024, env: withPath(process.env, PATH) });
|
|
@@ -11556,16 +12274,16 @@ var defaultAppServerClient = async (command, args, options) => {
|
|
|
11556
12274
|
const PATH = await augmentedPath();
|
|
11557
12275
|
const launch = await planDirectLaunch(command, args).catch(() => null);
|
|
11558
12276
|
if (!launch) return null;
|
|
11559
|
-
return new Promise((
|
|
12277
|
+
return new Promise((resolve7) => {
|
|
11560
12278
|
let child;
|
|
11561
12279
|
try {
|
|
11562
|
-
child = (0,
|
|
12280
|
+
child = (0, import_child_process5.spawn)(launch.file, launch.args, {
|
|
11563
12281
|
env: withPath(process.env, PATH),
|
|
11564
12282
|
stdio: ["pipe", "pipe", "ignore"],
|
|
11565
12283
|
windowsVerbatimArguments: launch.verbatim
|
|
11566
12284
|
});
|
|
11567
12285
|
} catch {
|
|
11568
|
-
|
|
12286
|
+
resolve7(null);
|
|
11569
12287
|
return;
|
|
11570
12288
|
}
|
|
11571
12289
|
let settled = false;
|
|
@@ -11574,7 +12292,7 @@ var defaultAppServerClient = async (command, args, options) => {
|
|
|
11574
12292
|
settled = true;
|
|
11575
12293
|
clearTimeout(timer);
|
|
11576
12294
|
killTree(child);
|
|
11577
|
-
|
|
12295
|
+
resolve7(value);
|
|
11578
12296
|
};
|
|
11579
12297
|
const timer = setTimeout(() => finish(null), options?.timeout ?? 2e4);
|
|
11580
12298
|
child.on("error", () => finish(null));
|
|
@@ -11641,7 +12359,7 @@ function geminiDiscoveryCacheKey(apiKey) {
|
|
|
11641
12359
|
return {
|
|
11642
12360
|
key: `gemini-models:${hash}`,
|
|
11643
12361
|
dir,
|
|
11644
|
-
file:
|
|
12362
|
+
file: path15.join(dir, `gemini_models_${hash}.json`)
|
|
11645
12363
|
};
|
|
11646
12364
|
}
|
|
11647
12365
|
function readGeminiCache(apiKey) {
|
|
@@ -12164,8 +12882,8 @@ var ModelDiscovery = class {
|
|
|
12164
12882
|
};
|
|
12165
12883
|
|
|
12166
12884
|
// src/services/ModelCatalog.ts
|
|
12167
|
-
var
|
|
12168
|
-
var
|
|
12885
|
+
var fs7 = __toESM(require("fs"));
|
|
12886
|
+
var path16 = __toESM(require("path"));
|
|
12169
12887
|
var CACHE_KEY = "model-catalog";
|
|
12170
12888
|
var CACHE_TTL_MS = 60 * 60 * 1e3;
|
|
12171
12889
|
function storageKey(baseUrl) {
|
|
@@ -12217,17 +12935,17 @@ var ModelCatalog = class {
|
|
|
12217
12935
|
const url = (baseUrl || "https://openrouter.ai/api/v1").replace(/\/+$/, "");
|
|
12218
12936
|
const key = storageKey(url);
|
|
12219
12937
|
const cacheDir = globalCacheDir();
|
|
12220
|
-
const cacheFile =
|
|
12221
|
-
if (
|
|
12938
|
+
const cacheFile = path16.join(cacheDir, `${key.replace(/[:/]/g, "_")}.json`);
|
|
12939
|
+
if (fs7.existsSync(cacheFile)) fs7.rmSync(cacheFile);
|
|
12222
12940
|
} catch {
|
|
12223
12941
|
}
|
|
12224
12942
|
}
|
|
12225
12943
|
static readCache(key) {
|
|
12226
12944
|
try {
|
|
12227
12945
|
const cacheDir = globalCacheDir();
|
|
12228
|
-
const cacheFile =
|
|
12229
|
-
if (!
|
|
12230
|
-
const raw =
|
|
12946
|
+
const cacheFile = path16.join(cacheDir, `${key.replace(/[:/]/g, "_")}.json`);
|
|
12947
|
+
if (!fs7.existsSync(cacheFile)) return null;
|
|
12948
|
+
const raw = fs7.readFileSync(cacheFile, "utf8");
|
|
12231
12949
|
const data = JSON.parse(raw);
|
|
12232
12950
|
if (Date.now() - data.cachedAt > CACHE_TTL_MS) return null;
|
|
12233
12951
|
return JSON.stringify(data.models);
|
|
@@ -12238,9 +12956,9 @@ var ModelCatalog = class {
|
|
|
12238
12956
|
static writeCache(key, models) {
|
|
12239
12957
|
try {
|
|
12240
12958
|
const cacheDir = globalCacheDir();
|
|
12241
|
-
|
|
12242
|
-
const cacheFile =
|
|
12243
|
-
|
|
12959
|
+
fs7.mkdirSync(cacheDir, { recursive: true });
|
|
12960
|
+
const cacheFile = path16.join(cacheDir, `${key.replace(/[:/]/g, "_")}.json`);
|
|
12961
|
+
fs7.writeFileSync(
|
|
12244
12962
|
cacheFile,
|
|
12245
12963
|
JSON.stringify({ models, cachedAt: Date.now() }, null, 2)
|
|
12246
12964
|
);
|
|
@@ -12507,9 +13225,9 @@ var ModelResolver = class {
|
|
|
12507
13225
|
};
|
|
12508
13226
|
|
|
12509
13227
|
// src/services/RunnerInstallation.ts
|
|
12510
|
-
var
|
|
13228
|
+
var import_child_process6 = require("child_process");
|
|
12511
13229
|
var import_util4 = require("util");
|
|
12512
|
-
var execAsync4 = (0, import_util4.promisify)(
|
|
13230
|
+
var execAsync4 = (0, import_util4.promisify)(import_child_process6.exec);
|
|
12513
13231
|
var defaultExec3 = async (command, options) => {
|
|
12514
13232
|
const PATH = await augmentedPath();
|
|
12515
13233
|
const { stdout } = await execAsync4(command, { ...options, env: withPath(process.env, PATH) });
|
|
@@ -12616,71 +13334,12 @@ var RunnerInstallation = class {
|
|
|
12616
13334
|
}
|
|
12617
13335
|
};
|
|
12618
13336
|
|
|
12619
|
-
// src/services/settingsEnvAllowlist.ts
|
|
12620
|
-
var ORDEWELL_SETTABLE_ENV = [
|
|
12621
|
-
"AI_PROVIDER",
|
|
12622
|
-
"ORCHESTRATOR_MODEL",
|
|
12623
|
-
"ORDEWELL_SUBAGENT_MODEL",
|
|
12624
|
-
"ORDEWELL_PLANNER_EFFORT",
|
|
12625
|
-
"ORDEWELL_RESEARCH_ENABLED",
|
|
12626
|
-
"ORDEWELL_MAX_PARALLEL"
|
|
12627
|
-
];
|
|
12628
|
-
var SETTINGS_ENV_REFUSED = [
|
|
12629
|
-
// Approval posture and pre-approved scope.
|
|
12630
|
-
"ORDEWELL_AUTONOMOUS_MODE",
|
|
12631
|
-
"ORDEWELL_APPROVAL_MODE",
|
|
12632
|
-
"ORDEWELL_APPROVAL_ALLOW",
|
|
12633
|
-
// Where settings are persisted.
|
|
12634
|
-
"ORDEWELL_SETTINGS_PATH",
|
|
12635
|
-
// Runtime options and dynamic loaders.
|
|
12636
|
-
"NODE_OPTIONS",
|
|
12637
|
-
"NODE_REPL_EXTERNAL_MODULE",
|
|
12638
|
-
"NODE_PATH",
|
|
12639
|
-
"ELECTRON_RUN_AS_NODE",
|
|
12640
|
-
"LD_PRELOAD",
|
|
12641
|
-
"LD_LIBRARY_PATH",
|
|
12642
|
-
"LD_AUDIT",
|
|
12643
|
-
"DYLD_INSERT_LIBRARIES",
|
|
12644
|
-
"DYLD_LIBRARY_PATH",
|
|
12645
|
-
"DYLD_FRAMEWORK_PATH",
|
|
12646
|
-
// Executable resolution.
|
|
12647
|
-
"PATH",
|
|
12648
|
-
"PATHEXT",
|
|
12649
|
-
"NODE"
|
|
12650
|
-
];
|
|
12651
|
-
var REFUSED = new Set(SETTINGS_ENV_REFUSED);
|
|
12652
|
-
function providerEnvKeys() {
|
|
12653
|
-
const keys = [];
|
|
12654
|
-
for (const meta of Object.values(ALL_PROVIDERS)) {
|
|
12655
|
-
if (meta.apiKeyEnvVar) keys.push(meta.apiKeyEnvVar);
|
|
12656
|
-
if (meta.baseUrlEnvVar) keys.push(meta.baseUrlEnvVar);
|
|
12657
|
-
for (const detect of meta.detectEnvVars) keys.push(detect);
|
|
12658
|
-
}
|
|
12659
|
-
return keys;
|
|
12660
|
-
}
|
|
12661
|
-
var PROVIDER_CREDENTIAL_ENV = new Set(providerEnvKeys());
|
|
12662
|
-
var SETTINGS_ENV_ALLOWLIST = new Set(
|
|
12663
|
-
[...providerEnvKeys(), ...ORDEWELL_SETTABLE_ENV].filter((key) => !REFUSED.has(key))
|
|
12664
|
-
);
|
|
12665
|
-
function admitSettingsEnv(env) {
|
|
12666
|
-
const accepted = {};
|
|
12667
|
-
const rejected = [];
|
|
12668
|
-
for (const [key, value] of Object.entries(env)) {
|
|
12669
|
-
if (REFUSED.has(key) || !SETTINGS_ENV_ALLOWLIST.has(key) || typeof value !== "string") {
|
|
12670
|
-
rejected.push(key);
|
|
12671
|
-
continue;
|
|
12672
|
-
}
|
|
12673
|
-
accepted[key] = value;
|
|
12674
|
-
}
|
|
12675
|
-
return { accepted, rejected };
|
|
12676
|
-
}
|
|
12677
|
-
|
|
12678
13337
|
// src/services/harness/CliAgentAiService.ts
|
|
12679
|
-
var
|
|
13338
|
+
var import_child_process7 = require("child_process");
|
|
12680
13339
|
|
|
12681
13340
|
// src/utils/workspace.ts
|
|
12682
|
-
var
|
|
12683
|
-
var
|
|
13341
|
+
var fs8 = __toESM(require("fs"));
|
|
13342
|
+
var path17 = __toESM(require("path"));
|
|
12684
13343
|
var WorkspaceNotFoundError = class extends Error {
|
|
12685
13344
|
constructor(workspace) {
|
|
12686
13345
|
super(`Workspace "${workspace}" does not exist.`);
|
|
@@ -12691,7 +13350,7 @@ var WorkspaceNotFoundError = class extends Error {
|
|
|
12691
13350
|
};
|
|
12692
13351
|
function defaultIsDirectory(candidate) {
|
|
12693
13352
|
try {
|
|
12694
|
-
return
|
|
13353
|
+
return fs8.statSync(candidate).isDirectory();
|
|
12695
13354
|
} catch {
|
|
12696
13355
|
return false;
|
|
12697
13356
|
}
|
|
@@ -12727,8 +13386,8 @@ var PROJECT_MARKER_FILES = [
|
|
|
12727
13386
|
"CMakeLists.txt"
|
|
12728
13387
|
];
|
|
12729
13388
|
function assertWorkspaceIsProject(workspace, deps = {}) {
|
|
12730
|
-
const realpath = deps.realpath ?? ((candidate) =>
|
|
12731
|
-
const exists = deps.exists ?? ((candidate) =>
|
|
13389
|
+
const realpath = deps.realpath ?? ((candidate) => fs8.realpathSync(candidate));
|
|
13390
|
+
const exists = deps.exists ?? ((candidate) => fs8.existsSync(candidate));
|
|
12732
13391
|
let resolved;
|
|
12733
13392
|
try {
|
|
12734
13393
|
resolved = realpath(workspace);
|
|
@@ -12736,7 +13395,7 @@ function assertWorkspaceIsProject(workspace, deps = {}) {
|
|
|
12736
13395
|
throw new WorkspaceNotAProjectError(workspace);
|
|
12737
13396
|
}
|
|
12738
13397
|
const hasMarker = [...PROJECT_MARKER_DIRS, ...PROJECT_MARKER_FILES].some(
|
|
12739
|
-
(marker2) => exists(
|
|
13398
|
+
(marker2) => exists(path17.join(resolved, marker2))
|
|
12740
13399
|
);
|
|
12741
13400
|
if (!hasMarker) throw new WorkspaceNotAProjectError(workspace);
|
|
12742
13401
|
}
|
|
@@ -12804,13 +13463,14 @@ var StdioAgentAdapter = class {
|
|
|
12804
13463
|
}
|
|
12805
13464
|
async start(opts) {
|
|
12806
13465
|
assertWorkspaceExists(opts.cwd, { isDirectory: this.deps.isDirectory });
|
|
12807
|
-
this.processEnded = new Promise((
|
|
12808
|
-
this.markEnded =
|
|
13466
|
+
this.processEnded = new Promise((resolve7) => {
|
|
13467
|
+
this.markEnded = resolve7;
|
|
12809
13468
|
});
|
|
12810
13469
|
const spec = this.spawnSpec(opts);
|
|
12811
13470
|
const resolvePath = this.deps.resolvePath ?? augmentedPath;
|
|
12812
13471
|
const PATH = await resolvePath();
|
|
12813
|
-
|
|
13472
|
+
const workspace = await (this.deps.workspaceEnv ?? workspaceEnvOf)(opts.cwd);
|
|
13473
|
+
this.spawnEnv = withPath(process.env, PATH, { ...workspace, ...spec.env });
|
|
12814
13474
|
const launch = await planDirectLaunch(spec.command, spec.args, {
|
|
12815
13475
|
platform: this.deps.platform,
|
|
12816
13476
|
resolvePath
|
|
@@ -12856,7 +13516,7 @@ ${err.message}`).slice(-STDERR_TAIL_CHARS);
|
|
|
12856
13516
|
onEvent({ type: "error", message: this.exitMessage() });
|
|
12857
13517
|
return;
|
|
12858
13518
|
}
|
|
12859
|
-
await new Promise((
|
|
13519
|
+
await new Promise((resolve7) => {
|
|
12860
13520
|
let settled = false;
|
|
12861
13521
|
const finish = () => {
|
|
12862
13522
|
if (settled) return;
|
|
@@ -12867,7 +13527,7 @@ ${err.message}`).slice(-STDERR_TAIL_CHARS);
|
|
|
12867
13527
|
signal?.removeEventListener("abort", onAbort);
|
|
12868
13528
|
this.process?.removeListener("exit", onExit);
|
|
12869
13529
|
this.process?.removeListener("error", onExit);
|
|
12870
|
-
|
|
13530
|
+
resolve7();
|
|
12871
13531
|
};
|
|
12872
13532
|
const emit = (event) => {
|
|
12873
13533
|
onEvent(event);
|
|
@@ -13082,14 +13742,14 @@ function codexSandboxUnavailableMessage() {
|
|
|
13082
13742
|
].join("\n");
|
|
13083
13743
|
}
|
|
13084
13744
|
function runProbe(deps, cwd, env, flags) {
|
|
13085
|
-
return new Promise((
|
|
13745
|
+
return new Promise((resolve7) => {
|
|
13086
13746
|
let output = "";
|
|
13087
13747
|
let settled = false;
|
|
13088
13748
|
const finish = (code) => {
|
|
13089
13749
|
if (settled) return;
|
|
13090
13750
|
settled = true;
|
|
13091
13751
|
clearTimeout(timer);
|
|
13092
|
-
|
|
13752
|
+
resolve7({ code, output });
|
|
13093
13753
|
};
|
|
13094
13754
|
const timer = setTimeout(() => finish(null), PROBE_TIMEOUT_MS);
|
|
13095
13755
|
timer.unref?.();
|
|
@@ -13172,8 +13832,8 @@ var CodexAdapter = class extends StdioAgentAdapter {
|
|
|
13172
13832
|
async handshake(opts) {
|
|
13173
13833
|
this.sandbox = await probeCodexSandbox(this.deps, opts.cwd, this.spawnEnv);
|
|
13174
13834
|
if (this.sandbox === "unavailable") throw new Error(codexSandboxUnavailableMessage());
|
|
13175
|
-
const ready = new Promise((
|
|
13176
|
-
this.settleHandshake =
|
|
13835
|
+
const ready = new Promise((resolve7) => {
|
|
13836
|
+
this.settleHandshake = resolve7;
|
|
13177
13837
|
});
|
|
13178
13838
|
this.writeLine({
|
|
13179
13839
|
jsonrpc: "2.0",
|
|
@@ -13186,8 +13846,8 @@ var CodexAdapter = class extends StdioAgentAdapter {
|
|
|
13186
13846
|
// A binary that rejects `app-server` outright dies immediately; waiting
|
|
13187
13847
|
// out the timeout would turn a one-line diagnostic into a 30s stall.
|
|
13188
13848
|
this.processEnded.then(() => false),
|
|
13189
|
-
new Promise((
|
|
13190
|
-
const t = setTimeout(() =>
|
|
13849
|
+
new Promise((resolve7) => {
|
|
13850
|
+
const t = setTimeout(() => resolve7(false), HANDSHAKE_TIMEOUT_MS);
|
|
13191
13851
|
t.unref?.();
|
|
13192
13852
|
})
|
|
13193
13853
|
]);
|
|
@@ -13464,12 +14124,12 @@ function describeError(err) {
|
|
|
13464
14124
|
return lines.join(": ");
|
|
13465
14125
|
}
|
|
13466
14126
|
function delay(ms, signal) {
|
|
13467
|
-
return new Promise((
|
|
13468
|
-
const timer = setTimeout(
|
|
14127
|
+
return new Promise((resolve7) => {
|
|
14128
|
+
const timer = setTimeout(resolve7, ms);
|
|
13469
14129
|
timer.unref?.();
|
|
13470
14130
|
signal?.addEventListener("abort", () => {
|
|
13471
14131
|
clearTimeout(timer);
|
|
13472
|
-
|
|
14132
|
+
resolve7();
|
|
13473
14133
|
}, { once: true });
|
|
13474
14134
|
});
|
|
13475
14135
|
}
|
|
@@ -13503,17 +14163,17 @@ var OpenCodeAdapter = class {
|
|
|
13503
14163
|
throw new ExecutableNotFoundError("opencode", PATH);
|
|
13504
14164
|
}
|
|
13505
14165
|
this.process = this.deps.spawn(launch.file, launch.args, {
|
|
13506
|
-
env: withPath(process.env, PATH),
|
|
14166
|
+
env: withPath(process.env, PATH, await (this.deps.workspaceEnv ?? workspaceEnvOf)(opts.cwd)),
|
|
13507
14167
|
stdio: ["pipe", "pipe", "pipe"],
|
|
13508
14168
|
cwd: opts.cwd,
|
|
13509
14169
|
windowsVerbatimArguments: launch.verbatim
|
|
13510
14170
|
});
|
|
13511
|
-
const banner = new Promise((
|
|
14171
|
+
const banner = new Promise((resolve7) => {
|
|
13512
14172
|
let seen = "";
|
|
13513
14173
|
const scan = (chunk) => {
|
|
13514
14174
|
seen += chunk.toString();
|
|
13515
14175
|
const match = seen.match(/https?:\/\/[^\s]+/);
|
|
13516
|
-
if (match)
|
|
14176
|
+
if (match) resolve7(match[0].replace(/[.,)]$/, ""));
|
|
13517
14177
|
};
|
|
13518
14178
|
this.process.stdout?.on("data", scan);
|
|
13519
14179
|
this.process.stderr?.on("data", (chunk) => {
|
|
@@ -13522,15 +14182,15 @@ var OpenCodeAdapter = class {
|
|
|
13522
14182
|
});
|
|
13523
14183
|
this.process.on("exit", () => {
|
|
13524
14184
|
this.exited = true;
|
|
13525
|
-
|
|
14185
|
+
resolve7(null);
|
|
13526
14186
|
});
|
|
13527
14187
|
this.process.on("error", (err) => {
|
|
13528
14188
|
this.exited = true;
|
|
13529
14189
|
this.stderrTail = (this.stderrTail + `
|
|
13530
14190
|
${err.message}`).slice(-STDERR_TAIL_CHARS2);
|
|
13531
|
-
|
|
14191
|
+
resolve7(null);
|
|
13532
14192
|
});
|
|
13533
|
-
const timer = setTimeout(() =>
|
|
14193
|
+
const timer = setTimeout(() => resolve7(null), SERVER_READY_TIMEOUT_MS);
|
|
13534
14194
|
timer.unref?.();
|
|
13535
14195
|
});
|
|
13536
14196
|
this.baseUrl = await banner;
|
|
@@ -13563,8 +14223,8 @@ ${this.stderrTail.trim()}` : ""}`);
|
|
|
13563
14223
|
const streamAbort = new AbortController();
|
|
13564
14224
|
let connected = () => {
|
|
13565
14225
|
};
|
|
13566
|
-
const streamReady = new Promise((
|
|
13567
|
-
connected =
|
|
14226
|
+
const streamReady = new Promise((resolve7) => {
|
|
14227
|
+
connected = resolve7;
|
|
13568
14228
|
});
|
|
13569
14229
|
const live = this.streamEvents(
|
|
13570
14230
|
streamAbort.signal,
|
|
@@ -13768,14 +14428,14 @@ ${part.text}` : part.text });
|
|
|
13768
14428
|
}
|
|
13769
14429
|
}
|
|
13770
14430
|
}
|
|
13771
|
-
async json(method,
|
|
13772
|
-
const response = await this.deps.fetch(`${this.baseUrl}${
|
|
14431
|
+
async json(method, path26, body, signal) {
|
|
14432
|
+
const response = await this.deps.fetch(`${this.baseUrl}${path26}`, {
|
|
13773
14433
|
method,
|
|
13774
14434
|
headers: { "content-type": "application/json" },
|
|
13775
14435
|
...body === void 0 ? {} : { body: JSON.stringify(body) },
|
|
13776
14436
|
...signal ? { signal } : {}
|
|
13777
14437
|
});
|
|
13778
|
-
if (!response.ok) throw new Error(`${method} ${
|
|
14438
|
+
if (!response.ok) throw new Error(`${method} ${path26} failed: ${response.status} ${response.statusText}`);
|
|
13779
14439
|
return await response.json().catch(() => null);
|
|
13780
14440
|
}
|
|
13781
14441
|
nativeSessionId() {
|
|
@@ -13868,7 +14528,7 @@ var CliAgentAiService = class {
|
|
|
13868
14528
|
if (!runner) throw new Error(`${config.aiProvider} is not a coding-agent planner.`);
|
|
13869
14529
|
this.runner = runner;
|
|
13870
14530
|
this.processDeps = {
|
|
13871
|
-
spawn: deps.spawn ??
|
|
14531
|
+
spawn: deps.spawn ?? import_child_process7.spawn,
|
|
13872
14532
|
fetch: deps.fetch ?? globalThis.fetch,
|
|
13873
14533
|
resolvePath: deps.resolvePath,
|
|
13874
14534
|
platform: deps.platform,
|
|
@@ -14229,8 +14889,8 @@ var CliAgentAiService = class {
|
|
|
14229
14889
|
this.lastNativeSessionId = previousSessionId;
|
|
14230
14890
|
}
|
|
14231
14891
|
}
|
|
14232
|
-
async researchAndPlan(userDescription, runners, modelsByRunner,
|
|
14233
|
-
const contextStr = await collectResearchContext(
|
|
14892
|
+
async researchAndPlan(userDescription, runners, modelsByRunner, fs15, onProgress, _fetcher, runnerModes, modes = DEFAULT_PLANNER_MODES, signal) {
|
|
14893
|
+
const contextStr = await collectResearchContext(fs15, runners);
|
|
14234
14894
|
const researchLog = [
|
|
14235
14895
|
{ id: `up-${Date.now()}`, type: "user_prompt", content: userDescription, timestamp: (/* @__PURE__ */ new Date()).toISOString() }
|
|
14236
14896
|
];
|
|
@@ -14444,33 +15104,9 @@ var PlannerConversation = class {
|
|
|
14444
15104
|
if (m.role !== "user" || index === 0) return [];
|
|
14445
15105
|
const line = m.content.split("\n")[0];
|
|
14446
15106
|
const preview = line.length > REWIND_PREVIEW_WIDTH ? `${line.slice(0, REWIND_PREVIEW_WIDTH - 1)}\u2026` : line;
|
|
14447
|
-
return [{ index, preview, timestamp: m.timestamp }];
|
|
15107
|
+
return [{ index, preview, content: m.content, timestamp: m.timestamp }];
|
|
14448
15108
|
});
|
|
14449
15109
|
}
|
|
14450
|
-
/**
|
|
14451
|
-
* Cut the dialogue back to just before the user message at `index` (a
|
|
14452
|
-
* position in the transcript), discarding it and everything after. The task
|
|
14453
|
-
* list is the host's and rides along untouched — rewinding moves where the
|
|
14454
|
-
* conversation resumes, not what the plan is. The planner's research trace
|
|
14455
|
-
* goes back to the same point, by time: its entries carry no link to the
|
|
14456
|
-
* message that caused them.
|
|
14457
|
-
*/
|
|
14458
|
-
rewind(index) {
|
|
14459
|
-
this.assertIdle("rewind the conversation");
|
|
14460
|
-
const plan = this.requirePlan();
|
|
14461
|
-
if (!this.rewindTargets().some((t) => t.index === index)) {
|
|
14462
|
-
if (index > 0) throw new ConversationEditError(`No user message at position ${index} to rewind to.`);
|
|
14463
|
-
throw new ConversationEditError(this.transcript[0]?.kind === "compaction" ? "The conversation was condensed there \u2014 a rewind cannot reach back past the summary." : "The first message is the goal \u2014 start a new session to change it.");
|
|
14464
|
-
}
|
|
14465
|
-
const cutoff = this.transcript[index].timestamp;
|
|
14466
|
-
const rewound = this.host.mutate(() => {
|
|
14467
|
-
plan.conversationHistory = this.transcript.slice(0, index);
|
|
14468
|
-
plan.researchLog = (plan.researchLog ?? []).filter((e) => e.timestamp < cutoff);
|
|
14469
|
-
return true;
|
|
14470
|
-
}, () => this.host.broadcastPlan());
|
|
14471
|
-
this.reset();
|
|
14472
|
-
return rewound;
|
|
14473
|
-
}
|
|
14474
15110
|
/**
|
|
14475
15111
|
* Replace the transcript with a summary of it, keeping the last two
|
|
14476
15112
|
* exchanges as they were, and drop the live context so the next message
|
|
@@ -14546,6 +15182,28 @@ var PlannerConversation = class {
|
|
|
14546
15182
|
researchLog: plan?.researchLog ?? []
|
|
14547
15183
|
});
|
|
14548
15184
|
}
|
|
15185
|
+
/**
|
|
15186
|
+
* A copy of the dialogue as it stood just before the user message at
|
|
15187
|
+
* `index` (a position from {@link rewindTargets}) — what a rewind forks
|
|
15188
|
+
* from — plus that message's full text, for a surface to offer back. This
|
|
15189
|
+
* conversation is not touched. The research trace is cut at the same point
|
|
15190
|
+
* by time: its entries carry no link to the message that caused them.
|
|
15191
|
+
*/
|
|
15192
|
+
cloneBefore(index) {
|
|
15193
|
+
this.assertIdle("rewind the conversation");
|
|
15194
|
+
if (!this.rewindTargets().some((t) => t.index === index)) {
|
|
15195
|
+
if (index > 0) throw new ConversationEditError(`No user message at position ${index} to rewind to.`);
|
|
15196
|
+
throw new ConversationEditError(this.transcript[0]?.kind === "compaction" ? "The conversation was condensed there \u2014 a rewind cannot reach back past the summary." : "The first message is the goal \u2014 start a new session to change it.");
|
|
15197
|
+
}
|
|
15198
|
+
const { content: rewoundMessage, timestamp: cutoff } = this.transcript[index];
|
|
15199
|
+
return {
|
|
15200
|
+
dialogue: structuredClone({
|
|
15201
|
+
conversationHistory: this.transcript.slice(0, index),
|
|
15202
|
+
researchLog: (this.host.plan()?.researchLog ?? []).filter((e) => e.timestamp < cutoff)
|
|
15203
|
+
}),
|
|
15204
|
+
rewoundMessage
|
|
15205
|
+
};
|
|
15206
|
+
}
|
|
14549
15207
|
/** A one-shot `modifyPlan` exchange. Call inside the host's mutation ritual. */
|
|
14550
15208
|
recordModification(request, requestedAt, taskCount) {
|
|
14551
15209
|
this.append("user", request, { timestamp: requestedAt });
|
|
@@ -15131,26 +15789,26 @@ function retargetTaskRunner(task, runner, catalog) {
|
|
|
15131
15789
|
}
|
|
15132
15790
|
|
|
15133
15791
|
// src/services/SkillsService.ts
|
|
15134
|
-
var
|
|
15135
|
-
var
|
|
15792
|
+
var fs9 = __toESM(require("fs"));
|
|
15793
|
+
var path19 = __toESM(require("path"));
|
|
15136
15794
|
|
|
15137
15795
|
// src/services/builtinSkills.ts
|
|
15138
|
-
var
|
|
15796
|
+
var path18 = __toESM(require("path"));
|
|
15139
15797
|
var import_url = require("url");
|
|
15140
15798
|
var import_meta = {};
|
|
15141
15799
|
function builtinSkillsDir() {
|
|
15142
|
-
const dir = typeof __dirname !== "undefined" && __dirname ? __dirname :
|
|
15800
|
+
const dir = typeof __dirname !== "undefined" && __dirname ? __dirname : path18.dirname((0, import_url.fileURLToPath)(
|
|
15143
15801
|
// @ts-expect-error - import.meta is only valid under an ESM module setting
|
|
15144
15802
|
import_meta.url
|
|
15145
15803
|
));
|
|
15146
|
-
return
|
|
15804
|
+
return path18.join(dir, "..", "skills");
|
|
15147
15805
|
}
|
|
15148
15806
|
|
|
15149
15807
|
// src/services/SkillsService.ts
|
|
15150
15808
|
var BUILTIN_SKILL_NAMES = ["grilling", "to-spec", "improve-codebase-architecture"];
|
|
15151
15809
|
var RETIRED_BUILTIN_SKILL_NAMES = ["grill-me"];
|
|
15152
15810
|
function parseSkillFile(filePath, name, source) {
|
|
15153
|
-
const raw =
|
|
15811
|
+
const raw = fs9.readFileSync(filePath, "utf8");
|
|
15154
15812
|
const frontmatter = {};
|
|
15155
15813
|
let content = raw;
|
|
15156
15814
|
if (raw.startsWith("---\n")) {
|
|
@@ -15179,23 +15837,23 @@ function parseSkillFile(filePath, name, source) {
|
|
|
15179
15837
|
return { name, description: metadata.description, metadata, content, path: filePath, source };
|
|
15180
15838
|
}
|
|
15181
15839
|
function copyDirSync2(src, dest) {
|
|
15182
|
-
|
|
15183
|
-
const entries =
|
|
15840
|
+
fs9.mkdirSync(dest, { recursive: true });
|
|
15841
|
+
const entries = fs9.readdirSync(src, { withFileTypes: true });
|
|
15184
15842
|
for (const entry of entries) {
|
|
15185
|
-
const srcPath =
|
|
15186
|
-
const destPath =
|
|
15843
|
+
const srcPath = path19.join(src, entry.name);
|
|
15844
|
+
const destPath = path19.join(dest, entry.name);
|
|
15187
15845
|
if (entry.isDirectory()) copyDirSync2(srcPath, destPath);
|
|
15188
|
-
else
|
|
15846
|
+
else fs9.copyFileSync(srcPath, destPath);
|
|
15189
15847
|
}
|
|
15190
15848
|
}
|
|
15191
15849
|
function readDir(dir, source) {
|
|
15192
|
-
if (!
|
|
15193
|
-
const entries =
|
|
15850
|
+
if (!fs9.existsSync(dir)) return [];
|
|
15851
|
+
const entries = fs9.readdirSync(dir, { withFileTypes: true });
|
|
15194
15852
|
const skills = [];
|
|
15195
15853
|
for (const entry of entries) {
|
|
15196
15854
|
if (!entry.isDirectory()) continue;
|
|
15197
|
-
const skillFile =
|
|
15198
|
-
if (!
|
|
15855
|
+
const skillFile = path19.join(dir, entry.name, "SKILL.md");
|
|
15856
|
+
if (!fs9.existsSync(skillFile)) continue;
|
|
15199
15857
|
const parsed = parseSkillFile(skillFile, entry.name, source);
|
|
15200
15858
|
if (parsed) skills.push(parsed);
|
|
15201
15859
|
}
|
|
@@ -15207,22 +15865,22 @@ var SkillsService = class {
|
|
|
15207
15865
|
}
|
|
15208
15866
|
workspaceRoot;
|
|
15209
15867
|
localDir() {
|
|
15210
|
-
return this.workspaceRoot ?
|
|
15868
|
+
return this.workspaceRoot ? path19.join(this.workspaceRoot, ".ordewell", "skills") : void 0;
|
|
15211
15869
|
}
|
|
15212
15870
|
globalDir() {
|
|
15213
|
-
return
|
|
15871
|
+
return path19.join(globalDataDir(), "skills");
|
|
15214
15872
|
}
|
|
15215
15873
|
seed(name) {
|
|
15216
|
-
const dest =
|
|
15217
|
-
if (
|
|
15874
|
+
const dest = path19.join(this.globalDir(), name);
|
|
15875
|
+
if (fs9.existsSync(dest)) return false;
|
|
15218
15876
|
let srcDir;
|
|
15219
15877
|
try {
|
|
15220
|
-
srcDir =
|
|
15878
|
+
srcDir = path19.join(builtinSkillsDir(), name);
|
|
15221
15879
|
} catch (err) {
|
|
15222
15880
|
console.warn(`Could not locate built-in skills dir: ${String(err)}`);
|
|
15223
15881
|
return false;
|
|
15224
15882
|
}
|
|
15225
|
-
if (!
|
|
15883
|
+
if (!fs9.existsSync(srcDir)) {
|
|
15226
15884
|
console.warn(`Built-in skill not found in package: ${name}`);
|
|
15227
15885
|
return false;
|
|
15228
15886
|
}
|
|
@@ -15236,14 +15894,14 @@ var SkillsService = class {
|
|
|
15236
15894
|
isUnmodifiedRetiredSeed(dir, expectedName) {
|
|
15237
15895
|
let entries;
|
|
15238
15896
|
try {
|
|
15239
|
-
entries =
|
|
15897
|
+
entries = fs9.readdirSync(dir, { withFileTypes: true });
|
|
15240
15898
|
} catch {
|
|
15241
15899
|
return false;
|
|
15242
15900
|
}
|
|
15243
15901
|
if (entries.length !== 1 || entries[0].name !== "SKILL.md" || !entries[0].isFile()) return false;
|
|
15244
15902
|
let raw;
|
|
15245
15903
|
try {
|
|
15246
|
-
raw =
|
|
15904
|
+
raw = fs9.readFileSync(path19.join(dir, "SKILL.md"), "utf8");
|
|
15247
15905
|
} catch {
|
|
15248
15906
|
return false;
|
|
15249
15907
|
}
|
|
@@ -15260,11 +15918,11 @@ var SkillsService = class {
|
|
|
15260
15918
|
}
|
|
15261
15919
|
pruneRetired() {
|
|
15262
15920
|
for (const name of RETIRED_BUILTIN_SKILL_NAMES) {
|
|
15263
|
-
const dest =
|
|
15264
|
-
if (!
|
|
15921
|
+
const dest = path19.join(this.globalDir(), name);
|
|
15922
|
+
if (!fs9.existsSync(dest)) continue;
|
|
15265
15923
|
if (!this.isUnmodifiedRetiredSeed(dest, name)) continue;
|
|
15266
15924
|
try {
|
|
15267
|
-
|
|
15925
|
+
fs9.rmSync(dest, { recursive: true, force: true });
|
|
15268
15926
|
} catch (err) {
|
|
15269
15927
|
console.warn(`Could not prune retired skill ${name}: ${String(err)}`);
|
|
15270
15928
|
}
|
|
@@ -15275,11 +15933,11 @@ var SkillsService = class {
|
|
|
15275
15933
|
if (BUILTIN_SKILL_NAMES.includes(name)) this.seed(name);
|
|
15276
15934
|
const localDir = this.localDir();
|
|
15277
15935
|
if (localDir) {
|
|
15278
|
-
const localFile =
|
|
15279
|
-
if (
|
|
15936
|
+
const localFile = path19.join(localDir, name, "SKILL.md");
|
|
15937
|
+
if (fs9.existsSync(localFile)) return parseSkillFile(localFile, name, "local");
|
|
15280
15938
|
}
|
|
15281
|
-
const globalFile =
|
|
15282
|
-
if (
|
|
15939
|
+
const globalFile = path19.join(this.globalDir(), name, "SKILL.md");
|
|
15940
|
+
if (fs9.existsSync(globalFile)) return parseSkillFile(globalFile, name, "global");
|
|
15283
15941
|
return void 0;
|
|
15284
15942
|
}
|
|
15285
15943
|
listSkills() {
|
|
@@ -15360,8 +16018,8 @@ function truncateCheckpointSummary(summary) {
|
|
|
15360
16018
|
}
|
|
15361
16019
|
|
|
15362
16020
|
// src/utils/sessionStore.ts
|
|
15363
|
-
var
|
|
15364
|
-
var
|
|
16021
|
+
var fs10 = __toESM(require("fs"));
|
|
16022
|
+
var path20 = __toESM(require("path"));
|
|
15365
16023
|
|
|
15366
16024
|
// src/utils/sessionId.ts
|
|
15367
16025
|
var import_crypto2 = require("crypto");
|
|
@@ -15372,7 +16030,7 @@ function mintSessionId() {
|
|
|
15372
16030
|
// src/utils/sessionStore.ts
|
|
15373
16031
|
var SESSIONS_SUBDIR = "sessions";
|
|
15374
16032
|
function getSessionsDir(baseDir) {
|
|
15375
|
-
return
|
|
16033
|
+
return path20.join(getStateDir(baseDir), SESSIONS_SUBDIR);
|
|
15376
16034
|
}
|
|
15377
16035
|
var REJECTION_MESSAGE = "This session was created with an older version of Ordewell. Please re-plan.";
|
|
15378
16036
|
function hasScalarRunner(parsed) {
|
|
@@ -15394,11 +16052,11 @@ function legacySessionFilename(session) {
|
|
|
15394
16052
|
return `${filenameStem(session)}_${session.id.slice(0, 8)}.json`;
|
|
15395
16053
|
}
|
|
15396
16054
|
function dropLegacyFile(sessionsDir, meta) {
|
|
15397
|
-
const legacy =
|
|
15398
|
-
if (legacy ===
|
|
16055
|
+
const legacy = path20.join(sessionsDir, legacySessionFilename(meta));
|
|
16056
|
+
if (legacy === path20.join(sessionsDir, sessionFilename(meta)) || !fs10.existsSync(legacy)) return;
|
|
15399
16057
|
try {
|
|
15400
|
-
const held = JSON.parse(
|
|
15401
|
-
if (held === meta.id)
|
|
16058
|
+
const held = JSON.parse(fs10.readFileSync(legacy, "utf-8")).meta?.id;
|
|
16059
|
+
if (held === meta.id) fs10.unlinkSync(legacy);
|
|
15402
16060
|
} catch {
|
|
15403
16061
|
}
|
|
15404
16062
|
}
|
|
@@ -15421,18 +16079,18 @@ function saveSession(plan, goal, baseDir, id) {
|
|
|
15421
16079
|
plan: JSON.parse(JSON.stringify(plan))
|
|
15422
16080
|
};
|
|
15423
16081
|
const filename = sessionFilename(meta);
|
|
15424
|
-
const filePath =
|
|
15425
|
-
|
|
16082
|
+
const filePath = path20.join(sessionsDir, filename);
|
|
16083
|
+
fs10.writeFileSync(filePath, JSON.stringify(session, null, 2));
|
|
15426
16084
|
dropLegacyFile(sessionsDir, meta);
|
|
15427
16085
|
return meta;
|
|
15428
16086
|
}
|
|
15429
16087
|
function listSessions(baseDir, logger = defaultLogger) {
|
|
15430
16088
|
const sessionsDir = getSessionsDir(baseDir);
|
|
15431
|
-
if (!
|
|
15432
|
-
const files =
|
|
16089
|
+
if (!fs10.existsSync(sessionsDir)) return [];
|
|
16090
|
+
const files = fs10.readdirSync(sessionsDir).filter((f) => f.endsWith(".json"));
|
|
15433
16091
|
return files.map((f) => {
|
|
15434
16092
|
try {
|
|
15435
|
-
const raw =
|
|
16093
|
+
const raw = fs10.readFileSync(path20.join(sessionsDir, f), "utf-8");
|
|
15436
16094
|
const parsed = JSON.parse(raw);
|
|
15437
16095
|
if (hasScalarRunner(parsed)) {
|
|
15438
16096
|
logger.warn("sessionStore", `session file ${f}: ${REJECTION_MESSAGE}`);
|
|
@@ -15447,10 +16105,10 @@ function listSessions(baseDir, logger = defaultLogger) {
|
|
|
15447
16105
|
}).filter((m) => m !== null).sort((a, b) => new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime());
|
|
15448
16106
|
}
|
|
15449
16107
|
function findSessionFile(sessionsDir, sessionId, logger) {
|
|
15450
|
-
const files =
|
|
16108
|
+
const files = fs10.readdirSync(sessionsDir).filter((f) => f.endsWith(".json"));
|
|
15451
16109
|
for (const f of files) {
|
|
15452
16110
|
try {
|
|
15453
|
-
const raw =
|
|
16111
|
+
const raw = fs10.readFileSync(path20.join(sessionsDir, f), "utf-8");
|
|
15454
16112
|
const session = JSON.parse(raw);
|
|
15455
16113
|
if (session.meta.id === sessionId) return f;
|
|
15456
16114
|
} catch (err) {
|
|
@@ -15461,11 +16119,11 @@ function findSessionFile(sessionsDir, sessionId, logger) {
|
|
|
15461
16119
|
}
|
|
15462
16120
|
function loadSession(sessionId, baseDir, logger = defaultLogger) {
|
|
15463
16121
|
const sessionsDir = getSessionsDir(baseDir);
|
|
15464
|
-
if (!
|
|
16122
|
+
if (!fs10.existsSync(sessionsDir)) return null;
|
|
15465
16123
|
try {
|
|
15466
16124
|
const file = findSessionFile(sessionsDir, sessionId, logger);
|
|
15467
16125
|
if (file) {
|
|
15468
|
-
const raw =
|
|
16126
|
+
const raw = fs10.readFileSync(path20.join(sessionsDir, file), "utf-8");
|
|
15469
16127
|
const parsed = JSON.parse(raw);
|
|
15470
16128
|
if (hasScalarRunner(parsed)) {
|
|
15471
16129
|
logger.warn("sessionStore", `session file ${file}: ${REJECTION_MESSAGE}`);
|
|
@@ -15496,11 +16154,11 @@ function getLatestSession(baseDir, logger = defaultLogger) {
|
|
|
15496
16154
|
}
|
|
15497
16155
|
function deleteSession(sessionId, baseDir, logger = defaultLogger) {
|
|
15498
16156
|
const sessionsDir = getSessionsDir(baseDir);
|
|
15499
|
-
if (!
|
|
16157
|
+
if (!fs10.existsSync(sessionsDir)) return false;
|
|
15500
16158
|
try {
|
|
15501
16159
|
const file = findSessionFile(sessionsDir, sessionId, logger);
|
|
15502
16160
|
if (file) {
|
|
15503
|
-
|
|
16161
|
+
fs10.unlinkSync(path20.join(sessionsDir, file));
|
|
15504
16162
|
return true;
|
|
15505
16163
|
}
|
|
15506
16164
|
} catch (err) {
|
|
@@ -16032,26 +16690,30 @@ var Session = class {
|
|
|
16032
16690
|
*/
|
|
16033
16691
|
forkConversation() {
|
|
16034
16692
|
if (!this.plan) throw new ConversationEditError("No planning conversation to fork");
|
|
16035
|
-
|
|
16036
|
-
const sessionId = mintSessionId();
|
|
16037
|
-
const plan = forkPlanState(this.plan, this.store.planTasks, dialogue, (/* @__PURE__ */ new Date()).toISOString());
|
|
16038
|
-
saveSession(plan, this.goal, this.workspace, sessionId);
|
|
16039
|
-
return { sessionId, goal: this.goal, workspace: this.workspace };
|
|
16693
|
+
return this.saveFork(this.conversation.clone());
|
|
16040
16694
|
}
|
|
16041
16695
|
/**
|
|
16042
|
-
*
|
|
16043
|
-
* position from {@link rewindTargets})
|
|
16044
|
-
*
|
|
16045
|
-
*
|
|
16696
|
+
* Fork the conversation from just before a user message (a transcript
|
|
16697
|
+
* position from {@link rewindTargets}): the fork holds everything said
|
|
16698
|
+
* before it and the current task list, like {@link forkConversation}, and
|
|
16699
|
+
* this session keeps its whole history. The message's full text comes back
|
|
16700
|
+
* so a surface can offer it for resending.
|
|
16046
16701
|
*/
|
|
16047
16702
|
rewindConversation(userMessageIndex) {
|
|
16048
16703
|
if (!this.plan) throw new ConversationEditError("No planning conversation to rewind");
|
|
16049
|
-
|
|
16704
|
+
const { dialogue, rewoundMessage } = this.conversation.cloneBefore(userMessageIndex);
|
|
16705
|
+
return { ...this.saveFork(dialogue), rewoundMessage };
|
|
16706
|
+
}
|
|
16707
|
+
saveFork(dialogue) {
|
|
16708
|
+
const sessionId = mintSessionId();
|
|
16709
|
+
const plan = forkPlanState(this.plan, this.store.planTasks, dialogue, (/* @__PURE__ */ new Date()).toISOString());
|
|
16710
|
+
saveSession(plan, this.goal, this.workspace, sessionId);
|
|
16711
|
+
return { sessionId, goal: this.goal, workspace: this.workspace };
|
|
16050
16712
|
}
|
|
16051
16713
|
/**
|
|
16052
16714
|
* Condense the conversation on the user's say-so: a hidden planner turn
|
|
16053
16715
|
* summarises it, and the summary replaces everything but the last two
|
|
16054
|
-
* exchanges. Conversation only
|
|
16716
|
+
* exchanges. Conversation only — the tasks, and any run
|
|
16055
16717
|
* executing them, are untouched. Atomic: a failed or stopped turn changes
|
|
16056
16718
|
* nothing.
|
|
16057
16719
|
*/
|
|
@@ -16177,6 +16839,10 @@ var Session = class {
|
|
|
16177
16839
|
}
|
|
16178
16840
|
await this.orchestrator.runTask(taskId);
|
|
16179
16841
|
}
|
|
16842
|
+
/** Start whatever the scheduler can now fit — after the parallel limit was raised mid-run, say. */
|
|
16843
|
+
async reschedule() {
|
|
16844
|
+
await this.orchestrator.tick();
|
|
16845
|
+
}
|
|
16180
16846
|
async retryTask(taskId) {
|
|
16181
16847
|
await this.orchestrator.retryTask(taskId);
|
|
16182
16848
|
this.persist();
|
|
@@ -16324,8 +16990,9 @@ var Session = class {
|
|
|
16324
16990
|
* The opt-in way through a merge conflict: add an AI task, on the conflicted
|
|
16325
16991
|
* task's own runner and model, that merges its branch by hand in a worktree
|
|
16326
16992
|
* of its own. When that task lands, the conflicted one lands through it (see
|
|
16327
|
-
* {@link TaskOrchestrator.linkConflictResolver}).
|
|
16328
|
-
*
|
|
16993
|
+
* {@link TaskOrchestrator.linkConflictResolver}). The automatic answer to a
|
|
16994
|
+
* conflict is a repair, a new attempt of the same task (ADR-0015); a task is
|
|
16995
|
+
* only ever added to the plan by this call.
|
|
16329
16996
|
*/
|
|
16330
16997
|
async resolveConflictAsTask(taskId) {
|
|
16331
16998
|
if (!this.plan) return null;
|
|
@@ -16933,7 +17600,7 @@ function buildRunnerInvocation(opts) {
|
|
|
16933
17600
|
}
|
|
16934
17601
|
|
|
16935
17602
|
// src/services/HeadlessRunner.ts
|
|
16936
|
-
var
|
|
17603
|
+
var import_child_process8 = require("child_process");
|
|
16937
17604
|
|
|
16938
17605
|
// src/services/AbstractRunner.ts
|
|
16939
17606
|
var import_events = require("events");
|
|
@@ -16988,7 +17655,7 @@ var AbstractRunner = class {
|
|
|
16988
17655
|
function defaultHasScriptCmd() {
|
|
16989
17656
|
if (process.platform === "win32") return false;
|
|
16990
17657
|
try {
|
|
16991
|
-
(0,
|
|
17658
|
+
(0, import_child_process8.execSync)("which script", { stdio: "ignore" });
|
|
16992
17659
|
return true;
|
|
16993
17660
|
} catch {
|
|
16994
17661
|
return false;
|
|
@@ -17078,7 +17745,7 @@ var HeadlessRunner = class extends AbstractRunner {
|
|
|
17078
17745
|
defaultInteractive = false;
|
|
17079
17746
|
constructor(deps = {}) {
|
|
17080
17747
|
super();
|
|
17081
|
-
this.spawnImpl = deps.spawnImpl ??
|
|
17748
|
+
this.spawnImpl = deps.spawnImpl ?? import_child_process8.spawn;
|
|
17082
17749
|
this.hasScriptCmd = deps.hasScriptCmd ?? defaultHasScriptCmd;
|
|
17083
17750
|
this.resolvePath = deps.resolvePath ?? augmentedPath;
|
|
17084
17751
|
this.launchDeps = { resolvePath: this.resolvePath, ...deps.launchDeps };
|
|
@@ -17118,7 +17785,7 @@ var HeadlessRunner = class extends AbstractRunner {
|
|
|
17118
17785
|
return {
|
|
17119
17786
|
launch: await planDirectLaunch(command, args, this.launchDeps),
|
|
17120
17787
|
resolvedPath,
|
|
17121
|
-
env: invocation.env,
|
|
17788
|
+
env: { ...opts.env, ...invocation.env },
|
|
17122
17789
|
pty,
|
|
17123
17790
|
submitPromptKey: invocation.submitPromptKey
|
|
17124
17791
|
};
|
|
@@ -17137,14 +17804,14 @@ var HeadlessRunner = class extends AbstractRunner {
|
|
|
17137
17804
|
};
|
|
17138
17805
|
|
|
17139
17806
|
// src/services/TmuxRunner.ts
|
|
17140
|
-
var
|
|
17807
|
+
var import_child_process10 = require("child_process");
|
|
17141
17808
|
var import_util5 = require("util");
|
|
17142
17809
|
var import_os = require("os");
|
|
17143
17810
|
var import_path = require("path");
|
|
17144
17811
|
var import_fs2 = require("fs");
|
|
17145
17812
|
|
|
17146
17813
|
// src/utils/tmux.ts
|
|
17147
|
-
var
|
|
17814
|
+
var import_child_process9 = require("child_process");
|
|
17148
17815
|
function tmuxSessionName(port) {
|
|
17149
17816
|
return `ordewell-${port}`;
|
|
17150
17817
|
}
|
|
@@ -17157,7 +17824,7 @@ function tmuxWindowName(taskId, planSessionId) {
|
|
|
17157
17824
|
return `t-${scope}${slug(taskId).slice(0, 16)}`;
|
|
17158
17825
|
}
|
|
17159
17826
|
var defaultProbe = () => {
|
|
17160
|
-
(0,
|
|
17827
|
+
(0, import_child_process9.execSync)(process.platform === "win32" ? "where tmux" : "which tmux", { stdio: "ignore" });
|
|
17161
17828
|
};
|
|
17162
17829
|
function hasTmux(probe = defaultProbe) {
|
|
17163
17830
|
try {
|
|
@@ -17169,7 +17836,7 @@ function hasTmux(probe = defaultProbe) {
|
|
|
17169
17836
|
}
|
|
17170
17837
|
var defaultHasBin = (bin) => {
|
|
17171
17838
|
try {
|
|
17172
|
-
(0,
|
|
17839
|
+
(0, import_child_process9.execSync)(`${process.platform === "win32" ? "where" : "which"} ${bin}`, { stdio: "ignore" });
|
|
17173
17840
|
return true;
|
|
17174
17841
|
} catch {
|
|
17175
17842
|
return false;
|
|
@@ -17192,7 +17859,7 @@ function clipboardCopyCommand(hasBin = defaultHasBin, platform = process.platfor
|
|
|
17192
17859
|
|
|
17193
17860
|
// src/services/TmuxRunner.ts
|
|
17194
17861
|
var EXIT_RE = /<<<ORDEWELL_TMUX_EXIT:(\d+)>>>/;
|
|
17195
|
-
var execFileAsync2 = (0, import_util5.promisify)(
|
|
17862
|
+
var execFileAsync2 = (0, import_util5.promisify)(import_child_process10.execFile);
|
|
17196
17863
|
var defaultExecFile3 = async (command, args) => {
|
|
17197
17864
|
const { stdout, stderr } = await execFileAsync2(command, args);
|
|
17198
17865
|
return { stdout: String(stdout), stderr: String(stderr) };
|
|
@@ -17430,7 +18097,7 @@ var TmuxRunner = class extends AbstractRunner {
|
|
|
17430
18097
|
(0, import_path.join)(this.logDir, `${id}.log`),
|
|
17431
18098
|
this.socket
|
|
17432
18099
|
);
|
|
17433
|
-
invocation.env = { ...invocation.env, PATH: await this.resolvePath() };
|
|
18100
|
+
invocation.env = { ...opts.env, ...invocation.env, PATH: await this.resolvePath() };
|
|
17434
18101
|
await session.start(invocation.command, invocation.args, opts.cwd, invocation.env);
|
|
17435
18102
|
if (invocation.submitPromptKey) session.write("\r");
|
|
17436
18103
|
this.registerSession(id, session);
|
|
@@ -17510,18 +18177,18 @@ function tokensMatch(presented, expected) {
|
|
|
17510
18177
|
}
|
|
17511
18178
|
|
|
17512
18179
|
// src/plugins/RunnerRegistry.ts
|
|
17513
|
-
var
|
|
17514
|
-
var
|
|
17515
|
-
var
|
|
17516
|
-
var
|
|
18180
|
+
var fs12 = __toESM(require("fs"));
|
|
18181
|
+
var path23 = __toESM(require("path"));
|
|
18182
|
+
var os6 = __toESM(require("os"));
|
|
18183
|
+
var import_child_process11 = require("child_process");
|
|
17517
18184
|
var import_crypto4 = require("crypto");
|
|
17518
18185
|
|
|
17519
18186
|
// src/plugins/FsPluginStore.ts
|
|
17520
|
-
var
|
|
17521
|
-
var
|
|
18187
|
+
var fs11 = __toESM(require("fs"));
|
|
18188
|
+
var path22 = __toESM(require("path"));
|
|
17522
18189
|
|
|
17523
18190
|
// src/plugins/pluginNames.ts
|
|
17524
|
-
var
|
|
18191
|
+
var path21 = __toESM(require("path"));
|
|
17525
18192
|
var PLUGIN_NAME_PATTERN = /^[A-Za-z0-9](?:[A-Za-z0-9._-]*[A-Za-z0-9])?$/;
|
|
17526
18193
|
var MAX_PLUGIN_NAME_LENGTH = 64;
|
|
17527
18194
|
function isPlainPluginName(name) {
|
|
@@ -17536,10 +18203,10 @@ function assertPlainPluginName(name) {
|
|
|
17536
18203
|
}
|
|
17537
18204
|
function resolvePluginInstallDir(pluginsDir, name) {
|
|
17538
18205
|
assertPlainPluginName(name);
|
|
17539
|
-
const root =
|
|
17540
|
-
const dest =
|
|
17541
|
-
const
|
|
17542
|
-
if (
|
|
18206
|
+
const root = path21.resolve(pluginsDir);
|
|
18207
|
+
const dest = path21.resolve(root, name);
|
|
18208
|
+
const relative5 = path21.relative(root, dest);
|
|
18209
|
+
if (relative5 !== name || relative5 === "" || path21.isAbsolute(relative5)) {
|
|
17543
18210
|
throw new Error(`Plugin install path escapes the plugins directory: ${JSON.stringify(name)}`);
|
|
17544
18211
|
}
|
|
17545
18212
|
return dest;
|
|
@@ -17554,13 +18221,13 @@ function isValidManifest(obj) {
|
|
|
17554
18221
|
|
|
17555
18222
|
// src/plugins/FsPluginStore.ts
|
|
17556
18223
|
function userPluginsDir() {
|
|
17557
|
-
return
|
|
18224
|
+
return path22.join(globalDataDir(), "plugins");
|
|
17558
18225
|
}
|
|
17559
18226
|
function loadManifestFromDir(dir) {
|
|
17560
|
-
const manifestPath =
|
|
17561
|
-
if (!
|
|
18227
|
+
const manifestPath = path22.join(dir, "manifest.json");
|
|
18228
|
+
if (!fs11.existsSync(manifestPath)) return null;
|
|
17562
18229
|
try {
|
|
17563
|
-
const raw =
|
|
18230
|
+
const raw = fs11.readFileSync(manifestPath, "utf8");
|
|
17564
18231
|
const parsed = JSON.parse(raw);
|
|
17565
18232
|
if (!isValidManifest(parsed)) return null;
|
|
17566
18233
|
return parsed;
|
|
@@ -17570,29 +18237,29 @@ function loadManifestFromDir(dir) {
|
|
|
17570
18237
|
}
|
|
17571
18238
|
function copyDirSync3(src, dest) {
|
|
17572
18239
|
ensureDir(dest);
|
|
17573
|
-
const entries =
|
|
18240
|
+
const entries = fs11.readdirSync(src, { withFileTypes: true });
|
|
17574
18241
|
for (const entry of entries) {
|
|
17575
|
-
const srcPath =
|
|
17576
|
-
const destPath =
|
|
18242
|
+
const srcPath = path22.join(src, entry.name);
|
|
18243
|
+
const destPath = path22.join(dest, entry.name);
|
|
17577
18244
|
if (entry.isDirectory()) {
|
|
17578
18245
|
copyDirSync3(srcPath, destPath);
|
|
17579
18246
|
} else {
|
|
17580
|
-
|
|
18247
|
+
fs11.copyFileSync(srcPath, destPath);
|
|
17581
18248
|
}
|
|
17582
18249
|
}
|
|
17583
18250
|
}
|
|
17584
18251
|
function rmDirSync(dir) {
|
|
17585
|
-
if (!
|
|
17586
|
-
const entries =
|
|
18252
|
+
if (!fs11.existsSync(dir)) return;
|
|
18253
|
+
const entries = fs11.readdirSync(dir, { withFileTypes: true });
|
|
17587
18254
|
for (const entry of entries) {
|
|
17588
|
-
const p =
|
|
18255
|
+
const p = path22.join(dir, entry.name);
|
|
17589
18256
|
if (entry.isDirectory()) {
|
|
17590
18257
|
rmDirSync(p);
|
|
17591
18258
|
} else {
|
|
17592
|
-
|
|
18259
|
+
fs11.unlinkSync(p);
|
|
17593
18260
|
}
|
|
17594
18261
|
}
|
|
17595
|
-
|
|
18262
|
+
fs11.rmdirSync(dir);
|
|
17596
18263
|
}
|
|
17597
18264
|
var FsPluginStore = class {
|
|
17598
18265
|
getUserPluginsDir() {
|
|
@@ -17600,10 +18267,10 @@ var FsPluginStore = class {
|
|
|
17600
18267
|
}
|
|
17601
18268
|
listUserPluginDirs() {
|
|
17602
18269
|
const dir = userPluginsDir();
|
|
17603
|
-
if (!
|
|
18270
|
+
if (!fs11.existsSync(dir)) return [];
|
|
17604
18271
|
let entries;
|
|
17605
18272
|
try {
|
|
17606
|
-
entries =
|
|
18273
|
+
entries = fs11.readdirSync(dir, { withFileTypes: true });
|
|
17607
18274
|
} catch {
|
|
17608
18275
|
return [];
|
|
17609
18276
|
}
|
|
@@ -17611,7 +18278,7 @@ var FsPluginStore = class {
|
|
|
17611
18278
|
}
|
|
17612
18279
|
listDir(dir) {
|
|
17613
18280
|
try {
|
|
17614
|
-
return
|
|
18281
|
+
return fs11.readdirSync(dir);
|
|
17615
18282
|
} catch {
|
|
17616
18283
|
return [];
|
|
17617
18284
|
}
|
|
@@ -17629,20 +18296,20 @@ var FsPluginStore = class {
|
|
|
17629
18296
|
ensureDir(dir);
|
|
17630
18297
|
}
|
|
17631
18298
|
writeFile(filePath, content) {
|
|
17632
|
-
|
|
18299
|
+
fs11.writeFileSync(filePath, content);
|
|
17633
18300
|
}
|
|
17634
18301
|
readFile(filePath) {
|
|
17635
18302
|
try {
|
|
17636
|
-
return
|
|
18303
|
+
return fs11.readFileSync(filePath, "utf8");
|
|
17637
18304
|
} catch {
|
|
17638
18305
|
return null;
|
|
17639
18306
|
}
|
|
17640
18307
|
}
|
|
17641
|
-
dirExists(
|
|
17642
|
-
return
|
|
18308
|
+
dirExists(path26) {
|
|
18309
|
+
return fs11.existsSync(path26) && fs11.statSync(path26).isDirectory();
|
|
17643
18310
|
}
|
|
17644
|
-
exists(
|
|
17645
|
-
return
|
|
18311
|
+
exists(path26) {
|
|
18312
|
+
return fs11.existsSync(path26);
|
|
17646
18313
|
}
|
|
17647
18314
|
};
|
|
17648
18315
|
|
|
@@ -17669,7 +18336,15 @@ var CLAUDE_CODE_MANIFEST = {
|
|
|
17669
18336
|
"{{/if}}",
|
|
17670
18337
|
"{{prompt}}"
|
|
17671
18338
|
],
|
|
17672
|
-
promptInArgs: true
|
|
18339
|
+
promptInArgs: true,
|
|
18340
|
+
// Interactive sessions show these even with --dangerously-skip-permissions:
|
|
18341
|
+
// a folder Claude Code has never been trusted in (a fresh clone, or a repo
|
|
18342
|
+
// only ever planned in headlessly), and the first Bypass Permissions run.
|
|
18343
|
+
blockingPrompts: [
|
|
18344
|
+
{ phrase: "Is this a project you created or one you trust", asks: "whether to trust the task's folder" },
|
|
18345
|
+
{ phrase: "Do you trust the files in this folder", asks: "whether to trust the task's folder" },
|
|
18346
|
+
{ phrase: "running in Bypass Permissions mode", asks: "you to accept Bypass Permissions mode" }
|
|
18347
|
+
]
|
|
17673
18348
|
},
|
|
17674
18349
|
features: {
|
|
17675
18350
|
modelSelection: true,
|
|
@@ -18013,7 +18688,7 @@ function isReservedRunnerName(name) {
|
|
|
18013
18688
|
return RESERVED_RUNNER_NAMES.has(name.toLowerCase());
|
|
18014
18689
|
}
|
|
18015
18690
|
function gitClone(url, destDir) {
|
|
18016
|
-
(0,
|
|
18691
|
+
(0, import_child_process11.execFileSync)("git", ["clone", "--depth", "1", "--", url, destDir], {
|
|
18017
18692
|
stdio: "pipe",
|
|
18018
18693
|
timeout: 3e4,
|
|
18019
18694
|
// A repository that demands credentials should fail, not hang on a prompt.
|
|
@@ -18074,7 +18749,7 @@ var RunnerRegistry = class {
|
|
|
18074
18749
|
return entry?.source === "builtin";
|
|
18075
18750
|
}
|
|
18076
18751
|
installFromPath(sourcePath) {
|
|
18077
|
-
const absPath =
|
|
18752
|
+
const absPath = path23.resolve(sourcePath);
|
|
18078
18753
|
if (!this.store.dirExists(absPath)) {
|
|
18079
18754
|
throw new Error(`Plugin source must be a directory: ${absPath}`);
|
|
18080
18755
|
}
|
|
@@ -18086,7 +18761,7 @@ var RunnerRegistry = class {
|
|
|
18086
18761
|
}
|
|
18087
18762
|
installFromGit(url) {
|
|
18088
18763
|
const validated = assertInstallablePluginUrl(url);
|
|
18089
|
-
const tmpDir =
|
|
18764
|
+
const tmpDir = path23.join(os6.tmpdir(), `ordewell-plugin-${(0, import_crypto4.randomBytes)(12).toString("hex")}`);
|
|
18090
18765
|
this.store.ensureDir(tmpDir);
|
|
18091
18766
|
try {
|
|
18092
18767
|
try {
|
|
@@ -18099,7 +18774,7 @@ var RunnerRegistry = class {
|
|
|
18099
18774
|
const hasManifest = contents.some((f) => f === "manifest.json");
|
|
18100
18775
|
let sourceDir = tmpDir;
|
|
18101
18776
|
if (!hasManifest) {
|
|
18102
|
-
const onlyEntry = contents.length === 1 ?
|
|
18777
|
+
const onlyEntry = contents.length === 1 ? path23.join(tmpDir, contents[0]) : void 0;
|
|
18103
18778
|
if (!onlyEntry || !this.store.dirExists(onlyEntry)) {
|
|
18104
18779
|
throw new Error(`No manifest.json found in repository: ${validated.toString()}`);
|
|
18105
18780
|
}
|
|
@@ -18147,7 +18822,7 @@ var RunnerRegistry = class {
|
|
|
18147
18822
|
}
|
|
18148
18823
|
createSkeleton(name, outputDir) {
|
|
18149
18824
|
assertPlainPluginName(name);
|
|
18150
|
-
const dir =
|
|
18825
|
+
const dir = path23.resolve(outputDir, name);
|
|
18151
18826
|
this.store.ensureDir(dir);
|
|
18152
18827
|
const manifest = {
|
|
18153
18828
|
name,
|
|
@@ -18179,8 +18854,8 @@ var RunnerRegistry = class {
|
|
|
18179
18854
|
]
|
|
18180
18855
|
}
|
|
18181
18856
|
};
|
|
18182
|
-
this.store.writeFile(
|
|
18183
|
-
const runSh =
|
|
18857
|
+
this.store.writeFile(path23.join(dir, "manifest.json"), JSON.stringify(manifest, null, 2) + "\n");
|
|
18858
|
+
const runSh = path23.join(dir, "run.sh");
|
|
18184
18859
|
const scriptContent = `#!/usr/bin/env bash
|
|
18185
18860
|
# Custom runner script for ${name}
|
|
18186
18861
|
# Receives:
|
|
@@ -18206,30 +18881,30 @@ echo "Running ${name} with prompt: $prompt"
|
|
|
18206
18881
|
};
|
|
18207
18882
|
function fsChmodSync(fpath, mode) {
|
|
18208
18883
|
try {
|
|
18209
|
-
|
|
18884
|
+
fs12.chmodSync(fpath, mode);
|
|
18210
18885
|
} catch {
|
|
18211
18886
|
}
|
|
18212
18887
|
}
|
|
18213
18888
|
|
|
18214
18889
|
// src/utils/stateStore.ts
|
|
18215
|
-
var
|
|
18216
|
-
var
|
|
18890
|
+
var fs13 = __toESM(require("fs"));
|
|
18891
|
+
var path24 = __toESM(require("path"));
|
|
18217
18892
|
var STATE_FILE = "state.json";
|
|
18218
18893
|
function getStatePath(baseDir) {
|
|
18219
|
-
return
|
|
18894
|
+
return path24.join(getStateDir(baseDir), STATE_FILE);
|
|
18220
18895
|
}
|
|
18221
18896
|
function saveState(plan, baseDir) {
|
|
18222
18897
|
const statePath = getStatePath(baseDir ?? process.cwd());
|
|
18223
|
-
ensureDir(
|
|
18898
|
+
ensureDir(path24.dirname(statePath));
|
|
18224
18899
|
plan.lastUpdated = (/* @__PURE__ */ new Date()).toISOString();
|
|
18225
|
-
|
|
18900
|
+
fs13.writeFileSync(statePath, JSON.stringify(plan, null, 2));
|
|
18226
18901
|
}
|
|
18227
18902
|
function loadState(baseDir, logger = defaultLogger) {
|
|
18228
18903
|
const dir = baseDir ?? process.cwd();
|
|
18229
18904
|
const statePath = getStatePath(dir);
|
|
18230
|
-
if (!
|
|
18905
|
+
if (!fs13.existsSync(statePath)) return null;
|
|
18231
18906
|
try {
|
|
18232
|
-
const raw =
|
|
18907
|
+
const raw = fs13.readFileSync(statePath, "utf-8");
|
|
18233
18908
|
const parsed = JSON.parse(raw);
|
|
18234
18909
|
if (!Array.isArray(parsed.runners)) {
|
|
18235
18910
|
logger.warn("stateStore", `rejecting state from ${statePath}: old format (scalar "runner" instead of "runners" array). Please re-plan.`);
|
|
@@ -18246,16 +18921,16 @@ function loadState(baseDir, logger = defaultLogger) {
|
|
|
18246
18921
|
function clearState(baseDir) {
|
|
18247
18922
|
const dir = baseDir ?? process.cwd();
|
|
18248
18923
|
const statePath = getStatePath(dir);
|
|
18249
|
-
if (
|
|
18924
|
+
if (fs13.existsSync(statePath)) fs13.unlinkSync(statePath);
|
|
18250
18925
|
}
|
|
18251
18926
|
function stateExists(baseDir) {
|
|
18252
18927
|
const dir = baseDir ?? process.cwd();
|
|
18253
|
-
return
|
|
18928
|
+
return fs13.existsSync(getStatePath(dir));
|
|
18254
18929
|
}
|
|
18255
18930
|
|
|
18256
18931
|
// src/services/SettingsService.ts
|
|
18257
|
-
var
|
|
18258
|
-
var
|
|
18932
|
+
var fs14 = __toESM(require("fs"));
|
|
18933
|
+
var path25 = __toESM(require("path"));
|
|
18259
18934
|
var DEFAULTS = {
|
|
18260
18935
|
tdd: { enabled: true },
|
|
18261
18936
|
verification: { enabled: false }
|
|
@@ -18263,7 +18938,7 @@ var DEFAULTS = {
|
|
|
18263
18938
|
function getSettingsPath() {
|
|
18264
18939
|
const override = process.env.ORDEWELL_SETTINGS_PATH;
|
|
18265
18940
|
if (override && override.trim()) return override.trim();
|
|
18266
|
-
return
|
|
18941
|
+
return path25.join(globalDataDir(), "settings.json");
|
|
18267
18942
|
}
|
|
18268
18943
|
function readPlannerModels(raw) {
|
|
18269
18944
|
if (!raw || typeof raw !== "object" || Array.isArray(raw)) return void 0;
|
|
@@ -18296,7 +18971,7 @@ var SettingsService = class {
|
|
|
18296
18971
|
}
|
|
18297
18972
|
fileMtimeMs() {
|
|
18298
18973
|
try {
|
|
18299
|
-
return
|
|
18974
|
+
return fs14.statSync(this.filePath).mtimeMs;
|
|
18300
18975
|
} catch {
|
|
18301
18976
|
return null;
|
|
18302
18977
|
}
|
|
@@ -18368,8 +19043,8 @@ var SettingsService = class {
|
|
|
18368
19043
|
load() {
|
|
18369
19044
|
migrateOldConfigDir();
|
|
18370
19045
|
try {
|
|
18371
|
-
if (
|
|
18372
|
-
const raw = JSON.parse(
|
|
19046
|
+
if (fs14.existsSync(this.filePath)) {
|
|
19047
|
+
const raw = JSON.parse(fs14.readFileSync(this.filePath, "utf-8"));
|
|
18373
19048
|
const settings = {
|
|
18374
19049
|
tdd: { enabled: raw.tdd?.enabled ?? DEFAULTS.tdd.enabled },
|
|
18375
19050
|
verification: { enabled: raw.verification?.enabled ?? DEFAULTS.verification.enabled }
|
|
@@ -18391,11 +19066,11 @@ var SettingsService = class {
|
|
|
18391
19066
|
return { ...DEFAULTS, tdd: { ...DEFAULTS.tdd }, verification: { ...DEFAULTS.verification } };
|
|
18392
19067
|
}
|
|
18393
19068
|
persist() {
|
|
18394
|
-
const dir =
|
|
18395
|
-
if (!
|
|
18396
|
-
|
|
19069
|
+
const dir = path25.dirname(this.filePath);
|
|
19070
|
+
if (!fs14.existsSync(dir)) {
|
|
19071
|
+
fs14.mkdirSync(dir, { recursive: true });
|
|
18397
19072
|
}
|
|
18398
|
-
|
|
19073
|
+
fs14.writeFileSync(this.filePath, JSON.stringify(this.cache, null, 2));
|
|
18399
19074
|
this.cachedMtimeMs = this.fileMtimeMs();
|
|
18400
19075
|
}
|
|
18401
19076
|
};
|
|
@@ -18462,6 +19137,7 @@ var PlannerModelMemory = class {
|
|
|
18462
19137
|
ConversationEditError,
|
|
18463
19138
|
DAEMON_SUBPROTOCOL,
|
|
18464
19139
|
DAEMON_TOKEN_SUBPROTOCOL_PREFIX,
|
|
19140
|
+
DEFAULT_MAX_PARALLEL,
|
|
18465
19141
|
DENY_ALL,
|
|
18466
19142
|
EmbeddedNewlineError,
|
|
18467
19143
|
EnvConfig,
|
|
@@ -18535,6 +19211,7 @@ var PlannerModelMemory = class {
|
|
|
18535
19211
|
augmentPromptWithPriorOutputs,
|
|
18536
19212
|
augmentedPath,
|
|
18537
19213
|
bearerHeaderValue,
|
|
19214
|
+
buildConflictRepairPrompt,
|
|
18538
19215
|
buildConflictResolutionPrompt,
|
|
18539
19216
|
buildConversationSystemPrompt,
|
|
18540
19217
|
buildFallbackGrepArgs,
|
|
@@ -18554,6 +19231,7 @@ var PlannerModelMemory = class {
|
|
|
18554
19231
|
canMergeTasks,
|
|
18555
19232
|
canSetDependencies,
|
|
18556
19233
|
canSplitTask,
|
|
19234
|
+
capConflictFiles,
|
|
18557
19235
|
checkDepsResolve,
|
|
18558
19236
|
checkImmutableLog,
|
|
18559
19237
|
checkInProgress,
|
|
@@ -18642,6 +19320,7 @@ var PlannerModelMemory = class {
|
|
|
18642
19320
|
modifyValidationFeedback,
|
|
18643
19321
|
normalizeAgentArgs,
|
|
18644
19322
|
normalizeGeminiModel,
|
|
19323
|
+
parseMaxParallel,
|
|
18645
19324
|
parsePartialPlan,
|
|
18646
19325
|
parsePlanJson,
|
|
18647
19326
|
parseTaskOpsJson,
|