runcastle 1.0.8 → 1.0.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 +246 -9
- package/index.js +246 -9
- package/package.json +1 -1
- package/skills/burner/implement-ticket.md +15 -1
- package/web/assets/{index-Y3ehCYIg.js → index-B0l9lJT5.js} +2 -2
- package/web/assets/index-JQYIwCj3.css +1 -0
- package/web/index.html +2 -2
- package/web/assets/index-EKuI75rm.css +0 -1
package/bin/runcastle.js
CHANGED
|
@@ -27,7 +27,8 @@ __export(exports_paths, {
|
|
|
27
27
|
envPath: () => envPath,
|
|
28
28
|
dbPath: () => dbPath,
|
|
29
29
|
dataDir: () => dataDir,
|
|
30
|
-
configPath: () => configPath
|
|
30
|
+
configPath: () => configPath,
|
|
31
|
+
burnCacheDir: () => burnCacheDir
|
|
31
32
|
});
|
|
32
33
|
import { homedir } from "os";
|
|
33
34
|
import { join } from "path";
|
|
@@ -52,6 +53,9 @@ function sessionDir(id) {
|
|
|
52
53
|
function worktreeDir(projectId, slug) {
|
|
53
54
|
return join(dataDir(), "worktrees", projectId, slug);
|
|
54
55
|
}
|
|
56
|
+
function burnCacheDir(pm) {
|
|
57
|
+
return join(dataDir(), "cache", pm);
|
|
58
|
+
}
|
|
55
59
|
function sandboxBuildDir() {
|
|
56
60
|
return join(dataDir(), "sandbox-build");
|
|
57
61
|
}
|
|
@@ -100,7 +104,10 @@ var init_config = __esm(() => {
|
|
|
100
104
|
sandbox: z.enum(["docker", "podman", "noSandbox"]).default("docker"),
|
|
101
105
|
mainBranch: z.string().default("main"),
|
|
102
106
|
sandboxImage: z.string().optional(),
|
|
103
|
-
burnConcurrency: z.number().int().min(1).max(8).default(3)
|
|
107
|
+
burnConcurrency: z.number().int().min(1).max(8).default(3),
|
|
108
|
+
burnMaxIterations: z.number().int().min(1).max(10).default(3),
|
|
109
|
+
setupCommand: z.string().optional(),
|
|
110
|
+
burnWorkspace: z.enum(["auto", "mounted", "isolated"]).default("auto")
|
|
104
111
|
}));
|
|
105
112
|
});
|
|
106
113
|
|
|
@@ -136,6 +143,13 @@ function loadConfig(env = process.env) {
|
|
|
136
143
|
if (env.RUNCASTLE_BURN_CONCURRENCY) {
|
|
137
144
|
overrides.burnConcurrency = Number(env.RUNCASTLE_BURN_CONCURRENCY);
|
|
138
145
|
}
|
|
146
|
+
if (env.RUNCASTLE_BURN_MAX_ITERATIONS) {
|
|
147
|
+
overrides.burnMaxIterations = Number(env.RUNCASTLE_BURN_MAX_ITERATIONS);
|
|
148
|
+
}
|
|
149
|
+
if (env.RUNCASTLE_SETUP_COMMAND)
|
|
150
|
+
overrides.setupCommand = env.RUNCASTLE_SETUP_COMMAND;
|
|
151
|
+
if (env.RUNCASTLE_BURN_WORKSPACE)
|
|
152
|
+
overrides.burnWorkspace = env.RUNCASTLE_BURN_WORKSPACE;
|
|
139
153
|
const base = typeof fileConfig === "object" && fileConfig !== null ? fileConfig : {};
|
|
140
154
|
return RuncastleConfig.parse({ ...base, ...overrides });
|
|
141
155
|
}
|
|
@@ -2492,6 +2506,46 @@ var init_skills_root = __esm(() => {
|
|
|
2492
2506
|
SKILLS_MARKER = join7("packs", "runcastle");
|
|
2493
2507
|
});
|
|
2494
2508
|
|
|
2509
|
+
// src/services/agent-stream.ts
|
|
2510
|
+
function beginTranscript(ticketId) {
|
|
2511
|
+
transcripts.set(ticketId, { live: true, chunks: [], nextIndex: 0, bytes: 0 });
|
|
2512
|
+
}
|
|
2513
|
+
function appendTranscript(ticketId, chunk) {
|
|
2514
|
+
const t = transcripts.get(ticketId);
|
|
2515
|
+
if (!t)
|
|
2516
|
+
return;
|
|
2517
|
+
const full = { ...chunk, i: t.nextIndex, ts: chunk.ts ?? Date.now() };
|
|
2518
|
+
t.nextIndex += 1;
|
|
2519
|
+
t.chunks.push(full);
|
|
2520
|
+
t.bytes += full.text.length;
|
|
2521
|
+
while (t.chunks.length > MAX_CHUNKS || t.bytes > MAX_BYTES && t.chunks.length > 1) {
|
|
2522
|
+
const dropped = t.chunks.shift();
|
|
2523
|
+
if (!dropped)
|
|
2524
|
+
break;
|
|
2525
|
+
t.bytes -= dropped.text.length;
|
|
2526
|
+
}
|
|
2527
|
+
}
|
|
2528
|
+
function endTranscript(ticketId) {
|
|
2529
|
+
const t = transcripts.get(ticketId);
|
|
2530
|
+
if (t)
|
|
2531
|
+
t.live = false;
|
|
2532
|
+
}
|
|
2533
|
+
function readTranscript(ticketId, after = -1) {
|
|
2534
|
+
const t = transcripts.get(ticketId);
|
|
2535
|
+
if (!t)
|
|
2536
|
+
return { live: false, chunks: [], firstIndex: 0, nextIndex: 0 };
|
|
2537
|
+
return {
|
|
2538
|
+
live: t.live,
|
|
2539
|
+
chunks: after < 0 ? [...t.chunks] : t.chunks.filter((c) => c.i > after),
|
|
2540
|
+
firstIndex: t.chunks[0]?.i ?? t.nextIndex,
|
|
2541
|
+
nextIndex: t.nextIndex
|
|
2542
|
+
};
|
|
2543
|
+
}
|
|
2544
|
+
var MAX_CHUNKS = 4000, MAX_BYTES = 1500000, transcripts;
|
|
2545
|
+
var init_agent_stream = __esm(() => {
|
|
2546
|
+
transcripts = new Map;
|
|
2547
|
+
});
|
|
2548
|
+
|
|
2495
2549
|
// src/workflows/ticket-burner.ts
|
|
2496
2550
|
import { existsSync as existsSync10, mkdirSync as mkdirSync4, readFileSync as readFileSync4, readdirSync as readdirSync3 } from "fs";
|
|
2497
2551
|
import { dirname as dirname3, join as join8 } from "path";
|
|
@@ -2604,6 +2658,97 @@ function parseEnvFile(content) {
|
|
|
2604
2658
|
}
|
|
2605
2659
|
return out;
|
|
2606
2660
|
}
|
|
2661
|
+
function detectPackageManager(tc) {
|
|
2662
|
+
const pinned = tc.packageManagerField?.split("@")[0];
|
|
2663
|
+
if (pinned && PACKAGE_MANAGERS.includes(pinned)) {
|
|
2664
|
+
return pinned;
|
|
2665
|
+
}
|
|
2666
|
+
if (tc.lockfiles.bun)
|
|
2667
|
+
return "bun";
|
|
2668
|
+
if (tc.lockfiles.pnpm)
|
|
2669
|
+
return "pnpm";
|
|
2670
|
+
if (tc.lockfiles.yarn)
|
|
2671
|
+
return "yarn";
|
|
2672
|
+
if (tc.lockfiles.npm)
|
|
2673
|
+
return "npm";
|
|
2674
|
+
return tc.hasPackageJson ? "npm" : undefined;
|
|
2675
|
+
}
|
|
2676
|
+
function resolveSetupCommand(tc, override) {
|
|
2677
|
+
const trimmed = override?.trim();
|
|
2678
|
+
if (trimmed)
|
|
2679
|
+
return trimmed;
|
|
2680
|
+
const pm = detectPackageManager(tc);
|
|
2681
|
+
if (!pm)
|
|
2682
|
+
return;
|
|
2683
|
+
switch (pm) {
|
|
2684
|
+
case "bun":
|
|
2685
|
+
return tc.lockfiles.bun ? "bun install --frozen-lockfile" : "bun install";
|
|
2686
|
+
case "pnpm":
|
|
2687
|
+
return tc.lockfiles.pnpm ? "corepack pnpm install --frozen-lockfile" : "corepack pnpm install";
|
|
2688
|
+
case "yarn":
|
|
2689
|
+
return tc.lockfiles.yarn ? "corepack yarn install --frozen-lockfile" : "corepack yarn install";
|
|
2690
|
+
case "npm":
|
|
2691
|
+
return tc.lockfiles.npm ? "npm ci" : "npm install";
|
|
2692
|
+
}
|
|
2693
|
+
}
|
|
2694
|
+
function cacheMountFor(pm, hostPath) {
|
|
2695
|
+
const sandboxPath = PM_CACHE_SANDBOX_PATHS[pm];
|
|
2696
|
+
return sandboxPath ? { hostPath, sandboxPath } : undefined;
|
|
2697
|
+
}
|
|
2698
|
+
function resolveBurnWorkspaceMode(config2, platform = process.platform) {
|
|
2699
|
+
if (config2.sandbox === "noSandbox")
|
|
2700
|
+
return "mounted";
|
|
2701
|
+
if (config2.burnWorkspace === "auto")
|
|
2702
|
+
return platform === "linux" ? "mounted" : "isolated";
|
|
2703
|
+
return config2.burnWorkspace;
|
|
2704
|
+
}
|
|
2705
|
+
function buildIsolatedSetupCommand(tempBranch, setupCommand) {
|
|
2706
|
+
const hookFile = `${ISOLATED_REPO_PATH}/.git/hooks/post-commit`;
|
|
2707
|
+
const parts = [
|
|
2708
|
+
`git -C ${SANDBOX_WORKSPACE_PATH} config receive.denyCurrentBranch updateInstead`,
|
|
2709
|
+
`git clone ${SANDBOX_WORKSPACE_PATH} ${ISOLATED_REPO_PATH}`,
|
|
2710
|
+
`printf '#!/bin/sh\\nexec git push --quiet origin HEAD:%s\\n' '${tempBranch}' > ${hookFile}`,
|
|
2711
|
+
`chmod +x ${hookFile}`
|
|
2712
|
+
];
|
|
2713
|
+
if (setupCommand)
|
|
2714
|
+
parts.push(`cd ${ISOLATED_REPO_PATH} && ${setupCommand}`);
|
|
2715
|
+
return parts.join(" && ");
|
|
2716
|
+
}
|
|
2717
|
+
function buildWorkspaceNotes(mode) {
|
|
2718
|
+
if (mode === "mounted") {
|
|
2719
|
+
return "Work in the current directory \u2014 it is the repo checkout on your branch.";
|
|
2720
|
+
}
|
|
2721
|
+
return [
|
|
2722
|
+
`Your working repository is \`${ISOLATED_REPO_PATH}\` \u2014 a clone on the container's fast native filesystem, with dependencies already installed. Do ALL work there: \`cd ${ISOLATED_REPO_PATH}\` first; every file you read, edit, test, and commit lives under it.`,
|
|
2723
|
+
"",
|
|
2724
|
+
`The directory you start in (\`${SANDBOX_WORKSPACE_PATH}\`) is a slow mounted mirror used only to collect your commits \u2014 never edit files, install, or run tests there. Your commits sync back automatically (a post-commit hook pushes them); just commit as normal.`,
|
|
2725
|
+
"",
|
|
2726
|
+
`If you are blocked and write \`BLOCKED.md\`, write it at \`${ISOLATED_REPO_PATH}/BLOCKED.md\` AND copy it to \`${SANDBOX_WORKSPACE_PATH}/BLOCKED.md\` so the orchestrator can see it.`
|
|
2727
|
+
].join(`
|
|
2728
|
+
`);
|
|
2729
|
+
}
|
|
2730
|
+
function readRepoToolchain(repoPath) {
|
|
2731
|
+
const pkgPath = join8(repoPath, "package.json");
|
|
2732
|
+
const hasPackageJson = existsSync10(pkgPath);
|
|
2733
|
+
let packageManagerField;
|
|
2734
|
+
if (hasPackageJson) {
|
|
2735
|
+
try {
|
|
2736
|
+
const pkg = JSON.parse(readFileSync4(pkgPath, "utf8"));
|
|
2737
|
+
if (typeof pkg.packageManager === "string")
|
|
2738
|
+
packageManagerField = pkg.packageManager;
|
|
2739
|
+
} catch {}
|
|
2740
|
+
}
|
|
2741
|
+
return {
|
|
2742
|
+
hasPackageJson,
|
|
2743
|
+
...packageManagerField !== undefined ? { packageManagerField } : {},
|
|
2744
|
+
lockfiles: {
|
|
2745
|
+
bun: existsSync10(join8(repoPath, "bun.lock")) || existsSync10(join8(repoPath, "bun.lockb")),
|
|
2746
|
+
pnpm: existsSync10(join8(repoPath, "pnpm-lock.yaml")),
|
|
2747
|
+
yarn: existsSync10(join8(repoPath, "yarn.lock")),
|
|
2748
|
+
npm: existsSync10(join8(repoPath, "package-lock.json"))
|
|
2749
|
+
}
|
|
2750
|
+
};
|
|
2751
|
+
}
|
|
2607
2752
|
function createStreamThrottle(emit2, opts = {}) {
|
|
2608
2753
|
const intervalMs = opts.intervalMs ?? 2000;
|
|
2609
2754
|
const maxChars = opts.maxChars ?? 500;
|
|
@@ -2855,8 +3000,11 @@ function buildBurnAgent(config2, token, model) {
|
|
|
2855
3000
|
}
|
|
2856
3001
|
return agent;
|
|
2857
3002
|
}
|
|
2858
|
-
function selectSandbox(config2) {
|
|
2859
|
-
const imageOpts = {
|
|
3003
|
+
function selectSandbox(config2, mounts = []) {
|
|
3004
|
+
const imageOpts = {
|
|
3005
|
+
imageName: resolveSandboxImage(config2),
|
|
3006
|
+
...mounts.length > 0 ? { mounts } : {}
|
|
3007
|
+
};
|
|
2860
3008
|
switch (config2.sandbox) {
|
|
2861
3009
|
case "docker":
|
|
2862
3010
|
return docker(imageOpts);
|
|
@@ -2869,31 +3017,74 @@ function selectSandbox(config2) {
|
|
|
2869
3017
|
async function realExecuteTicketRun(ctx, ticket, config2, token, model, land) {
|
|
2870
3018
|
const { project, feature } = ctx;
|
|
2871
3019
|
const tempBranch = ticketBranchName(feature.slug, ticket.seq, newId("b").slice(2, 10));
|
|
3020
|
+
const workspaceMode = resolveBurnWorkspaceMode(config2);
|
|
2872
3021
|
const template = readFileSync4(burnerTemplatePath(), "utf8");
|
|
2873
3022
|
const prompt = renderTicketPrompt(template, {
|
|
2874
3023
|
TICKET_JSON: buildTicketJson(ticket),
|
|
2875
3024
|
FEATURE_BRIEF: buildFeatureBrief(feature),
|
|
2876
3025
|
DOCS_DIGEST: readDocsDigestFromDisk(project.id, feature.slug),
|
|
2877
|
-
COMMIT_CONVENTION: `ticket(${ticket.seq}): <summary
|
|
3026
|
+
COMMIT_CONVENTION: `ticket(${ticket.seq}): <summary>`,
|
|
3027
|
+
WORKSPACE_NOTES: buildWorkspaceNotes(workspaceMode)
|
|
2878
3028
|
});
|
|
3029
|
+
const toolchain = readRepoToolchain(project.repoPath);
|
|
3030
|
+
const pm = detectPackageManager(toolchain);
|
|
3031
|
+
const setupCommand = resolveSetupCommand(toolchain, config2.setupCommand);
|
|
3032
|
+
const mounts = [];
|
|
3033
|
+
if (config2.sandbox !== "noSandbox" && pm) {
|
|
3034
|
+
const mount = cacheMountFor(pm, burnCacheDir(pm));
|
|
3035
|
+
if (mount) {
|
|
3036
|
+
mkdirSync4(mount.hostPath, { recursive: true });
|
|
3037
|
+
mounts.push(mount);
|
|
3038
|
+
}
|
|
3039
|
+
}
|
|
3040
|
+
const hookCommand = workspaceMode === "isolated" ? buildIsolatedSetupCommand(tempBranch, setupCommand) : setupCommand;
|
|
3041
|
+
if (hookCommand) {
|
|
3042
|
+
ctx.emitEvent({
|
|
3043
|
+
type: "burn.setup",
|
|
3044
|
+
message: workspaceMode === "isolated" ? `preparing isolated workspace (native-FS clone)${setupCommand ? ` + deps install: ${setupCommand}` : ""}` : `installing deps before agent start: ${setupCommand}`,
|
|
3045
|
+
ticketId: ticket.id
|
|
3046
|
+
});
|
|
3047
|
+
}
|
|
2879
3048
|
mkdirSync4(logsDir(), { recursive: true });
|
|
2880
3049
|
const logFilePath = join8(logsDir(), `burn-${feature.id}-${ticket.seq}.log`);
|
|
2881
3050
|
const throttle = createStreamThrottle((e) => ctx.emitEvent({ ...e, ticketId: ticket.id }));
|
|
3051
|
+
beginTranscript(ticket.id);
|
|
3052
|
+
const onStreamEvent = (event) => {
|
|
3053
|
+
throttle.onEvent(event);
|
|
3054
|
+
if (event.type === "text") {
|
|
3055
|
+
appendTranscript(ticket.id, { kind: "text", text: event.message });
|
|
3056
|
+
} else if (event.type === "toolCall") {
|
|
3057
|
+
appendTranscript(ticket.id, {
|
|
3058
|
+
kind: "tool",
|
|
3059
|
+
text: event.formattedArgs ?? "",
|
|
3060
|
+
name: event.name
|
|
3061
|
+
});
|
|
3062
|
+
}
|
|
3063
|
+
};
|
|
2882
3064
|
const runOptions = {
|
|
2883
3065
|
agent: buildBurnAgent(config2, token, model),
|
|
2884
|
-
sandbox: selectSandbox(config2),
|
|
3066
|
+
sandbox: selectSandbox(config2, mounts),
|
|
2885
3067
|
cwd: project.repoPath,
|
|
2886
3068
|
prompt,
|
|
2887
3069
|
branchStrategy: { type: "branch", branch: tempBranch, baseBranch: feature.branch },
|
|
2888
3070
|
signal: ctx.signal,
|
|
2889
3071
|
name: `ticket-${ticket.seq}`,
|
|
2890
|
-
|
|
3072
|
+
maxIterations: config2.burnMaxIterations,
|
|
3073
|
+
...hookCommand ? {
|
|
3074
|
+
hooks: {
|
|
3075
|
+
sandbox: {
|
|
3076
|
+
onSandboxReady: [{ command: hookCommand, timeoutMs: SETUP_HOOK_TIMEOUT_MS }]
|
|
3077
|
+
}
|
|
3078
|
+
}
|
|
3079
|
+
} : {},
|
|
3080
|
+
logging: { type: "file", path: logFilePath, onAgentStreamEvent: onStreamEvent }
|
|
2891
3081
|
};
|
|
2892
3082
|
let result;
|
|
2893
3083
|
try {
|
|
2894
3084
|
result = await run(runOptions);
|
|
2895
3085
|
} catch (err) {
|
|
2896
3086
|
throttle.flush();
|
|
3087
|
+
endTranscript(ticket.id);
|
|
2897
3088
|
if (ctx.signal.aborted)
|
|
2898
3089
|
throw err;
|
|
2899
3090
|
const msg = err instanceof Error ? err.message : String(err);
|
|
@@ -2910,6 +3101,7 @@ async function realExecuteTicketRun(ctx, ticket, config2, token, model, land) {
|
|
|
2910
3101
|
return { status: "failed", error: msg };
|
|
2911
3102
|
}
|
|
2912
3103
|
throttle.flush();
|
|
3104
|
+
endTranscript(ticket.id);
|
|
2913
3105
|
const blocked = readBlockedFile([result.preservedWorktreePath, project.repoPath]);
|
|
2914
3106
|
const outcome = interpretRunResult(result, blocked);
|
|
2915
3107
|
if (outcome.status !== "done")
|
|
@@ -2958,14 +3150,28 @@ function readTokenFromEnvFile(path) {
|
|
|
2958
3150
|
const token = fromFile && fromFile.length > 0 ? fromFile : process.env.CLAUDE_CODE_OAUTH_TOKEN;
|
|
2959
3151
|
return token && token.length > 0 ? token : undefined;
|
|
2960
3152
|
}
|
|
2961
|
-
var AUTH_MISSING_EVENT = "auth.missing", AUTH_MISSING_MESSAGE = "run `claude setup-token` and put CLAUDE_CODE_OAUTH_TOKEN in ~/.runcastle/.env", PLACEHOLDERS, ticketBurner;
|
|
3153
|
+
var AUTH_MISSING_EVENT = "auth.missing", AUTH_MISSING_MESSAGE = "run `claude setup-token` and put CLAUDE_CODE_OAUTH_TOKEN in ~/.runcastle/.env", PLACEHOLDERS, PACKAGE_MANAGERS, PM_CACHE_SANDBOX_PATHS, SANDBOX_WORKSPACE_PATH = "/home/agent/workspace", ISOLATED_REPO_PATH = "/home/agent/repo", SETUP_HOOK_TIMEOUT_MS, ticketBurner;
|
|
2962
3154
|
var init_ticket_burner = __esm(() => {
|
|
2963
3155
|
init_src();
|
|
2964
3156
|
init_config_load();
|
|
2965
3157
|
init_paths();
|
|
2966
3158
|
init_skills_root();
|
|
3159
|
+
init_agent_stream();
|
|
2967
3160
|
init_git();
|
|
2968
|
-
PLACEHOLDERS = [
|
|
3161
|
+
PLACEHOLDERS = [
|
|
3162
|
+
"TICKET_JSON",
|
|
3163
|
+
"FEATURE_BRIEF",
|
|
3164
|
+
"DOCS_DIGEST",
|
|
3165
|
+
"COMMIT_CONVENTION",
|
|
3166
|
+
"WORKSPACE_NOTES"
|
|
3167
|
+
];
|
|
3168
|
+
PACKAGE_MANAGERS = ["bun", "pnpm", "yarn", "npm"];
|
|
3169
|
+
PM_CACHE_SANDBOX_PATHS = {
|
|
3170
|
+
bun: "~/.bun/install/cache",
|
|
3171
|
+
yarn: "~/.cache/yarn",
|
|
3172
|
+
npm: "~/.npm"
|
|
3173
|
+
};
|
|
3174
|
+
SETUP_HOOK_TIMEOUT_MS = 15 * 60000;
|
|
2969
3175
|
ticketBurner = {
|
|
2970
3176
|
id: "ticket-burner",
|
|
2971
3177
|
run: (ctx) => burnRun(ctx, resolveBurnDeps(ctx))
|
|
@@ -4742,6 +4948,17 @@ import { eq as eq9, isNull } from "drizzle-orm";
|
|
|
4742
4948
|
function listProjects(ctx) {
|
|
4743
4949
|
return ctx.db.select().from(projects).where(isNull(projects.closedAt)).all().map(rowToProject);
|
|
4744
4950
|
}
|
|
4951
|
+
function isTranslatedWindowsMount(repoPath, platform = process.platform) {
|
|
4952
|
+
return platform === "linux" && /^\/mnt\/[a-z](\/|$)/i.test(repoPath);
|
|
4953
|
+
}
|
|
4954
|
+
function warnIfTranslatedMount(ctx, projectId, repoPath) {
|
|
4955
|
+
if (!isTranslatedWindowsMount(repoPath))
|
|
4956
|
+
return;
|
|
4957
|
+
emitProject(ctx, projectId, {
|
|
4958
|
+
type: "project.slow-path",
|
|
4959
|
+
message: `${repoPath} is a Windows drive mounted into Linux (/mnt) \u2014 git and burns pay a per-file translation tax here; clone the repo into the Linux filesystem (e.g. ~/projects) for native speed`
|
|
4960
|
+
});
|
|
4961
|
+
}
|
|
4745
4962
|
async function openProject(ctx, repoPath) {
|
|
4746
4963
|
await assertRepoTolerant(ctx, repoPath);
|
|
4747
4964
|
const mainBranch = await detectMainBranchTolerant(ctx, repoPath);
|
|
@@ -4754,6 +4971,7 @@ async function openProject(ctx, repoPath) {
|
|
|
4754
4971
|
type: "project.opened",
|
|
4755
4972
|
message: `project ${existing.name} re-opened at ${repoPath} (${mainBranch})`
|
|
4756
4973
|
});
|
|
4974
|
+
warnIfTranslatedMount(ctx, existing.id, repoPath);
|
|
4757
4975
|
return reopened;
|
|
4758
4976
|
}
|
|
4759
4977
|
const row = {
|
|
@@ -4770,6 +4988,7 @@ async function openProject(ctx, repoPath) {
|
|
|
4770
4988
|
type: "project.opened",
|
|
4771
4989
|
message: `project ${name} at ${repoPath} (${mainBranch})`
|
|
4772
4990
|
});
|
|
4991
|
+
warnIfTranslatedMount(ctx, project.id, repoPath);
|
|
4773
4992
|
return project;
|
|
4774
4993
|
}
|
|
4775
4994
|
function closeProject(ctx, projectId) {
|
|
@@ -4844,11 +5063,13 @@ var init_project = __esm(() => {
|
|
|
4844
5063
|
import * as z8 from "zod";
|
|
4845
5064
|
var runRouter;
|
|
4846
5065
|
var init_run = __esm(() => {
|
|
5066
|
+
init_agent_stream();
|
|
4847
5067
|
init_repo();
|
|
4848
5068
|
init_runner();
|
|
4849
5069
|
init_context();
|
|
4850
5070
|
runRouter = router({
|
|
4851
5071
|
get: publicProcedure.input(z8.object({ runId: z8.string() })).query(({ ctx, input }) => getRunRow(ctx, input.runId)),
|
|
5072
|
+
agentTranscript: publicProcedure.input(z8.object({ ticketId: z8.string(), after: z8.number().int().optional() })).query(({ input }) => readTranscript(input.ticketId, input.after ?? -1)),
|
|
4852
5073
|
cancel: publicProcedure.input(z8.object({ runId: z8.string() })).mutation(({ input }) => {
|
|
4853
5074
|
cancelRun(input.runId);
|
|
4854
5075
|
return { ok: true };
|
|
@@ -5084,6 +5305,22 @@ var init_settings = __esm(() => {
|
|
|
5084
5305
|
valueSchema: z9.number().int().min(1).max(8),
|
|
5085
5306
|
parseEnv: (raw) => Number(raw)
|
|
5086
5307
|
},
|
|
5308
|
+
{
|
|
5309
|
+
key: "burnMaxIterations",
|
|
5310
|
+
configKey: "burnMaxIterations",
|
|
5311
|
+
envVar: "RUNCASTLE_BURN_MAX_ITERATIONS",
|
|
5312
|
+
restartRequired: false,
|
|
5313
|
+
valueSchema: z9.number().int().min(1).max(10),
|
|
5314
|
+
parseEnv: (raw) => Number(raw)
|
|
5315
|
+
},
|
|
5316
|
+
{
|
|
5317
|
+
key: "setupCommand",
|
|
5318
|
+
configKey: "setupCommand",
|
|
5319
|
+
envVar: "RUNCASTLE_SETUP_COMMAND",
|
|
5320
|
+
restartRequired: false,
|
|
5321
|
+
valueSchema: z9.string().min(1),
|
|
5322
|
+
parseEnv: idEnv
|
|
5323
|
+
},
|
|
5087
5324
|
{
|
|
5088
5325
|
key: "mainBranch",
|
|
5089
5326
|
configKey: "mainBranch",
|