runcastle 1.4.6 → 1.4.8
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 +228 -150
- package/index.js +228 -150
- package/package.json +1 -1
- package/skills/packs/runcastle/skills/ideate/SKILL.md +3 -2
- package/web/assets/{index-7hbR8_dm.js → index-CUoLAKY6.js} +28 -28
- package/web/index.html +1 -1
package/bin/runcastle.js
CHANGED
|
@@ -4640,6 +4640,63 @@ var init_git = __esm(() => {
|
|
|
4640
4640
|
];
|
|
4641
4641
|
});
|
|
4642
4642
|
|
|
4643
|
+
// src/doctor/system-exec.ts
|
|
4644
|
+
import { spawn as spawn4 } from "child_process";
|
|
4645
|
+
function createSystemExec(opts = {}) {
|
|
4646
|
+
const timeoutMs = opts.timeoutMs ?? DEFAULT_EXEC_TIMEOUT_MS;
|
|
4647
|
+
const containFn = opts.containFn ?? containHostProcess;
|
|
4648
|
+
return (command, args) => new Promise((resolve4) => {
|
|
4649
|
+
const { file, args: spawnArgs } = resolveSpawnTarget(command, args);
|
|
4650
|
+
let stdout = "";
|
|
4651
|
+
let stderr = "";
|
|
4652
|
+
let settled = false;
|
|
4653
|
+
const settle = (outcome) => {
|
|
4654
|
+
if (settled)
|
|
4655
|
+
return;
|
|
4656
|
+
settled = true;
|
|
4657
|
+
clearTimeout(timer);
|
|
4658
|
+
resolve4(outcome);
|
|
4659
|
+
};
|
|
4660
|
+
const child = spawn4(file, spawnArgs, { cwd: opts.cwd, windowsHide: true });
|
|
4661
|
+
const containment = child.pid === undefined ? null : containFn(child.pid);
|
|
4662
|
+
const timer = setTimeout(() => {
|
|
4663
|
+
if (containment)
|
|
4664
|
+
containment.kill();
|
|
4665
|
+
else if (child.pid !== undefined)
|
|
4666
|
+
killProcessTree(child.pid);
|
|
4667
|
+
const note = `${command} ${args.join(" ")} timed out after ${Math.round(timeoutMs / 1000)}s`;
|
|
4668
|
+
settle({
|
|
4669
|
+
ok: true,
|
|
4670
|
+
code: null,
|
|
4671
|
+
stdout,
|
|
4672
|
+
stderr: stderr ? `${stderr}
|
|
4673
|
+
${note}` : note,
|
|
4674
|
+
timedOut: true
|
|
4675
|
+
});
|
|
4676
|
+
}, timeoutMs);
|
|
4677
|
+
child.stdout?.on("data", (d) => {
|
|
4678
|
+
stdout += d.toString();
|
|
4679
|
+
});
|
|
4680
|
+
child.stderr?.on("data", (d) => {
|
|
4681
|
+
stderr += d.toString();
|
|
4682
|
+
});
|
|
4683
|
+
child.on("error", (err) => {
|
|
4684
|
+
containment?.kill();
|
|
4685
|
+
settle({ ok: false, code: null, stdout, stderr: stderr || String(err) });
|
|
4686
|
+
});
|
|
4687
|
+
child.on("close", (code) => {
|
|
4688
|
+
containment?.kill();
|
|
4689
|
+
settle({ ok: true, code: code ?? 0, stdout, stderr });
|
|
4690
|
+
});
|
|
4691
|
+
});
|
|
4692
|
+
}
|
|
4693
|
+
var DEFAULT_EXEC_TIMEOUT_MS = 60000;
|
|
4694
|
+
var init_system_exec = __esm(() => {
|
|
4695
|
+
init_kill_tree();
|
|
4696
|
+
init_contain_host();
|
|
4697
|
+
init_resolve_executable();
|
|
4698
|
+
});
|
|
4699
|
+
|
|
4643
4700
|
// src/launcher/edit-guard.ts
|
|
4644
4701
|
import { isAbsolute, relative, resolve as resolve4 } from "path";
|
|
4645
4702
|
function guardsEdits(kind) {
|
|
@@ -4759,6 +4816,64 @@ var init_install_guard = __esm(() => {
|
|
|
4759
4816
|
HEREDOC_OPENER = /(?<!<)<<(?!<)(-?)[ \t]*(?:'([^'\n]+)'|"([^"\n]+)"|\\?([\w.-]+))/g;
|
|
4760
4817
|
});
|
|
4761
4818
|
|
|
4819
|
+
// src/launcher/skills-root.ts
|
|
4820
|
+
import { existsSync as existsSync7 } from "fs";
|
|
4821
|
+
import { dirname as dirname3, join as join7, resolve as resolve6 } from "path";
|
|
4822
|
+
import { fileURLToPath as fileURLToPath3 } from "url";
|
|
4823
|
+
function resolveSkillsRoot(fromDir) {
|
|
4824
|
+
const override = process.env[SKILLS_DIR_ENV];
|
|
4825
|
+
if (override) {
|
|
4826
|
+
const root = resolve6(override);
|
|
4827
|
+
if (existsSync7(join7(root, SKILLS_MARKER)))
|
|
4828
|
+
return root;
|
|
4829
|
+
throw new Error(`${SKILLS_DIR_ENV}=${override} is not a runcastle skills root (missing ${SKILLS_MARKER})`);
|
|
4830
|
+
}
|
|
4831
|
+
const searched = [];
|
|
4832
|
+
let dir = fromDir;
|
|
4833
|
+
for (let i = 0;i < 8; i += 1) {
|
|
4834
|
+
const candidate = join7(dir, "packages", "skills");
|
|
4835
|
+
searched.push(candidate);
|
|
4836
|
+
if (existsSync7(join7(candidate, SKILLS_MARKER)))
|
|
4837
|
+
return candidate;
|
|
4838
|
+
const parent = dirname3(dir);
|
|
4839
|
+
if (parent === dir)
|
|
4840
|
+
break;
|
|
4841
|
+
dir = parent;
|
|
4842
|
+
}
|
|
4843
|
+
throw new Error(`runcastle skills root not found; searched:
|
|
4844
|
+
${searched.join(`
|
|
4845
|
+
`)}`);
|
|
4846
|
+
}
|
|
4847
|
+
function resolvePluginDir(fromDir = dirname3(fileURLToPath3(import.meta.url))) {
|
|
4848
|
+
const rel = join7("packages", "skills", "packs", "runcastle");
|
|
4849
|
+
const override = process.env[SKILLS_DIR_ENV];
|
|
4850
|
+
if (override) {
|
|
4851
|
+
const dir2 = join7(resolve6(override), "packs", "runcastle");
|
|
4852
|
+
if (existsSync7(dir2))
|
|
4853
|
+
return dir2;
|
|
4854
|
+
throw new Error(`${SKILLS_DIR_ENV}=${override} has no plugin dir at ${dir2}`);
|
|
4855
|
+
}
|
|
4856
|
+
const searched = [];
|
|
4857
|
+
let dir = fromDir;
|
|
4858
|
+
for (let i = 0;i < 8; i += 1) {
|
|
4859
|
+
const candidate = join7(dir, rel);
|
|
4860
|
+
searched.push(candidate);
|
|
4861
|
+
if (existsSync7(candidate))
|
|
4862
|
+
return candidate;
|
|
4863
|
+
const parent = dirname3(dir);
|
|
4864
|
+
if (parent === dir)
|
|
4865
|
+
break;
|
|
4866
|
+
dir = parent;
|
|
4867
|
+
}
|
|
4868
|
+
throw new Error(`runcastle plugin dir (${rel}) not found; searched:
|
|
4869
|
+
${searched.join(`
|
|
4870
|
+
`)}`);
|
|
4871
|
+
}
|
|
4872
|
+
var SKILLS_DIR_ENV = "RUNCASTLE_SKILLS_DIR", SKILLS_MARKER;
|
|
4873
|
+
var init_skills_root = __esm(() => {
|
|
4874
|
+
SKILLS_MARKER = join7("packs", "runcastle");
|
|
4875
|
+
});
|
|
4876
|
+
|
|
4762
4877
|
// src/launcher/runtimes/skills.ts
|
|
4763
4878
|
function skillRef(runtime, skill) {
|
|
4764
4879
|
return SPELLINGS[runtime](skill);
|
|
@@ -4814,10 +4929,10 @@ var init_skills = __esm(() => {
|
|
|
4814
4929
|
|
|
4815
4930
|
// src/launcher/artifacts.ts
|
|
4816
4931
|
import { mkdirSync as mkdirSync3, writeFileSync as writeFileSync2 } from "fs";
|
|
4817
|
-
import { fileURLToPath as
|
|
4818
|
-
import { join as
|
|
4932
|
+
import { fileURLToPath as fileURLToPath4 } from "url";
|
|
4933
|
+
import { join as join8 } from "path";
|
|
4819
4934
|
function hookClientPath() {
|
|
4820
|
-
return resolveAsset(ASSET_ENV.hookClient,
|
|
4935
|
+
return resolveAsset(ASSET_ENV.hookClient, fileURLToPath4(new URL("./hook-client.ts", import.meta.url)));
|
|
4821
4936
|
}
|
|
4822
4937
|
function serverUrlFor(config2) {
|
|
4823
4938
|
return `http://localhost:${config2.serverPort}`;
|
|
@@ -5261,6 +5376,10 @@ function renderProjectPrompt(brief, runtime = DEFAULT_RUNTIME) {
|
|
|
5261
5376
|
].join(`
|
|
5262
5377
|
`);
|
|
5263
5378
|
}
|
|
5379
|
+
function skillPackReadRule(packDir) {
|
|
5380
|
+
const posix2 = packDir.replace(/\\/g, "/").replace(/^([A-Za-z]):/, (_, drive) => `/${drive.toLowerCase()}`).replace(/\/+$/, "");
|
|
5381
|
+
return `Read(/${posix2}/**)`;
|
|
5382
|
+
}
|
|
5264
5383
|
function entrySkillDenyRules(kind) {
|
|
5265
5384
|
const allowed = new Set(entrySkillsFor(kind));
|
|
5266
5385
|
return ENTRY_SKILLS.filter((skill) => !allowed.has(skill)).flatMap((skill) => [
|
|
@@ -5279,7 +5398,7 @@ function sessionBashAllowRules(kind) {
|
|
|
5279
5398
|
}
|
|
5280
5399
|
return SESSION_BASH_ALLOW_RULES;
|
|
5281
5400
|
}
|
|
5282
|
-
function renderSettings(hookClient, kind) {
|
|
5401
|
+
function renderSettings(hookClient, kind, skillPackDir) {
|
|
5283
5402
|
const cmd = (event) => ({
|
|
5284
5403
|
type: "command",
|
|
5285
5404
|
command: `bun run "${hookClient}" ${event}`,
|
|
@@ -5287,7 +5406,11 @@ function renderSettings(hookClient, kind) {
|
|
|
5287
5406
|
});
|
|
5288
5407
|
return {
|
|
5289
5408
|
permissions: {
|
|
5290
|
-
allow: [
|
|
5409
|
+
allow: [
|
|
5410
|
+
...RUNCASTLE_MCP_ALLOW_RULES,
|
|
5411
|
+
...sessionBashAllowRules(kind),
|
|
5412
|
+
...skillPackDir ? [skillPackReadRule(skillPackDir)] : []
|
|
5413
|
+
],
|
|
5291
5414
|
deny: entrySkillDenyRules(kind)
|
|
5292
5415
|
},
|
|
5293
5416
|
hooks: {
|
|
@@ -5348,15 +5471,15 @@ function renderSessionPrompt(input) {
|
|
|
5348
5471
|
return renderProjectPrompt(projectBrief, runtime);
|
|
5349
5472
|
throw new Error(`session ${session.id} has no feature and no project-session brief`);
|
|
5350
5473
|
}
|
|
5351
|
-
async function writeSessionArtifacts(input) {
|
|
5474
|
+
async function writeSessionArtifacts(input, pluginDir = resolvePluginDir()) {
|
|
5352
5475
|
const { session, config: config2 } = input;
|
|
5353
5476
|
const dir = sessionDir(session.id);
|
|
5354
5477
|
mkdirSync3(dir, { recursive: true });
|
|
5355
|
-
const systemPromptPath =
|
|
5356
|
-
const settingsPath =
|
|
5357
|
-
const mcpConfigPath =
|
|
5478
|
+
const systemPromptPath = join8(dir, "system-prompt.md");
|
|
5479
|
+
const settingsPath = join8(dir, "settings.json");
|
|
5480
|
+
const mcpConfigPath = join8(dir, "mcp.json");
|
|
5358
5481
|
writeFileSync2(systemPromptPath, renderSessionPrompt(input), "utf8");
|
|
5359
|
-
writeFileSync2(settingsPath, JSON.stringify(renderSettings(hookClientPath(), session.kind), null, 2), "utf8");
|
|
5482
|
+
writeFileSync2(settingsPath, JSON.stringify(renderSettings(hookClientPath(), session.kind, pluginDir), null, 2), "utf8");
|
|
5360
5483
|
writeFileSync2(mcpConfigPath, JSON.stringify(renderMcpConfig(session, config2), null, 2), "utf8");
|
|
5361
5484
|
return { systemPromptPath, settingsPath, mcpConfigPath };
|
|
5362
5485
|
}
|
|
@@ -5368,6 +5491,7 @@ var init_artifacts = __esm(() => {
|
|
|
5368
5491
|
init_asset_paths();
|
|
5369
5492
|
init_edit_guard();
|
|
5370
5493
|
init_install_guard();
|
|
5494
|
+
init_skills_root();
|
|
5371
5495
|
init_skills();
|
|
5372
5496
|
RUNCASTLE_MCP_ALLOW_RULES = [
|
|
5373
5497
|
"mcp__runcastle__get_feature_context",
|
|
@@ -5416,64 +5540,6 @@ var init_artifacts = __esm(() => {
|
|
|
5416
5540
|
SESSION_START_SOURCES = ["startup", "resume", "clear", "compact", "fork"];
|
|
5417
5541
|
});
|
|
5418
5542
|
|
|
5419
|
-
// src/launcher/skills-root.ts
|
|
5420
|
-
import { existsSync as existsSync7 } from "fs";
|
|
5421
|
-
import { dirname as dirname3, join as join8, resolve as resolve6 } from "path";
|
|
5422
|
-
import { fileURLToPath as fileURLToPath4 } from "url";
|
|
5423
|
-
function resolveSkillsRoot(fromDir) {
|
|
5424
|
-
const override = process.env[SKILLS_DIR_ENV];
|
|
5425
|
-
if (override) {
|
|
5426
|
-
const root = resolve6(override);
|
|
5427
|
-
if (existsSync7(join8(root, SKILLS_MARKER)))
|
|
5428
|
-
return root;
|
|
5429
|
-
throw new Error(`${SKILLS_DIR_ENV}=${override} is not a runcastle skills root (missing ${SKILLS_MARKER})`);
|
|
5430
|
-
}
|
|
5431
|
-
const searched = [];
|
|
5432
|
-
let dir = fromDir;
|
|
5433
|
-
for (let i = 0;i < 8; i += 1) {
|
|
5434
|
-
const candidate = join8(dir, "packages", "skills");
|
|
5435
|
-
searched.push(candidate);
|
|
5436
|
-
if (existsSync7(join8(candidate, SKILLS_MARKER)))
|
|
5437
|
-
return candidate;
|
|
5438
|
-
const parent = dirname3(dir);
|
|
5439
|
-
if (parent === dir)
|
|
5440
|
-
break;
|
|
5441
|
-
dir = parent;
|
|
5442
|
-
}
|
|
5443
|
-
throw new Error(`runcastle skills root not found; searched:
|
|
5444
|
-
${searched.join(`
|
|
5445
|
-
`)}`);
|
|
5446
|
-
}
|
|
5447
|
-
function resolvePluginDir(fromDir = dirname3(fileURLToPath4(import.meta.url))) {
|
|
5448
|
-
const rel = join8("packages", "skills", "packs", "runcastle");
|
|
5449
|
-
const override = process.env[SKILLS_DIR_ENV];
|
|
5450
|
-
if (override) {
|
|
5451
|
-
const dir2 = join8(resolve6(override), "packs", "runcastle");
|
|
5452
|
-
if (existsSync7(dir2))
|
|
5453
|
-
return dir2;
|
|
5454
|
-
throw new Error(`${SKILLS_DIR_ENV}=${override} has no plugin dir at ${dir2}`);
|
|
5455
|
-
}
|
|
5456
|
-
const searched = [];
|
|
5457
|
-
let dir = fromDir;
|
|
5458
|
-
for (let i = 0;i < 8; i += 1) {
|
|
5459
|
-
const candidate = join8(dir, rel);
|
|
5460
|
-
searched.push(candidate);
|
|
5461
|
-
if (existsSync7(candidate))
|
|
5462
|
-
return candidate;
|
|
5463
|
-
const parent = dirname3(dir);
|
|
5464
|
-
if (parent === dir)
|
|
5465
|
-
break;
|
|
5466
|
-
dir = parent;
|
|
5467
|
-
}
|
|
5468
|
-
throw new Error(`runcastle plugin dir (${rel}) not found; searched:
|
|
5469
|
-
${searched.join(`
|
|
5470
|
-
`)}`);
|
|
5471
|
-
}
|
|
5472
|
-
var SKILLS_DIR_ENV = "RUNCASTLE_SKILLS_DIR", SKILLS_MARKER;
|
|
5473
|
-
var init_skills_root = __esm(() => {
|
|
5474
|
-
SKILLS_MARKER = join8("packs", "runcastle");
|
|
5475
|
-
});
|
|
5476
|
-
|
|
5477
5543
|
// src/launcher/runtimes/types.ts
|
|
5478
5544
|
function assertHeadlessKickoff(headless, line) {
|
|
5479
5545
|
if (headless && !line)
|
|
@@ -5511,8 +5577,27 @@ function buildClaudeArgs(input) {
|
|
|
5511
5577
|
...input.kickoffLine ? [input.kickoffLine] : []
|
|
5512
5578
|
];
|
|
5513
5579
|
}
|
|
5514
|
-
|
|
5580
|
+
function findBackgroundSessionIn(listing, ccSessionId) {
|
|
5581
|
+
if (!Array.isArray(listing))
|
|
5582
|
+
return;
|
|
5583
|
+
const held = listing.find((entry) => typeof entry === "object" && entry !== null && entry.sessionId === ccSessionId && entry.kind === "background");
|
|
5584
|
+
if (!held)
|
|
5585
|
+
return;
|
|
5586
|
+
return { sessionId: ccSessionId, shortId: typeof held.id === "string" ? held.id : ccSessionId.slice(0, 8) };
|
|
5587
|
+
}
|
|
5588
|
+
async function listClaudeAgents() {
|
|
5589
|
+
try {
|
|
5590
|
+
const out = await createSystemExec({ timeoutMs: AGENTS_LISTING_TIMEOUT_MS })(CLAUDE_BIN, ["agents", "--json"]);
|
|
5591
|
+
if (!out.ok || out.timedOut)
|
|
5592
|
+
return;
|
|
5593
|
+
return JSON.parse(out.stdout);
|
|
5594
|
+
} catch {
|
|
5595
|
+
return;
|
|
5596
|
+
}
|
|
5597
|
+
}
|
|
5598
|
+
var CC_NESTING_ENV, KICKOFF_LINES, AGENTS_LISTING_TIMEOUT_MS = 1e4, CLAUDE_BIN = "claude", claudeRuntime;
|
|
5515
5599
|
var init_claude = __esm(() => {
|
|
5600
|
+
init_system_exec();
|
|
5516
5601
|
init_resolve_executable();
|
|
5517
5602
|
init_artifacts();
|
|
5518
5603
|
init_skills_root();
|
|
@@ -5544,11 +5629,12 @@ var init_claude = __esm(() => {
|
|
|
5544
5629
|
};
|
|
5545
5630
|
},
|
|
5546
5631
|
async writeArtifacts(input) {
|
|
5547
|
-
const
|
|
5632
|
+
const pluginDir = resolvePluginDir();
|
|
5633
|
+
const files = await writeSessionArtifacts({ ...input, runtime: this.id }, pluginDir);
|
|
5548
5634
|
return {
|
|
5549
5635
|
files: [files.systemPromptPath, files.settingsPath, files.mcpConfigPath],
|
|
5550
5636
|
argv: buildClaudeArgs({
|
|
5551
|
-
pluginDir
|
|
5637
|
+
pluginDir,
|
|
5552
5638
|
settingsPath: files.settingsPath,
|
|
5553
5639
|
mcpConfigPath: files.mcpConfigPath,
|
|
5554
5640
|
systemPromptPath: files.systemPromptPath,
|
|
@@ -5561,13 +5647,17 @@ var init_claude = __esm(() => {
|
|
|
5561
5647
|
}),
|
|
5562
5648
|
env: {
|
|
5563
5649
|
RUNCASTLE_SESSION_ID: input.session.id,
|
|
5564
|
-
RUNCASTLE_SERVER_URL: input.serverUrl
|
|
5650
|
+
RUNCASTLE_SERVER_URL: input.serverUrl,
|
|
5651
|
+
CLAUDE_CODE_DISABLE_AGENT_VIEW: "1"
|
|
5565
5652
|
},
|
|
5566
5653
|
envScrub: CC_NESTING_ENV
|
|
5567
5654
|
};
|
|
5568
5655
|
},
|
|
5569
5656
|
kickoffLine(kind) {
|
|
5570
5657
|
return KICKOFF_LINES[kind];
|
|
5658
|
+
},
|
|
5659
|
+
async findBackgroundSession(ccSessionId) {
|
|
5660
|
+
return findBackgroundSessionIn(await listClaudeAgents(), ccSessionId);
|
|
5571
5661
|
}
|
|
5572
5662
|
};
|
|
5573
5663
|
});
|
|
@@ -8214,7 +8304,7 @@ var init_chat_branch = __esm(() => {
|
|
|
8214
8304
|
});
|
|
8215
8305
|
|
|
8216
8306
|
// src/workflows/kill-registry.ts
|
|
8217
|
-
import { spawn as
|
|
8307
|
+
import { spawn as spawn5 } from "child_process";
|
|
8218
8308
|
function log2(msg) {
|
|
8219
8309
|
console.error(`[kill-registry] ${msg}`);
|
|
8220
8310
|
}
|
|
@@ -8231,7 +8321,7 @@ function runDockerCommand(args) {
|
|
|
8231
8321
|
const timer = setTimeout(() => settle(false), DOCKER_COMMAND_TIMEOUT_MS);
|
|
8232
8322
|
timer.unref?.();
|
|
8233
8323
|
try {
|
|
8234
|
-
const child =
|
|
8324
|
+
const child = spawn5("docker", args, { windowsHide: true, stdio: "ignore" });
|
|
8235
8325
|
child.on("exit", (code) => settle(code === 0));
|
|
8236
8326
|
child.on("close", (code) => settle(code === 0));
|
|
8237
8327
|
child.on("error", () => settle(false));
|
|
@@ -8362,63 +8452,6 @@ var init_kill_registry = __esm(() => {
|
|
|
8362
8452
|
REGISTRY_KEY2 = Symbol.for("runcastle.kill.registry");
|
|
8363
8453
|
});
|
|
8364
8454
|
|
|
8365
|
-
// src/doctor/system-exec.ts
|
|
8366
|
-
import { spawn as spawn5 } from "child_process";
|
|
8367
|
-
function createSystemExec(opts = {}) {
|
|
8368
|
-
const timeoutMs = opts.timeoutMs ?? DEFAULT_EXEC_TIMEOUT_MS;
|
|
8369
|
-
const containFn = opts.containFn ?? containHostProcess;
|
|
8370
|
-
return (command, args) => new Promise((resolve9) => {
|
|
8371
|
-
const { file, args: spawnArgs } = resolveSpawnTarget(command, args);
|
|
8372
|
-
let stdout = "";
|
|
8373
|
-
let stderr = "";
|
|
8374
|
-
let settled = false;
|
|
8375
|
-
const settle = (outcome) => {
|
|
8376
|
-
if (settled)
|
|
8377
|
-
return;
|
|
8378
|
-
settled = true;
|
|
8379
|
-
clearTimeout(timer);
|
|
8380
|
-
resolve9(outcome);
|
|
8381
|
-
};
|
|
8382
|
-
const child = spawn5(file, spawnArgs, { cwd: opts.cwd, windowsHide: true });
|
|
8383
|
-
const containment = child.pid === undefined ? null : containFn(child.pid);
|
|
8384
|
-
const timer = setTimeout(() => {
|
|
8385
|
-
if (containment)
|
|
8386
|
-
containment.kill();
|
|
8387
|
-
else if (child.pid !== undefined)
|
|
8388
|
-
killProcessTree(child.pid);
|
|
8389
|
-
const note = `${command} ${args.join(" ")} timed out after ${Math.round(timeoutMs / 1000)}s`;
|
|
8390
|
-
settle({
|
|
8391
|
-
ok: true,
|
|
8392
|
-
code: null,
|
|
8393
|
-
stdout,
|
|
8394
|
-
stderr: stderr ? `${stderr}
|
|
8395
|
-
${note}` : note,
|
|
8396
|
-
timedOut: true
|
|
8397
|
-
});
|
|
8398
|
-
}, timeoutMs);
|
|
8399
|
-
child.stdout?.on("data", (d) => {
|
|
8400
|
-
stdout += d.toString();
|
|
8401
|
-
});
|
|
8402
|
-
child.stderr?.on("data", (d) => {
|
|
8403
|
-
stderr += d.toString();
|
|
8404
|
-
});
|
|
8405
|
-
child.on("error", (err) => {
|
|
8406
|
-
containment?.kill();
|
|
8407
|
-
settle({ ok: false, code: null, stdout, stderr: stderr || String(err) });
|
|
8408
|
-
});
|
|
8409
|
-
child.on("close", (code) => {
|
|
8410
|
-
containment?.kill();
|
|
8411
|
-
settle({ ok: true, code: code ?? 0, stdout, stderr });
|
|
8412
|
-
});
|
|
8413
|
-
});
|
|
8414
|
-
}
|
|
8415
|
-
var DEFAULT_EXEC_TIMEOUT_MS = 60000;
|
|
8416
|
-
var init_system_exec = __esm(() => {
|
|
8417
|
-
init_kill_tree();
|
|
8418
|
-
init_contain_host();
|
|
8419
|
-
init_resolve_executable();
|
|
8420
|
-
});
|
|
8421
|
-
|
|
8422
8455
|
// src/services/settings.ts
|
|
8423
8456
|
import { existsSync as existsSync13, mkdirSync as mkdirSync7, readFileSync as readFileSync9, writeFileSync as writeFileSync6 } from "fs";
|
|
8424
8457
|
import { dirname as dirname6 } from "path";
|
|
@@ -57724,7 +57757,8 @@ function runCommand2(bin, args2) {
|
|
|
57724
57757
|
const timer = setTimeout(() => settle(false), COMMAND_TIMEOUT_MS);
|
|
57725
57758
|
timer.unref?.();
|
|
57726
57759
|
try {
|
|
57727
|
-
const
|
|
57760
|
+
const target = spawnTargetFor(bin, [...args2]);
|
|
57761
|
+
const child = spawn8(target.file, target.args, { windowsHide: true, stdio: "ignore" });
|
|
57728
57762
|
child.on("exit", (code) => settle(code === 0));
|
|
57729
57763
|
child.on("close", (code) => settle(code === 0));
|
|
57730
57764
|
child.on("error", () => settle(false));
|
|
@@ -57790,6 +57824,7 @@ async function reapRecorder(ticketId, options = {}) {
|
|
|
57790
57824
|
var AGENT_BROWSER_BIN = "agent-browser", DEFAULT_TIMEOUT_MS2 = 1e4, POLL_INTERVAL_MS = 100, COMMAND_TIMEOUT_MS = 5000, REAL_DEPS2;
|
|
57791
57825
|
var init_recorder_reap = __esm(() => {
|
|
57792
57826
|
init_paths();
|
|
57827
|
+
init_resolve_executable();
|
|
57793
57828
|
REAL_DEPS2 = {
|
|
57794
57829
|
findBrowser: () => findOnPath(AGENT_BROWSER_BIN),
|
|
57795
57830
|
fileExists: existsSync16,
|
|
@@ -58400,7 +58435,10 @@ function couldNotReview(ticket, reason, digest) {
|
|
|
58400
58435
|
**${headline2}**` : `**${headline2}**`
|
|
58401
58436
|
};
|
|
58402
58437
|
}
|
|
58403
|
-
var FFMPEG_BIN = "ffmpeg", spawnVersion = (path2) =>
|
|
58438
|
+
var FFMPEG_BIN = "ffmpeg", spawnVersion = (path2) => {
|
|
58439
|
+
const { file, args: args2 } = spawnTargetFor(path2, ["--version"]);
|
|
58440
|
+
return spawnSync(file, args2, { timeout: 3000, stdio: "ignore", windowsHide: true });
|
|
58441
|
+
}, withheldDrives, DECLARATION, REVIEW_DIRECTORY_FS;
|
|
58404
58442
|
var init_review_ticket = __esm(() => {
|
|
58405
58443
|
init_src();
|
|
58406
58444
|
init_paths();
|
|
@@ -58410,6 +58448,7 @@ var init_review_ticket = __esm(() => {
|
|
|
58410
58448
|
init_agent_stream();
|
|
58411
58449
|
init_git();
|
|
58412
58450
|
init_review_findings();
|
|
58451
|
+
init_resolve_executable();
|
|
58413
58452
|
init_kill_registry();
|
|
58414
58453
|
init_recorder_reap();
|
|
58415
58454
|
init_recorder_reap();
|
|
@@ -62837,6 +62876,24 @@ async function commitDocsCheckpoint(ctx, session, summary) {
|
|
|
62837
62876
|
}
|
|
62838
62877
|
await landChatCommits(ctx, project3, feature);
|
|
62839
62878
|
}
|
|
62879
|
+
function checkpointDocsAfterReply(ctx, session, summary) {
|
|
62880
|
+
let queue = docsCheckpointQueues.get(session.worktreePath);
|
|
62881
|
+
if (!queue) {
|
|
62882
|
+
queue = createSerialQueue();
|
|
62883
|
+
docsCheckpointQueues.set(session.worktreePath, queue);
|
|
62884
|
+
}
|
|
62885
|
+
queue(() => commitDocsCheckpoint(ctx, session, summary)).catch((e) => {
|
|
62886
|
+
try {
|
|
62887
|
+
emit(ctx, requireFeatureId(session), {
|
|
62888
|
+
type: "git.commit_pending",
|
|
62889
|
+
message: `docs checkpoint failed: ${e instanceof Error ? e.message : String(e)}`,
|
|
62890
|
+
data: { summary }
|
|
62891
|
+
});
|
|
62892
|
+
} catch (err) {
|
|
62893
|
+
console.error(`docs checkpoint failed for session ${session.id}`, e, err);
|
|
62894
|
+
}
|
|
62895
|
+
});
|
|
62896
|
+
}
|
|
62840
62897
|
function requireProject(ctx, session) {
|
|
62841
62898
|
if (!session.projectId) {
|
|
62842
62899
|
throw new GateError(`this tool belongs to a project-scoped session; this is a ${session.kind} session on a ` + "feature. Use get_feature_context and the pipeline tools for feature work.");
|
|
@@ -63577,7 +63634,7 @@ function buildMcpServer(audience) {
|
|
|
63577
63634
|
if (!rs)
|
|
63578
63635
|
return noSession();
|
|
63579
63636
|
const result = toolEmitTickets(rs.ctx, rs.session, args2);
|
|
63580
|
-
|
|
63637
|
+
checkpointDocsAfterReply(rs.ctx, rs.session, `tickets emitted (${result.stored})`);
|
|
63581
63638
|
return ok(result);
|
|
63582
63639
|
});
|
|
63583
63640
|
}
|
|
@@ -63599,7 +63656,7 @@ function buildMcpServer(audience) {
|
|
|
63599
63656
|
if (!rs)
|
|
63600
63657
|
return noSession();
|
|
63601
63658
|
const result = toolUpdateTicket(rs.ctx, rs.session, args2);
|
|
63602
|
-
|
|
63659
|
+
checkpointDocsAfterReply(rs.ctx, rs.session, `ticket ${result.ticket.seq} updated`);
|
|
63603
63660
|
return ok(result);
|
|
63604
63661
|
});
|
|
63605
63662
|
}
|
|
@@ -63616,7 +63673,7 @@ function buildMcpServer(audience) {
|
|
|
63616
63673
|
if (!rs)
|
|
63617
63674
|
return noSession();
|
|
63618
63675
|
const result = toolCancelTicket(rs.ctx, rs.session, args2);
|
|
63619
|
-
|
|
63676
|
+
checkpointDocsAfterReply(rs.ctx, rs.session, `ticket ${result.ticket.seq} cancelled`);
|
|
63620
63677
|
return ok(result);
|
|
63621
63678
|
});
|
|
63622
63679
|
}
|
|
@@ -63660,7 +63717,7 @@ function buildMcpServer(audience) {
|
|
|
63660
63717
|
return noSession();
|
|
63661
63718
|
const result = toolCompletePhase(rs.ctx, rs.session, args2);
|
|
63662
63719
|
if (result.ok) {
|
|
63663
|
-
|
|
63720
|
+
checkpointDocsAfterReply(rs.ctx, rs.session, `phase '${args2.phase}' complete`);
|
|
63664
63721
|
}
|
|
63665
63722
|
return ok(result);
|
|
63666
63723
|
});
|
|
@@ -63693,7 +63750,7 @@ async function readMcpBody(c) {
|
|
|
63693
63750
|
return;
|
|
63694
63751
|
}
|
|
63695
63752
|
}
|
|
63696
|
-
var DOCS_NOTE, TICKETS_NOTE, REVIEW_TITLE, ResolveFindingShape, ResolveFindingInput, DRAFTING_KINDS, ADRS_NOTE, FEATURE_INDEX_NOTE, FEATURE_INDEX_SHIPPED_CAP = 50, WORK_RECORD_DIGESTS_NOTE, FEATURE_KINDS, PROJECT_KINDS, ALL_AUDIENCES, TOOL_AUDIENCES, CompletablePhase, EventType, mcp, server_default;
|
|
63753
|
+
var DOCS_NOTE, TICKETS_NOTE, REVIEW_TITLE, ResolveFindingShape, ResolveFindingInput, docsCheckpointQueues, DRAFTING_KINDS, ADRS_NOTE, FEATURE_INDEX_NOTE, FEATURE_INDEX_SHIPPED_CAP = 50, WORK_RECORD_DIGESTS_NOTE, FEATURE_KINDS, PROJECT_KINDS, ALL_AUDIENCES, TOOL_AUDIENCES, CompletablePhase, EventType, mcp, server_default;
|
|
63697
63754
|
var init_server = __esm(() => {
|
|
63698
63755
|
init_src();
|
|
63699
63756
|
init_paths();
|
|
@@ -63712,6 +63769,7 @@ var init_server = __esm(() => {
|
|
|
63712
63769
|
init_review_findings();
|
|
63713
63770
|
init_git();
|
|
63714
63771
|
init_knowledge();
|
|
63772
|
+
init_landing_queue();
|
|
63715
63773
|
init_planning();
|
|
63716
63774
|
init_project_notes();
|
|
63717
63775
|
init_repo();
|
|
@@ -63736,6 +63794,7 @@ var init_server = __esm(() => {
|
|
|
63736
63794
|
});
|
|
63737
63795
|
}
|
|
63738
63796
|
});
|
|
63797
|
+
docsCheckpointQueues = new Map;
|
|
63739
63798
|
DRAFTING_KINDS = ["chat"];
|
|
63740
63799
|
ADRS_NOTE = "adrs[] is an index of the project\u2019s LIVE decisions (superseded ones are already omitted), not " + "their text. Read the ones your work touches with read_adr({ relPath }) \u2014 they bind you the " + "same either way. They are also plain files in this worktree at docs/adr/.";
|
|
63741
63800
|
FEATURE_INDEX_NOTE = "featureIndex lists every in-flight feature, then every draft, then the most recently shipped, " + "one `slug \u2014 title [state]` line each. A trailing `\xB7 waits on: a, b \u2713` lists the features it " + "must wait to merge (\u2713 = already shipped), and `[draft, waiting]` marks a draft that cannot " + "start until every unmarked one ships. A shipped feature\u2019s docs live at docs/features/<slug>/ " + "in this worktree; an in-flight or draft feature\u2019s docs live on an unmerged branch. Any " + "feature\u2019s brief is read_feature_brief({ slug }); what one actually did is " + "get_work_record({ featureSlug }).";
|
|
@@ -65368,16 +65427,17 @@ function handlePtyExit(ctx, feature, session, meta, exitCode) {
|
|
|
65368
65427
|
if (session.runtime === "codex" && feature) {
|
|
65369
65428
|
noteResolvedMerge(ctx, session, feature).catch(() => {});
|
|
65370
65429
|
}
|
|
65430
|
+
let resumeFailureReported;
|
|
65371
65431
|
if (diedBeforeLive && meta.resumeSessionId) {
|
|
65372
|
-
|
|
65373
|
-
|
|
65374
|
-
|
|
65375
|
-
|
|
65376
|
-
|
|
65377
|
-
|
|
65378
|
-
|
|
65379
|
-
|
|
65380
|
-
}
|
|
65432
|
+
const resumeSessionId = meta.resumeSessionId;
|
|
65433
|
+
const report = (background) => emitResumeFailed(ctx, session, resumeSessionId, exitCode, background);
|
|
65434
|
+
if (meta.findBackgroundSession) {
|
|
65435
|
+
resumeFailureReported = meta.findBackgroundSession(resumeSessionId).catch(() => {
|
|
65436
|
+
return;
|
|
65437
|
+
}).then(report);
|
|
65438
|
+
} else {
|
|
65439
|
+
report(undefined);
|
|
65440
|
+
}
|
|
65381
65441
|
}
|
|
65382
65442
|
if (session.purpose === "self-plan" && feature) {
|
|
65383
65443
|
const current2 = getFeatureRow(ctx, feature.id);
|
|
@@ -65393,19 +65453,36 @@ function handlePtyExit(ctx, feature, session, meta, exitCode) {
|
|
|
65393
65453
|
message: ptyExitMessage(exitCode),
|
|
65394
65454
|
data: { sessionId: session.id, exitCode: exitCode ?? null }
|
|
65395
65455
|
});
|
|
65456
|
+
return resumeFailureReported;
|
|
65457
|
+
}
|
|
65458
|
+
function emitResumeFailed(ctx, session, resumeSessionId, exitCode, background) {
|
|
65459
|
+
const exited = `resume failed for ${session.kind} \u2014 the session exited before starting (code ${exitCode ?? "unknown"})`;
|
|
65460
|
+
const short = background?.shortId;
|
|
65461
|
+
emitForSession(ctx, session, {
|
|
65462
|
+
type: "session.resume_failed",
|
|
65463
|
+
message: short ? `${exited}: its conversation is running as a Claude Code background session (${short}), which cannot be resumed here. ` + `Run \`claude attach ${short}\` to look at it, then \`claude stop ${short}\` (stopping keeps the conversation), then relaunch.` : `${exited}; the previous conversation is still resumable`,
|
|
65464
|
+
data: {
|
|
65465
|
+
sessionId: session.id,
|
|
65466
|
+
resumeSessionId,
|
|
65467
|
+
exitCode: exitCode ?? null,
|
|
65468
|
+
...short ? { backgroundSessionId: short } : {}
|
|
65469
|
+
}
|
|
65470
|
+
});
|
|
65396
65471
|
}
|
|
65397
65472
|
function spawnEmbeddedPty(ctx, feature, session, worktreePath, runtime5, spec, meta = {}) {
|
|
65398
65473
|
const { file, args: args2 } = spawnTargetFor(runtime5.resolveBinary(), spec.argv);
|
|
65399
65474
|
const env = { ...process.env, ...spec.env };
|
|
65400
65475
|
for (const key of spec.envScrub)
|
|
65401
65476
|
delete env[key];
|
|
65477
|
+
const findBackgroundSession = runtime5.findBackgroundSession?.bind(runtime5);
|
|
65478
|
+
const exitMeta = findBackgroundSession ? { ...meta, findBackgroundSession } : meta;
|
|
65402
65479
|
try {
|
|
65403
65480
|
const entry = ptyRegistry().create({
|
|
65404
65481
|
sessionId: session.id,
|
|
65405
65482
|
cmd: file,
|
|
65406
65483
|
args: args2,
|
|
65407
65484
|
opts: { cwd: worktreePath, env, cols: 80, rows: 24, useConpty: true },
|
|
65408
|
-
onExit: ({ exitCode }) => handlePtyExit(ctx, feature, session,
|
|
65485
|
+
onExit: ({ exitCode }) => void handlePtyExit(ctx, feature, session, exitMeta, exitCode)
|
|
65409
65486
|
});
|
|
65410
65487
|
if (feature)
|
|
65411
65488
|
startDocsWatch(ctx, feature);
|
|
@@ -65974,8 +66051,8 @@ var init_transcripts = __esm(() => {
|
|
|
65974
66051
|
|
|
65975
66052
|
// src/services/conversations.ts
|
|
65976
66053
|
function withoutKickoff(turns, kind, runtime5) {
|
|
65977
|
-
const
|
|
65978
|
-
return turns.filter((turn) => !(turn.role === "user" && promptMatchesKickoff(kickoff, turn.text)));
|
|
66054
|
+
const kickoffs = [kickoffLineFor(kind, undefined, runtime5), selfPlanKickoffFor(runtime5)];
|
|
66055
|
+
return turns.filter((turn) => !(turn.role === "user" && kickoffs.some((kickoff) => promptMatchesKickoff(kickoff, turn.text))));
|
|
65979
66056
|
}
|
|
65980
66057
|
function promptMatchesKickoff(line, prompt) {
|
|
65981
66058
|
if (!prompt)
|
|
@@ -66069,6 +66146,7 @@ var TITLE_MAX = 60, MATCH_PREFIX = 40, INTERRUPTED = "[Request interrupted by us
|
|
|
66069
66146
|
var init_conversations = __esm(() => {
|
|
66070
66147
|
init_src();
|
|
66071
66148
|
init_sessions();
|
|
66149
|
+
init_skills();
|
|
66072
66150
|
init_transcripts();
|
|
66073
66151
|
});
|
|
66074
66152
|
|