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