runcastle 1.2.9 → 1.2.10
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/bin/runcastle.js +295 -183
- package/drizzle/0030_productive_marvex.sql +1 -0
- package/drizzle/0031_backfill_feature_base_branch.sql +10 -0
- package/drizzle/0032_gigantic_tempest.sql +1 -0
- package/drizzle/meta/0030_snapshot.json +956 -0
- package/drizzle/meta/0031_snapshot.json +956 -0
- package/drizzle/meta/0032_snapshot.json +949 -0
- package/drizzle/meta/_journal.json +22 -1
- package/index.js +293 -181
- package/package.json +1 -1
- package/skills/packs/runcastle/skills/project/SKILL.md +11 -1
- package/web/assets/index-BCAA3_oq.css +1 -0
- package/web/assets/index-BlVLhZP1.js +142 -0
- package/web/index.html +2 -2
- package/web/assets/index-BRjcVBgj.js +0 -142
- package/web/assets/index-CNSn8X1O.css +0 -1
package/bin/runcastle.js
CHANGED
|
@@ -147,7 +147,6 @@ var init_config = __esm(() => {
|
|
|
147
147
|
models: z.array(ModelEntry).default([]),
|
|
148
148
|
sandbox: z.enum(["docker", "podman", "noSandbox"]).default("docker"),
|
|
149
149
|
sessionMcp: z.enum(["inherit", "runcastleOnly"]).default("inherit"),
|
|
150
|
-
mainBranch: z.string().default("main"),
|
|
151
150
|
sandboxImage: z.string().optional(),
|
|
152
151
|
burnConcurrency: z.number().int().min(1).max(8).default(3),
|
|
153
152
|
burnCpus: z.number().positive().max(256).optional(),
|
|
@@ -277,7 +276,7 @@ var init_schemas = __esm(() => {
|
|
|
277
276
|
id: z2.string(),
|
|
278
277
|
name: z2.string(),
|
|
279
278
|
repoPath: z2.string(),
|
|
280
|
-
|
|
279
|
+
sessionBranch: z2.string().optional(),
|
|
281
280
|
devCommand: z2.string().optional(),
|
|
282
281
|
model: z2.string().optional(),
|
|
283
282
|
setupCommand: z2.string().optional(),
|
|
@@ -544,7 +543,7 @@ var init_db_schema = __esm(() => {
|
|
|
544
543
|
id: text("id").primaryKey(),
|
|
545
544
|
name: text("name").notNull(),
|
|
546
545
|
repoPath: text("repo_path").notNull(),
|
|
547
|
-
|
|
546
|
+
sessionBranch: text("session_branch"),
|
|
548
547
|
devCommand: text("dev_command"),
|
|
549
548
|
model: text("model"),
|
|
550
549
|
sandbox: text("sandbox"),
|
|
@@ -817,8 +816,6 @@ function loadConfig(env = process.env) {
|
|
|
817
816
|
overrides.sandbox = env.RUNCASTLE_SANDBOX;
|
|
818
817
|
if (env.RUNCASTLE_SESSION_MCP)
|
|
819
818
|
overrides.sessionMcp = env.RUNCASTLE_SESSION_MCP;
|
|
820
|
-
if (env.RUNCASTLE_MAIN_BRANCH)
|
|
821
|
-
overrides.mainBranch = env.RUNCASTLE_MAIN_BRANCH;
|
|
822
819
|
if (env.RUNCASTLE_SANDBOX_IMAGE)
|
|
823
820
|
overrides.sandboxImage = env.RUNCASTLE_SANDBOX_IMAGE;
|
|
824
821
|
num("burnConcurrency", env.RUNCASTLE_BURN_CONCURRENCY);
|
|
@@ -1760,7 +1757,7 @@ function rowToProject(row) {
|
|
|
1760
1757
|
id: row.id,
|
|
1761
1758
|
name: row.name,
|
|
1762
1759
|
repoPath: row.repoPath,
|
|
1763
|
-
|
|
1760
|
+
sessionBranch: row.sessionBranch ?? undefined,
|
|
1764
1761
|
devCommand: row.devCommand ?? undefined,
|
|
1765
1762
|
model: row.model ?? undefined,
|
|
1766
1763
|
setupCommand: row.setupCommand ?? undefined,
|
|
@@ -2498,11 +2495,12 @@ function markVerified(ctx, projectId, keys, sha) {
|
|
|
2498
2495
|
async function listFindings(ctx, project) {
|
|
2499
2496
|
const rows = ctx.db.select().from(projectFindings).where(eq4(projectFindings.projectId, project.id)).all();
|
|
2500
2497
|
const distances = new Map;
|
|
2501
|
-
|
|
2502
|
-
|
|
2503
|
-
|
|
2504
|
-
|
|
2505
|
-
|
|
2498
|
+
const shas = new Set(rows.flatMap((row) => row.establishedSha ? [row.establishedSha] : []));
|
|
2499
|
+
if (shas.size > 0) {
|
|
2500
|
+
const mainLine = await detectMainBranch(project.repoPath);
|
|
2501
|
+
for (const sha of shas) {
|
|
2502
|
+
distances.set(sha, await commitsSince(project.repoPath, sha, mainLine));
|
|
2503
|
+
}
|
|
2506
2504
|
}
|
|
2507
2505
|
const order = new Map(PREPARED_KEYS.map((k, i) => [k, i]));
|
|
2508
2506
|
return rows.filter((row) => isPreparedKey(row.key)).sort((a, b) => (order.get(a.key) ?? 0) - (order.get(b.key) ?? 0)).map((row) => {
|
|
@@ -2571,8 +2569,11 @@ function migrationPaths(paths) {
|
|
|
2571
2569
|
function featureBranch(slug) {
|
|
2572
2570
|
return `feature/${slug}`;
|
|
2573
2571
|
}
|
|
2574
|
-
function
|
|
2575
|
-
|
|
2572
|
+
function featureBase(feature) {
|
|
2573
|
+
if (!feature.baseBranch) {
|
|
2574
|
+
throw new GateError(`feature ${feature.slug} has no recorded base branch \u2014 it has not been cut from one yet`);
|
|
2575
|
+
}
|
|
2576
|
+
return feature.baseBranch;
|
|
2576
2577
|
}
|
|
2577
2578
|
function git(repoPath) {
|
|
2578
2579
|
return simpleGit(repoPath);
|
|
@@ -2622,9 +2623,31 @@ async function detectMainBranch(repoPath) {
|
|
|
2622
2623
|
} catch {}
|
|
2623
2624
|
return "main";
|
|
2624
2625
|
}
|
|
2626
|
+
async function resolveSessionBranch(project) {
|
|
2627
|
+
const stored = project.sessionBranch?.trim();
|
|
2628
|
+
if (!stored)
|
|
2629
|
+
return detectMainBranch(project.repoPath);
|
|
2630
|
+
const local = await git(project.repoPath).branchLocal();
|
|
2631
|
+
if (!local.all.includes(stored)) {
|
|
2632
|
+
throw new GateError(`the configured session landing branch \`${stored}\` no longer exists in ${project.repoPath} \u2014 pick where this chat's work should land`);
|
|
2633
|
+
}
|
|
2634
|
+
return stored;
|
|
2635
|
+
}
|
|
2636
|
+
async function sessionBranchView(project) {
|
|
2637
|
+
const stored = project.sessionBranch?.trim() || null;
|
|
2638
|
+
const detected = await detectMainBranch(project.repoPath);
|
|
2639
|
+
return { stored, effective: stored ?? detected, detected };
|
|
2640
|
+
}
|
|
2641
|
+
async function currentCheckoutBranch(project) {
|
|
2642
|
+
const local = await git(project.repoPath).branchLocal();
|
|
2643
|
+
if (local.detached || !local.current) {
|
|
2644
|
+
throw new InvalidInputError(`${project.repoPath} has no branch checked out (detached HEAD) \u2014 name the branch to fork from`);
|
|
2645
|
+
}
|
|
2646
|
+
return local.current;
|
|
2647
|
+
}
|
|
2625
2648
|
async function createFeatureBranch(project, slug, base) {
|
|
2626
2649
|
const branch = featureBranch(slug);
|
|
2627
|
-
const from = base
|
|
2650
|
+
const from = base.trim();
|
|
2628
2651
|
const g = git(project.repoPath);
|
|
2629
2652
|
const branches = await g.branchLocal();
|
|
2630
2653
|
if (branches.all.includes(branch))
|
|
@@ -2638,7 +2661,9 @@ async function createFeatureBranch(project, slug, base) {
|
|
|
2638
2661
|
async function listBranches(project) {
|
|
2639
2662
|
const g = git(project.repoPath);
|
|
2640
2663
|
const local = await g.branchLocal();
|
|
2641
|
-
const
|
|
2664
|
+
const detachedRef = local.detached ? local.current : null;
|
|
2665
|
+
const current = local.detached ? "" : local.current;
|
|
2666
|
+
const branches = local.all.filter((name) => !name.startsWith("feature/") && name !== detachedRef);
|
|
2642
2667
|
const localSet = new Set(local.all);
|
|
2643
2668
|
let remoteBranches = [];
|
|
2644
2669
|
try {
|
|
@@ -2648,7 +2673,7 @@ async function listBranches(project) {
|
|
|
2648
2673
|
return !short.startsWith("feature/") && !localSet.has(short);
|
|
2649
2674
|
});
|
|
2650
2675
|
} catch {}
|
|
2651
|
-
return { current
|
|
2676
|
+
return { current, branches, remoteBranches };
|
|
2652
2677
|
}
|
|
2653
2678
|
async function resolveBaseBranch(project, base) {
|
|
2654
2679
|
const g = git(project.repoPath);
|
|
@@ -2674,7 +2699,7 @@ async function ensureTalkWorktree(project, feature) {
|
|
|
2674
2699
|
const worktreePath = worktreeDir(project.id, feature.slug);
|
|
2675
2700
|
const branch = featureBranch(feature.slug);
|
|
2676
2701
|
const g = git(project.repoPath);
|
|
2677
|
-
await ensureBranchExists(g, branch, feature
|
|
2702
|
+
await ensureBranchExists(g, branch, featureBase(feature));
|
|
2678
2703
|
if (await worktreeIsValid(g, worktreePath, branch))
|
|
2679
2704
|
return worktreePath;
|
|
2680
2705
|
if (existsSync6(worktreePath) && (await registeredWorktrees(g)).has(canon(worktreePath))) {
|
|
@@ -2756,8 +2781,8 @@ function slash(path) {
|
|
|
2756
2781
|
async function ensureProjectWorktree(project, onLanded) {
|
|
2757
2782
|
const worktreePath = worktreeDir(project.id, PROJECT_WORKTREE_SLUG);
|
|
2758
2783
|
const g = git(project.repoPath);
|
|
2759
|
-
const base = project
|
|
2760
|
-
const landed = await landProjectBranch(project);
|
|
2784
|
+
const base = await resolveSessionBranch(project);
|
|
2785
|
+
const landed = await landProjectBranch(project, base);
|
|
2761
2786
|
if (landed)
|
|
2762
2787
|
onLanded?.(landed);
|
|
2763
2788
|
const stillAhead = await branchCommitsAhead(project.repoPath, base, PROJECT_BRANCH);
|
|
@@ -2766,12 +2791,13 @@ async function ensureProjectWorktree(project, onLanded) {
|
|
|
2766
2791
|
await g.raw(["branch", PROJECT_BRANCH, base]);
|
|
2767
2792
|
}
|
|
2768
2793
|
if (await worktreeIsValid(g, worktreePath, PROJECT_BRANCH))
|
|
2769
|
-
return worktreePath;
|
|
2794
|
+
return { worktreePath, base };
|
|
2770
2795
|
if (existsSync6(worktreePath) && (await registeredWorktrees(g)).has(canon(worktreePath))) {
|
|
2771
2796
|
if (await checkoutInWorktree(worktreePath, PROJECT_BRANCH))
|
|
2772
|
-
return worktreePath;
|
|
2797
|
+
return { worktreePath, base };
|
|
2773
2798
|
}
|
|
2774
|
-
|
|
2799
|
+
const added = await addWorktree(g, worktreePath, PROJECT_BRANCH, "project worktree");
|
|
2800
|
+
return { worktreePath: added, base };
|
|
2775
2801
|
}
|
|
2776
2802
|
async function checkoutInWorktree(worktreePath, branch) {
|
|
2777
2803
|
const gw = git(worktreePath);
|
|
@@ -2935,7 +2961,7 @@ async function branchCommitsAhead(repoPath, base, branch) {
|
|
|
2935
2961
|
}
|
|
2936
2962
|
}
|
|
2937
2963
|
async function reviewCommitCount(project, feature) {
|
|
2938
|
-
const base =
|
|
2964
|
+
const base = featureBase(feature);
|
|
2939
2965
|
const branch = featureBranch(feature.slug);
|
|
2940
2966
|
try {
|
|
2941
2967
|
const out = (await git(project.repoPath).raw(["rev-list", "--count", `${base}..${branch}`])).trim();
|
|
@@ -2946,7 +2972,7 @@ async function reviewCommitCount(project, feature) {
|
|
|
2946
2972
|
}
|
|
2947
2973
|
}
|
|
2948
2974
|
async function featureBranchDelta(project, feature) {
|
|
2949
|
-
const base =
|
|
2975
|
+
const base = featureBase(feature);
|
|
2950
2976
|
const branch = featureBranch(feature.slug);
|
|
2951
2977
|
try {
|
|
2952
2978
|
const out = await git(project.repoPath).raw(["diff", "--stat", `${base}...${branch}`]);
|
|
@@ -3121,12 +3147,13 @@ async function deleteBranchDetachingWorktrees(g, repoPath, branch) {
|
|
|
3121
3147
|
return false;
|
|
3122
3148
|
}
|
|
3123
3149
|
}
|
|
3124
|
-
async function landProjectBranch(project) {
|
|
3125
|
-
const ahead = await branchCommitsAhead(project.repoPath,
|
|
3150
|
+
async function landProjectBranch(project, base) {
|
|
3151
|
+
const ahead = await branchCommitsAhead(project.repoPath, base, PROJECT_BRANCH);
|
|
3126
3152
|
if (ahead.length === 0)
|
|
3127
3153
|
return null;
|
|
3128
|
-
const res = await mergeTempBranch(project.repoPath,
|
|
3154
|
+
const res = await mergeTempBranch(project.repoPath, base, PROJECT_BRANCH);
|
|
3129
3155
|
return {
|
|
3156
|
+
base,
|
|
3130
3157
|
commits: ahead.length,
|
|
3131
3158
|
landed: res.ok,
|
|
3132
3159
|
...res.conflict ? { conflict: true } : {},
|
|
@@ -3507,7 +3534,7 @@ async function stopDryRun(ctx, project, state) {
|
|
|
3507
3534
|
});
|
|
3508
3535
|
const { participating, failure } = dryRunVerdict(project, state);
|
|
3509
3536
|
if (!failure && participating.length > 0) {
|
|
3510
|
-
const sha = await headSha(project.repoPath, project.
|
|
3537
|
+
const sha = await headSha(project.repoPath, await detectMainBranch(project.repoPath)) ?? null;
|
|
3511
3538
|
markVerified(ctx, project.id, participating, sha);
|
|
3512
3539
|
emitProject(ctx, project.id, {
|
|
3513
3540
|
type: "prep.dryrun.verified",
|
|
@@ -3669,7 +3696,7 @@ async function detectDbDrift(ctx, project, feature, previousBranch, driveBranch)
|
|
|
3669
3696
|
return drift;
|
|
3670
3697
|
}
|
|
3671
3698
|
async function mergeFeature(project, feature) {
|
|
3672
|
-
const target =
|
|
3699
|
+
const target = featureBase(feature);
|
|
3673
3700
|
if (testDriveState) {
|
|
3674
3701
|
throw new GateError("Cannot merge while a test drive is active \u2014 stop it first");
|
|
3675
3702
|
}
|
|
@@ -4273,7 +4300,7 @@ function renderDriveFixPrompt(brief) {
|
|
|
4273
4300
|
`);
|
|
4274
4301
|
}
|
|
4275
4302
|
function renderProjectPrompt(brief, runtime = DEFAULT_RUNTIME) {
|
|
4276
|
-
const { project, branch, worktreePath } = brief;
|
|
4303
|
+
const { project, branch, worktreePath, base } = brief;
|
|
4277
4304
|
return [
|
|
4278
4305
|
`# runcastle \u2014 ${project.name} (project session)`,
|
|
4279
4306
|
"",
|
|
@@ -4299,8 +4326,8 @@ function renderProjectPrompt(brief, runtime = DEFAULT_RUNTIME) {
|
|
|
4299
4326
|
"## Where you are working \u2014 and where your writes go",
|
|
4300
4327
|
`- Working directory: \`${worktreePath}\` \u2014 a runcastle-owned worktree, NOT the human's`,
|
|
4301
4328
|
` checkout at \`${project.repoPath}\`.`,
|
|
4302
|
-
`- Branch: \`${branch}\`, cut from \`${
|
|
4303
|
-
`- **Your commits land on \`${
|
|
4329
|
+
`- Branch: \`${branch}\`, cut from \`${base}\` when this session launched.`,
|
|
4330
|
+
`- **Your commits land on \`${base}\` when this terminal closes**, arriving`,
|
|
4304
4331
|
" in the human's checkout the way a `git pull` does. Write real code and real docs \u2014",
|
|
4305
4332
|
" there is no sandbox here \u2014 and commit everything you write before you finish.",
|
|
4306
4333
|
" A session that edits without committing leaves a dirty tree, which is exactly what",
|
|
@@ -4587,10 +4614,23 @@ var init_claude = __esm(() => {
|
|
|
4587
4614
|
};
|
|
4588
4615
|
});
|
|
4589
4616
|
|
|
4590
|
-
// src/
|
|
4617
|
+
// src/services/codex-auth.ts
|
|
4591
4618
|
import { existsSync as existsSync8 } from "fs";
|
|
4592
4619
|
import { homedir as homedir2 } from "os";
|
|
4593
4620
|
import { join as join9 } from "path";
|
|
4621
|
+
function codexHomeDir(env = process.env) {
|
|
4622
|
+
return env.CODEX_HOME ?? join9(env.HOME ?? env.USERPROFILE ?? homedir2(), ".codex");
|
|
4623
|
+
}
|
|
4624
|
+
function codexAuthFile(env = process.env) {
|
|
4625
|
+
return join9(codexHomeDir(env), "auth.json");
|
|
4626
|
+
}
|
|
4627
|
+
function codexLoggedIn(env = process.env, fileExists = existsSync8) {
|
|
4628
|
+
return fileExists(codexAuthFile(env));
|
|
4629
|
+
}
|
|
4630
|
+
var init_codex_auth = () => {};
|
|
4631
|
+
|
|
4632
|
+
// src/doctor/doctor.ts
|
|
4633
|
+
import { existsSync as existsSync9 } from "fs";
|
|
4594
4634
|
async function versionProbe(exec, id, label, bin, fix) {
|
|
4595
4635
|
const out = await exec(bin, ["--version"]);
|
|
4596
4636
|
if (out.ok && out.code === 0) {
|
|
@@ -4622,9 +4662,6 @@ async function nodeProbe(exec) {
|
|
|
4622
4662
|
async function gitProbe(exec) {
|
|
4623
4663
|
return versionProbe(exec, "git", "Git", "git", "Install Git: winget install Git.Git / brew install git / apt install git");
|
|
4624
4664
|
}
|
|
4625
|
-
function codexAuthFile(env) {
|
|
4626
|
-
return join9(env.CODEX_HOME ?? join9(env.HOME ?? env.USERPROFILE ?? homedir2(), ".codex"), "auth.json");
|
|
4627
|
-
}
|
|
4628
4665
|
function statusUnsupported(out) {
|
|
4629
4666
|
return /unknown command|unrecognized subcommand|unexpected argument|invalid subcommand/i.test(`${out.stderr} ${out.stdout}`);
|
|
4630
4667
|
}
|
|
@@ -4656,23 +4693,38 @@ async function runtimeAuthProbe(spec, exec, env, fileExists, severity) {
|
|
|
4656
4693
|
fix: spec.installFix
|
|
4657
4694
|
};
|
|
4658
4695
|
}
|
|
4696
|
+
const authFile = spec.authFile?.(env);
|
|
4697
|
+
if (authFile) {
|
|
4698
|
+
const present = fileExists(authFile);
|
|
4699
|
+
const statusCommand = `${spec.bin} ${spec.authStatusArgs.join(" ")}`;
|
|
4700
|
+
let coda;
|
|
4701
|
+
if (statusUnsupported(out)) {
|
|
4702
|
+
coda = ` \u2014 this ${spec.bin} cannot report login state`;
|
|
4703
|
+
} else if (out.code === 0 === present) {
|
|
4704
|
+
coda = ` \u2014 \`${statusCommand}\` agrees`;
|
|
4705
|
+
} else if (present) {
|
|
4706
|
+
coda = `, but \`${statusCommand}\` reports logged out \u2014 burns borrow the file, so this host counts as signed in`;
|
|
4707
|
+
} else {
|
|
4708
|
+
coda = `, but \`${statusCommand}\` reports a login \u2014 a burn borrows the file, and it is not there`;
|
|
4709
|
+
}
|
|
4710
|
+
return present ? { ...base, status: "ok", detail: `credentials found at ${authFile}${coda}` } : { ...loggedOut, detail: `no credentials at ${authFile}${coda}` };
|
|
4711
|
+
}
|
|
4659
4712
|
if (out.code === 0)
|
|
4660
4713
|
return { ...base, status: "ok", detail: `${spec.bin} reports a login` };
|
|
4661
4714
|
if (!statusUnsupported(out))
|
|
4662
4715
|
return loggedOut;
|
|
4663
|
-
|
|
4664
|
-
|
|
4665
|
-
|
|
4666
|
-
|
|
4667
|
-
|
|
4668
|
-
detail: `this ${spec.bin} cannot report login state \u2014 it prompts at launch`
|
|
4669
|
-
};
|
|
4670
|
-
}
|
|
4671
|
-
return fileExists(authFile) ? { ...base, status: "ok", detail: `credentials found at ${authFile}` } : { ...loggedOut, detail: `no credentials at ${authFile}` };
|
|
4716
|
+
return {
|
|
4717
|
+
...base,
|
|
4718
|
+
status: "ok",
|
|
4719
|
+
detail: `this ${spec.bin} cannot report login state \u2014 it prompts at launch`
|
|
4720
|
+
};
|
|
4672
4721
|
}
|
|
4673
4722
|
function runtimeAfkKeyProbe(spec, env, severity = "error") {
|
|
4723
|
+
const id = spec.ids.afkKey;
|
|
4724
|
+
if (!id)
|
|
4725
|
+
return;
|
|
4674
4726
|
const base = {
|
|
4675
|
-
id
|
|
4727
|
+
id,
|
|
4676
4728
|
label: `${spec.label} AFK ${spec.afkNoun} (${spec.afkKey})`,
|
|
4677
4729
|
tier: 2,
|
|
4678
4730
|
severity,
|
|
@@ -4691,11 +4743,12 @@ function runtimeAfkKeyProbe(spec, env, severity = "error") {
|
|
|
4691
4743
|
};
|
|
4692
4744
|
}
|
|
4693
4745
|
async function runtimeProbes(spec, deps) {
|
|
4694
|
-
const fileExists = deps.fileExists ??
|
|
4746
|
+
const fileExists = deps.fileExists ?? existsSync9;
|
|
4747
|
+
const afkKeyProbe = runtimeAfkKeyProbe(spec, deps.env, deps.severity);
|
|
4695
4748
|
return [
|
|
4696
4749
|
await runtimeBinaryProbe(spec, deps.exec, deps.severity),
|
|
4697
4750
|
await runtimeAuthProbe(spec, deps.exec, deps.env, fileExists, deps.severity),
|
|
4698
|
-
|
|
4751
|
+
...afkKeyProbe ? [afkKeyProbe] : []
|
|
4699
4752
|
];
|
|
4700
4753
|
}
|
|
4701
4754
|
async function gitIdentityProbe(exec, cwd) {
|
|
@@ -4849,6 +4902,7 @@ function exitCodeFor(report, mode) {
|
|
|
4849
4902
|
var RUNTIME_SPECS;
|
|
4850
4903
|
var init_doctor = __esm(() => {
|
|
4851
4904
|
init_src();
|
|
4905
|
+
init_codex_auth();
|
|
4852
4906
|
RUNTIME_SPECS = {
|
|
4853
4907
|
"claude-code": {
|
|
4854
4908
|
runtime: "claude-code",
|
|
@@ -4868,13 +4922,13 @@ var init_doctor = __esm(() => {
|
|
|
4868
4922
|
runtime: "codex",
|
|
4869
4923
|
label: "Codex",
|
|
4870
4924
|
bin: "codex",
|
|
4871
|
-
ids: { binary: "codex", auth: "codex-auth"
|
|
4925
|
+
ids: { binary: "codex", auth: "codex-auth" },
|
|
4872
4926
|
authStatusArgs: ["login", "status"],
|
|
4873
4927
|
loginCommand: "codex login",
|
|
4874
4928
|
loginArgs: ["login"],
|
|
4875
4929
|
afkKey: "CODEX_API_KEY",
|
|
4876
4930
|
afkNoun: "API key",
|
|
4877
|
-
afkFix: "
|
|
4931
|
+
afkFix: "Set CODEX_API_KEY in ~/.runcastle/.env to bill an OpenAI API key instead of your ChatGPT login",
|
|
4878
4932
|
installFix: "Install Codex: npm install -g @openai/codex (or: brew install codex)",
|
|
4879
4933
|
binOverrideEnv: "RUNCASTLE_CODEX_BIN",
|
|
4880
4934
|
authFile: codexAuthFile
|
|
@@ -4885,31 +4939,33 @@ var init_doctor = __esm(() => {
|
|
|
4885
4939
|
// src/launcher/runtimes/codex.ts
|
|
4886
4940
|
import {
|
|
4887
4941
|
copyFileSync,
|
|
4888
|
-
existsSync as
|
|
4942
|
+
existsSync as existsSync10,
|
|
4889
4943
|
mkdirSync as mkdirSync4,
|
|
4890
4944
|
readFileSync as readFileSync4,
|
|
4891
4945
|
readdirSync as readdirSync2,
|
|
4892
4946
|
statSync,
|
|
4893
4947
|
writeFileSync as writeFileSync3
|
|
4894
4948
|
} from "fs";
|
|
4895
|
-
import { homedir as homedir3 } from "os";
|
|
4896
4949
|
import { basename, dirname as dirname3, join as join10, resolve as resolve7 } from "path";
|
|
4897
|
-
function
|
|
4950
|
+
function codexHomeDir2(sessionId) {
|
|
4898
4951
|
return join10(sessionDir(sessionId), "codex-home");
|
|
4899
4952
|
}
|
|
4900
4953
|
function realCodexAuthFile(env = process.env) {
|
|
4901
|
-
return
|
|
4954
|
+
return codexAuthFile(env);
|
|
4902
4955
|
}
|
|
4903
4956
|
function toml(value) {
|
|
4904
4957
|
return JSON.stringify(value);
|
|
4905
4958
|
}
|
|
4959
|
+
function approvalPolicyFor(permissionMode) {
|
|
4960
|
+
return permissionMode === undefined || permissionMode === "acceptEdits" ? "never" : "untrusted";
|
|
4961
|
+
}
|
|
4906
4962
|
function renderCodexConfig(input) {
|
|
4907
|
-
const { session, config: config2, model, worktreePath, inheritedMcpServers } = input;
|
|
4963
|
+
const { session, config: config2, model, worktreePath, inheritedMcpServers, permissionMode } = input;
|
|
4908
4964
|
return [
|
|
4909
4965
|
`# Generated by runcastle for session ${session.id} \u2014 rewritten at every launch.`,
|
|
4910
4966
|
`model = ${toml(model)}`,
|
|
4911
4967
|
'sandbox_mode = "workspace-write"',
|
|
4912
|
-
|
|
4968
|
+
`approval_policy = ${toml(approvalPolicyFor(permissionMode))}`,
|
|
4913
4969
|
"",
|
|
4914
4970
|
`[projects.${toml(worktreePath)}]`,
|
|
4915
4971
|
'trust_level = "trusted"',
|
|
@@ -4941,7 +4997,7 @@ function mcpServerTables(userConfigToml) {
|
|
|
4941
4997
|
}
|
|
4942
4998
|
function readInheritedMcpServers(env = process.env) {
|
|
4943
4999
|
const path = join10(dirname3(realCodexAuthFile(env)), "config.toml");
|
|
4944
|
-
if (!
|
|
5000
|
+
if (!existsSync10(path))
|
|
4945
5001
|
return "";
|
|
4946
5002
|
try {
|
|
4947
5003
|
return mcpServerTables(readFileSync4(path, "utf8"));
|
|
@@ -4965,15 +5021,15 @@ function renderCodexHooks(hookClient, kind, platform = process.platform) {
|
|
|
4965
5021
|
};
|
|
4966
5022
|
}
|
|
4967
5023
|
function writeCodexSkills(worktreePath, packDir) {
|
|
4968
|
-
if (!
|
|
5024
|
+
if (!existsSync10(worktreePath))
|
|
4969
5025
|
return [];
|
|
4970
5026
|
const source = join10(packDir, "skills");
|
|
4971
|
-
if (!
|
|
5027
|
+
if (!existsSync10(source))
|
|
4972
5028
|
return [];
|
|
4973
5029
|
const written = [];
|
|
4974
5030
|
for (const name of readdirSync2(source)) {
|
|
4975
5031
|
const skill = join10(source, name, "SKILL.md");
|
|
4976
|
-
if (!
|
|
5032
|
+
if (!existsSync10(skill))
|
|
4977
5033
|
continue;
|
|
4978
5034
|
const target = join10(worktreePath, CODEX_SKILLS_REL, name);
|
|
4979
5035
|
mkdirSync4(target, { recursive: true });
|
|
@@ -4992,7 +5048,7 @@ function excludeFromGit(worktreePath, entry) {
|
|
|
4992
5048
|
return;
|
|
4993
5049
|
mkdirSync4(info, { recursive: true });
|
|
4994
5050
|
const path = join10(info, "exclude");
|
|
4995
|
-
const existing =
|
|
5051
|
+
const existing = existsSync10(path) ? readFileSync4(path, "utf8") : "";
|
|
4996
5052
|
if (existing.split(/\r?\n/).some((line) => line.trim() === entry))
|
|
4997
5053
|
return;
|
|
4998
5054
|
const head = existing.length > 0 && !existing.endsWith(`
|
|
@@ -5004,7 +5060,7 @@ function excludeFromGit(worktreePath, entry) {
|
|
|
5004
5060
|
}
|
|
5005
5061
|
function gitInfoDir(worktreePath) {
|
|
5006
5062
|
const dotGit = join10(worktreePath, ".git");
|
|
5007
|
-
if (!
|
|
5063
|
+
if (!existsSync10(dotGit))
|
|
5008
5064
|
return null;
|
|
5009
5065
|
if (statSync(dotGit).isDirectory())
|
|
5010
5066
|
return join10(dotGit, "info");
|
|
@@ -5025,6 +5081,7 @@ var CODEX_BIN = "codex", CODEX, CODEX_SKILLS_REL, CODEX_SKILLS_IGNORE = ".agents
|
|
|
5025
5081
|
var init_codex = __esm(() => {
|
|
5026
5082
|
init_paths();
|
|
5027
5083
|
init_doctor();
|
|
5084
|
+
init_codex_auth();
|
|
5028
5085
|
init_resolve_executable();
|
|
5029
5086
|
init_artifacts();
|
|
5030
5087
|
init_edit_guard();
|
|
@@ -5047,7 +5104,7 @@ var init_codex = __esm(() => {
|
|
|
5047
5104
|
doctorHint: 'Run `runcastle doctor` and fix its "Codex CLI" probe, then restart runcastle from a ' + "terminal where `codex --version` works."
|
|
5048
5105
|
};
|
|
5049
5106
|
}
|
|
5050
|
-
if (!
|
|
5107
|
+
if (!existsSync10(realCodexAuthFile())) {
|
|
5051
5108
|
return {
|
|
5052
5109
|
ok: false,
|
|
5053
5110
|
reason: "Codex is installed but not logged in \u2014 runcastle copies your own credentials into " + `each session's home, and there are none at ${realCodexAuthFile()}`,
|
|
@@ -5057,7 +5114,7 @@ var init_codex = __esm(() => {
|
|
|
5057
5114
|
return { ok: true };
|
|
5058
5115
|
},
|
|
5059
5116
|
async writeArtifacts(input) {
|
|
5060
|
-
const home =
|
|
5117
|
+
const home = codexHomeDir2(input.session.id);
|
|
5061
5118
|
mkdirSync4(home, { recursive: true });
|
|
5062
5119
|
const configPath2 = join10(home, "config.toml");
|
|
5063
5120
|
const hooksPath = join10(home, "hooks.json");
|
|
@@ -5067,13 +5124,14 @@ var init_codex = __esm(() => {
|
|
|
5067
5124
|
config: input.config,
|
|
5068
5125
|
model: input.model,
|
|
5069
5126
|
worktreePath: input.worktreePath,
|
|
5127
|
+
...input.permissionMode ? { permissionMode: input.permissionMode } : {},
|
|
5070
5128
|
...input.config.sessionMcp === "inherit" ? { inheritedMcpServers: readInheritedMcpServers() } : {}
|
|
5071
5129
|
}), "utf8");
|
|
5072
5130
|
writeFileSync3(hooksPath, JSON.stringify(renderCodexHooks(hookClientPath(), input.session.kind), null, 2), "utf8");
|
|
5073
5131
|
writeFileSync3(agentsPath, renderSessionPrompt({ ...input, runtime: this.id }), "utf8");
|
|
5074
5132
|
const files = [configPath2, hooksPath, agentsPath];
|
|
5075
5133
|
const auth = realCodexAuthFile();
|
|
5076
|
-
if (
|
|
5134
|
+
if (existsSync10(auth)) {
|
|
5077
5135
|
const copied = join10(home, "auth.json");
|
|
5078
5136
|
copyFileSync(auth, copied);
|
|
5079
5137
|
files.push(copied);
|
|
@@ -5375,20 +5433,18 @@ function landProjectSession(ctx, session) {
|
|
|
5375
5433
|
const project = getProjectById(ctx, session.projectId);
|
|
5376
5434
|
if (!project)
|
|
5377
5435
|
return;
|
|
5378
|
-
const
|
|
5379
|
-
|
|
5380
|
-
reportProjectLanding(ctx, project, res, { sessionId: session.id });
|
|
5381
|
-
}).catch((e) => {
|
|
5436
|
+
const landFailed = (base, e) => {
|
|
5437
|
+
const error = e instanceof Error ? e.message : String(e);
|
|
5382
5438
|
emitProject(ctx, project.id, {
|
|
5383
5439
|
type: "project.land_failed",
|
|
5384
|
-
message: `could not land the project session's work onto ${
|
|
5385
|
-
data: {
|
|
5386
|
-
sessionId: session.id,
|
|
5387
|
-
branch: PROJECT_BRANCH,
|
|
5388
|
-
error: e instanceof Error ? e.message : String(e)
|
|
5389
|
-
}
|
|
5440
|
+
message: `could not land the project session's work${base ? ` onto ${base}` : ""}: ${error} \u2014 ${LANDING_KEPT}`,
|
|
5441
|
+
data: { sessionId: session.id, branch: PROJECT_BRANCH, error }
|
|
5390
5442
|
});
|
|
5391
|
-
}
|
|
5443
|
+
};
|
|
5444
|
+
const landing = resolveSessionBranch(project).then((base) => landProjectBranch(project, base).then((res) => {
|
|
5445
|
+
if (res)
|
|
5446
|
+
reportProjectLanding(ctx, project, res, { sessionId: session.id });
|
|
5447
|
+
}, (e) => landFailed(base, e)), (e) => landFailed(null, e));
|
|
5392
5448
|
inFlightLandings.add(landing);
|
|
5393
5449
|
landing.finally(() => inFlightLandings.delete(landing));
|
|
5394
5450
|
}
|
|
@@ -5401,11 +5457,11 @@ function reportProjectLanding(ctx, project, res, data = {}) {
|
|
|
5401
5457
|
});
|
|
5402
5458
|
};
|
|
5403
5459
|
if (res.landed) {
|
|
5404
|
-
report("project.landed", `landed ${res.commits} commit(s) from the project session onto ${
|
|
5460
|
+
report("project.landed", `landed ${res.commits} commit(s) from the project session onto ${res.base}`, { commits: res.commits });
|
|
5405
5461
|
} else if (res.conflict) {
|
|
5406
|
-
report("project.land_conflict", `the project session's ${res.commits} commit(s) conflict with ${
|
|
5462
|
+
report("project.land_conflict", `the project session's ${res.commits} commit(s) conflict with ${res.base} \u2014 nothing was overwritten; ${LANDING_KEPT}`, { commits: res.commits, files: res.files ?? [] });
|
|
5407
5463
|
} else {
|
|
5408
|
-
report("project.land_failed", `could not land the project session's ${res.commits} commit(s) onto ${
|
|
5464
|
+
report("project.land_failed", `could not land the project session's ${res.commits} commit(s) onto ${res.base}: ${res.error ?? "unknown error"} \u2014 ${LANDING_KEPT}`, { commits: res.commits, error: res.error ?? null });
|
|
5409
5465
|
}
|
|
5410
5466
|
}
|
|
5411
5467
|
function markSessionEnded(ctx, id) {
|
|
@@ -5746,7 +5802,7 @@ var init_tickets = __esm(() => {
|
|
|
5746
5802
|
});
|
|
5747
5803
|
|
|
5748
5804
|
// src/services/gates.ts
|
|
5749
|
-
import { existsSync as
|
|
5805
|
+
import { existsSync as existsSync11 } from "fs";
|
|
5750
5806
|
import { and as and7, desc as desc4, eq as eq7, inArray as inArray3 } from "drizzle-orm";
|
|
5751
5807
|
function checkGate(ctx, check, feature) {
|
|
5752
5808
|
switch (check) {
|
|
@@ -5796,7 +5852,7 @@ function notYetTerminal(noun, statuses, order) {
|
|
|
5796
5852
|
}
|
|
5797
5853
|
function fileGate(ctx, feature, fileName, reason) {
|
|
5798
5854
|
const project = projectForFeature(ctx, feature);
|
|
5799
|
-
return
|
|
5855
|
+
return existsSync11(featureDocPath(project, feature, fileName)) ? { satisfied: true } : { satisfied: false, reason };
|
|
5800
5856
|
}
|
|
5801
5857
|
function docGate(ctx, feature, fileName, reason) {
|
|
5802
5858
|
const file = fileGate(ctx, feature, fileName, reason);
|
|
@@ -5860,14 +5916,14 @@ var init_gates = __esm(() => {
|
|
|
5860
5916
|
});
|
|
5861
5917
|
|
|
5862
5918
|
// src/services/knowledge.ts
|
|
5863
|
-
import { existsSync as
|
|
5919
|
+
import { existsSync as existsSync12, mkdirSync as mkdirSync5, readFileSync as readFileSync5, readdirSync as readdirSync3, writeFileSync as writeFileSync4 } from "fs";
|
|
5864
5920
|
import { join as join11, relative as relative2, resolve as resolve8, sep } from "path";
|
|
5865
5921
|
function scaffoldDocs(ctx, feature, opts = {}) {
|
|
5866
5922
|
const project = projectForFeature(ctx, feature);
|
|
5867
5923
|
const dir = featureDocsDir(project, feature);
|
|
5868
5924
|
mkdirSync5(dir, { recursive: true });
|
|
5869
5925
|
const briefPath = featureDocPath(project, feature, "brief.md");
|
|
5870
|
-
if (!
|
|
5926
|
+
if (!existsSync12(briefPath)) {
|
|
5871
5927
|
const override = opts.brief?.trim();
|
|
5872
5928
|
const created = new Date().toISOString();
|
|
5873
5929
|
const brief = override ? `${override}
|
|
@@ -5894,7 +5950,7 @@ function scaffoldDocs(ctx, feature, opts = {}) {
|
|
|
5894
5950
|
function scaffoldMapDoc(project, feature, seed) {
|
|
5895
5951
|
mkdirSync5(featureDocsDir(project, feature), { recursive: true });
|
|
5896
5952
|
const mapPath = featureDocPath(project, feature, "map.md");
|
|
5897
|
-
if (
|
|
5953
|
+
if (existsSync12(mapPath))
|
|
5898
5954
|
return;
|
|
5899
5955
|
writeFileSync4(mapPath, mapDocBody(feature, seed), "utf8");
|
|
5900
5956
|
}
|
|
@@ -5915,7 +5971,7 @@ function mapDocBody(feature, seed) {
|
|
|
5915
5971
|
}
|
|
5916
5972
|
function readCharter(repoRoot) {
|
|
5917
5973
|
const path = join11(repoRoot, CHARTER_FILE);
|
|
5918
|
-
if (!
|
|
5974
|
+
if (!existsSync12(path))
|
|
5919
5975
|
return;
|
|
5920
5976
|
try {
|
|
5921
5977
|
return readFileSync5(path, "utf8");
|
|
@@ -5925,7 +5981,7 @@ function readCharter(repoRoot) {
|
|
|
5925
5981
|
}
|
|
5926
5982
|
function listLiveAdrs(repoRoot) {
|
|
5927
5983
|
const dir = join11(repoRoot, ADR_DIR_REL);
|
|
5928
|
-
if (!
|
|
5984
|
+
if (!existsSync12(dir))
|
|
5929
5985
|
return [];
|
|
5930
5986
|
const docs2 = [];
|
|
5931
5987
|
for (const entry of readdirSync3(dir, { withFileTypes: true })) {
|
|
@@ -5946,7 +6002,7 @@ function listLiveAdrs(repoRoot) {
|
|
|
5946
6002
|
function listDocs(ctx, feature) {
|
|
5947
6003
|
const project = projectForFeature(ctx, feature);
|
|
5948
6004
|
const dir = featureDocsDir(project, feature);
|
|
5949
|
-
if (!
|
|
6005
|
+
if (!existsSync12(dir))
|
|
5950
6006
|
return [];
|
|
5951
6007
|
return readdirSync3(dir, { withFileTypes: true }).filter((e) => e.isFile() && e.name.endsWith(".md")).map((e) => ({ relPath: e.name, title: readTitle(join11(dir, e.name), e.name) })).sort((a, b) => a.relPath.localeCompare(b.relPath));
|
|
5952
6008
|
}
|
|
@@ -5959,7 +6015,7 @@ function readDoc(ctx, feature, relPath) {
|
|
|
5959
6015
|
if (rel === "" || rel.startsWith("..") || rel.split(sep).includes("..")) {
|
|
5960
6016
|
throw new InvalidInputError(`doc path escapes the feature docs dir: ${relPath}`);
|
|
5961
6017
|
}
|
|
5962
|
-
if (!
|
|
6018
|
+
if (!existsSync12(target))
|
|
5963
6019
|
throw new NotFoundError(`doc not found: ${relPath}`);
|
|
5964
6020
|
return { content: readFileSync5(target, "utf8") };
|
|
5965
6021
|
}
|
|
@@ -6017,12 +6073,12 @@ var init_end_session = __esm(() => {
|
|
|
6017
6073
|
});
|
|
6018
6074
|
|
|
6019
6075
|
// src/services/settings.ts
|
|
6020
|
-
import { existsSync as
|
|
6076
|
+
import { existsSync as existsSync13, mkdirSync as mkdirSync6, readFileSync as readFileSync6, writeFileSync as writeFileSync5 } from "fs";
|
|
6021
6077
|
import { dirname as dirname4 } from "path";
|
|
6022
6078
|
import * as z3 from "zod";
|
|
6023
6079
|
import { eq as eq8 } from "drizzle-orm";
|
|
6024
6080
|
function readRawConfig(configFile) {
|
|
6025
|
-
if (!
|
|
6081
|
+
if (!existsSync13(configFile))
|
|
6026
6082
|
return {};
|
|
6027
6083
|
try {
|
|
6028
6084
|
const parsed = JSON.parse(readFileSync6(configFile, "utf8"));
|
|
@@ -6052,6 +6108,7 @@ function projectOverrides(ctx, projectId) {
|
|
|
6052
6108
|
const row = ctx.db.select({
|
|
6053
6109
|
model: projects.model,
|
|
6054
6110
|
sandbox: projects.sandbox,
|
|
6111
|
+
sessionBranch: projects.sessionBranch,
|
|
6055
6112
|
devCommand: projects.devCommand,
|
|
6056
6113
|
setupCommand: projects.setupCommand,
|
|
6057
6114
|
verifyCommands: projects.verifyCommands,
|
|
@@ -6063,6 +6120,7 @@ function projectOverrides(ctx, projectId) {
|
|
|
6063
6120
|
return {
|
|
6064
6121
|
model: row?.model ?? null,
|
|
6065
6122
|
sandbox: row?.sandbox ?? null,
|
|
6123
|
+
sessionBranch: row?.sessionBranch ?? null,
|
|
6066
6124
|
devCommand: row?.devCommand ?? null,
|
|
6067
6125
|
setupCommand: row?.setupCommand ?? null,
|
|
6068
6126
|
verifyCommands: row?.verifyCommands ?? null,
|
|
@@ -6341,16 +6399,15 @@ var init_settings = __esm(() => {
|
|
|
6341
6399
|
parseEnv: idEnv
|
|
6342
6400
|
},
|
|
6343
6401
|
{
|
|
6344
|
-
key: "
|
|
6345
|
-
|
|
6346
|
-
envVar: "RUNCASTLE_MAIN_BRANCH",
|
|
6402
|
+
key: "devCommand",
|
|
6403
|
+
projectColumn: "devCommand",
|
|
6347
6404
|
restartRequired: false,
|
|
6348
6405
|
valueSchema: z3.string().min(1),
|
|
6349
6406
|
parseEnv: idEnv
|
|
6350
6407
|
},
|
|
6351
6408
|
{
|
|
6352
|
-
key: "
|
|
6353
|
-
projectColumn: "
|
|
6409
|
+
key: "sessionBranch",
|
|
6410
|
+
projectColumn: "sessionBranch",
|
|
6354
6411
|
restartRequired: false,
|
|
6355
6412
|
valueSchema: z3.string().min(1),
|
|
6356
6413
|
parseEnv: idEnv
|
|
@@ -6382,7 +6439,7 @@ var init_settings = __esm(() => {
|
|
|
6382
6439
|
});
|
|
6383
6440
|
|
|
6384
6441
|
// src/services/setup.ts
|
|
6385
|
-
import { copyFileSync as copyFileSync2, existsSync as
|
|
6442
|
+
import { copyFileSync as copyFileSync2, existsSync as existsSync14, mkdirSync as mkdirSync7, readFileSync as readFileSync7, readdirSync as readdirSync4, writeFileSync as writeFileSync6 } from "fs";
|
|
6386
6443
|
import { dirname as dirname5, join as join12 } from "path";
|
|
6387
6444
|
import { fileURLToPath as fileURLToPath4 } from "url";
|
|
6388
6445
|
async function writeGitIdentity(exec, identity) {
|
|
@@ -6490,7 +6547,7 @@ function resolveSandcastleBin() {
|
|
|
6490
6547
|
let dir = dirname5(fileURLToPath4(mainUrl));
|
|
6491
6548
|
for (let hops = 0;hops < 6; hops++) {
|
|
6492
6549
|
const manifestPath = join12(dir, "package.json");
|
|
6493
|
-
if (
|
|
6550
|
+
if (existsSync14(manifestPath)) {
|
|
6494
6551
|
const manifest = JSON.parse(readFileSync7(manifestPath, "utf8"));
|
|
6495
6552
|
if (manifest.name === "@ai-hero/sandcastle" && manifest.bin?.sandcastle) {
|
|
6496
6553
|
return join12(dir, manifest.bin.sandcastle);
|
|
@@ -6526,7 +6583,7 @@ function sandcastleTemplateDir() {
|
|
|
6526
6583
|
}
|
|
6527
6584
|
function scaffoldSandcastleConfig(templateDir, targetDir) {
|
|
6528
6585
|
const dir = join12(targetDir, ".sandcastle");
|
|
6529
|
-
if (
|
|
6586
|
+
if (existsSync14(dir))
|
|
6530
6587
|
return { scaffolded: false, dir };
|
|
6531
6588
|
mkdirSync7(dir, { recursive: true });
|
|
6532
6589
|
for (const name of readdirSync4(templateDir)) {
|
|
@@ -6573,7 +6630,7 @@ function createCredentialVerifier(exec, runtime = DEFAULT_RUNTIME) {
|
|
|
6573
6630
|
function fileAfkTokenIo(exec, runtime = DEFAULT_RUNTIME) {
|
|
6574
6631
|
const path = envPath();
|
|
6575
6632
|
return {
|
|
6576
|
-
read: () =>
|
|
6633
|
+
read: () => existsSync14(path) ? readFileSync7(path, "utf8") : "",
|
|
6577
6634
|
write: (content) => {
|
|
6578
6635
|
mkdirSync7(dirname5(path), { recursive: true });
|
|
6579
6636
|
writeFileSync6(path, content, "utf8");
|
|
@@ -6597,7 +6654,7 @@ var init_setup = __esm(() => {
|
|
|
6597
6654
|
};
|
|
6598
6655
|
RUNTIME_AUTH_SETUP_HINT = {
|
|
6599
6656
|
"claude-code": RUNTIME_SPECS["claude-code"].afkFix,
|
|
6600
|
-
codex: RUNTIME_SPECS.codex.
|
|
6657
|
+
codex: `Run \`${RUNTIME_SPECS.codex.loginCommand}\` on this host, then burn again`
|
|
6601
6658
|
};
|
|
6602
6659
|
LOGIN_TERMINAL_KIND = {
|
|
6603
6660
|
"claude-code": "claude-login",
|
|
@@ -6754,7 +6811,7 @@ var init_burn_guard = __esm(() => {
|
|
|
6754
6811
|
});
|
|
6755
6812
|
|
|
6756
6813
|
// src/workflows/review-ticket.ts
|
|
6757
|
-
import { existsSync as
|
|
6814
|
+
import { existsSync as existsSync15, mkdirSync as mkdirSync8, readFileSync as readFileSync8, rmSync as rmSync2, writeFileSync as writeFileSync7 } from "fs";
|
|
6758
6815
|
import { delimiter, join as join13 } from "path";
|
|
6759
6816
|
import { run } from "@ai-hero/sandcastle";
|
|
6760
6817
|
import { noSandbox } from "@ai-hero/sandcastle/sandboxes/no-sandbox";
|
|
@@ -6770,7 +6827,7 @@ function findOnPath(bin, env = process.env, platform = process.platform) {
|
|
|
6770
6827
|
for (const dir of dirs) {
|
|
6771
6828
|
for (const suffix of suffixes) {
|
|
6772
6829
|
const candidate = join13(dir, `${bin}${suffix}`);
|
|
6773
|
-
if (
|
|
6830
|
+
if (existsSync15(candidate))
|
|
6774
6831
|
return candidate;
|
|
6775
6832
|
}
|
|
6776
6833
|
}
|
|
@@ -6798,6 +6855,9 @@ async function releaseDriveQuietly() {
|
|
|
6798
6855
|
}
|
|
6799
6856
|
async function executeReviewTicket(ctx, ticket, deps) {
|
|
6800
6857
|
const { project, feature } = ctx;
|
|
6858
|
+
if (!feature.baseBranch) {
|
|
6859
|
+
return couldNotReview(ticket, `feature ${feature.slug} has no recorded base branch, so there is nothing to diff \`${feature.branch}\` against.`);
|
|
6860
|
+
}
|
|
6801
6861
|
if (!findOnPath(AGENT_BROWSER_BIN)) {
|
|
6802
6862
|
return couldNotReview(ticket, `\`${AGENT_BROWSER_BIN}\` is not on this machine's PATH, so there is no way to drive the app. Install it and re-burn this ticket.`);
|
|
6803
6863
|
}
|
|
@@ -6808,7 +6868,7 @@ async function executeReviewTicket(ctx, ticket, deps) {
|
|
|
6808
6868
|
DOCS_DIGEST: deps.docsDigest,
|
|
6809
6869
|
LAP_DIGESTS: buildLapDigestsBlock(deps.lapDigests),
|
|
6810
6870
|
FEATURE_BRANCH: feature.branch,
|
|
6811
|
-
BASE_BRANCH:
|
|
6871
|
+
BASE_BRANCH: feature.baseBranch,
|
|
6812
6872
|
DIGEST_PATH: artifacts.digestPath,
|
|
6813
6873
|
BLOCKED_PATH: artifacts.blockedPath,
|
|
6814
6874
|
WALKTHROUGH_PATH: artifacts.walkthroughPath
|
|
@@ -6890,13 +6950,18 @@ var init_review_ticket = __esm(() => {
|
|
|
6890
6950
|
});
|
|
6891
6951
|
|
|
6892
6952
|
// src/workflows/ticket-burner.ts
|
|
6893
|
-
import { existsSync as
|
|
6953
|
+
import { existsSync as existsSync16, mkdirSync as mkdirSync9, readFileSync as readFileSync9, readdirSync as readdirSync5, rmSync as rmSync3 } from "fs";
|
|
6894
6954
|
import { dirname as dirname6, join as join14, posix as posixPath, win32 as winPath } from "path";
|
|
6895
6955
|
import { fileURLToPath as fileURLToPath5 } from "url";
|
|
6896
6956
|
import { claudeCode, codex, run as run2 } from "@ai-hero/sandcastle";
|
|
6897
6957
|
import { docker } from "@ai-hero/sandcastle/sandboxes/docker";
|
|
6898
6958
|
import { podman } from "@ai-hero/sandcastle/sandboxes/podman";
|
|
6899
6959
|
import { noSandbox as noSandbox2 } from "@ai-hero/sandcastle/sandboxes/no-sandbox";
|
|
6960
|
+
function burnAuthReady(runtime, token, loggedIn = codexLoggedIn) {
|
|
6961
|
+
if (token !== undefined)
|
|
6962
|
+
return true;
|
|
6963
|
+
return runtime === "codex" && loggedIn();
|
|
6964
|
+
}
|
|
6900
6965
|
function isReviewTicket(ticket) {
|
|
6901
6966
|
return ticket.kind === "review";
|
|
6902
6967
|
}
|
|
@@ -7037,10 +7102,10 @@ _(The "${DROPPED_MAP_SECTIONS.join('" and "')}" sections are omitted here: they
|
|
|
7037
7102
|
}
|
|
7038
7103
|
function buildProjectStandards(repoPath) {
|
|
7039
7104
|
const present = [];
|
|
7040
|
-
if (
|
|
7105
|
+
if (existsSync16(join14(repoPath, AGENT_CONVENTIONS_FILE))) {
|
|
7041
7106
|
present.push("`CLAUDE.md` \u2014 this repo's agent-facing conventions, and the highest authority on them");
|
|
7042
7107
|
}
|
|
7043
|
-
if (
|
|
7108
|
+
if (existsSync16(join14(repoPath, CHARTER_FILE))) {
|
|
7044
7109
|
present.push(`\`${CHARTER_FILE}\` \u2014 the project charter: what this codebase is for`);
|
|
7045
7110
|
}
|
|
7046
7111
|
const adrs = listLiveAdrs(repoPath).map((a) => a.relPath);
|
|
@@ -7071,7 +7136,7 @@ function buildDriveNotes(settings) {
|
|
|
7071
7136
|
add("stop", settings.driveStopCommand);
|
|
7072
7137
|
add("dev server", settings.devCommand);
|
|
7073
7138
|
add("db reset", settings.dbResetCommand);
|
|
7074
|
-
const hasDir =
|
|
7139
|
+
const hasDir = existsSync16(join14(settings.repoPath, ".runcastle"));
|
|
7075
7140
|
if (configured.length === 0 && !hasDir) {
|
|
7076
7141
|
return [
|
|
7077
7142
|
"This project has no test-drive machinery configured, so there is nothing here for you to keep in step \u2014 do not invent any.",
|
|
@@ -7207,6 +7272,18 @@ function cacheMountFor(pm, hostPath) {
|
|
|
7207
7272
|
const sandboxPath = PM_CACHE_SANDBOX_PATHS[pm];
|
|
7208
7273
|
return sandboxPath ? { hostPath, sandboxPath } : undefined;
|
|
7209
7274
|
}
|
|
7275
|
+
function codexAuthMountFor(runtime, sandbox, env = process.env, loggedIn = codexLoggedIn) {
|
|
7276
|
+
if (runtime !== "codex" || sandbox === "noSandbox" || !loggedIn(env))
|
|
7277
|
+
return;
|
|
7278
|
+
return { hostPath: codexHomeDir(env), sandboxPath: CODEX_HOST_MOUNT_PATH, readonly: true };
|
|
7279
|
+
}
|
|
7280
|
+
function buildCodexAuthCopyCommand() {
|
|
7281
|
+
return `mkdir -p "$HOME/.codex" && cp "${CODEX_HOST_MOUNT_PATH}/auth.json" "$HOME/.codex/auth.json"`;
|
|
7282
|
+
}
|
|
7283
|
+
function chainSetupCommands(...steps) {
|
|
7284
|
+
const present = steps.filter((step) => !!step);
|
|
7285
|
+
return present.length > 0 ? present.join(" && ") : undefined;
|
|
7286
|
+
}
|
|
7210
7287
|
function resolveBurnWorkspaceMode(config2, platform = process.platform) {
|
|
7211
7288
|
if (config2.sandbox === "noSandbox")
|
|
7212
7289
|
return "mounted";
|
|
@@ -7269,7 +7346,7 @@ function attachedNoteIds(context) {
|
|
|
7269
7346
|
return ids2;
|
|
7270
7347
|
}
|
|
7271
7348
|
function attachmentSources(context) {
|
|
7272
|
-
return attachedNoteIds(context).map(annotationPath).filter((p) =>
|
|
7349
|
+
return attachedNoteIds(context).map(annotationPath).filter((p) => existsSync16(p));
|
|
7273
7350
|
}
|
|
7274
7351
|
function buildAttachmentCopyCommands(sources, platform = process.platform) {
|
|
7275
7352
|
if (sources.length === 0)
|
|
@@ -7312,7 +7389,7 @@ function buildVerifyNotes(config2) {
|
|
|
7312
7389
|
}
|
|
7313
7390
|
function readRepoToolchain(repoPath) {
|
|
7314
7391
|
const pkgPath = join14(repoPath, "package.json");
|
|
7315
|
-
const hasPackageJson =
|
|
7392
|
+
const hasPackageJson = existsSync16(pkgPath);
|
|
7316
7393
|
let packageManagerField;
|
|
7317
7394
|
if (hasPackageJson) {
|
|
7318
7395
|
try {
|
|
@@ -7325,10 +7402,10 @@ function readRepoToolchain(repoPath) {
|
|
|
7325
7402
|
hasPackageJson,
|
|
7326
7403
|
...packageManagerField !== undefined ? { packageManagerField } : {},
|
|
7327
7404
|
lockfiles: {
|
|
7328
|
-
bun:
|
|
7329
|
-
pnpm:
|
|
7330
|
-
yarn:
|
|
7331
|
-
npm:
|
|
7405
|
+
bun: existsSync16(join14(repoPath, "bun.lock")) || existsSync16(join14(repoPath, "bun.lockb")),
|
|
7406
|
+
pnpm: existsSync16(join14(repoPath, "pnpm-lock.yaml")),
|
|
7407
|
+
yarn: existsSync16(join14(repoPath, "yarn.lock")),
|
|
7408
|
+
npm: existsSync16(join14(repoPath, "package-lock.json"))
|
|
7332
7409
|
}
|
|
7333
7410
|
};
|
|
7334
7411
|
}
|
|
@@ -7704,6 +7781,19 @@ ${e.digest.trim()}`).join(`
|
|
|
7704
7781
|
|
|
7705
7782
|
`);
|
|
7706
7783
|
}
|
|
7784
|
+
function gateTicketAuth(deps) {
|
|
7785
|
+
const authMissing = deps.ticketAuthMissing;
|
|
7786
|
+
if (!authMissing || deps.config.sandbox === "noSandbox")
|
|
7787
|
+
return deps.executeTicketRun;
|
|
7788
|
+
return async (ctx, ticket, run3) => {
|
|
7789
|
+
const runtime = authMissing(ticket);
|
|
7790
|
+
if (!runtime)
|
|
7791
|
+
return deps.executeTicketRun(ctx, ticket, run3);
|
|
7792
|
+
const hint = `${runtime} is not authenticated \u2014 ${RUNTIME_AUTH_SETUP_HINT[runtime]}`;
|
|
7793
|
+
ctx.emitEvent({ type: AUTH_MISSING_EVENT, message: hint, ticketId: ticket.id });
|
|
7794
|
+
return { status: "failed", error: hint };
|
|
7795
|
+
};
|
|
7796
|
+
}
|
|
7707
7797
|
async function burnRun(ctx, deps) {
|
|
7708
7798
|
const tickets2 = ctx.tickets;
|
|
7709
7799
|
const burnable = tickets2.filter((t) => t.status !== "cancelled");
|
|
@@ -7723,7 +7813,7 @@ async function burnRun(ctx, deps) {
|
|
|
7723
7813
|
});
|
|
7724
7814
|
return { status: "failed", summary: `dependency cycle: ${path}` };
|
|
7725
7815
|
}
|
|
7726
|
-
const { done, digests } = await burnTickets(ctx, tickets2, deps
|
|
7816
|
+
const { done, digests } = await burnTickets(ctx, tickets2, gateTicketAuth(deps), deps.concurrency);
|
|
7727
7817
|
const summary = cancelled > 0 ? `${done}/${total} tickets done (${cancelled} cancelled)` : `${done}/${total} tickets done`;
|
|
7728
7818
|
ctx.emitEvent({ type: "burn.summary", message: summary, data: { done, total, cancelled } });
|
|
7729
7819
|
const digest = composeRunDigest(digests);
|
|
@@ -7760,11 +7850,11 @@ function readDocsDigest(projectId, slug) {
|
|
|
7760
7850
|
...missing ? { missing } : {}
|
|
7761
7851
|
});
|
|
7762
7852
|
const worktree = worktreeDir(projectId, slug);
|
|
7763
|
-
if (!
|
|
7853
|
+
if (!existsSync16(worktree)) {
|
|
7764
7854
|
return done("_No talk worktree on disk \u2014 docs digest skipped._", "no-worktree");
|
|
7765
7855
|
}
|
|
7766
7856
|
const docsDir = join14(worktree, ...docsRel.split("/"));
|
|
7767
|
-
if (!
|
|
7857
|
+
if (!existsSync16(docsDir)) {
|
|
7768
7858
|
return done("_No docs/features dir in the talk worktree \u2014 docs digest skipped._", "no-docs-dir");
|
|
7769
7859
|
}
|
|
7770
7860
|
const names = listFeatureDocs(docsDir).sort((a, b) => agentDigestDocOrder(a) - agentDigestDocOrder(b) || a.localeCompare(b));
|
|
@@ -7827,7 +7917,7 @@ function readAgentFile(dirs, file) {
|
|
|
7827
7917
|
if (!dir)
|
|
7828
7918
|
continue;
|
|
7829
7919
|
const p = join14(dir, file);
|
|
7830
|
-
if (
|
|
7920
|
+
if (existsSync16(p)) {
|
|
7831
7921
|
try {
|
|
7832
7922
|
return readFileSync9(p, "utf8");
|
|
7833
7923
|
} catch {}
|
|
@@ -7962,8 +8052,11 @@ async function realExecuteTicketRun(ctx, ticket, config2, token, model, land, en
|
|
|
7962
8052
|
mounts.push(mount);
|
|
7963
8053
|
}
|
|
7964
8054
|
}
|
|
8055
|
+
const codexAuthMount = codexAuthMountFor(model.runtime, config2.sandbox);
|
|
8056
|
+
if (codexAuthMount)
|
|
8057
|
+
mounts.push(codexAuthMount);
|
|
7965
8058
|
const guardInstall = guardInstalled ? buildGuardInstallCommand(model.runtime) : undefined;
|
|
7966
|
-
const
|
|
8059
|
+
const withPrelude = (setup) => chainSetupCommands(codexAuthMount ? buildCodexAuthCopyCommand() : undefined, guardInstall, setup);
|
|
7967
8060
|
const agentOptions = guardInstalled ? { bypassHookTrust: true } : {};
|
|
7968
8061
|
mkdirSync9(logsDir(), { recursive: true });
|
|
7969
8062
|
const logFilePath = join14(logsDir(), `burn-${feature.id}-${ticket.seq}.log`);
|
|
@@ -8023,7 +8116,7 @@ async function realExecuteTicketRun(ctx, ticket, config2, token, model, land, en
|
|
|
8023
8116
|
OTHER_SIDE: buildOtherSideBlock(otherSide),
|
|
8024
8117
|
MERGE_COMMAND: resolveMergeCommand(workspaceMode, feature.branch)
|
|
8025
8118
|
});
|
|
8026
|
-
const hookCommand =
|
|
8119
|
+
const hookCommand = withPrelude(workspaceMode === "isolated" ? buildIsolatedSetupCommand(resolveBranch, setupCommand, pm) : setupCommand);
|
|
8027
8120
|
appendTranscript(ticket.id, {
|
|
8028
8121
|
kind: "text",
|
|
8029
8122
|
text: `
|
|
@@ -8158,7 +8251,7 @@ ${blocked.trim()}`
|
|
|
8158
8251
|
let tempBranch = "";
|
|
8159
8252
|
for (let attempt = 1;; attempt++) {
|
|
8160
8253
|
tempBranch = ticketBranchName(feature.slug, ticket.seq, newId("b").slice(2, 10));
|
|
8161
|
-
const hookCommand =
|
|
8254
|
+
const hookCommand = withPrelude(workspaceMode === "isolated" ? buildIsolatedSetupCommand(tempBranch, setupCommand, pm) : setupCommand);
|
|
8162
8255
|
if (hookCommand && attempt === 1) {
|
|
8163
8256
|
ctx.emitEvent({
|
|
8164
8257
|
type: "burn.setup",
|
|
@@ -8337,14 +8430,24 @@ function resolveBurnDeps(ctx) {
|
|
|
8337
8430
|
dbResetCommand: ctx.project.dbResetCommand
|
|
8338
8431
|
})
|
|
8339
8432
|
};
|
|
8433
|
+
const ticketCredentials = (ticket) => {
|
|
8434
|
+
const ticketModel = resolveTicketModel(config2, ctx.project, ctx.modelOverride, ticket);
|
|
8435
|
+
return {
|
|
8436
|
+
model: ticketModel,
|
|
8437
|
+
token: ticketModel.runtime === model.runtime ? token : readTokenFromEnvFile(envPath(), ticketModel.runtime)
|
|
8438
|
+
};
|
|
8439
|
+
};
|
|
8340
8440
|
return {
|
|
8341
8441
|
config: config2,
|
|
8342
8442
|
runtime: model.runtime,
|
|
8343
|
-
hasAuthToken: token
|
|
8443
|
+
hasAuthToken: burnAuthReady(model.runtime, token),
|
|
8444
|
+
ticketAuthMissing: (ticket) => {
|
|
8445
|
+
const { model: ticketModel, token: ticketToken } = ticketCredentials(ticket);
|
|
8446
|
+
return burnAuthReady(ticketModel.runtime, ticketToken) ? undefined : ticketModel.runtime;
|
|
8447
|
+
},
|
|
8344
8448
|
concurrency: config2.burnConcurrency,
|
|
8345
8449
|
executeTicketRun: (c, ticket, run3) => {
|
|
8346
|
-
const
|
|
8347
|
-
const ticketToken = ticketModel.runtime === model.runtime ? token : readTokenFromEnvFile(envPath(), ticketModel.runtime);
|
|
8450
|
+
const { model: ticketModel, token: ticketToken } = ticketCredentials(ticket);
|
|
8348
8451
|
return isReviewTicket(ticket) ? executeReviewTicket(c, ticket, {
|
|
8349
8452
|
config: config2,
|
|
8350
8453
|
token: ticketToken,
|
|
@@ -8358,7 +8461,7 @@ function resolveBurnDeps(ctx) {
|
|
|
8358
8461
|
function readTokenFromEnvFile(path, runtime) {
|
|
8359
8462
|
const key = RUNTIME_AUTH_KEY[runtime];
|
|
8360
8463
|
let fromFile;
|
|
8361
|
-
if (
|
|
8464
|
+
if (existsSync16(path)) {
|
|
8362
8465
|
try {
|
|
8363
8466
|
fromFile = parseEnvFile2(readFileSync9(path, "utf8"))[key];
|
|
8364
8467
|
} catch {
|
|
@@ -8368,12 +8471,13 @@ function readTokenFromEnvFile(path, runtime) {
|
|
|
8368
8471
|
const token = fromFile && fromFile.length > 0 ? fromFile : process.env[key];
|
|
8369
8472
|
return token && token.length > 0 ? token : undefined;
|
|
8370
8473
|
}
|
|
8371
|
-
var AUTH_MISSING_EVENT = "auth.missing", DROPPED_MAP_SECTIONS, AGENT_CONVENTIONS_FILE = "CLAUDE.md", PACKAGE_MANAGERS, PM_CACHE_SANDBOX_PATHS, SANDBOX_WORKSPACE_PATH = "/home/agent/workspace", ISOLATED_REPO_PATH = "/home/agent/repo", RUN, BASH_PATTERNS, TOOL_NAME_CATEGORY, MAX_ATTRIBUTABLE_GAP_MS, BURN_WORKTREE_PATH, WORKTREE_REMOVAL_FAILURES, FATAL_ERROR_PATTERNS, RUNTIME_FATAL_ERROR_PATTERNS, RETRYABLE_ERROR_PATTERNS, RUNTIME_RETRYABLE_ERROR_PATTERNS, activeTicketAborts, SETUP_HOOK_TIMEOUT_MS, ticketBurner;
|
|
8474
|
+
var AUTH_MISSING_EVENT = "auth.missing", DROPPED_MAP_SECTIONS, AGENT_CONVENTIONS_FILE = "CLAUDE.md", PACKAGE_MANAGERS, PM_CACHE_SANDBOX_PATHS, CODEX_HOST_MOUNT_PATH = "/mnt/host-codex", SANDBOX_WORKSPACE_PATH = "/home/agent/workspace", ISOLATED_REPO_PATH = "/home/agent/repo", RUN, BASH_PATTERNS, TOOL_NAME_CATEGORY, MAX_ATTRIBUTABLE_GAP_MS, BURN_WORKTREE_PATH, WORKTREE_REMOVAL_FAILURES, FATAL_ERROR_PATTERNS, RUNTIME_FATAL_ERROR_PATTERNS, RETRYABLE_ERROR_PATTERNS, RUNTIME_RETRYABLE_ERROR_PATTERNS, activeTicketAborts, SETUP_HOOK_TIMEOUT_MS, ticketBurner;
|
|
8372
8475
|
var init_ticket_burner = __esm(() => {
|
|
8373
8476
|
init_src();
|
|
8374
8477
|
init_config_load();
|
|
8375
8478
|
init_paths();
|
|
8376
8479
|
init_skills_root();
|
|
8480
|
+
init_codex_auth();
|
|
8377
8481
|
init_knowledge();
|
|
8378
8482
|
init_setup();
|
|
8379
8483
|
init_agent_stream();
|
|
@@ -8432,7 +8536,10 @@ var init_ticket_burner = __esm(() => {
|
|
|
8432
8536
|
/\b401\b|\b403\b/,
|
|
8433
8537
|
/insufficient_quota|exceeded your current quota/i,
|
|
8434
8538
|
/model_not_found|does not exist or you do not have access/i,
|
|
8435
|
-
/CODEX_API_KEY
|
|
8539
|
+
/CODEX_API_KEY/,
|
|
8540
|
+
/not logged in/i,
|
|
8541
|
+
/\bauth (failed|required)\b/i,
|
|
8542
|
+
/refresh token/i
|
|
8436
8543
|
]
|
|
8437
8544
|
};
|
|
8438
8545
|
RETRYABLE_ERROR_PATTERNS = [
|
|
@@ -8545,14 +8652,16 @@ async function realExecuteResearchRun(ctx, waypoint, config2, token, model) {
|
|
|
8545
8652
|
mkdirSync10(logsDir(), { recursive: true });
|
|
8546
8653
|
const logFilePath = join15(logsDir(), `research-${feature.id}-${waypoint.seq}.log`);
|
|
8547
8654
|
const throttle = createResearchStreamThrottle((e) => ctx.emitEvent(e));
|
|
8655
|
+
const codexAuthMount = codexAuthMountFor(model.runtime, config2.sandbox);
|
|
8548
8656
|
const runOptions = {
|
|
8549
8657
|
agent: buildBurnAgent(config2, token, model),
|
|
8550
|
-
sandbox: selectSandbox(config2),
|
|
8658
|
+
sandbox: selectSandbox(config2, codexAuthMount ? [codexAuthMount] : []),
|
|
8551
8659
|
cwd: project.repoPath,
|
|
8552
8660
|
prompt,
|
|
8553
8661
|
branchStrategy: { type: "branch", branch: tempBranch, baseBranch: feature.branch },
|
|
8554
8662
|
signal: ctx.signal,
|
|
8555
8663
|
name: `research-${waypoint.seq}`,
|
|
8664
|
+
...codexAuthMount ? { hooks: { sandbox: { onSandboxReady: [{ command: buildCodexAuthCopyCommand() }] } } } : {},
|
|
8556
8665
|
logging: { type: "file", path: logFilePath, onAgentStreamEvent: throttle.onEvent }
|
|
8557
8666
|
};
|
|
8558
8667
|
let result;
|
|
@@ -8600,7 +8709,7 @@ function resolveResearchDeps(ctx) {
|
|
|
8600
8709
|
return {
|
|
8601
8710
|
config: config2,
|
|
8602
8711
|
runtime: model.runtime,
|
|
8603
|
-
hasAuthToken: token
|
|
8712
|
+
hasAuthToken: burnAuthReady(model.runtime, token),
|
|
8604
8713
|
executeResearchRun: (c, waypoint) => realExecuteResearchRun(c, waypoint, config2, token, model)
|
|
8605
8714
|
};
|
|
8606
8715
|
}
|
|
@@ -8786,8 +8895,7 @@ async function createFeature(ctx, input) {
|
|
|
8786
8895
|
const project = requireProjectById(ctx, input.projectId);
|
|
8787
8896
|
const slug = uniqueSlug(ctx, project.id, input.title);
|
|
8788
8897
|
const branch = `feature/${slug}`;
|
|
8789
|
-
const
|
|
8790
|
-
const cut = input.draft ? null : await ensureFeatureBranch(project, slug, requestedBase);
|
|
8898
|
+
const cut = input.draft ? null : await ensureFeatureBranch(project, slug, await requestedBase(project, input.baseBranch));
|
|
8791
8899
|
const row = {
|
|
8792
8900
|
id: newId("feat"),
|
|
8793
8901
|
projectId: project.id,
|
|
@@ -8837,8 +8945,8 @@ async function startDraft(ctx, featureId, opts = {}) {
|
|
|
8837
8945
|
throw new GateError(`feature ${feature.slug} is not a draft \u2014 it has already been started`);
|
|
8838
8946
|
}
|
|
8839
8947
|
const project = projectForFeature(ctx, feature);
|
|
8840
|
-
const
|
|
8841
|
-
const { branchReady, baseBranch } = await ensureFeatureBranch(project, feature.slug,
|
|
8948
|
+
const base = await requestedBase(project, opts.baseBranch);
|
|
8949
|
+
const { branchReady, baseBranch } = await ensureFeatureBranch(project, feature.slug, base);
|
|
8842
8950
|
ctx.db.update(features).set({ status: "active", baseBranch }).where(eq10(features.id, featureId)).run();
|
|
8843
8951
|
const started = { ...feature, status: "active", baseBranch };
|
|
8844
8952
|
emit(ctx, featureId, {
|
|
@@ -8898,8 +9006,8 @@ async function quickChange(ctx, input) {
|
|
|
8898
9006
|
throw new InvalidInputError("a quick change needs a sentence describing the change");
|
|
8899
9007
|
const slug = uniqueSlug(ctx, project.id, title);
|
|
8900
9008
|
const branch = `feature/${slug}`;
|
|
8901
|
-
const
|
|
8902
|
-
const { branchReady, baseBranch } = await ensureFeatureBranch(project, slug,
|
|
9009
|
+
const base = await requestedBase(project, input.baseBranch);
|
|
9010
|
+
const { branchReady, baseBranch } = await ensureFeatureBranch(project, slug, base);
|
|
8903
9011
|
const inserted = ctx.db.insert(features).values({
|
|
8904
9012
|
id: newId("feat"),
|
|
8905
9013
|
projectId: project.id,
|
|
@@ -8948,14 +9056,17 @@ async function quickChange(ctx, input) {
|
|
|
8948
9056
|
}
|
|
8949
9057
|
return feature;
|
|
8950
9058
|
}
|
|
8951
|
-
async function
|
|
9059
|
+
async function requestedBase(project, picked) {
|
|
9060
|
+
return picked?.trim() || await currentCheckoutBranch(project);
|
|
9061
|
+
}
|
|
9062
|
+
async function ensureFeatureBranch(project, slug, base) {
|
|
8952
9063
|
try {
|
|
8953
|
-
const baseBranch = await resolveBaseBranch(project,
|
|
9064
|
+
const baseBranch = await resolveBaseBranch(project, base);
|
|
8954
9065
|
await createFeatureBranch(project, slug, baseBranch);
|
|
8955
9066
|
return { branchReady: true, baseBranch };
|
|
8956
9067
|
} catch (e) {
|
|
8957
9068
|
if (isNotImplemented(e))
|
|
8958
|
-
return { branchReady: false, baseBranch:
|
|
9069
|
+
return { branchReady: false, baseBranch: base };
|
|
8959
9070
|
throw e;
|
|
8960
9071
|
}
|
|
8961
9072
|
}
|
|
@@ -9298,7 +9409,7 @@ var init_features = __esm(() => {
|
|
|
9298
9409
|
});
|
|
9299
9410
|
|
|
9300
9411
|
// src/services/test-notes.ts
|
|
9301
|
-
import { existsSync as
|
|
9412
|
+
import { existsSync as existsSync18, mkdirSync as mkdirSync11, rmSync as rmSync5, writeFileSync as writeFileSync8 } from "fs";
|
|
9302
9413
|
import { dirname as dirname8 } from "path";
|
|
9303
9414
|
import { asc as asc4, eq as eq11 } from "drizzle-orm";
|
|
9304
9415
|
function rowToNote(row) {
|
|
@@ -9311,7 +9422,7 @@ function rowToNote(row) {
|
|
|
9311
9422
|
author: row.author,
|
|
9312
9423
|
ticketId: row.ticketId ?? undefined,
|
|
9313
9424
|
videoTimestamp: row.videoTimestamp ?? undefined,
|
|
9314
|
-
screenshotUrl:
|
|
9425
|
+
screenshotUrl: existsSync18(annotationPath(row.id)) ? noteScreenshotUrl(row.id) : undefined,
|
|
9315
9426
|
createdAt: row.createdAt,
|
|
9316
9427
|
updatedAt: row.updatedAt
|
|
9317
9428
|
});
|
|
@@ -9519,7 +9630,7 @@ var init_test_notes = __esm(() => {
|
|
|
9519
9630
|
});
|
|
9520
9631
|
|
|
9521
9632
|
// src/mcp/server.ts
|
|
9522
|
-
import { existsSync as
|
|
9633
|
+
import { existsSync as existsSync19, readFileSync as readFileSync11 } from "fs";
|
|
9523
9634
|
import { join as join16, relative as relative3, resolve as resolve9, sep as sep2 } from "path";
|
|
9524
9635
|
import { StreamableHTTPTransport } from "@hono/mcp";
|
|
9525
9636
|
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
@@ -9873,7 +9984,7 @@ async function toolCreateFeature(ctx, session, input) {
|
|
|
9873
9984
|
phase: feature.phase
|
|
9874
9985
|
};
|
|
9875
9986
|
}
|
|
9876
|
-
function toolGetProjectContext(ctx, session) {
|
|
9987
|
+
async function toolGetProjectContext(ctx, session) {
|
|
9877
9988
|
const project = requireProject(ctx, session);
|
|
9878
9989
|
const charter = readCharter(session.worktreePath);
|
|
9879
9990
|
return {
|
|
@@ -9881,7 +9992,18 @@ function toolGetProjectContext(ctx, session) {
|
|
|
9881
9992
|
...charter !== undefined ? { charter } : {},
|
|
9882
9993
|
adrs: listLiveAdrs(session.worktreePath).map(adrRef),
|
|
9883
9994
|
adrsNote: ADRS_NOTE,
|
|
9884
|
-
featureIndex: list(ctx, project.id).map(featureIndexLine)
|
|
9995
|
+
featureIndex: list(ctx, project.id).map(featureIndexLine),
|
|
9996
|
+
baseBranches: await readBaseBranches(project)
|
|
9997
|
+
};
|
|
9998
|
+
}
|
|
9999
|
+
async function readBaseBranches(project) {
|
|
10000
|
+
const { current: current2, branches, remoteBranches } = await listBranches(project);
|
|
10001
|
+
const selectable = [...branches, ...remoteBranches];
|
|
10002
|
+
return {
|
|
10003
|
+
current: current2,
|
|
10004
|
+
currentIsSelectable: selectable.includes(current2),
|
|
10005
|
+
selectable,
|
|
10006
|
+
detectedMain: await detectMainBranch(project.repoPath)
|
|
9885
10007
|
};
|
|
9886
10008
|
}
|
|
9887
10009
|
function adrRef(adr) {
|
|
@@ -9903,7 +10025,7 @@ function toolReadAdr(ctx, session, input) {
|
|
|
9903
10025
|
if (rel === "" || rel.startsWith("..") || rel.split(sep2).includes("..")) {
|
|
9904
10026
|
throw new InvalidInputError(`adr path escapes ${ADR_DIR_REL}: ${input.relPath}`);
|
|
9905
10027
|
}
|
|
9906
|
-
if (!
|
|
10028
|
+
if (!existsSync19(target))
|
|
9907
10029
|
throw new NotFoundError(`adr not found: ${input.relPath}`);
|
|
9908
10030
|
return { relPath: `${ADR_DIR_REL}/${name}`, content: readFileSync11(target, "utf8") };
|
|
9909
10031
|
}
|
|
@@ -10100,7 +10222,7 @@ function buildMcpServer(audience) {
|
|
|
10100
10222
|
inputSchema: {
|
|
10101
10223
|
title: z4.string().min(1).describe("The feature\u2019s name; its slug and branch derive from this."),
|
|
10102
10224
|
oneLiner: z4.string().optional().describe("One line saying what this feature is. Required for every shape EXCEPT `tickets`, " + "where the first ticket\u2019s prose becomes the one-liner and anything sent here is " + "ignored."),
|
|
10103
|
-
baseBranch: z4.string().optional().describe("Branch to cut from
|
|
10225
|
+
baseBranch: z4.string().optional().describe("Branch to cut from \u2014 always pass it explicitly, naming the base you told the " + "human you would use (see `get_project_context`\u2019s `baseBranches`). Omitted, it " + "falls back to whatever the project checkout happens to be on. Drafts pass none: " + "a parked feature picks its base at Start."),
|
|
10104
10226
|
brief: z4.string().optional().describe("The reasoning you just worked out with the human \u2014 why this feature exists and " + "what it must NOT swallow. Becomes brief.md verbatim, and without it that " + "reasoning is lost. Unused by the `tickets` shape, whose brief IS the proses."),
|
|
10105
10227
|
draft: z4.boolean().optional().describe("PARK it instead of starting it: a row and its brief, no branch and no files, " + "until the human clicks Start. Ask them per feature whether to start or park."),
|
|
10106
10228
|
tickets: z4.array(z4.string().min(1)).min(1).optional().describe("The quick-change shape: work too small to deserve a grill. Creates ONE feature at " + "the implementation phase carrying a ticket per prose (plus the review ticket " + "every batch closes with). Send every sentence of one quick change in a SINGLE " + "call \u2014 calling this once per ticket makes one feature per ticket.")
|
|
@@ -10115,13 +10237,13 @@ function buildMcpServer(audience) {
|
|
|
10115
10237
|
if (wants("get_project_context")) {
|
|
10116
10238
|
server.registerTool("get_project_context", {
|
|
10117
10239
|
title: "Get project context",
|
|
10118
|
-
description: "The project as it stands: the project row, the charter (CONTEXT.md) in full, an INDEX " + "of every live ADR (superseded ones omitted) and a one-line index of every feature. " + "ADR bodies are not inlined \u2014 read the ones your work touches with `read_adr`. Shipped " + "features carry their one-liner and docs path (readable on disk); in-flight ones carry " + "slug, phase, lap and ticket counts but no docs path, because their docs live on an " + "unmerged branch. Use the slug with `get_work_record` to see what one actually did.",
|
|
10240
|
+
description: "The project as it stands: the project row, the charter (CONTEXT.md) in full, an INDEX " + "of every live ADR (superseded ones omitted) and a one-line index of every feature. " + "ADR bodies are not inlined \u2014 read the ones your work touches with `read_adr`. Shipped " + "features carry their one-liner and docs path (readable on disk); in-flight ones carry " + "slug, phase, lap and ticket counts but no docs path, because their docs live on an " + "unmerged branch. Use the slug with `get_work_record` to see what one actually did. " + "Plus `baseBranches` \u2014 the branch a new feature would cut from: the checkout\u2019s `current` " + "branch, `currentIsSelectable` (false mid test drive or on a detached HEAD, the one case " + "with no default), every `selectable` base, and the `detectedMain` line to suggest then.",
|
|
10119
10241
|
inputSchema: {}
|
|
10120
10242
|
}, async (_args, extra) => {
|
|
10121
10243
|
const rs = await resolveCtxSession(extra);
|
|
10122
10244
|
if (!rs)
|
|
10123
10245
|
return noSession();
|
|
10124
|
-
return ok(toolGetProjectContext(rs.ctx, rs.session));
|
|
10246
|
+
return ok(await toolGetProjectContext(rs.ctx, rs.session));
|
|
10125
10247
|
});
|
|
10126
10248
|
}
|
|
10127
10249
|
if (wants("read_adr")) {
|
|
@@ -10943,7 +11065,7 @@ var init_stream = __esm(() => {
|
|
|
10943
11065
|
});
|
|
10944
11066
|
|
|
10945
11067
|
// src/routes/web.ts
|
|
10946
|
-
import { existsSync as
|
|
11068
|
+
import { existsSync as existsSync20, statSync as statSync4 } from "fs";
|
|
10947
11069
|
import { readFile } from "fs/promises";
|
|
10948
11070
|
import { extname, join as join17, normalize, resolve as resolve10, sep as sep3 } from "path";
|
|
10949
11071
|
function contentType(path) {
|
|
@@ -10996,7 +11118,7 @@ function mountWebApp(app, distDir) {
|
|
|
10996
11118
|
}
|
|
10997
11119
|
function mountWebAppIfBuilt(app) {
|
|
10998
11120
|
const dist = resolveWebDist();
|
|
10999
|
-
if (
|
|
11121
|
+
if (existsSync20(join17(dist, "index.html")))
|
|
11000
11122
|
mountWebApp(app, dist);
|
|
11001
11123
|
}
|
|
11002
11124
|
var MIME;
|
|
@@ -11047,12 +11169,12 @@ var init_version = () => {};
|
|
|
11047
11169
|
|
|
11048
11170
|
// src/services/install-id.ts
|
|
11049
11171
|
import { randomUUID } from "crypto";
|
|
11050
|
-
import { existsSync as
|
|
11172
|
+
import { existsSync as existsSync21, mkdirSync as mkdirSync12, readFileSync as readFileSync13, writeFileSync as writeFileSync9 } from "fs";
|
|
11051
11173
|
import { dirname as dirname10 } from "path";
|
|
11052
11174
|
function getInstallId() {
|
|
11053
11175
|
const file = installIdPath();
|
|
11054
11176
|
try {
|
|
11055
|
-
if (
|
|
11177
|
+
if (existsSync21(file)) {
|
|
11056
11178
|
const existing = readFileSync13(file, "utf8").trim();
|
|
11057
11179
|
if (UUID_RE.test(existing))
|
|
11058
11180
|
return existing;
|
|
@@ -11252,7 +11374,7 @@ var init_events2 = __esm(() => {
|
|
|
11252
11374
|
});
|
|
11253
11375
|
|
|
11254
11376
|
// src/launcher/launcher.ts
|
|
11255
|
-
import { existsSync as
|
|
11377
|
+
import { existsSync as existsSync22 } from "fs";
|
|
11256
11378
|
import { join as join19 } from "path";
|
|
11257
11379
|
import { and as and9, eq as eq13 } from "drizzle-orm";
|
|
11258
11380
|
function ptyExitMessage(exitCode) {
|
|
@@ -11638,7 +11760,7 @@ async function launchProjectSession(ctx, input, opts = {}) {
|
|
|
11638
11760
|
const model = resolveModelEntry("project", ctx.config, project);
|
|
11639
11761
|
const runtime = runtimeAdapterFor(model.runtime);
|
|
11640
11762
|
assertRuntimeReady(runtime, opts);
|
|
11641
|
-
const worktreePath = await ensureProjectWorktree(project, (res) => reportProjectLanding(ctx, project, res, { retried: true }));
|
|
11763
|
+
const { worktreePath, base } = await ensureProjectWorktree(project, (res) => reportProjectLanding(ctx, project, res, { retried: true }));
|
|
11642
11764
|
const session = createSessionRow(ctx, {
|
|
11643
11765
|
projectId: project.id,
|
|
11644
11766
|
kind: "project",
|
|
@@ -11691,7 +11813,7 @@ async function launchProjectSession(ctx, input, opts = {}) {
|
|
|
11691
11813
|
session,
|
|
11692
11814
|
project,
|
|
11693
11815
|
config: ctx.config,
|
|
11694
|
-
projectBrief: { project, branch: PROJECT_BRANCH, worktreePath },
|
|
11816
|
+
projectBrief: { project, branch: PROJECT_BRANCH, worktreePath, base },
|
|
11695
11817
|
worktreePath,
|
|
11696
11818
|
serverUrl: serverUrlFor(ctx.config),
|
|
11697
11819
|
model: model.id,
|
|
@@ -11725,7 +11847,7 @@ async function buildPrepareBrief(ctx, project) {
|
|
|
11725
11847
|
};
|
|
11726
11848
|
}
|
|
11727
11849
|
function probePrepareHost(repoPath) {
|
|
11728
|
-
const has = (rel) =>
|
|
11850
|
+
const has = (rel) => existsSync22(join19(repoPath, rel));
|
|
11729
11851
|
const lock = LOCKFILES.find(([file]) => has(file));
|
|
11730
11852
|
return {
|
|
11731
11853
|
platform: process.platform,
|
|
@@ -12075,10 +12197,10 @@ var init_feature = __esm(() => {
|
|
|
12075
12197
|
});
|
|
12076
12198
|
|
|
12077
12199
|
// src/services/fsbrowse.ts
|
|
12078
|
-
import { existsSync as
|
|
12079
|
-
import { homedir as
|
|
12200
|
+
import { existsSync as existsSync23, readdirSync as readdirSync6, statSync as statSync5 } from "fs";
|
|
12201
|
+
import { homedir as homedir3 } from "os";
|
|
12080
12202
|
import { basename as basename3, dirname as dirname11, isAbsolute as isAbsolute2, join as join21, resolve as resolve11, sep as sep4 } from "path";
|
|
12081
|
-
function expandPath(input, home =
|
|
12203
|
+
function expandPath(input, home = homedir3()) {
|
|
12082
12204
|
const trimmed = input.trim();
|
|
12083
12205
|
if (trimmed === "")
|
|
12084
12206
|
return trimmed;
|
|
@@ -12091,31 +12213,31 @@ function expandPath(input, home = homedir4()) {
|
|
|
12091
12213
|
}
|
|
12092
12214
|
function looksLikeRepo(dir) {
|
|
12093
12215
|
try {
|
|
12094
|
-
return
|
|
12216
|
+
return existsSync23(join21(dir, ".git"));
|
|
12095
12217
|
} catch {
|
|
12096
12218
|
return false;
|
|
12097
12219
|
}
|
|
12098
12220
|
}
|
|
12099
|
-
function listRoots(home =
|
|
12221
|
+
function listRoots(home = homedir3()) {
|
|
12100
12222
|
const roots = [];
|
|
12101
12223
|
if (process.platform === "win32") {
|
|
12102
12224
|
for (let code = 67;code <= 90; code++) {
|
|
12103
12225
|
const drive = `${String.fromCharCode(code)}:${sep4}`;
|
|
12104
12226
|
try {
|
|
12105
|
-
if (
|
|
12227
|
+
if (existsSync23(drive))
|
|
12106
12228
|
roots.push({ label: drive.replace(/\\$/, ""), path: drive, kind: "drive" });
|
|
12107
12229
|
} catch {}
|
|
12108
12230
|
}
|
|
12109
12231
|
} else {
|
|
12110
12232
|
roots.push({ label: "/", path: "/", kind: "drive" });
|
|
12111
12233
|
}
|
|
12112
|
-
if (
|
|
12234
|
+
if (existsSync23(home))
|
|
12113
12235
|
roots.push({ label: "Home", path: home, kind: "home" });
|
|
12114
12236
|
const seen = new Set(roots.map(pathKey));
|
|
12115
12237
|
for (const name of COMMON_DIRS) {
|
|
12116
12238
|
const candidate = join21(home, name);
|
|
12117
12239
|
const key = pathKey({ path: candidate });
|
|
12118
|
-
if (seen.has(key) || !
|
|
12240
|
+
if (seen.has(key) || !existsSync23(candidate))
|
|
12119
12241
|
continue;
|
|
12120
12242
|
seen.add(key);
|
|
12121
12243
|
roots.push({ label: name, path: candidate, kind: "common" });
|
|
@@ -12141,12 +12263,12 @@ function parentOf(dir) {
|
|
|
12141
12263
|
const parent = dirname11(dir);
|
|
12142
12264
|
return parent === dir ? null : parent;
|
|
12143
12265
|
}
|
|
12144
|
-
function browseDir(input, showHidden = false, home =
|
|
12266
|
+
function browseDir(input, showHidden = false, home = homedir3()) {
|
|
12145
12267
|
const target = input && input.trim() !== "" ? expandPath(input, home) : home;
|
|
12146
12268
|
if (!isAbsolute2(target)) {
|
|
12147
12269
|
throw new InvalidInputError(`path is not absolute: ${target}`);
|
|
12148
12270
|
}
|
|
12149
|
-
if (!
|
|
12271
|
+
if (!existsSync23(target)) {
|
|
12150
12272
|
throw new InvalidInputError(`path does not exist: ${target}`);
|
|
12151
12273
|
}
|
|
12152
12274
|
let dir = target;
|
|
@@ -12371,7 +12493,7 @@ var init_conversations = __esm(() => {
|
|
|
12371
12493
|
});
|
|
12372
12494
|
|
|
12373
12495
|
// src/services/projects.ts
|
|
12374
|
-
import { existsSync as
|
|
12496
|
+
import { existsSync as existsSync24 } from "fs";
|
|
12375
12497
|
import { basename as basename4, join as join22 } from "path";
|
|
12376
12498
|
import { eq as eq14, isNull } from "drizzle-orm";
|
|
12377
12499
|
function listProjects(ctx) {
|
|
@@ -12391,15 +12513,14 @@ function warnIfTranslatedMount(ctx, projectId, repoPath) {
|
|
|
12391
12513
|
async function openProject(ctx, rawPath) {
|
|
12392
12514
|
const repoPath = expandPath(rawPath);
|
|
12393
12515
|
await assertRepoTolerant(ctx, repoPath);
|
|
12394
|
-
const mainBranch = await detectMainBranchTolerant(ctx, repoPath);
|
|
12395
12516
|
const name = basename4(repoPath) || repoPath;
|
|
12396
12517
|
const existing = getProjectByRepoPath(ctx, repoPath) ?? findProjectByCanonPath(ctx, repoPath);
|
|
12397
12518
|
if (existing) {
|
|
12398
|
-
ctx.db.update(projects).set({
|
|
12399
|
-
const reopened = { ...existing,
|
|
12519
|
+
ctx.db.update(projects).set({ repoPath, closedAt: null }).where(eq14(projects.id, existing.id)).run();
|
|
12520
|
+
const reopened = { ...existing, repoPath, closedAt: undefined };
|
|
12400
12521
|
emitProject(ctx, existing.id, {
|
|
12401
12522
|
type: "project.opened",
|
|
12402
|
-
message: `project ${existing.name} re-opened at ${repoPath}
|
|
12523
|
+
message: `project ${existing.name} re-opened at ${repoPath}`
|
|
12403
12524
|
});
|
|
12404
12525
|
warnIfTranslatedMount(ctx, existing.id, repoPath);
|
|
12405
12526
|
return reopened;
|
|
@@ -12408,7 +12529,6 @@ async function openProject(ctx, rawPath) {
|
|
|
12408
12529
|
id: newId("proj"),
|
|
12409
12530
|
name,
|
|
12410
12531
|
repoPath,
|
|
12411
|
-
mainBranch,
|
|
12412
12532
|
devCommand: null,
|
|
12413
12533
|
closedAt: null
|
|
12414
12534
|
};
|
|
@@ -12416,7 +12536,7 @@ async function openProject(ctx, rawPath) {
|
|
|
12416
12536
|
const project = rowToProject(inserted);
|
|
12417
12537
|
emitProject(ctx, project.id, {
|
|
12418
12538
|
type: "project.opened",
|
|
12419
|
-
message: `project ${name} at ${repoPath}
|
|
12539
|
+
message: `project ${name} at ${repoPath}`
|
|
12420
12540
|
});
|
|
12421
12541
|
warnIfTranslatedMount(ctx, project.id, repoPath);
|
|
12422
12542
|
return project;
|
|
@@ -12453,20 +12573,11 @@ async function assertRepoTolerant(ctx, repoPath) {
|
|
|
12453
12573
|
} catch (e) {
|
|
12454
12574
|
if (!isNotImplemented(e))
|
|
12455
12575
|
throw e;
|
|
12456
|
-
if (!
|
|
12576
|
+
if (!existsSync24(join22(repoPath, ".git"))) {
|
|
12457
12577
|
throw new InvalidInputError(`not a git repository: ${repoPath}`);
|
|
12458
12578
|
}
|
|
12459
12579
|
}
|
|
12460
12580
|
}
|
|
12461
|
-
async function detectMainBranchTolerant(ctx, repoPath) {
|
|
12462
|
-
try {
|
|
12463
|
-
return await detectMainBranch(repoPath);
|
|
12464
|
-
} catch (e) {
|
|
12465
|
-
if (!isNotImplemented(e))
|
|
12466
|
-
throw e;
|
|
12467
|
-
return ctx.config.mainBranch;
|
|
12468
|
-
}
|
|
12469
|
-
}
|
|
12470
12581
|
var init_projects = __esm(() => {
|
|
12471
12582
|
init_src();
|
|
12472
12583
|
init_schema();
|
|
@@ -12496,6 +12607,7 @@ var init_project = __esm(() => {
|
|
|
12496
12607
|
roots: publicProcedure.query(() => listRoots()),
|
|
12497
12608
|
browse: publicProcedure.input(z8.object({ dir: z8.string().optional(), showHidden: z8.boolean().optional() }).optional()).query(({ input }) => browseDir(input?.dir, input?.showHidden ?? false)),
|
|
12498
12609
|
branches: publicProcedure.input(z8.object({ projectId: z8.string() })).query(({ ctx, input }) => listBranches(requireProjectById(ctx, input.projectId))),
|
|
12610
|
+
sessionBranch: publicProcedure.input(z8.object({ projectId: z8.string() })).query(({ ctx, input }) => sessionBranchView(requireProjectById(ctx, input.projectId))),
|
|
12499
12611
|
open: publicProcedure.input(z8.object({ repoPath: z8.string().min(1) })).mutation(({ ctx, input }) => openProject(ctx, input.repoPath)),
|
|
12500
12612
|
close: publicProcedure.input(z8.object({ projectId: z8.string() })).mutation(({ ctx, input }) => closeProject(ctx, input.projectId)),
|
|
12501
12613
|
rename: publicProcedure.input(z8.object({ projectId: z8.string(), name: ProjectName })).mutation(({ ctx, input }) => renameProject(ctx, input.projectId, input.name)),
|
|
@@ -12913,7 +13025,7 @@ __export(exports_cli, {
|
|
|
12913
13025
|
resolveDoctorEnv: () => resolveDoctorEnv,
|
|
12914
13026
|
parseMode: () => parseMode
|
|
12915
13027
|
});
|
|
12916
|
-
import { existsSync as
|
|
13028
|
+
import { existsSync as existsSync25, readFileSync as readFileSync15 } from "fs";
|
|
12917
13029
|
function parseMode(argv) {
|
|
12918
13030
|
return argv.includes("--gate") || argv.includes("--boot") ? "gate" : "diagnostic";
|
|
12919
13031
|
}
|
|
@@ -12921,7 +13033,7 @@ function envWithAfkCredentials() {
|
|
|
12921
13033
|
const merged = { ...process.env };
|
|
12922
13034
|
try {
|
|
12923
13035
|
const path = envPath();
|
|
12924
|
-
if (
|
|
13036
|
+
if (existsSync25(path)) {
|
|
12925
13037
|
const fromFile = parseEnvFile2(readFileSync15(path, "utf8"));
|
|
12926
13038
|
for (const runtime of AGENT_RUNTIMES) {
|
|
12927
13039
|
const key = RUNTIME_SPECS[runtime].afkKey;
|