runcastle 1.4.4 → 1.4.7
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 +207 -95
- package/index.js +207 -95
- package/package.json +1 -1
- package/skills/packs/runcastle/skills/ideate/SKILL.md +61 -6
- package/skills/packs/runcastle/skills/ideate/references/preview-starter.html +274 -0
- package/skills/packs/runcastle/skills/ideate/references/preview.md +98 -0
- package/skills/packs/runcastle/skills/revisit/SKILL.md +3 -1
- package/skills/packs/runcastle/skills/tickets/SKILL.md +6 -0
- package/web/assets/index-bNgQedte.css +1 -0
- package/web/assets/index-e056iqcB.js +188 -0
- package/web/index.html +2 -2
- package/web/assets/index-BAbR9IXb.css +0 -1
- package/web/assets/index-CW8ioVQ6.js +0 -188
package/bin/runcastle.js
CHANGED
|
@@ -45,7 +45,7 @@ function agentDigestFillRank(relPath) {
|
|
|
45
45
|
const i = AGENT_DIGEST_FILL_ORDER.indexOf(relPath.toLowerCase());
|
|
46
46
|
return i === -1 ? AGENT_DIGEST_FILL_ORDER.length : i;
|
|
47
47
|
}
|
|
48
|
-
var AGENT_DIGEST_DOCS, WITHHELD_FEATURE_DOCS, AGENT_DIGEST_FILL_ORDER;
|
|
48
|
+
var AGENT_DIGEST_DOCS, PREVIEW_FILE = "preview.html", WITHHELD_FEATURE_DOCS, AGENT_DIGEST_FILL_ORDER;
|
|
49
49
|
var init_docs = __esm(() => {
|
|
50
50
|
AGENT_DIGEST_DOCS = ["brief.md", "decisions.md", "spec.md"];
|
|
51
51
|
WITHHELD_FEATURE_DOCS = {
|
|
@@ -264,7 +264,7 @@ var init_schemas = __esm(() => {
|
|
|
264
264
|
"drive-fix"
|
|
265
265
|
]);
|
|
266
266
|
PROJECT_SESSION_KINDS = ["prepare", "project"];
|
|
267
|
-
SessionPurpose = z3.enum(["resolve-conflict", "start-lap"]);
|
|
267
|
+
SessionPurpose = z3.enum(["resolve-conflict", "start-lap", "self-plan"]);
|
|
268
268
|
MergeBranchPair = z3.object({
|
|
269
269
|
mergeFrom: z3.string(),
|
|
270
270
|
mergeInto: z3.string()
|
|
@@ -1659,8 +1659,9 @@ ${why.replace(/\n/g, `\r
|
|
|
1659
1659
|
containment?.kill();
|
|
1660
1660
|
fireExit(code ?? 0, signal ? 1 : undefined);
|
|
1661
1661
|
});
|
|
1662
|
+
child.stdin?.on("error", () => {});
|
|
1662
1663
|
function sendToHost(obj) {
|
|
1663
|
-
if (!child.stdin || child.stdin.destroyed)
|
|
1664
|
+
if (exitFired || !child.stdin || child.stdin.destroyed)
|
|
1664
1665
|
return;
|
|
1665
1666
|
try {
|
|
1666
1667
|
child.stdin.write(JSON.stringify(obj) + `
|
|
@@ -2627,7 +2628,7 @@ function noteDocsChange(ctx, featureId, filename) {
|
|
|
2627
2628
|
const entry = watches.get(featureId);
|
|
2628
2629
|
if (!entry)
|
|
2629
2630
|
return;
|
|
2630
|
-
if (filename !== null && !filename.endsWith(".md"))
|
|
2631
|
+
if (filename !== null && !filename.endsWith(".md") && filename !== PREVIEW_FILE)
|
|
2631
2632
|
return;
|
|
2632
2633
|
if (filename)
|
|
2633
2634
|
entry.changed.add(filename);
|
|
@@ -2654,6 +2655,7 @@ function emitDocsChanged(ctx, featureId) {
|
|
|
2654
2655
|
}
|
|
2655
2656
|
var DOCS_WATCH_DEBOUNCE_MS = 300, watches;
|
|
2656
2657
|
var init_docs_watch = __esm(() => {
|
|
2658
|
+
init_src();
|
|
2657
2659
|
init_events();
|
|
2658
2660
|
init_feature_docs();
|
|
2659
2661
|
init_repo();
|
|
@@ -3576,6 +3578,9 @@ function chatBranchName(slug, unique) {
|
|
|
3576
3578
|
function burnWorktreePath(repoPath, branch) {
|
|
3577
3579
|
return join6(repoPath, ".sandcastle", "worktrees", branch.replace(/\//g, "-"));
|
|
3578
3580
|
}
|
|
3581
|
+
async function excludeBurnWorktrees(repoPath) {
|
|
3582
|
+
await excludePath(repoPath, BURN_WORKTREES_PATTERN);
|
|
3583
|
+
}
|
|
3579
3584
|
async function excludeFilePath(repoPath) {
|
|
3580
3585
|
const raw = (await git(repoPath).raw(["rev-parse", "--git-path", "info/exclude"])).trim();
|
|
3581
3586
|
return resolve3(repoPath, raw);
|
|
@@ -4616,7 +4621,7 @@ async function mergeInProgress(g) {
|
|
|
4616
4621
|
return false;
|
|
4617
4622
|
}
|
|
4618
4623
|
}
|
|
4619
|
-
var DOCS_PATHSPEC = "docs/features", MIGRATION_DIR_RE, DRIZZLE_SQL_RE, DENY_DIRTY = "Working tree has uncommitted changes \u2014 commit or stash first", DENY_ACTIVE = "A test drive is already active \u2014 stop it first", DENY_ACTIVE_RUN = "Feature has an active run \u2014 wait for it to finish", DENY_NONE_ACTIVE = "No test drive is active", DENY_DRY_RUN_ACTIVE = "A preparation dry-run is in progress \u2014 stop it first", DENY_NO_DRY_RUN = "No preparation dry-run is in progress", DENY_NO_REVIEW_DRIVE = "No review drive is in progress for this feature", INITIAL_COMMIT_SUBJECT = "runcastle: initial commit", TICKET_BRANCH_PREFIX = "runcastle/ticket/", CHAT_BRANCH_PREFIX = "runcastle/chat/", PROJECT_BRANCH = "runcastle/project", TEMP_BRANCH_PREFIXES, TEMP_BRANCH_SLUG_MAX = 16, testDriveState, reviewDriveRelease, readinessTiming, DRY_RUN_SLUG = "prep-dry-run", PROJECT_DRIVE_SLUG = "project-drive", DENY_NO_PROJECT_DRIVE = "No project drive is running for this project";
|
|
4624
|
+
var DOCS_PATHSPEC = "docs/features", MIGRATION_DIR_RE, DRIZZLE_SQL_RE, DENY_DIRTY = "Working tree has uncommitted changes \u2014 commit or stash first", DENY_ACTIVE = "A test drive is already active \u2014 stop it first", DENY_ACTIVE_RUN = "Feature has an active run \u2014 wait for it to finish", DENY_NONE_ACTIVE = "No test drive is active", DENY_DRY_RUN_ACTIVE = "A preparation dry-run is in progress \u2014 stop it first", DENY_NO_DRY_RUN = "No preparation dry-run is in progress", DENY_NO_REVIEW_DRIVE = "No review drive is in progress for this feature", INITIAL_COMMIT_SUBJECT = "runcastle: initial commit", TICKET_BRANCH_PREFIX = "runcastle/ticket/", CHAT_BRANCH_PREFIX = "runcastle/chat/", PROJECT_BRANCH = "runcastle/project", TEMP_BRANCH_PREFIXES, TEMP_BRANCH_SLUG_MAX = 16, BURN_WORKTREES_PATTERN = ".sandcastle/worktrees/", testDriveState, reviewDriveRelease, readinessTiming, DRY_RUN_SLUG = "prep-dry-run", PROJECT_DRIVE_SLUG = "project-drive", DENY_NO_PROJECT_DRIVE = "No project drive is running for this project";
|
|
4620
4625
|
var init_git = __esm(() => {
|
|
4621
4626
|
init_src();
|
|
4622
4627
|
init_paths();
|
|
@@ -4754,6 +4759,64 @@ var init_install_guard = __esm(() => {
|
|
|
4754
4759
|
HEREDOC_OPENER = /(?<!<)<<(?!<)(-?)[ \t]*(?:'([^'\n]+)'|"([^"\n]+)"|\\?([\w.-]+))/g;
|
|
4755
4760
|
});
|
|
4756
4761
|
|
|
4762
|
+
// src/launcher/skills-root.ts
|
|
4763
|
+
import { existsSync as existsSync7 } from "fs";
|
|
4764
|
+
import { dirname as dirname3, join as join7, resolve as resolve6 } from "path";
|
|
4765
|
+
import { fileURLToPath as fileURLToPath3 } from "url";
|
|
4766
|
+
function resolveSkillsRoot(fromDir) {
|
|
4767
|
+
const override = process.env[SKILLS_DIR_ENV];
|
|
4768
|
+
if (override) {
|
|
4769
|
+
const root = resolve6(override);
|
|
4770
|
+
if (existsSync7(join7(root, SKILLS_MARKER)))
|
|
4771
|
+
return root;
|
|
4772
|
+
throw new Error(`${SKILLS_DIR_ENV}=${override} is not a runcastle skills root (missing ${SKILLS_MARKER})`);
|
|
4773
|
+
}
|
|
4774
|
+
const searched = [];
|
|
4775
|
+
let dir = fromDir;
|
|
4776
|
+
for (let i = 0;i < 8; i += 1) {
|
|
4777
|
+
const candidate = join7(dir, "packages", "skills");
|
|
4778
|
+
searched.push(candidate);
|
|
4779
|
+
if (existsSync7(join7(candidate, SKILLS_MARKER)))
|
|
4780
|
+
return candidate;
|
|
4781
|
+
const parent = dirname3(dir);
|
|
4782
|
+
if (parent === dir)
|
|
4783
|
+
break;
|
|
4784
|
+
dir = parent;
|
|
4785
|
+
}
|
|
4786
|
+
throw new Error(`runcastle skills root not found; searched:
|
|
4787
|
+
${searched.join(`
|
|
4788
|
+
`)}`);
|
|
4789
|
+
}
|
|
4790
|
+
function resolvePluginDir(fromDir = dirname3(fileURLToPath3(import.meta.url))) {
|
|
4791
|
+
const rel = join7("packages", "skills", "packs", "runcastle");
|
|
4792
|
+
const override = process.env[SKILLS_DIR_ENV];
|
|
4793
|
+
if (override) {
|
|
4794
|
+
const dir2 = join7(resolve6(override), "packs", "runcastle");
|
|
4795
|
+
if (existsSync7(dir2))
|
|
4796
|
+
return dir2;
|
|
4797
|
+
throw new Error(`${SKILLS_DIR_ENV}=${override} has no plugin dir at ${dir2}`);
|
|
4798
|
+
}
|
|
4799
|
+
const searched = [];
|
|
4800
|
+
let dir = fromDir;
|
|
4801
|
+
for (let i = 0;i < 8; i += 1) {
|
|
4802
|
+
const candidate = join7(dir, rel);
|
|
4803
|
+
searched.push(candidate);
|
|
4804
|
+
if (existsSync7(candidate))
|
|
4805
|
+
return candidate;
|
|
4806
|
+
const parent = dirname3(dir);
|
|
4807
|
+
if (parent === dir)
|
|
4808
|
+
break;
|
|
4809
|
+
dir = parent;
|
|
4810
|
+
}
|
|
4811
|
+
throw new Error(`runcastle plugin dir (${rel}) not found; searched:
|
|
4812
|
+
${searched.join(`
|
|
4813
|
+
`)}`);
|
|
4814
|
+
}
|
|
4815
|
+
var SKILLS_DIR_ENV = "RUNCASTLE_SKILLS_DIR", SKILLS_MARKER;
|
|
4816
|
+
var init_skills_root = __esm(() => {
|
|
4817
|
+
SKILLS_MARKER = join7("packs", "runcastle");
|
|
4818
|
+
});
|
|
4819
|
+
|
|
4757
4820
|
// src/launcher/runtimes/skills.ts
|
|
4758
4821
|
function skillRef(runtime, skill) {
|
|
4759
4822
|
return SPELLINGS[runtime](skill);
|
|
@@ -4774,6 +4837,9 @@ function entrySkillsFor(kind) {
|
|
|
4774
4837
|
function chatKickoffFor(runtime, opening) {
|
|
4775
4838
|
return opening === "ideate" ? `Proceed with your task: invoke the ${skillRef(runtime, "ideate")} skill and drive this ` + "feature's ideation to completion." : `Proceed with your task: invoke the ${skillRef(runtime, "revisit")} skill and continue this ` + "feature's conversation.";
|
|
4776
4839
|
}
|
|
4840
|
+
function selfPlanKickoffFor(runtime) {
|
|
4841
|
+
return "Proceed with your task: no human is present and nobody will reply to you. Invoke the " + `${skillRef(runtime, "ideate")} skill and follow its "${PLANNING_WITHOUT_A_HUMAN}" section ` + "to plan this feature by yourself, through the preview, the spec and the tickets, in this one run.";
|
|
4842
|
+
}
|
|
4777
4843
|
function kickoffLinesFor(runtime) {
|
|
4778
4844
|
const skill = (name) => skillRef(runtime, name);
|
|
4779
4845
|
return {
|
|
@@ -4789,7 +4855,7 @@ function projectTriageKickoffFor(runtime) {
|
|
|
4789
4855
|
function prepareConfirmKickoffFor(runtime) {
|
|
4790
4856
|
return `Proceed with your task: invoke the ${skillRef(runtime, "prepare")} skill. Every prepared field already ` + "has a value, so this is a confirmation, not a preparation \u2014 tell me what is recorded and " + "how stale it is, ask whether it still holds, and stop. Do not re-derive a settled value.";
|
|
4791
4857
|
}
|
|
4792
|
-
var PACK = "runcastle", SPELLINGS, ENTRY_SKILLS;
|
|
4858
|
+
var PACK = "runcastle", SPELLINGS, ENTRY_SKILLS, PLANNING_WITHOUT_A_HUMAN = "Planning without a human";
|
|
4793
4859
|
var init_skills = __esm(() => {
|
|
4794
4860
|
SPELLINGS = {
|
|
4795
4861
|
"claude-code": (skill) => `/${PACK}:${skill}`,
|
|
@@ -4806,10 +4872,10 @@ var init_skills = __esm(() => {
|
|
|
4806
4872
|
|
|
4807
4873
|
// src/launcher/artifacts.ts
|
|
4808
4874
|
import { mkdirSync as mkdirSync3, writeFileSync as writeFileSync2 } from "fs";
|
|
4809
|
-
import { fileURLToPath as
|
|
4810
|
-
import { join as
|
|
4875
|
+
import { fileURLToPath as fileURLToPath4 } from "url";
|
|
4876
|
+
import { join as join8 } from "path";
|
|
4811
4877
|
function hookClientPath() {
|
|
4812
|
-
return resolveAsset(ASSET_ENV.hookClient,
|
|
4878
|
+
return resolveAsset(ASSET_ENV.hookClient, fileURLToPath4(new URL("./hook-client.ts", import.meta.url)));
|
|
4813
4879
|
}
|
|
4814
4880
|
function serverUrlFor(config2) {
|
|
4815
4881
|
return `http://localhost:${config2.serverPort}`;
|
|
@@ -4830,7 +4896,8 @@ function chatOpening(feature, planning) {
|
|
|
4830
4896
|
function renderSystemPrompt(feature, kind, lap, purpose, runtime = DEFAULT_RUNTIME, carried, planning) {
|
|
4831
4897
|
if (lap !== undefined)
|
|
4832
4898
|
return renderRevisitPrompt(feature, lap, purpose, runtime, carried);
|
|
4833
|
-
|
|
4899
|
+
const selfPlan = purpose === "self-plan";
|
|
4900
|
+
if (kind === "chat" && !selfPlan && chatOpening(feature, planning) === "revisit") {
|
|
4834
4901
|
return renderRevisitPrompt(feature, lap, purpose, runtime, carried);
|
|
4835
4902
|
}
|
|
4836
4903
|
const docs2 = featureDocsRel(feature.slug);
|
|
@@ -4863,12 +4930,26 @@ function renderSystemPrompt(feature, kind, lap, purpose, runtime = DEFAULT_RUNTI
|
|
|
4863
4930
|
noCodeRule(docs2),
|
|
4864
4931
|
otherEntrySkillsRule(),
|
|
4865
4932
|
"",
|
|
4933
|
+
...selfPlan ? [selfPlanRule(), ""] : [],
|
|
4866
4934
|
"## Your task",
|
|
4867
4935
|
`Begin by invoking the \`${skillRef(runtime, "ideate")}\` skill and drive the ideation session to completion.`,
|
|
4868
4936
|
""
|
|
4869
4937
|
].join(`
|
|
4870
4938
|
`);
|
|
4871
4939
|
}
|
|
4940
|
+
function selfPlanRule() {
|
|
4941
|
+
return [
|
|
4942
|
+
"## No human is present",
|
|
4943
|
+
"This is a self-planning run: nobody is watching and nobody will reply.",
|
|
4944
|
+
"- Never end your turn to ask a question \u2014 ending the turn ends the run. Ask your design",
|
|
4945
|
+
" questions of yourself, as the skill describes, and answer them.",
|
|
4946
|
+
"- A refused tool is not a wait: do not retry it or wait for approval. Decide on what you",
|
|
4947
|
+
" know and treat that decision as a candidate to check first.",
|
|
4948
|
+
`- Follow the ideation skill's "${PLANNING_WITHOUT_A_HUMAN}" section through the preview, the`,
|
|
4949
|
+
" spec and the tickets, in this one run."
|
|
4950
|
+
].join(`
|
|
4951
|
+
`);
|
|
4952
|
+
}
|
|
4872
4953
|
function reviewEvidenceSection(lap, docs2, carried) {
|
|
4873
4954
|
const evidence = carried?.reviewEvidence ?? [];
|
|
4874
4955
|
return [
|
|
@@ -5238,6 +5319,10 @@ function renderProjectPrompt(brief, runtime = DEFAULT_RUNTIME) {
|
|
|
5238
5319
|
].join(`
|
|
5239
5320
|
`);
|
|
5240
5321
|
}
|
|
5322
|
+
function skillPackReadRule(packDir) {
|
|
5323
|
+
const posix2 = packDir.replace(/\\/g, "/").replace(/^([A-Za-z]):/, (_, drive) => `/${drive.toLowerCase()}`).replace(/\/+$/, "");
|
|
5324
|
+
return `Read(/${posix2}/**)`;
|
|
5325
|
+
}
|
|
5241
5326
|
function entrySkillDenyRules(kind) {
|
|
5242
5327
|
const allowed = new Set(entrySkillsFor(kind));
|
|
5243
5328
|
return ENTRY_SKILLS.filter((skill) => !allowed.has(skill)).flatMap((skill) => [
|
|
@@ -5256,7 +5341,7 @@ function sessionBashAllowRules(kind) {
|
|
|
5256
5341
|
}
|
|
5257
5342
|
return SESSION_BASH_ALLOW_RULES;
|
|
5258
5343
|
}
|
|
5259
|
-
function renderSettings(hookClient, kind) {
|
|
5344
|
+
function renderSettings(hookClient, kind, skillPackDir) {
|
|
5260
5345
|
const cmd = (event) => ({
|
|
5261
5346
|
type: "command",
|
|
5262
5347
|
command: `bun run "${hookClient}" ${event}`,
|
|
@@ -5264,7 +5349,11 @@ function renderSettings(hookClient, kind) {
|
|
|
5264
5349
|
});
|
|
5265
5350
|
return {
|
|
5266
5351
|
permissions: {
|
|
5267
|
-
allow: [
|
|
5352
|
+
allow: [
|
|
5353
|
+
...RUNCASTLE_MCP_ALLOW_RULES,
|
|
5354
|
+
...sessionBashAllowRules(kind),
|
|
5355
|
+
...skillPackDir ? [skillPackReadRule(skillPackDir)] : []
|
|
5356
|
+
],
|
|
5268
5357
|
deny: entrySkillDenyRules(kind)
|
|
5269
5358
|
},
|
|
5270
5359
|
hooks: {
|
|
@@ -5325,15 +5414,15 @@ function renderSessionPrompt(input) {
|
|
|
5325
5414
|
return renderProjectPrompt(projectBrief, runtime);
|
|
5326
5415
|
throw new Error(`session ${session.id} has no feature and no project-session brief`);
|
|
5327
5416
|
}
|
|
5328
|
-
async function writeSessionArtifacts(input) {
|
|
5417
|
+
async function writeSessionArtifacts(input, pluginDir = resolvePluginDir()) {
|
|
5329
5418
|
const { session, config: config2 } = input;
|
|
5330
5419
|
const dir = sessionDir(session.id);
|
|
5331
5420
|
mkdirSync3(dir, { recursive: true });
|
|
5332
|
-
const systemPromptPath =
|
|
5333
|
-
const settingsPath =
|
|
5334
|
-
const mcpConfigPath =
|
|
5421
|
+
const systemPromptPath = join8(dir, "system-prompt.md");
|
|
5422
|
+
const settingsPath = join8(dir, "settings.json");
|
|
5423
|
+
const mcpConfigPath = join8(dir, "mcp.json");
|
|
5335
5424
|
writeFileSync2(systemPromptPath, renderSessionPrompt(input), "utf8");
|
|
5336
|
-
writeFileSync2(settingsPath, JSON.stringify(renderSettings(hookClientPath(), session.kind), null, 2), "utf8");
|
|
5425
|
+
writeFileSync2(settingsPath, JSON.stringify(renderSettings(hookClientPath(), session.kind, pluginDir), null, 2), "utf8");
|
|
5337
5426
|
writeFileSync2(mcpConfigPath, JSON.stringify(renderMcpConfig(session, config2), null, 2), "utf8");
|
|
5338
5427
|
return { systemPromptPath, settingsPath, mcpConfigPath };
|
|
5339
5428
|
}
|
|
@@ -5345,6 +5434,7 @@ var init_artifacts = __esm(() => {
|
|
|
5345
5434
|
init_asset_paths();
|
|
5346
5435
|
init_edit_guard();
|
|
5347
5436
|
init_install_guard();
|
|
5437
|
+
init_skills_root();
|
|
5348
5438
|
init_skills();
|
|
5349
5439
|
RUNCASTLE_MCP_ALLOW_RULES = [
|
|
5350
5440
|
"mcp__runcastle__get_feature_context",
|
|
@@ -5393,65 +5483,11 @@ var init_artifacts = __esm(() => {
|
|
|
5393
5483
|
SESSION_START_SOURCES = ["startup", "resume", "clear", "compact", "fork"];
|
|
5394
5484
|
});
|
|
5395
5485
|
|
|
5396
|
-
// src/launcher/skills-root.ts
|
|
5397
|
-
import { existsSync as existsSync7 } from "fs";
|
|
5398
|
-
import { dirname as dirname3, join as join8, resolve as resolve6 } from "path";
|
|
5399
|
-
import { fileURLToPath as fileURLToPath4 } from "url";
|
|
5400
|
-
function resolveSkillsRoot(fromDir) {
|
|
5401
|
-
const override = process.env[SKILLS_DIR_ENV];
|
|
5402
|
-
if (override) {
|
|
5403
|
-
const root = resolve6(override);
|
|
5404
|
-
if (existsSync7(join8(root, SKILLS_MARKER)))
|
|
5405
|
-
return root;
|
|
5406
|
-
throw new Error(`${SKILLS_DIR_ENV}=${override} is not a runcastle skills root (missing ${SKILLS_MARKER})`);
|
|
5407
|
-
}
|
|
5408
|
-
const searched = [];
|
|
5409
|
-
let dir = fromDir;
|
|
5410
|
-
for (let i = 0;i < 8; i += 1) {
|
|
5411
|
-
const candidate = join8(dir, "packages", "skills");
|
|
5412
|
-
searched.push(candidate);
|
|
5413
|
-
if (existsSync7(join8(candidate, SKILLS_MARKER)))
|
|
5414
|
-
return candidate;
|
|
5415
|
-
const parent = dirname3(dir);
|
|
5416
|
-
if (parent === dir)
|
|
5417
|
-
break;
|
|
5418
|
-
dir = parent;
|
|
5419
|
-
}
|
|
5420
|
-
throw new Error(`runcastle skills root not found; searched:
|
|
5421
|
-
${searched.join(`
|
|
5422
|
-
`)}`);
|
|
5423
|
-
}
|
|
5424
|
-
function resolvePluginDir(fromDir = dirname3(fileURLToPath4(import.meta.url))) {
|
|
5425
|
-
const rel = join8("packages", "skills", "packs", "runcastle");
|
|
5426
|
-
const override = process.env[SKILLS_DIR_ENV];
|
|
5427
|
-
if (override) {
|
|
5428
|
-
const dir2 = join8(resolve6(override), "packs", "runcastle");
|
|
5429
|
-
if (existsSync7(dir2))
|
|
5430
|
-
return dir2;
|
|
5431
|
-
throw new Error(`${SKILLS_DIR_ENV}=${override} has no plugin dir at ${dir2}`);
|
|
5432
|
-
}
|
|
5433
|
-
const searched = [];
|
|
5434
|
-
let dir = fromDir;
|
|
5435
|
-
for (let i = 0;i < 8; i += 1) {
|
|
5436
|
-
const candidate = join8(dir, rel);
|
|
5437
|
-
searched.push(candidate);
|
|
5438
|
-
if (existsSync7(candidate))
|
|
5439
|
-
return candidate;
|
|
5440
|
-
const parent = dirname3(dir);
|
|
5441
|
-
if (parent === dir)
|
|
5442
|
-
break;
|
|
5443
|
-
dir = parent;
|
|
5444
|
-
}
|
|
5445
|
-
throw new Error(`runcastle plugin dir (${rel}) not found; searched:
|
|
5446
|
-
${searched.join(`
|
|
5447
|
-
`)}`);
|
|
5448
|
-
}
|
|
5449
|
-
var SKILLS_DIR_ENV = "RUNCASTLE_SKILLS_DIR", SKILLS_MARKER;
|
|
5450
|
-
var init_skills_root = __esm(() => {
|
|
5451
|
-
SKILLS_MARKER = join8("packs", "runcastle");
|
|
5452
|
-
});
|
|
5453
|
-
|
|
5454
5486
|
// src/launcher/runtimes/types.ts
|
|
5487
|
+
function assertHeadlessKickoff(headless, line) {
|
|
5488
|
+
if (headless && !line)
|
|
5489
|
+
throw new Error("a headless launch needs a kickoff line as its prompt");
|
|
5490
|
+
}
|
|
5455
5491
|
function assertKickoffArgv(line) {
|
|
5456
5492
|
if (line !== undefined && line.length > MAX_KICKOFF_ARGV) {
|
|
5457
5493
|
throw new Error(`kickoff line is ${line.length} characters; maximum is ${MAX_KICKOFF_ARGV}`);
|
|
@@ -5462,9 +5498,11 @@ var MAX_KICKOFF_ARGV = 30000;
|
|
|
5462
5498
|
// src/launcher/runtimes/claude.ts
|
|
5463
5499
|
function buildClaudeArgs(input) {
|
|
5464
5500
|
assertKickoffArgv(input.kickoffLine);
|
|
5501
|
+
assertHeadlessKickoff(input.headless, input.kickoffLine);
|
|
5465
5502
|
const permissionMode = input.permissionMode ?? "acceptEdits";
|
|
5466
|
-
const resume = input.resumeSessionId ? ["--resume", input.resumeSessionId] : [];
|
|
5503
|
+
const resume = input.resumeSessionId && !input.headless ? ["--resume", input.resumeSessionId] : [];
|
|
5467
5504
|
return [
|
|
5505
|
+
...input.headless ? ["-p"] : [],
|
|
5468
5506
|
...resume,
|
|
5469
5507
|
"--settings",
|
|
5470
5508
|
input.settingsPath,
|
|
@@ -5515,11 +5553,12 @@ var init_claude = __esm(() => {
|
|
|
5515
5553
|
};
|
|
5516
5554
|
},
|
|
5517
5555
|
async writeArtifacts(input) {
|
|
5518
|
-
const
|
|
5556
|
+
const pluginDir = resolvePluginDir();
|
|
5557
|
+
const files = await writeSessionArtifacts({ ...input, runtime: this.id }, pluginDir);
|
|
5519
5558
|
return {
|
|
5520
5559
|
files: [files.systemPromptPath, files.settingsPath, files.mcpConfigPath],
|
|
5521
5560
|
argv: buildClaudeArgs({
|
|
5522
|
-
pluginDir
|
|
5561
|
+
pluginDir,
|
|
5523
5562
|
settingsPath: files.settingsPath,
|
|
5524
5563
|
mcpConfigPath: files.mcpConfigPath,
|
|
5525
5564
|
systemPromptPath: files.systemPromptPath,
|
|
@@ -5527,7 +5566,8 @@ var init_claude = __esm(() => {
|
|
|
5527
5566
|
model: input.model,
|
|
5528
5567
|
...input.resumeSessionId ? { resumeSessionId: input.resumeSessionId } : {},
|
|
5529
5568
|
...input.kickoffLine ? { kickoffLine: input.kickoffLine } : {},
|
|
5530
|
-
strictMcp: input.config.sessionMcp === "runcastleOnly"
|
|
5569
|
+
strictMcp: input.config.sessionMcp === "runcastleOnly",
|
|
5570
|
+
...input.headless ? { headless: true } : {}
|
|
5531
5571
|
}),
|
|
5532
5572
|
env: {
|
|
5533
5573
|
RUNCASTLE_SESSION_ID: input.session.id,
|
|
@@ -7137,12 +7177,16 @@ function toml(value) {
|
|
|
7137
7177
|
function approvalPolicyFor(permissionMode) {
|
|
7138
7178
|
return permissionMode === undefined || permissionMode === "acceptEdits" ? "never" : "on-request";
|
|
7139
7179
|
}
|
|
7180
|
+
function sandboxModeFor(permissionMode, platform) {
|
|
7181
|
+
return platform === "win32" && approvalPolicyFor(permissionMode) === "never" ? "danger-full-access" : "workspace-write";
|
|
7182
|
+
}
|
|
7140
7183
|
function renderCodexConfig(input) {
|
|
7141
7184
|
const { session, config: config2, model, worktreePath, inheritedMcpServers, permissionMode } = input;
|
|
7185
|
+
const platform = input.platform ?? process.platform;
|
|
7142
7186
|
return [
|
|
7143
7187
|
`# Generated by runcastle for session ${session.id} \u2014 rewritten at every launch.`,
|
|
7144
7188
|
`model = ${toml(model)}`,
|
|
7145
|
-
|
|
7189
|
+
`sandbox_mode = ${toml(sandboxModeFor(permissionMode, platform))}`,
|
|
7146
7190
|
`approval_policy = ${toml(approvalPolicyFor(permissionMode))}`,
|
|
7147
7191
|
"check_for_update_on_startup = false",
|
|
7148
7192
|
"",
|
|
@@ -7156,6 +7200,7 @@ function renderCodexConfig(input) {
|
|
|
7156
7200
|
"[mcp_servers.runcastle]",
|
|
7157
7201
|
`url = ${toml(`${serverUrlFor(config2)}/mcp`)}`,
|
|
7158
7202
|
`http_headers = { "X-Runcastle-Session" = ${toml(session.id)} }`,
|
|
7203
|
+
'default_tools_approval_mode = "approve"',
|
|
7159
7204
|
""
|
|
7160
7205
|
].join(`
|
|
7161
7206
|
`);
|
|
@@ -7264,6 +7309,9 @@ function gitInfoDir(worktreePath) {
|
|
|
7264
7309
|
}
|
|
7265
7310
|
function buildCodexArgs(input) {
|
|
7266
7311
|
assertKickoffArgv(input.kickoffLine);
|
|
7312
|
+
assertHeadlessKickoff(input.headless, input.kickoffLine);
|
|
7313
|
+
if (input.headless)
|
|
7314
|
+
return ["exec", "--dangerously-bypass-hook-trust", input.kickoffLine];
|
|
7267
7315
|
return [
|
|
7268
7316
|
...input.resumeSessionId ? ["resume", input.resumeSessionId] : [],
|
|
7269
7317
|
"--dangerously-bypass-hook-trust",
|
|
@@ -7336,7 +7384,8 @@ var init_codex = __esm(() => {
|
|
|
7336
7384
|
files,
|
|
7337
7385
|
argv: buildCodexArgs({
|
|
7338
7386
|
...input.resumeSessionId ? { resumeSessionId: input.resumeSessionId } : {},
|
|
7339
|
-
...input.kickoffLine ? { kickoffLine: input.kickoffLine } : {}
|
|
7387
|
+
...input.kickoffLine ? { kickoffLine: input.kickoffLine } : {},
|
|
7388
|
+
...input.headless ? { headless: true } : {}
|
|
7340
7389
|
}),
|
|
7341
7390
|
env: {
|
|
7342
7391
|
CODEX_HOME: home,
|
|
@@ -7971,6 +8020,12 @@ function listDocs(ctx, feature) {
|
|
|
7971
8020
|
return { relPath: e.name, title: readTitle(path, e.name), ...writtenAt(path) };
|
|
7972
8021
|
}).sort((a, b) => a.relPath.localeCompare(b.relPath));
|
|
7973
8022
|
}
|
|
8023
|
+
function previewSummary(ctx, feature) {
|
|
8024
|
+
const path = featureDocPath(projectForFeature(ctx, feature), feature, PREVIEW_FILE);
|
|
8025
|
+
if (!existsSync12(path))
|
|
8026
|
+
return null;
|
|
8027
|
+
return { relPath: PREVIEW_FILE, ...writtenAt(path) };
|
|
8028
|
+
}
|
|
7974
8029
|
function readDoc(ctx, feature, relPath) {
|
|
7975
8030
|
const project = projectForFeature(ctx, feature);
|
|
7976
8031
|
const dir = featureDocsDir(project, feature);
|
|
@@ -8003,6 +8058,7 @@ function readTitle(path, fallback) {
|
|
|
8003
8058
|
}
|
|
8004
8059
|
var CHARTER_FILE = "CONTEXT.md", ADR_DIR_REL = "docs/adr", SUPERSEDED_RE;
|
|
8005
8060
|
var init_knowledge = __esm(() => {
|
|
8061
|
+
init_src();
|
|
8006
8062
|
init_errors();
|
|
8007
8063
|
init_events();
|
|
8008
8064
|
init_feature_docs();
|
|
@@ -57791,6 +57847,7 @@ async function runReviewGates(input) {
|
|
|
57791
57847
|
mkdirSync10(reviewGateLogDir(ticketId), { recursive: true });
|
|
57792
57848
|
await discardBranch();
|
|
57793
57849
|
await pinBranchAt(project3.repoPath, branch, sha);
|
|
57850
|
+
await excludeBurnWorktrees(project3.repoPath);
|
|
57794
57851
|
while (results.length < commands.length) {
|
|
57795
57852
|
await withSandbox(branch, async (sandbox3) => {
|
|
57796
57853
|
for (const [i, command] of commands.entries()) {
|
|
@@ -60553,6 +60610,7 @@ async function burnTicket(ctx, ticket, config2, token, model, hostAgentVersions,
|
|
|
60553
60610
|
await clearAttachments(burnWorktreePath(project3.repoPath, branch), project3.repoPath);
|
|
60554
60611
|
}
|
|
60555
60612
|
};
|
|
60613
|
+
await excludeBurnWorktrees(project3.repoPath);
|
|
60556
60614
|
const ticketJson = buildTicketJson(ticket);
|
|
60557
60615
|
const featureBrief = buildFeatureBrief(feature);
|
|
60558
60616
|
const docsDigest = blocks.docsDigest;
|
|
@@ -61660,6 +61718,7 @@ function getFeatureFull(ctx, id3) {
|
|
|
61660
61718
|
sessions: listSessionsByFeature(ctx, id3).map((session) => session.kind === "chat" && session.status === "ended" && !session.ccSessionId ? { ...session, title: null, transcriptMissing: true } : session),
|
|
61661
61719
|
runs: listRunsByFeature(ctx, id3),
|
|
61662
61720
|
docs: listDocs(ctx, feature),
|
|
61721
|
+
preview: previewSummary(ctx, feature),
|
|
61663
61722
|
dependsOn: dependsOnOf(ctx, id3),
|
|
61664
61723
|
blocks: blocksOf(ctx, id3)
|
|
61665
61724
|
};
|
|
@@ -62788,6 +62847,24 @@ async function commitDocsCheckpoint(ctx, session, summary) {
|
|
|
62788
62847
|
}
|
|
62789
62848
|
await landChatCommits(ctx, project3, feature);
|
|
62790
62849
|
}
|
|
62850
|
+
function checkpointDocsAfterReply(ctx, session, summary) {
|
|
62851
|
+
let queue = docsCheckpointQueues.get(session.worktreePath);
|
|
62852
|
+
if (!queue) {
|
|
62853
|
+
queue = createSerialQueue();
|
|
62854
|
+
docsCheckpointQueues.set(session.worktreePath, queue);
|
|
62855
|
+
}
|
|
62856
|
+
queue(() => commitDocsCheckpoint(ctx, session, summary)).catch((e) => {
|
|
62857
|
+
try {
|
|
62858
|
+
emit(ctx, requireFeatureId(session), {
|
|
62859
|
+
type: "git.commit_pending",
|
|
62860
|
+
message: `docs checkpoint failed: ${e instanceof Error ? e.message : String(e)}`,
|
|
62861
|
+
data: { summary }
|
|
62862
|
+
});
|
|
62863
|
+
} catch (err) {
|
|
62864
|
+
console.error(`docs checkpoint failed for session ${session.id}`, e, err);
|
|
62865
|
+
}
|
|
62866
|
+
});
|
|
62867
|
+
}
|
|
62791
62868
|
function requireProject(ctx, session) {
|
|
62792
62869
|
if (!session.projectId) {
|
|
62793
62870
|
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.");
|
|
@@ -63528,7 +63605,7 @@ function buildMcpServer(audience) {
|
|
|
63528
63605
|
if (!rs)
|
|
63529
63606
|
return noSession();
|
|
63530
63607
|
const result = toolEmitTickets(rs.ctx, rs.session, args2);
|
|
63531
|
-
|
|
63608
|
+
checkpointDocsAfterReply(rs.ctx, rs.session, `tickets emitted (${result.stored})`);
|
|
63532
63609
|
return ok(result);
|
|
63533
63610
|
});
|
|
63534
63611
|
}
|
|
@@ -63550,7 +63627,7 @@ function buildMcpServer(audience) {
|
|
|
63550
63627
|
if (!rs)
|
|
63551
63628
|
return noSession();
|
|
63552
63629
|
const result = toolUpdateTicket(rs.ctx, rs.session, args2);
|
|
63553
|
-
|
|
63630
|
+
checkpointDocsAfterReply(rs.ctx, rs.session, `ticket ${result.ticket.seq} updated`);
|
|
63554
63631
|
return ok(result);
|
|
63555
63632
|
});
|
|
63556
63633
|
}
|
|
@@ -63567,7 +63644,7 @@ function buildMcpServer(audience) {
|
|
|
63567
63644
|
if (!rs)
|
|
63568
63645
|
return noSession();
|
|
63569
63646
|
const result = toolCancelTicket(rs.ctx, rs.session, args2);
|
|
63570
|
-
|
|
63647
|
+
checkpointDocsAfterReply(rs.ctx, rs.session, `ticket ${result.ticket.seq} cancelled`);
|
|
63571
63648
|
return ok(result);
|
|
63572
63649
|
});
|
|
63573
63650
|
}
|
|
@@ -63611,7 +63688,7 @@ function buildMcpServer(audience) {
|
|
|
63611
63688
|
return noSession();
|
|
63612
63689
|
const result = toolCompletePhase(rs.ctx, rs.session, args2);
|
|
63613
63690
|
if (result.ok) {
|
|
63614
|
-
|
|
63691
|
+
checkpointDocsAfterReply(rs.ctx, rs.session, `phase '${args2.phase}' complete`);
|
|
63615
63692
|
}
|
|
63616
63693
|
return ok(result);
|
|
63617
63694
|
});
|
|
@@ -63644,7 +63721,7 @@ async function readMcpBody(c) {
|
|
|
63644
63721
|
return;
|
|
63645
63722
|
}
|
|
63646
63723
|
}
|
|
63647
|
-
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;
|
|
63724
|
+
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;
|
|
63648
63725
|
var init_server = __esm(() => {
|
|
63649
63726
|
init_src();
|
|
63650
63727
|
init_paths();
|
|
@@ -63663,6 +63740,7 @@ var init_server = __esm(() => {
|
|
|
63663
63740
|
init_review_findings();
|
|
63664
63741
|
init_git();
|
|
63665
63742
|
init_knowledge();
|
|
63743
|
+
init_landing_queue();
|
|
63666
63744
|
init_planning();
|
|
63667
63745
|
init_project_notes();
|
|
63668
63746
|
init_repo();
|
|
@@ -63687,6 +63765,7 @@ var init_server = __esm(() => {
|
|
|
63687
63765
|
});
|
|
63688
63766
|
}
|
|
63689
63767
|
});
|
|
63768
|
+
docsCheckpointQueues = new Map;
|
|
63690
63769
|
DRAFTING_KINDS = ["chat"];
|
|
63691
63770
|
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/.";
|
|
63692
63771
|
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 }).";
|
|
@@ -64725,7 +64804,7 @@ function ptyExitMessage(exitCode) {
|
|
|
64725
64804
|
const label = typeof exitCode === "number" ? String(exitCode) : "unknown";
|
|
64726
64805
|
return `terminal exited (code ${label})`;
|
|
64727
64806
|
}
|
|
64728
|
-
function chatKickoffHeader(ctx, feature, runtime5 = DEFAULT_RUNTIME) {
|
|
64807
|
+
function chatKickoffHeader(ctx, feature, runtime5 = DEFAULT_RUNTIME, opening = chatKickoffFor(runtime5, chatOpening(feature, planningFacts(ctx, feature)))) {
|
|
64729
64808
|
const counts = new Map;
|
|
64730
64809
|
for (const ticket of listByFeature(ctx, feature.id)) {
|
|
64731
64810
|
counts.set(ticket.status, (counts.get(ticket.status) ?? 0) + 1);
|
|
@@ -64738,7 +64817,6 @@ function chatKickoffHeader(ctx, feature, runtime5 = DEFAULT_RUNTIME) {
|
|
|
64738
64817
|
const outcome = !latestRun ? "never driven" : latestReview?.status === "done" ? "passed" : latestReview?.status === "failed" ? "failed" : "unverified";
|
|
64739
64818
|
review = ` Drive outcome: ${outcome}; see the review evidence in get_feature_context.`;
|
|
64740
64819
|
}
|
|
64741
|
-
const opening = chatKickoffFor(runtime5, chatOpening(feature, planningFacts(ctx, feature)));
|
|
64742
64820
|
return `${opening} Feature state: ${feature.phase}; lap ${feature.lap}; tickets: ${ticketSummary}; latest run: ${latestRun?.status ?? "none"}.${review} Call get_feature_context for the full picture.`;
|
|
64743
64821
|
}
|
|
64744
64822
|
function assertRuntimeReady(runtime5, opts) {
|
|
@@ -64765,6 +64843,21 @@ function assertSpawnable(ctx, feature, excludeSessionId) {
|
|
|
64765
64843
|
throw new GateError(`a ${live[0].kind} session is already live for ${feature.slug} \u2014 only one terminal per feature; end or resume it first`);
|
|
64766
64844
|
}
|
|
64767
64845
|
}
|
|
64846
|
+
function isSelfPlan(input) {
|
|
64847
|
+
return input.kind === "chat" && input.purpose === "self-plan";
|
|
64848
|
+
}
|
|
64849
|
+
function assertSelfPlannable(ctx, feature) {
|
|
64850
|
+
if (feature.phase !== "planning") {
|
|
64851
|
+
throw new GateError(`${feature.slug} is past planning, so there is nothing for it to plan by itself \u2014 open the chat to change it`);
|
|
64852
|
+
}
|
|
64853
|
+
if (chatOpening(feature, planningFacts(ctx, feature)) !== "ideate") {
|
|
64854
|
+
throw new GateError(`${feature.slug} already has planning on record (decisions, a spec or tickets), so it cannot plan itself from scratch \u2014 open the chat to carry on`);
|
|
64855
|
+
}
|
|
64856
|
+
assertSpawnable(ctx, feature);
|
|
64857
|
+
}
|
|
64858
|
+
function selfPlanRunningError(feature) {
|
|
64859
|
+
return new GateError(`${feature.slug} is planning itself \u2014 stop the run first, then open the chat to pick up where it got to`);
|
|
64860
|
+
}
|
|
64768
64861
|
async function ensureWorktree(ctx, project3, feature) {
|
|
64769
64862
|
const running3 = activeRunFor(ctx, feature.id);
|
|
64770
64863
|
const besideRun = !!running3 && workflowClaimsFeatureBranch(running3.workflow);
|
|
@@ -64846,7 +64939,11 @@ async function launchSession(ctx, input, opts = {}) {
|
|
|
64846
64939
|
requireNotDraft(feature);
|
|
64847
64940
|
if (input.kind === "chat") {
|
|
64848
64941
|
const liveChat = activeSessionsForFeature(ctx, feature.id).find((s) => s.kind === "chat");
|
|
64849
|
-
if (liveChat
|
|
64942
|
+
if (liveChat?.purpose === "self-plan")
|
|
64943
|
+
throw selfPlanRunningError(feature);
|
|
64944
|
+
if (isSelfPlan(input))
|
|
64945
|
+
assertSelfPlannable(ctx, feature);
|
|
64946
|
+
else if (liveChat && isStartLap(feature, input))
|
|
64850
64947
|
endSession(ctx, liveChat.id);
|
|
64851
64948
|
else if (liveChat)
|
|
64852
64949
|
return briefLiveChat(ctx, feature, liveChat, input);
|
|
@@ -64866,8 +64963,9 @@ async function launchSession(ctx, input, opts = {}) {
|
|
|
64866
64963
|
});
|
|
64867
64964
|
const carried = startLapCarriedWork(ctx, feature.id);
|
|
64868
64965
|
const plan = planLaunchKickoff(feature, input, carried);
|
|
64966
|
+
const selfPlan = isSelfPlan(input);
|
|
64869
64967
|
if (input.kind === "chat") {
|
|
64870
|
-
const header = chatKickoffHeader(ctx, feature, runtime5.id);
|
|
64968
|
+
const header = chatKickoffHeader(ctx, feature, runtime5.id, selfPlan ? selfPlanKickoffFor(runtime5.id) : undefined);
|
|
64871
64969
|
plan.line = plan.line ? `${plan.line} ${header}` : header;
|
|
64872
64970
|
}
|
|
64873
64971
|
let resumeSessionId;
|
|
@@ -64880,6 +64978,8 @@ async function launchSession(ctx, input, opts = {}) {
|
|
|
64880
64978
|
markSessionEnded(ctx, session.id);
|
|
64881
64979
|
throw e;
|
|
64882
64980
|
}
|
|
64981
|
+
}
|
|
64982
|
+
if (input.kind === "chat" && !selfPlan) {
|
|
64883
64983
|
const prior = mostRecentResumableSession(ctx, feature.id, "chat");
|
|
64884
64984
|
if (prior?.ccSessionId) {
|
|
64885
64985
|
resumedFrom = prior;
|
|
@@ -64919,12 +65019,13 @@ async function launchSession(ctx, input, opts = {}) {
|
|
|
64919
65019
|
const kickoffLine = resumeSessionId && input.kind !== "chat" ? undefined : kickoffLineFor(input.kind, plan.line, runtime5.id);
|
|
64920
65020
|
emit(ctx, feature.id, {
|
|
64921
65021
|
type: "session.launching",
|
|
64922
|
-
message: `launching ${input.kind} session`,
|
|
65022
|
+
message: selfPlan ? "launching a self-planning run" : `launching ${input.kind} session`,
|
|
64923
65023
|
data: {
|
|
64924
65024
|
sessionId: session.id,
|
|
64925
65025
|
kind: input.kind,
|
|
64926
65026
|
worktreePath,
|
|
64927
|
-
...modelStamp(model)
|
|
65027
|
+
...modelStamp(model),
|
|
65028
|
+
...input.purpose ? { purpose: input.purpose } : {}
|
|
64928
65029
|
}
|
|
64929
65030
|
});
|
|
64930
65031
|
if (resumeSessionId) {
|
|
@@ -64961,7 +65062,8 @@ async function launchSession(ctx, input, opts = {}) {
|
|
|
64961
65062
|
model: model.id,
|
|
64962
65063
|
resumeSessionId,
|
|
64963
65064
|
resumeSourceSessionId: resumedFrom?.id,
|
|
64964
|
-
kickoffLine
|
|
65065
|
+
kickoffLine,
|
|
65066
|
+
...selfPlan ? { headless: true } : {}
|
|
64965
65067
|
});
|
|
64966
65068
|
if (opts.spawn === false) {
|
|
64967
65069
|
emit(ctx, feature.id, {
|
|
@@ -65307,6 +65409,15 @@ function handlePtyExit(ctx, feature, session, meta, exitCode) {
|
|
|
65307
65409
|
}
|
|
65308
65410
|
});
|
|
65309
65411
|
}
|
|
65412
|
+
if (session.purpose === "self-plan" && feature) {
|
|
65413
|
+
const current2 = getFeatureRow(ctx, feature.id);
|
|
65414
|
+
const complete4 = current2.ticketsReadyLap === current2.lap;
|
|
65415
|
+
emitForSession(ctx, session, {
|
|
65416
|
+
type: "session.self_plan_ended",
|
|
65417
|
+
message: complete4 ? "planning finished" : "planning stopped before the plan was complete",
|
|
65418
|
+
data: { sessionId: session.id, complete: complete4, exitCode: exitCode ?? null }
|
|
65419
|
+
});
|
|
65420
|
+
}
|
|
65310
65421
|
emitForSession(ctx, session, {
|
|
65311
65422
|
type: "session.pty_exited",
|
|
65312
65423
|
message: ptyExitMessage(exitCode),
|
|
@@ -65893,8 +66004,8 @@ var init_transcripts = __esm(() => {
|
|
|
65893
66004
|
|
|
65894
66005
|
// src/services/conversations.ts
|
|
65895
66006
|
function withoutKickoff(turns, kind, runtime5) {
|
|
65896
|
-
const
|
|
65897
|
-
return turns.filter((turn) => !(turn.role === "user" && promptMatchesKickoff(kickoff, turn.text)));
|
|
66007
|
+
const kickoffs = [kickoffLineFor(kind, undefined, runtime5), selfPlanKickoffFor(runtime5)];
|
|
66008
|
+
return turns.filter((turn) => !(turn.role === "user" && kickoffs.some((kickoff) => promptMatchesKickoff(kickoff, turn.text))));
|
|
65898
66009
|
}
|
|
65899
66010
|
function promptMatchesKickoff(line, prompt) {
|
|
65900
66011
|
if (!prompt)
|
|
@@ -65988,6 +66099,7 @@ var TITLE_MAX = 60, MATCH_PREFIX = 40, INTERRUPTED = "[Request interrupted by us
|
|
|
65988
66099
|
var init_conversations = __esm(() => {
|
|
65989
66100
|
init_src();
|
|
65990
66101
|
init_sessions();
|
|
66102
|
+
init_skills();
|
|
65991
66103
|
init_transcripts();
|
|
65992
66104
|
});
|
|
65993
66105
|
|