@autopilot-harness/cli 0.2.8 → 0.2.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/assets/templates/skills/autopilot-on/SKILL.md.tpl +12 -1
- package/dist/assets/templates/skills/autopilot-run/SKILL.md.tpl +19 -5
- package/dist/assets/vendor/runtime.mjs +279 -75
- package/dist/bin.d.ts +1 -1
- package/dist/bin.d.ts.map +1 -1
- package/dist/bin.js +1 -0
- package/dist/bin.js.map +1 -1
- package/dist/init/types.d.ts +1 -1
- package/dist/init/types.d.ts.map +1 -1
- package/dist/init/types.js +1 -1
- package/dist/init/types.js.map +1 -1
- package/dist/init/wizard-helpers.d.ts.map +1 -1
- package/dist/init/wizard-helpers.js +44 -2
- package/dist/init/wizard-helpers.js.map +1 -1
- package/dist/sqlite-warning-predicate.d.ts +5 -0
- package/dist/sqlite-warning-predicate.d.ts.map +1 -0
- package/dist/sqlite-warning-predicate.js +8 -0
- package/dist/sqlite-warning-predicate.js.map +1 -0
- package/dist/status-doctor.d.ts.map +1 -1
- package/dist/status-doctor.js +93 -0
- package/dist/status-doctor.js.map +1 -1
- package/dist/suppress-sqlite-warning.d.ts +3 -0
- package/dist/suppress-sqlite-warning.d.ts.map +1 -0
- package/dist/suppress-sqlite-warning.js +47 -0
- package/dist/suppress-sqlite-warning.js.map +1 -0
- package/package.json +15 -15
- package/LICENSE +0 -21
|
@@ -14,7 +14,7 @@ var en_default = {
|
|
|
14
14
|
},
|
|
15
15
|
skill: {
|
|
16
16
|
autopilot_on: {
|
|
17
|
-
description: "Start
|
|
17
|
+
description: "Start planning only after /autopilot-on or configured ON phrases (e.g. Autopilot ON; per project triggers.on); not for casual chat."
|
|
18
18
|
},
|
|
19
19
|
autopilot_run: {
|
|
20
20
|
description: "Start Executing \u2014 run checklist"
|
|
@@ -90,7 +90,7 @@ var zh_CN_default = {
|
|
|
90
90
|
},
|
|
91
91
|
skill: {
|
|
92
92
|
autopilot_on: {
|
|
93
|
-
description: "\u5F00\u59CB\u89C4\u5212
|
|
93
|
+
description: "\u4EC5\u5728\u7528\u6237\u53D1\u51FA /autopilot-on \u6216\u300C\u5F00\u542F\u81EA\u52A8\u9A7E\u9A76\u300D\u7B49\u914D\u7F6E\u89E6\u53D1\u8BED\uFF08\u4EE5\u9879\u76EE triggers.on \u4E3A\u51C6\uFF09\u540E\u5F00\u59CB\u89C4\u5212\uFF1B\u52FF\u7528\u4E8E\u666E\u901A\u95F2\u804A\u3002"
|
|
94
94
|
},
|
|
95
95
|
autopilot_run: {
|
|
96
96
|
description: "\u5F00\u59CB\u6267\u884C \u2014 \u8DD1 checklist"
|
|
@@ -1890,6 +1890,95 @@ function sessionReviewRunnable(session, reviewScope) {
|
|
|
1890
1890
|
return isChecklistExecuting(session);
|
|
1891
1891
|
}
|
|
1892
1892
|
|
|
1893
|
+
// ../core/src/plans-bind.ts
|
|
1894
|
+
import path5 from "node:path";
|
|
1895
|
+
var MULTI_PLAN_EDIT_TRACK = "_multi";
|
|
1896
|
+
function isBoundRunTrackId(trackId) {
|
|
1897
|
+
return typeof trackId === "string" && trackId !== "_pending" && trackId !== MULTI_PLAN_EDIT_TRACK && isSafeTrackSlug(trackId);
|
|
1898
|
+
}
|
|
1899
|
+
function extractPlansSlugFromPath(filePath, projectRoot, plansDir = "plans") {
|
|
1900
|
+
if (!filePath || filePath.includes("\0")) return null;
|
|
1901
|
+
const root = normalizeProjectRoot(projectRoot);
|
|
1902
|
+
if (!root) return null;
|
|
1903
|
+
const safePlans = normalizeInProjectPlansDir(root, plansDir);
|
|
1904
|
+
if (!safePlans) return null;
|
|
1905
|
+
let abs;
|
|
1906
|
+
try {
|
|
1907
|
+
abs = path5.isAbsolute(filePath) ? path5.resolve(filePath) : path5.resolve(root, filePath);
|
|
1908
|
+
} catch {
|
|
1909
|
+
return null;
|
|
1910
|
+
}
|
|
1911
|
+
if (!isLexicallyInsideProject(root, abs)) return null;
|
|
1912
|
+
let rel;
|
|
1913
|
+
try {
|
|
1914
|
+
rel = path5.relative(root, abs);
|
|
1915
|
+
} catch {
|
|
1916
|
+
return null;
|
|
1917
|
+
}
|
|
1918
|
+
if (!rel || rel.startsWith("..") || path5.isAbsolute(rel)) return null;
|
|
1919
|
+
const parts = rel.split(/[/\\]/).filter(Boolean);
|
|
1920
|
+
const dirParts = safePlans.split(/[/\\]/).filter(Boolean);
|
|
1921
|
+
if (parts.length < dirParts.length + 2) return null;
|
|
1922
|
+
for (let i = 0; i < dirParts.length; i++) {
|
|
1923
|
+
if (parts[i] !== dirParts[i]) return null;
|
|
1924
|
+
}
|
|
1925
|
+
const slug = parts[dirParts.length];
|
|
1926
|
+
if (!isSafeTrackSlug(slug)) return null;
|
|
1927
|
+
return slug;
|
|
1928
|
+
}
|
|
1929
|
+
function notePlansDirEdit(store, conversationId, projectRoot, filePath, plansDir = "plans") {
|
|
1930
|
+
const id = conversationId.trim();
|
|
1931
|
+
if (!id || !store.isConversationIdOk(id)) return;
|
|
1932
|
+
const slug = extractPlansSlugFromPath(filePath, projectRoot, plansDir);
|
|
1933
|
+
if (!slug) return;
|
|
1934
|
+
const root = normalizeProjectRoot(projectRoot) ?? projectRoot;
|
|
1935
|
+
store.exclusiveWrite(() => {
|
|
1936
|
+
const existing = store.getSession(id);
|
|
1937
|
+
if (!existing) {
|
|
1938
|
+
store.upsertSession({
|
|
1939
|
+
conversation_id: id,
|
|
1940
|
+
project_root: root,
|
|
1941
|
+
code_root: root,
|
|
1942
|
+
track_id: slug,
|
|
1943
|
+
phase: "idle",
|
|
1944
|
+
armed: 0,
|
|
1945
|
+
paused: 0,
|
|
1946
|
+
checklist_path: ""
|
|
1947
|
+
});
|
|
1948
|
+
return { commit: true, value: void 0 };
|
|
1949
|
+
}
|
|
1950
|
+
if (existing.phase === "executing") {
|
|
1951
|
+
return { commit: false, value: void 0 };
|
|
1952
|
+
}
|
|
1953
|
+
if (existing.pending_action === "run" || existing.pending_action === "replan") {
|
|
1954
|
+
return { commit: false, value: void 0 };
|
|
1955
|
+
}
|
|
1956
|
+
const tid = existing.track_id;
|
|
1957
|
+
if (tid === MULTI_PLAN_EDIT_TRACK) {
|
|
1958
|
+
return { commit: false, value: void 0 };
|
|
1959
|
+
}
|
|
1960
|
+
if (!tid || tid === "_pending") {
|
|
1961
|
+
store.upsertSession({
|
|
1962
|
+
conversation_id: id,
|
|
1963
|
+
project_root: existing.project_root,
|
|
1964
|
+
code_root: existing.code_root,
|
|
1965
|
+
track_id: slug
|
|
1966
|
+
});
|
|
1967
|
+
return { commit: true, value: void 0 };
|
|
1968
|
+
}
|
|
1969
|
+
if (tid === slug) {
|
|
1970
|
+
return { commit: false, value: void 0 };
|
|
1971
|
+
}
|
|
1972
|
+
store.upsertSession({
|
|
1973
|
+
conversation_id: id,
|
|
1974
|
+
project_root: existing.project_root,
|
|
1975
|
+
code_root: existing.code_root,
|
|
1976
|
+
track_id: MULTI_PLAN_EDIT_TRACK
|
|
1977
|
+
});
|
|
1978
|
+
return { commit: true, value: void 0 };
|
|
1979
|
+
});
|
|
1980
|
+
}
|
|
1981
|
+
|
|
1893
1982
|
// ../core/src/transcript-followup.ts
|
|
1894
1983
|
import fs6 from "node:fs";
|
|
1895
1984
|
|
|
@@ -3113,10 +3202,10 @@ var ReviewEngine = class {
|
|
|
3113
3202
|
* salvage again — the prior tip does not cover the new failure.
|
|
3114
3203
|
*/
|
|
3115
3204
|
classifyCompletedOrphan(transcriptPath) {
|
|
3116
|
-
const
|
|
3117
|
-
if (!
|
|
3205
|
+
const path10 = transcriptPath?.trim();
|
|
3206
|
+
if (!path10) return "none";
|
|
3118
3207
|
try {
|
|
3119
|
-
const events = readTranscriptTail(
|
|
3208
|
+
const events = readTranscriptTail(path10);
|
|
3120
3209
|
const errIdx = latestUnresolvedTurnEndedErrorIndex(events);
|
|
3121
3210
|
if (errIdx < 0) return "none";
|
|
3122
3211
|
for (let i = events.length - 1; i > errIdx; i--) {
|
|
@@ -4210,8 +4299,8 @@ var ReviewEngine = class {
|
|
|
4210
4299
|
let unchecked = checklist.unchecked;
|
|
4211
4300
|
let next = checklist.next;
|
|
4212
4301
|
let targets = null;
|
|
4213
|
-
const
|
|
4214
|
-
const onChecklistPath = isChecklistExecuting(lockedSession) &&
|
|
4302
|
+
const path10 = lockedSession.checklist_path?.trim() ?? "";
|
|
4303
|
+
const onChecklistPath = isChecklistExecuting(lockedSession) && path10.length > 0;
|
|
4215
4304
|
if (onChecklistPath) {
|
|
4216
4305
|
const refreshed = this.parseSessionChecklist(lockedSession);
|
|
4217
4306
|
if (!refreshed?.checklist) {
|
|
@@ -4431,7 +4520,9 @@ function applyOn(store, conversationId, projectRoot, opts) {
|
|
|
4431
4520
|
userMessage: `Invalid track slug "${sanitizeSessionDisplayText(raw).slice(0, 64)}".`
|
|
4432
4521
|
};
|
|
4433
4522
|
}
|
|
4434
|
-
const
|
|
4523
|
+
const prevTid = session?.track_id ?? "_pending";
|
|
4524
|
+
const trackId = opts?.slug ?? (session?.track_id && isBoundRunTrackId(session.track_id) ? session.track_id : "_pending");
|
|
4525
|
+
const checklistPath = trackId === prevTid ? session?.checklist_path ?? "" : "";
|
|
4435
4526
|
const platform = resolveSessionPlatform(
|
|
4436
4527
|
opts?.platform,
|
|
4437
4528
|
session?.platform ?? "cursor"
|
|
@@ -4445,7 +4536,10 @@ function applyOn(store, conversationId, projectRoot, opts) {
|
|
|
4445
4536
|
armed: 0,
|
|
4446
4537
|
paused: 0,
|
|
4447
4538
|
paused_reason: null,
|
|
4448
|
-
track_id:
|
|
4539
|
+
track_id: trackId,
|
|
4540
|
+
checklist_path: checklistPath,
|
|
4541
|
+
pending_action: null,
|
|
4542
|
+
track_candidates_json: null,
|
|
4449
4543
|
platform
|
|
4450
4544
|
});
|
|
4451
4545
|
return { ok: true, session: s2 };
|
|
@@ -4460,7 +4554,10 @@ function applyOn(store, conversationId, projectRoot, opts) {
|
|
|
4460
4554
|
paused: 0,
|
|
4461
4555
|
paused_reason: null,
|
|
4462
4556
|
track_id: trackId,
|
|
4463
|
-
checklist_path:
|
|
4557
|
+
checklist_path: checklistPath,
|
|
4558
|
+
// ON returns to planning — drop mid-flow run/replan pick state.
|
|
4559
|
+
pending_action: null,
|
|
4560
|
+
track_candidates_json: null
|
|
4464
4561
|
});
|
|
4465
4562
|
return { ok: true, session: s };
|
|
4466
4563
|
}
|
|
@@ -4568,7 +4665,7 @@ function applyResumeReview(store, conversationId) {
|
|
|
4568
4665
|
|
|
4569
4666
|
// ../core/src/project-config.ts
|
|
4570
4667
|
import fs7 from "node:fs";
|
|
4571
|
-
import
|
|
4668
|
+
import path6 from "node:path";
|
|
4572
4669
|
var MAX_CONFIG_BYTES = 1e6;
|
|
4573
4670
|
var DEFAULT_PROJECT_REVIEW_CONFIG = {
|
|
4574
4671
|
confirmRounds: 5,
|
|
@@ -4728,7 +4825,7 @@ function loadProjectReviewConfig(projectRoot) {
|
|
|
4728
4825
|
if (!root) {
|
|
4729
4826
|
return cloneDefaultProjectReviewConfig();
|
|
4730
4827
|
}
|
|
4731
|
-
const configPath =
|
|
4828
|
+
const configPath = path6.join(root, ".autopilot", "config.yml");
|
|
4732
4829
|
try {
|
|
4733
4830
|
const nofollow = typeof fs7.constants.O_NOFOLLOW === "number" ? fs7.constants.O_NOFOLLOW : 0;
|
|
4734
4831
|
if (nofollow === 0) {
|
|
@@ -4916,11 +5013,11 @@ function createConfiguredReviewEngine(store, projectRoot, localeBundle, preloade
|
|
|
4916
5013
|
|
|
4917
5014
|
// ../core/src/phase-actions.ts
|
|
4918
5015
|
import fs9 from "node:fs";
|
|
4919
|
-
import
|
|
5016
|
+
import path8 from "node:path";
|
|
4920
5017
|
|
|
4921
5018
|
// ../core/src/list-tracks.ts
|
|
4922
5019
|
import fs8 from "node:fs";
|
|
4923
|
-
import
|
|
5020
|
+
import path7 from "node:path";
|
|
4924
5021
|
function isRunnableTrack(t) {
|
|
4925
5022
|
if (t.paused) return false;
|
|
4926
5023
|
const unchecked = t.checklistTotal - t.checklistDone;
|
|
@@ -4931,7 +5028,7 @@ function readPlansDir(root, plansDir = "plans") {
|
|
|
4931
5028
|
if (typeof plansDir !== "string" || !root || root.includes("\0") || plansDir.includes("\0")) {
|
|
4932
5029
|
return [];
|
|
4933
5030
|
}
|
|
4934
|
-
const dir =
|
|
5031
|
+
const dir = path7.join(root, plansDir);
|
|
4935
5032
|
try {
|
|
4936
5033
|
const lst = fs8.lstatSync(dir);
|
|
4937
5034
|
if (lst.isSymbolicLink() || !lst.isDirectory()) return [];
|
|
@@ -4993,7 +5090,7 @@ function listTracks(root, store, filter = "all", plansDir = "plans") {
|
|
|
4993
5090
|
const slugs = readPlansDir(root, plansDir);
|
|
4994
5091
|
const tracks = [];
|
|
4995
5092
|
for (const slug of slugs) {
|
|
4996
|
-
const trackDir =
|
|
5093
|
+
const trackDir = path7.join(root, plansDir, slug);
|
|
4997
5094
|
try {
|
|
4998
5095
|
const lst = fs8.lstatSync(trackDir);
|
|
4999
5096
|
if (lst.isSymbolicLink() || !lst.isDirectory()) continue;
|
|
@@ -5001,8 +5098,8 @@ function listTracks(root, store, filter = "all", plansDir = "plans") {
|
|
|
5001
5098
|
} catch {
|
|
5002
5099
|
continue;
|
|
5003
5100
|
}
|
|
5004
|
-
const planPath =
|
|
5005
|
-
const checklistPath =
|
|
5101
|
+
const planPath = path7.join(trackDir, "plan.md");
|
|
5102
|
+
const checklistPath = path7.join(trackDir, "checklist.md");
|
|
5006
5103
|
let checklistTotal = 0;
|
|
5007
5104
|
let checklistDone = 0;
|
|
5008
5105
|
const checklistInProject = isRealpathInsideProject(root, checklistPath);
|
|
@@ -5098,13 +5195,33 @@ function canEnterExecuting(options) {
|
|
|
5098
5195
|
}
|
|
5099
5196
|
|
|
5100
5197
|
// ../core/src/phase-actions.ts
|
|
5198
|
+
function displayUntrusted(raw, max = 64) {
|
|
5199
|
+
const text = typeof raw === "string" ? raw : String(raw ?? "");
|
|
5200
|
+
return sanitizeSessionDisplayText(text).slice(0, max) || "?";
|
|
5201
|
+
}
|
|
5202
|
+
function isChannelANeedPick(fail) {
|
|
5203
|
+
return fail.needPick === true && fail.busy !== true;
|
|
5204
|
+
}
|
|
5205
|
+
function formatOneExecutorBusyMessage(trackId, conversationId) {
|
|
5206
|
+
const occTrack = displayUntrusted(trackId || "(unknown)");
|
|
5207
|
+
const rawCid = typeof conversationId === "string" ? conversationId : String(conversationId ?? "");
|
|
5208
|
+
const occSession = displayUntrusted(shortConversationId(rawCid) || "?") || "?";
|
|
5209
|
+
return `Another session is already executing (track: ${occTrack}, session: ${occSession}). Send Autopilot OFF there or wait, then retry. If the host only shows opaque blocked, run: npx @autopilot-harness/cli status or npx @autopilot-harness/cli doctor`;
|
|
5210
|
+
}
|
|
5211
|
+
function logBusyOccupier(message) {
|
|
5212
|
+
try {
|
|
5213
|
+
process.stderr.write(`[autopilot] ${message}
|
|
5214
|
+
`);
|
|
5215
|
+
} catch {
|
|
5216
|
+
}
|
|
5217
|
+
}
|
|
5101
5218
|
function nowIso2() {
|
|
5102
5219
|
return (/* @__PURE__ */ new Date()).toISOString();
|
|
5103
5220
|
}
|
|
5104
5221
|
function checklistPathFor(projectRoot, slug, plansDir) {
|
|
5105
5222
|
const root = normalizeProjectRoot(projectRoot);
|
|
5106
|
-
if (!root) return
|
|
5107
|
-
return
|
|
5223
|
+
if (!root) return path8.join(plansDir, slug, "checklist.md");
|
|
5224
|
+
return path8.join(root, plansDir, slug, "checklist.md");
|
|
5108
5225
|
}
|
|
5109
5226
|
function sameChecklistBinding(stored, rebuilt, projectRoot) {
|
|
5110
5227
|
if (stored === rebuilt) return true;
|
|
@@ -5114,8 +5231,8 @@ function sameChecklistBinding(stored, rebuilt, projectRoot) {
|
|
|
5114
5231
|
const root = normalizeProjectRoot(projectRoot);
|
|
5115
5232
|
if (!root) return false;
|
|
5116
5233
|
try {
|
|
5117
|
-
const absStored =
|
|
5118
|
-
const absRebuilt =
|
|
5234
|
+
const absStored = path8.isAbsolute(stored) ? path8.resolve(stored) : path8.resolve(root, stored);
|
|
5235
|
+
const absRebuilt = path8.isAbsolute(rebuilt) ? path8.resolve(rebuilt) : path8.resolve(root, rebuilt);
|
|
5119
5236
|
return absStored === absRebuilt;
|
|
5120
5237
|
} catch {
|
|
5121
5238
|
return false;
|
|
@@ -5136,7 +5253,7 @@ function isChecklistPathAllowed(projectRoot, checklistPath) {
|
|
|
5136
5253
|
if (!root) return false;
|
|
5137
5254
|
try {
|
|
5138
5255
|
fs9.lstatSync(
|
|
5139
|
-
|
|
5256
|
+
path8.isAbsolute(checklistPath) ? checklistPath : path8.resolve(root, checklistPath)
|
|
5140
5257
|
);
|
|
5141
5258
|
return isRealpathInsideProject(root, checklistPath);
|
|
5142
5259
|
} catch {
|
|
@@ -5173,8 +5290,8 @@ function upsertTrack(store, slug, checklistPath, plansDir, projectRoot) {
|
|
|
5173
5290
|
const ts = nowIso2();
|
|
5174
5291
|
const root = normalizeProjectRoot(projectRoot);
|
|
5175
5292
|
if (!root) return;
|
|
5176
|
-
const planPath =
|
|
5177
|
-
const briefPath =
|
|
5293
|
+
const planPath = path8.join(root, plansDir, slug, "plan.md");
|
|
5294
|
+
const briefPath = path8.join(root, plansDir, slug, "brief.md");
|
|
5178
5295
|
store.db.prepare(
|
|
5179
5296
|
`INSERT INTO tracks (track_id, slug, checklist_path, plan_path, brief_path, updated_at)
|
|
5180
5297
|
VALUES (?, ?, ?, ?, ?, ?)
|
|
@@ -5197,7 +5314,7 @@ function candidatePayload(tracks) {
|
|
|
5197
5314
|
return JSON.stringify(
|
|
5198
5315
|
tracks.map((t) => ({
|
|
5199
5316
|
slug: t.slug,
|
|
5200
|
-
title: t.title,
|
|
5317
|
+
title: sanitizeSessionDisplayText(t.title || t.slug) || t.slug,
|
|
5201
5318
|
phase: t.phase,
|
|
5202
5319
|
progress: `${t.checklistDone}/${t.checklistTotal}`
|
|
5203
5320
|
}))
|
|
@@ -5211,7 +5328,7 @@ function resolveRunSlug(store, session, projectRoot, plansDir, requestedSlug) {
|
|
|
5211
5328
|
if (!isSafeTrackSlug(requestedSlug)) {
|
|
5212
5329
|
return {
|
|
5213
5330
|
kind: "none",
|
|
5214
|
-
userMessage: `Invalid track slug "${requestedSlug}".`
|
|
5331
|
+
userMessage: `Invalid track slug "${displayUntrusted(requestedSlug)}".`
|
|
5215
5332
|
};
|
|
5216
5333
|
}
|
|
5217
5334
|
const hit = runnable.find((t) => t.slug === requestedSlug);
|
|
@@ -5220,17 +5337,18 @@ function resolveRunSlug(store, session, projectRoot, plansDir, requestedSlug) {
|
|
|
5220
5337
|
if (!all.some((t) => t.slug === requestedSlug)) {
|
|
5221
5338
|
return {
|
|
5222
5339
|
kind: "none",
|
|
5223
|
-
userMessage: `Track "${requestedSlug}" not found or has no unchecked checklist items.`
|
|
5340
|
+
userMessage: `Track "${displayUntrusted(requestedSlug)}" not found or has no unchecked checklist items.`
|
|
5224
5341
|
};
|
|
5225
5342
|
}
|
|
5226
5343
|
return {
|
|
5227
5344
|
kind: "none",
|
|
5228
|
-
userMessage: `Track "${requestedSlug}" is not runnable (paused or no unchecked items).`
|
|
5345
|
+
userMessage: `Track "${displayUntrusted(requestedSlug)}" is not runnable (paused or no unchecked items).`
|
|
5229
5346
|
};
|
|
5230
5347
|
}
|
|
5231
5348
|
return { kind: "slug", slug: requestedSlug };
|
|
5232
5349
|
}
|
|
5233
|
-
|
|
5350
|
+
const midPick = session.pending_action === "run" || session.pending_action === "replan";
|
|
5351
|
+
if (!midPick && isBoundRunTrackId(session.track_id)) {
|
|
5234
5352
|
const bound = runnable.find((t) => t.slug === session.track_id);
|
|
5235
5353
|
if (bound) {
|
|
5236
5354
|
return { kind: "slug", slug: bound.slug };
|
|
@@ -5243,6 +5361,9 @@ function resolveRunSlug(store, session, projectRoot, plansDir, requestedSlug) {
|
|
|
5243
5361
|
};
|
|
5244
5362
|
}
|
|
5245
5363
|
if (runnable.length === 1) {
|
|
5364
|
+
if (session.pending_action === "replan") {
|
|
5365
|
+
return { kind: "pick", candidates: runnable };
|
|
5366
|
+
}
|
|
5246
5367
|
return { kind: "slug", slug: runnable[0].slug };
|
|
5247
5368
|
}
|
|
5248
5369
|
return { kind: "pick", candidates: runnable };
|
|
@@ -5281,18 +5402,25 @@ function applyRun(store, conversationId, projectRoot, opts) {
|
|
|
5281
5402
|
return { ok: false, userMessage: resolved.userMessage };
|
|
5282
5403
|
}
|
|
5283
5404
|
if (resolved.kind === "pick") {
|
|
5405
|
+
const clearingStaleBind = isBoundRunTrackId(session.track_id);
|
|
5406
|
+
const trackForPick = clearingStaleBind ? "_pending" : session.track_id;
|
|
5284
5407
|
store.upsertSession({
|
|
5285
5408
|
conversation_id: conversationId,
|
|
5286
5409
|
project_root: session.project_root,
|
|
5287
5410
|
code_root: session.code_root,
|
|
5411
|
+
track_id: trackForPick,
|
|
5412
|
+
// Drop checklist binding with the stale track so planning state stays consistent.
|
|
5413
|
+
...clearingStaleBind ? { checklist_path: "" } : {},
|
|
5288
5414
|
pending_action: "run",
|
|
5289
5415
|
track_candidates_json: candidatePayload(resolved.candidates),
|
|
5290
5416
|
armed: 0
|
|
5291
5417
|
// phase unchanged — do not write executing
|
|
5292
5418
|
});
|
|
5293
|
-
const lines = resolved.candidates.map(
|
|
5294
|
-
|
|
5295
|
-
|
|
5419
|
+
const lines = resolved.candidates.map((t, i) => {
|
|
5420
|
+
const title = sanitizeSessionDisplayText(t.title || t.slug) || t.slug;
|
|
5421
|
+
const left = t.checklistTotal - t.checklistDone;
|
|
5422
|
+
return ` ${i + 1}. ${t.slug} \u2014 ${title} (${left}/${t.checklistTotal} left)`;
|
|
5423
|
+
}).join("\n");
|
|
5296
5424
|
return {
|
|
5297
5425
|
ok: false,
|
|
5298
5426
|
needPick: true,
|
|
@@ -5308,7 +5436,7 @@ Reply with a number or /autopilot-run <slug>.`
|
|
|
5308
5436
|
if (!isSafeTrackSlug(slug)) {
|
|
5309
5437
|
return {
|
|
5310
5438
|
ok: false,
|
|
5311
|
-
userMessage: `Invalid track slug "${slug}".`
|
|
5439
|
+
userMessage: `Invalid track slug "${displayUntrusted(slug)}".`
|
|
5312
5440
|
};
|
|
5313
5441
|
}
|
|
5314
5442
|
const checklistPath = checklistPathFor(projectRoot, slug, plansDir);
|
|
@@ -5325,15 +5453,20 @@ Reply with a number or /autopilot-run <slug>.`
|
|
|
5325
5453
|
};
|
|
5326
5454
|
}
|
|
5327
5455
|
try {
|
|
5328
|
-
|
|
5456
|
+
const result = store.exclusiveWrite(() => {
|
|
5329
5457
|
if (concurrencyMode === "one_executor") {
|
|
5330
5458
|
const other = store.findExecutingSession(conversationId);
|
|
5331
5459
|
if (other) {
|
|
5460
|
+
const userMessage = formatOneExecutorBusyMessage(
|
|
5461
|
+
other.track_id,
|
|
5462
|
+
other.conversation_id
|
|
5463
|
+
);
|
|
5332
5464
|
return {
|
|
5333
5465
|
commit: false,
|
|
5334
5466
|
value: {
|
|
5335
5467
|
ok: false,
|
|
5336
|
-
|
|
5468
|
+
busy: true,
|
|
5469
|
+
userMessage
|
|
5337
5470
|
}
|
|
5338
5471
|
};
|
|
5339
5472
|
}
|
|
@@ -5379,12 +5512,19 @@ Reply with a number or /autopilot-run <slug>.`
|
|
|
5379
5512
|
}
|
|
5380
5513
|
return { commit: true, value: { ok: true, session: updated } };
|
|
5381
5514
|
});
|
|
5515
|
+
if (!result.ok && result.busy === true) {
|
|
5516
|
+
logBusyOccupier(result.userMessage);
|
|
5517
|
+
}
|
|
5518
|
+
return result;
|
|
5382
5519
|
} catch (err) {
|
|
5383
5520
|
const msg = err instanceof Error ? err.message : String(err);
|
|
5384
5521
|
if (/busy|locked|SQLITE_BUSY|SQLITE_LOCKED/i.test(msg)) {
|
|
5522
|
+
const userMessage = "State database is busy; retry Autopilot RUN in a moment.";
|
|
5523
|
+
logBusyOccupier(userMessage);
|
|
5385
5524
|
return {
|
|
5386
5525
|
ok: false,
|
|
5387
|
-
|
|
5526
|
+
busy: true,
|
|
5527
|
+
userMessage
|
|
5388
5528
|
};
|
|
5389
5529
|
}
|
|
5390
5530
|
throw err;
|
|
@@ -5406,29 +5546,40 @@ function applyReplan(store, conversationId, projectRoot, opts) {
|
|
|
5406
5546
|
projectRoot,
|
|
5407
5547
|
opts?.platform
|
|
5408
5548
|
);
|
|
5409
|
-
let slug = opts?.slug
|
|
5410
|
-
if (
|
|
5549
|
+
let slug = opts?.slug;
|
|
5550
|
+
if (!slug) {
|
|
5551
|
+
const tid = session.track_id;
|
|
5552
|
+
const midPick = session.pending_action === "run" || session.pending_action === "replan";
|
|
5553
|
+
slug = !midPick && isBoundRunTrackId(tid) ? tid : void 0;
|
|
5554
|
+
}
|
|
5555
|
+
if (slug && !isSafeTrackSlug(slug)) {
|
|
5411
5556
|
return {
|
|
5412
5557
|
ok: false,
|
|
5413
|
-
userMessage: `Invalid track slug "${slug}".`
|
|
5558
|
+
userMessage: `Invalid track slug "${displayUntrusted(slug)}".`
|
|
5414
5559
|
};
|
|
5415
5560
|
}
|
|
5416
|
-
if (!slug
|
|
5561
|
+
if (!slug) {
|
|
5417
5562
|
const all = listTracks(projectRoot, store, "all", plansDir).filter(
|
|
5418
5563
|
(t) => isSafeTrackSlug(t.slug)
|
|
5419
5564
|
);
|
|
5420
|
-
if (all.length === 1) {
|
|
5565
|
+
if (all.length === 1 && session.pending_action !== "run") {
|
|
5421
5566
|
slug = all[0].slug;
|
|
5422
|
-
} else if (all.length > 1) {
|
|
5567
|
+
} else if (all.length > 1 || all.length === 1 && session.pending_action === "run") {
|
|
5568
|
+
const clearingStaleBind = isBoundRunTrackId(session.track_id);
|
|
5423
5569
|
store.upsertSession({
|
|
5424
5570
|
conversation_id: conversationId,
|
|
5425
5571
|
project_root: session.project_root,
|
|
5426
5572
|
code_root: session.code_root,
|
|
5573
|
+
track_id: clearingStaleBind ? "_pending" : session.track_id,
|
|
5574
|
+
...clearingStaleBind ? { checklist_path: "" } : {},
|
|
5427
5575
|
pending_action: "replan",
|
|
5428
5576
|
track_candidates_json: candidatePayload(all),
|
|
5429
5577
|
armed: 0
|
|
5430
5578
|
});
|
|
5431
|
-
const lines = all.map((t, i) =>
|
|
5579
|
+
const lines = all.map((t, i) => {
|
|
5580
|
+
const title = sanitizeSessionDisplayText(t.title || t.slug) || t.slug;
|
|
5581
|
+
return ` ${i + 1}. ${t.slug} \u2014 ${title}`;
|
|
5582
|
+
}).join("\n");
|
|
5432
5583
|
return {
|
|
5433
5584
|
ok: false,
|
|
5434
5585
|
needPick: true,
|
|
@@ -5496,7 +5647,7 @@ function applyTrackPick(store, conversationId, projectRoot, pick, opts) {
|
|
|
5496
5647
|
if (pending !== "run" && pending !== "replan") {
|
|
5497
5648
|
return {
|
|
5498
5649
|
ok: false,
|
|
5499
|
-
userMessage: `Unknown pending action "${pending}".`
|
|
5650
|
+
userMessage: `Unknown pending action "${displayUntrusted(pending)}".`
|
|
5500
5651
|
};
|
|
5501
5652
|
}
|
|
5502
5653
|
if (!session.track_candidates_json) {
|
|
@@ -5526,14 +5677,14 @@ function applyTrackPick(store, conversationId, projectRoot, pick, opts) {
|
|
|
5526
5677
|
if (!slug || !isSafeTrackSlug(slug)) {
|
|
5527
5678
|
return {
|
|
5528
5679
|
ok: false,
|
|
5529
|
-
userMessage: `Invalid selection "${pick}". Choose 1\u2013${candidates.length}.`
|
|
5680
|
+
userMessage: `Invalid selection "${displayUntrusted(pick)}". Choose 1\u2013${candidates.length}.`
|
|
5530
5681
|
};
|
|
5531
5682
|
}
|
|
5532
5683
|
} else {
|
|
5533
5684
|
if (!isSafeTrackSlug(pick)) {
|
|
5534
5685
|
return {
|
|
5535
5686
|
ok: false,
|
|
5536
|
-
userMessage: `Invalid track slug "${pick}".`
|
|
5687
|
+
userMessage: `Invalid track slug "${displayUntrusted(pick)}".`
|
|
5537
5688
|
};
|
|
5538
5689
|
}
|
|
5539
5690
|
slug = pick;
|
|
@@ -5542,7 +5693,7 @@ function applyTrackPick(store, conversationId, projectRoot, pick, opts) {
|
|
|
5542
5693
|
)) {
|
|
5543
5694
|
return {
|
|
5544
5695
|
ok: false,
|
|
5545
|
-
userMessage: `Unknown slug "${pick}".`
|
|
5696
|
+
userMessage: `Unknown slug "${displayUntrusted(pick)}".`
|
|
5546
5697
|
};
|
|
5547
5698
|
}
|
|
5548
5699
|
}
|
|
@@ -5565,7 +5716,7 @@ import { spawnSync } from "node:child_process";
|
|
|
5565
5716
|
|
|
5566
5717
|
// ../core/src/autopilot-ignore.ts
|
|
5567
5718
|
import fs10 from "node:fs";
|
|
5568
|
-
import
|
|
5719
|
+
import path9 from "node:path";
|
|
5569
5720
|
var DEFAULT_AUTOPILOT_IGNORE_TEXT = `# Autopilot \u2014 paths that do NOT trigger self-review (gitignore syntax).
|
|
5570
5721
|
#
|
|
5571
5722
|
# What this file is:
|
|
@@ -5764,17 +5915,17 @@ function realpathForCompare(absPath) {
|
|
|
5764
5915
|
return fs10.realpathSync(absPath);
|
|
5765
5916
|
} catch {
|
|
5766
5917
|
}
|
|
5767
|
-
let dir =
|
|
5768
|
-
const base =
|
|
5918
|
+
let dir = path9.dirname(absPath);
|
|
5919
|
+
const base = path9.basename(absPath);
|
|
5769
5920
|
const missing = [];
|
|
5770
5921
|
for (; ; ) {
|
|
5771
5922
|
try {
|
|
5772
5923
|
const realDir = fs10.realpathSync(dir);
|
|
5773
|
-
return
|
|
5924
|
+
return path9.join(realDir, ...missing, base);
|
|
5774
5925
|
} catch {
|
|
5775
|
-
const parent =
|
|
5926
|
+
const parent = path9.dirname(dir);
|
|
5776
5927
|
if (parent === dir) return absPath;
|
|
5777
|
-
missing.unshift(
|
|
5928
|
+
missing.unshift(path9.basename(dir));
|
|
5778
5929
|
dir = parent;
|
|
5779
5930
|
}
|
|
5780
5931
|
}
|
|
@@ -5784,19 +5935,19 @@ function toProjectRelativePath(filePath, projectRoot) {
|
|
|
5784
5935
|
if (!projectRoot?.trim()) {
|
|
5785
5936
|
return normalizeRelativePath(posix);
|
|
5786
5937
|
}
|
|
5787
|
-
const root = realpathForCompare(
|
|
5938
|
+
const root = realpathForCompare(path9.resolve(projectRoot));
|
|
5788
5939
|
const abs = realpathForCompare(
|
|
5789
|
-
|
|
5940
|
+
path9.isAbsolute(posix) ? path9.resolve(posix) : path9.resolve(root, posix)
|
|
5790
5941
|
);
|
|
5791
|
-
const rel =
|
|
5792
|
-
if (rel.startsWith("..") ||
|
|
5942
|
+
const rel = path9.relative(root, abs);
|
|
5943
|
+
if (rel.startsWith("..") || path9.isAbsolute(rel)) {
|
|
5793
5944
|
return "";
|
|
5794
5945
|
}
|
|
5795
5946
|
return normalizeRelativePath(rel.replace(/\\/g, "/"));
|
|
5796
5947
|
}
|
|
5797
5948
|
function loadAutopilotIgnorePatterns(projectRoot) {
|
|
5798
|
-
const root =
|
|
5799
|
-
const filePath =
|
|
5949
|
+
const root = path9.resolve(projectRoot);
|
|
5950
|
+
const filePath = path9.join(root, ".autopilotignore");
|
|
5800
5951
|
let st;
|
|
5801
5952
|
try {
|
|
5802
5953
|
st = fs10.lstatSync(filePath);
|
|
@@ -5858,6 +6009,20 @@ function isProductCodeEdit(filePath, opts) {
|
|
|
5858
6009
|
}
|
|
5859
6010
|
|
|
5860
6011
|
// ../ports/cursor/src/index.ts
|
|
6012
|
+
function normalizeBlockSubmitMessage(message) {
|
|
6013
|
+
return typeof message === "string" && message.trim().length > 0 ? message : "Request blocked.";
|
|
6014
|
+
}
|
|
6015
|
+
function blockSubmit(message) {
|
|
6016
|
+
const text = normalizeBlockSubmitMessage(message);
|
|
6017
|
+
return {
|
|
6018
|
+
continue: false,
|
|
6019
|
+
user_message: text,
|
|
6020
|
+
userMessage: text
|
|
6021
|
+
};
|
|
6022
|
+
}
|
|
6023
|
+
function allowSubmit() {
|
|
6024
|
+
return { continue: true };
|
|
6025
|
+
}
|
|
5861
6026
|
function cid(p) {
|
|
5862
6027
|
return (p.conversation_id ?? p.conversationId ?? "").trim();
|
|
5863
6028
|
}
|
|
@@ -5912,7 +6077,7 @@ function normalizeCursorStopStatus(payload) {
|
|
|
5912
6077
|
}
|
|
5913
6078
|
function handleBeforeSubmitPrompt(store, payload, projectRoot, portConfig) {
|
|
5914
6079
|
const conversationId = cid(payload);
|
|
5915
|
-
if (!conversationId) return
|
|
6080
|
+
if (!conversationId) return allowSubmit();
|
|
5916
6081
|
const prompt = payload.prompt ?? payload.content ?? "";
|
|
5917
6082
|
try {
|
|
5918
6083
|
store.clearPendingFollowupIf(
|
|
@@ -5932,7 +6097,7 @@ function handleBeforeSubmitPrompt(store, payload, projectRoot, portConfig) {
|
|
|
5932
6097
|
if (trigger) {
|
|
5933
6098
|
if (trigger.kind === "off") {
|
|
5934
6099
|
applyOff(store, conversationId);
|
|
5935
|
-
return
|
|
6100
|
+
return allowSubmit();
|
|
5936
6101
|
}
|
|
5937
6102
|
if (trigger.kind === "on") {
|
|
5938
6103
|
const result = applyOn(store, conversationId, projectRoot, {
|
|
@@ -5940,22 +6105,22 @@ function handleBeforeSubmitPrompt(store, payload, projectRoot, portConfig) {
|
|
|
5940
6105
|
slug: trigger.slug
|
|
5941
6106
|
});
|
|
5942
6107
|
if (!result.ok) {
|
|
5943
|
-
return
|
|
6108
|
+
return blockSubmit(result.userMessage);
|
|
5944
6109
|
}
|
|
5945
|
-
return
|
|
6110
|
+
return allowSubmit();
|
|
5946
6111
|
}
|
|
5947
6112
|
if (trigger.kind === "resume") {
|
|
5948
6113
|
const result = applyResume(store, conversationId, {
|
|
5949
6114
|
slug: trigger.slug
|
|
5950
6115
|
});
|
|
5951
6116
|
if (!result.ok) {
|
|
5952
|
-
return
|
|
6117
|
+
return blockSubmit(result.userMessage);
|
|
5953
6118
|
}
|
|
5954
|
-
return
|
|
6119
|
+
return allowSubmit();
|
|
5955
6120
|
}
|
|
5956
6121
|
if (trigger.kind === "resume_review") {
|
|
5957
6122
|
applyResumeReview(store, conversationId);
|
|
5958
|
-
return
|
|
6123
|
+
return allowSubmit();
|
|
5959
6124
|
}
|
|
5960
6125
|
if (trigger.kind === "run") {
|
|
5961
6126
|
const result = applyRun(store, conversationId, projectRoot, {
|
|
@@ -5963,9 +6128,12 @@ function handleBeforeSubmitPrompt(store, payload, projectRoot, portConfig) {
|
|
|
5963
6128
|
config: actionConfig
|
|
5964
6129
|
});
|
|
5965
6130
|
if (!result.ok) {
|
|
5966
|
-
|
|
6131
|
+
if (isChannelANeedPick(result)) {
|
|
6132
|
+
return allowSubmit();
|
|
6133
|
+
}
|
|
6134
|
+
return blockSubmit(result.userMessage);
|
|
5967
6135
|
}
|
|
5968
|
-
return
|
|
6136
|
+
return allowSubmit();
|
|
5969
6137
|
}
|
|
5970
6138
|
if (trigger.kind === "replan") {
|
|
5971
6139
|
const result = applyReplan(store, conversationId, projectRoot, {
|
|
@@ -5973,9 +6141,9 @@ function handleBeforeSubmitPrompt(store, payload, projectRoot, portConfig) {
|
|
|
5973
6141
|
config: actionConfig
|
|
5974
6142
|
});
|
|
5975
6143
|
if (!result.ok) {
|
|
5976
|
-
return
|
|
6144
|
+
return blockSubmit(result.userMessage);
|
|
5977
6145
|
}
|
|
5978
|
-
return
|
|
6146
|
+
return allowSubmit();
|
|
5979
6147
|
}
|
|
5980
6148
|
if (trigger.kind === "track_pick" && trigger.trackPick) {
|
|
5981
6149
|
const result = applyTrackPick(
|
|
@@ -5986,21 +6154,28 @@ function handleBeforeSubmitPrompt(store, payload, projectRoot, portConfig) {
|
|
|
5986
6154
|
{ config: actionConfig }
|
|
5987
6155
|
);
|
|
5988
6156
|
if (!result.ok) {
|
|
5989
|
-
|
|
6157
|
+
if (isChannelANeedPick(result)) {
|
|
6158
|
+
return allowSubmit();
|
|
6159
|
+
}
|
|
6160
|
+
return blockSubmit(result.userMessage);
|
|
5990
6161
|
}
|
|
5991
|
-
return
|
|
6162
|
+
return allowSubmit();
|
|
5992
6163
|
}
|
|
5993
|
-
return
|
|
6164
|
+
return allowSubmit();
|
|
5994
6165
|
}
|
|
5995
6166
|
if (!isHarnessFollowupMessage(prompt)) {
|
|
5996
6167
|
store.clearChainPending(conversationId);
|
|
5997
6168
|
}
|
|
5998
|
-
return
|
|
6169
|
+
return allowSubmit();
|
|
5999
6170
|
}
|
|
6000
6171
|
function handleAfterFileEdit(store, payload, projectRoot) {
|
|
6001
6172
|
const conversationId = cid(payload);
|
|
6002
6173
|
const filePath = payload.file_path ?? payload.filePath ?? "";
|
|
6003
6174
|
if (!conversationId || !filePath) return;
|
|
6175
|
+
try {
|
|
6176
|
+
notePlansDirEdit(store, conversationId, projectRoot, filePath);
|
|
6177
|
+
} catch {
|
|
6178
|
+
}
|
|
6004
6179
|
if (!isProductCodeEdit(filePath, { projectRoot })) return;
|
|
6005
6180
|
const cfg = loadProjectReviewConfig(projectRoot);
|
|
6006
6181
|
if (cfg.reviewScope === "project") {
|
|
@@ -6135,6 +6310,25 @@ function blockReason(message, fallback) {
|
|
|
6135
6310
|
const m = typeof message === "string" ? message.trim() : "";
|
|
6136
6311
|
return m || fallback;
|
|
6137
6312
|
}
|
|
6313
|
+
function allowNeedPickContext(userMessage, candidates) {
|
|
6314
|
+
const fromMessage = typeof userMessage === "string" && userMessage.trim().length > 0 ? userMessage : "";
|
|
6315
|
+
const slugs = [
|
|
6316
|
+
...new Set(
|
|
6317
|
+
(candidates ?? []).map((c) => c && typeof c.slug === "string" ? c.slug.trim() : "").filter((s) => s.length > 0 && isSafeTrackSlug(s))
|
|
6318
|
+
)
|
|
6319
|
+
];
|
|
6320
|
+
const ctx = fromMessage || (slugs.length > 0 ? `Select a plan to execute:
|
|
6321
|
+
|
|
6322
|
+
${slugs.map((s, i) => ` ${i + 1}. ${s}`).join("\n")}
|
|
6323
|
+
|
|
6324
|
+
Reply with a number or /autopilot-run <slug>.` : "Select a plan to execute. Reply with a number or /autopilot-run <slug>.");
|
|
6325
|
+
return {
|
|
6326
|
+
hookSpecificOutput: {
|
|
6327
|
+
hookEventName: "UserPromptSubmit",
|
|
6328
|
+
additionalContext: ctx
|
|
6329
|
+
}
|
|
6330
|
+
};
|
|
6331
|
+
}
|
|
6138
6332
|
function filePathFromClaudeEdit(payload) {
|
|
6139
6333
|
const input = payload.tool_input ?? payload.toolInput ?? {};
|
|
6140
6334
|
if (!input || typeof input !== "object" || Array.isArray(input)) return "";
|
|
@@ -6232,6 +6426,9 @@ function handleUserPromptSubmit(store, payload, projectRoot, portConfig) {
|
|
|
6232
6426
|
});
|
|
6233
6427
|
if (!result.ok) {
|
|
6234
6428
|
stampClaudePlatform(store, conversationId, projectRoot);
|
|
6429
|
+
if (isChannelANeedPick(result)) {
|
|
6430
|
+
return allowNeedPickContext(result.userMessage, result.candidates);
|
|
6431
|
+
}
|
|
6235
6432
|
return {
|
|
6236
6433
|
decision: "block",
|
|
6237
6434
|
reason: blockReason(result.userMessage, gateFallback)
|
|
@@ -6264,6 +6461,9 @@ function handleUserPromptSubmit(store, payload, projectRoot, portConfig) {
|
|
|
6264
6461
|
);
|
|
6265
6462
|
if (!result.ok) {
|
|
6266
6463
|
stampClaudePlatform(store, conversationId, projectRoot);
|
|
6464
|
+
if (isChannelANeedPick(result)) {
|
|
6465
|
+
return allowNeedPickContext(result.userMessage, result.candidates);
|
|
6466
|
+
}
|
|
6267
6467
|
return {
|
|
6268
6468
|
decision: "block",
|
|
6269
6469
|
reason: blockReason(result.userMessage, gateFallback)
|
|
@@ -6285,6 +6485,10 @@ function handlePostToolUse(store, payload, projectRoot) {
|
|
|
6285
6485
|
if (!conversationId || !isClaudeEditTool(toolName)) return;
|
|
6286
6486
|
const filePath = filePathFromClaudeEdit(payload);
|
|
6287
6487
|
if (!filePath) return;
|
|
6488
|
+
try {
|
|
6489
|
+
notePlansDirEdit(store, conversationId, projectRoot, filePath);
|
|
6490
|
+
} catch {
|
|
6491
|
+
}
|
|
6288
6492
|
if (!isProductCodeEdit(filePath, { projectRoot })) return;
|
|
6289
6493
|
const cfg = loadProjectReviewConfig(projectRoot);
|
|
6290
6494
|
if (cfg.reviewScope === "project") {
|