@prestyj/cli 5.20.0 → 5.22.0
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/README.md +1 -1
- package/dist/app-sidecar.js +153 -8
- package/dist/app-sidecar.js.map +1 -1
- package/dist/cli.js +5 -10
- package/dist/cli.js.map +1 -1
- package/dist/core/agent-session.d.ts +6 -0
- package/dist/core/agent-session.js +11 -3
- package/dist/core/agent-session.js.map +1 -1
- package/dist/core/autopilot-cycle.d.ts +3 -4
- package/dist/core/autopilot-cycle.js +18 -11
- package/dist/core/autopilot-cycle.js.map +1 -1
- package/dist/core/autopilot-gate.d.ts +1 -3
- package/dist/core/autopilot-gate.js +4 -16
- package/dist/core/autopilot-gate.js.map +1 -1
- package/dist/core/event-bus.d.ts +3 -0
- package/dist/core/event-bus.js +5 -0
- package/dist/core/event-bus.js.map +1 -1
- package/dist/core/ideal-review.js +7 -1
- package/dist/core/ideal-review.js.map +1 -1
- package/dist/core/nolan-context.d.ts +1 -2
- package/dist/core/nolan-context.js +81 -21
- package/dist/core/nolan-context.js.map +1 -1
- package/dist/core/nolan-prompt.js +52 -48
- package/dist/core/nolan-prompt.js.map +1 -1
- package/dist/core/prompt-command-expansion.d.ts +13 -0
- package/dist/core/prompt-command-expansion.js +48 -0
- package/dist/core/prompt-command-expansion.js.map +1 -0
- package/dist/core/run-status.d.ts +17 -0
- package/dist/core/run-status.js +28 -0
- package/dist/core/run-status.js.map +1 -0
- package/dist/core/session-history.js +5 -14
- package/dist/core/session-history.js.map +1 -1
- package/dist/core/verification-gate.d.ts +6 -1
- package/dist/core/verification-gate.js +26 -4
- package/dist/core/verification-gate.js.map +1 -1
- package/dist/core/worktree.d.ts +161 -0
- package/dist/core/worktree.js +574 -0
- package/dist/core/worktree.js.map +1 -0
- package/dist/tools/subagent.d.ts +1 -0
- package/dist/tools/subagent.js +66 -2
- package/dist/tools/subagent.js.map +1 -1
- package/dist/ui/prompt-routing.js +2 -1
- package/dist/ui/prompt-routing.js.map +1 -1
- package/package.json +5 -5
package/README.md
CHANGED
|
@@ -177,7 +177,7 @@ Plus the [Grep MCP](https://grep.dev) for searching across 1M+ public GitHub rep
|
|
|
177
177
|
|
|
178
178
|
Only GPT-5.6 Sol/Terra at **Ultra** delegates proactively. Lower Sol/Terra levels use async agents only when the user or project/skill instructions request delegation; other models receive no proactive policy.
|
|
179
179
|
|
|
180
|
-
Children share the parent working directory
|
|
180
|
+
Children share the parent working directory by default, so parallel writes must target disjoint files or subsystems. Pass `isolate: true` to `subagent` to give a child its own copy of the repo on a new branch instead — the right choice when several children edit at once, or when a child's changes should land on a branch of their own. The copy is reclaimed automatically when the child finishes without committing; a child that did commit keeps its branch, because that branch is the deliverable. `/integrate` then merges those branches one at a time, running the suite between each. Async fan-out is one level deep, child output is bounded, idle workers reap after 10 minutes, and workers are not resumable after a CLI/app restart.
|
|
181
181
|
|
|
182
182
|
Parent cancellation interrupts active children. Session disposal shuts down every worker process alongside background commands, LSP servers, and MCP connections.
|
|
183
183
|
|
package/dist/app-sidecar.js
CHANGED
|
@@ -28,12 +28,14 @@ import { RunLifecycle } from "./core/run-lifecycle.js";
|
|
|
28
28
|
import { RunClaim } from "./core/run-claim.js";
|
|
29
29
|
import { CHAT_AGENT_IDS, chatAgentSessionsDir, createChatAgent, parseChatAgentId, switchChatAgent, } from "./chat-agents/index.js";
|
|
30
30
|
import { buildJiwaTools, JiwaStore } from "./chat-agents/jiwa.js";
|
|
31
|
+
import { createWorktree, removeWorktree, sweepWorktrees, worktreeStatuses, } from "./core/worktree.js";
|
|
31
32
|
import { buildMemoryTools, MemoryStore } from "./chat-agents/memory.js";
|
|
32
33
|
import { buildNolanSystemPrompt, buildNolanAutopilotSystemPrompt } from "./core/nolan-prompt.js";
|
|
33
34
|
import { buildNolanDigest, buildNolanAutopilotContext, buildNolanAutopilotPlanContext, } from "./core/nolan-context.js";
|
|
34
35
|
import { parseAutopilotVerdict } from "./core/autopilot-verdict.js";
|
|
35
36
|
import { isWorkflowCommandText, countAssistantMessages, shouldStartAutopilotCycle, extractTurnToolCalls, isMechanicalOnlyTurn, } from "./core/autopilot-gate.js";
|
|
36
37
|
import { driveAutopilotCycle, frameAutopilotInjection } from "./core/autopilot-cycle.js";
|
|
38
|
+
import { describeRunVerification, describeTurnVerification } from "./core/run-status.js";
|
|
37
39
|
import { validateNolanModelPref, effectiveNolanModel, } from "./core/nolan-model.js";
|
|
38
40
|
import { normalizeAutopilotMarkersForHistory, normalizeAppMarkersForHistory, normalizeNolanTurnsForHistory, getHistoryMessageVisibility, replayMessagesInOrder, restoreUserRow, restoreAssistantTexts, resolveRestoredCommand, autopilotMarkerCopySeed, } from "./core/session-history.js";
|
|
39
41
|
import { sessionToMarkdown, defaultExportFilename, } from "./core/session-export.js";
|
|
@@ -1829,6 +1831,11 @@ async function createSession(deps, opts) {
|
|
|
1829
1831
|
recordApprovedPlanMarkers(d.text);
|
|
1830
1832
|
});
|
|
1831
1833
|
session.eventBus.on("thinking_delta", (d) => broadcast("thinking_delta", d));
|
|
1834
|
+
session.eventBus.on("retry", (d) => {
|
|
1835
|
+
if (!d.silent)
|
|
1836
|
+
broadcast("retry", { reason: d.reason, attempt: d.attempt, delayMs: d.delayMs });
|
|
1837
|
+
});
|
|
1838
|
+
session.eventBus.on("max_turns", (d) => broadcast("max_turns", d));
|
|
1832
1839
|
// The agent consumed queued steering at a turn boundary. Re-broadcast as the
|
|
1833
1840
|
// usual `queued` depth update so the webview drops the pending affordance the
|
|
1834
1841
|
// moment the message lands in the loop, not at run_end.
|
|
@@ -2138,8 +2145,10 @@ async function createSession(deps, opts) {
|
|
|
2138
2145
|
// his own Ideal self-review adds latency and can corrupt the verdict shape.
|
|
2139
2146
|
nolanAgent.setIdealReviewSuppressed(true);
|
|
2140
2147
|
await nolanAgent.initialize();
|
|
2141
|
-
//
|
|
2142
|
-
|
|
2148
|
+
// Keep review text/tools silent; report usage only for whole-task accounting.
|
|
2149
|
+
nolanAgent.eventBus.on("turn_end", (d) => {
|
|
2150
|
+
broadcast("autopilot_usage", { outputTokens: d.usage.outputTokens });
|
|
2151
|
+
});
|
|
2143
2152
|
nolanAutoSession = nolanAgent;
|
|
2144
2153
|
log("INFO", "app-sidecar", "nolan autopilot session ready", {
|
|
2145
2154
|
provider: target.provider,
|
|
@@ -2181,7 +2190,7 @@ async function createSession(deps, opts) {
|
|
|
2181
2190
|
}
|
|
2182
2191
|
// Core provider-run bracket. Standalone runs own a lifecycle generation;
|
|
2183
2192
|
// injected autopilot runs share the cycle's outer generation.
|
|
2184
|
-
async function runAgent(label, run) {
|
|
2193
|
+
async function runAgent(label, run, reviewPending = () => false) {
|
|
2185
2194
|
const ownsGeneration = !runLifecycle.running;
|
|
2186
2195
|
const generation = ownsGeneration
|
|
2187
2196
|
? runLifecycle.begin(abortOwnedWork).generation
|
|
@@ -2194,7 +2203,11 @@ async function createSession(deps, opts) {
|
|
|
2194
2203
|
const cancelGenAtStart = cancelGeneration;
|
|
2195
2204
|
const assistantsBeforeRun = countAssistantMessages(session.getMessages());
|
|
2196
2205
|
let runSucceeded = false;
|
|
2197
|
-
broadcast("run_start", {
|
|
2206
|
+
broadcast("run_start", {
|
|
2207
|
+
text: label,
|
|
2208
|
+
runState: runLifecycle.state,
|
|
2209
|
+
continued: !ownsGeneration,
|
|
2210
|
+
});
|
|
2198
2211
|
try {
|
|
2199
2212
|
if (!runLifecycle.isCancellationRequested(generation))
|
|
2200
2213
|
await run();
|
|
@@ -2265,6 +2278,16 @@ async function createSession(deps, opts) {
|
|
|
2265
2278
|
broadcast("run_end", {
|
|
2266
2279
|
...(cancelled ? { cancelled: true } : {}),
|
|
2267
2280
|
...(verificationProblem ? { unverified: true } : {}),
|
|
2281
|
+
failed: !cancelled && !runSucceeded,
|
|
2282
|
+
// The cycle refuses an unresolved verification gate. Do not advertise
|
|
2283
|
+
// a review handoff that will exit before emitting any review events.
|
|
2284
|
+
reviewPending: !cancelled &&
|
|
2285
|
+
runSucceeded &&
|
|
2286
|
+
!verificationProblem &&
|
|
2287
|
+
(reviewPending() || !ownsGeneration),
|
|
2288
|
+
...describeRunVerification(session.getVerificationEvidence(), verificationProblem),
|
|
2289
|
+
turnVerification: describeTurnVerification(session.getRunVerificationActivity(), verificationProblem),
|
|
2290
|
+
...(verificationProblem ? { verificationReason: verificationProblem } : {}),
|
|
2268
2291
|
runState: runLifecycle.state,
|
|
2269
2292
|
});
|
|
2270
2293
|
}
|
|
@@ -2553,7 +2576,7 @@ async function createSession(deps, opts) {
|
|
|
2553
2576
|
else {
|
|
2554
2577
|
await session.prompt(next.text);
|
|
2555
2578
|
}
|
|
2556
|
-
});
|
|
2579
|
+
}, () => autopilot);
|
|
2557
2580
|
const decision = shouldStartAutopilotCycle({
|
|
2558
2581
|
enabled: autopilot,
|
|
2559
2582
|
cancelled: autopilotCancelled,
|
|
@@ -2575,7 +2598,8 @@ async function createSession(deps, opts) {
|
|
|
2575
2598
|
});
|
|
2576
2599
|
await runAutopilotCycle(next.text);
|
|
2577
2600
|
}
|
|
2578
|
-
else
|
|
2601
|
+
else {
|
|
2602
|
+
broadcast("autopilot_ignored", { reason: decision.reason });
|
|
2579
2603
|
log("INFO", "app-sidecar", "autopilot skipped (queued turn)", {
|
|
2580
2604
|
reason: decision.reason,
|
|
2581
2605
|
});
|
|
@@ -2879,6 +2903,7 @@ async function createSession(deps, opts) {
|
|
|
2879
2903
|
mode,
|
|
2880
2904
|
chatAgent,
|
|
2881
2905
|
running,
|
|
2906
|
+
reviewPending: autopilotActive,
|
|
2882
2907
|
runState: runLifecycle.state,
|
|
2883
2908
|
thinkingLevel: session.getThinkingLevel() ?? null,
|
|
2884
2909
|
supportedThinkingLevels: getSupportedThinkingLevels(st.provider, st.model),
|
|
@@ -3016,6 +3041,125 @@ async function createSession(deps, opts) {
|
|
|
3016
3041
|
});
|
|
3017
3042
|
return;
|
|
3018
3043
|
}
|
|
3044
|
+
// Create an isolated git worktree for a project so a second window can work
|
|
3045
|
+
// on the same repo without fighting the first over the working tree. The
|
|
3046
|
+
// caller passes the project cwd; the module resolves the repo's MAIN root
|
|
3047
|
+
// itself, so opening this from an already-linked worktree still branches
|
|
3048
|
+
// off the real repo rather than nesting.
|
|
3049
|
+
//
|
|
3050
|
+
// The dirty gate in `createWorktree` is deliberate: branching a main
|
|
3051
|
+
// checkout that has uncommitted work would strand those edits on a tree the
|
|
3052
|
+
// user is about to stop looking at. It surfaces here as a 409 the picker
|
|
3053
|
+
// renders verbatim.
|
|
3054
|
+
if (method === "POST" && url === "/worktree") {
|
|
3055
|
+
void readBody(req, res).then(async (raw) => {
|
|
3056
|
+
if (raw === null)
|
|
3057
|
+
return;
|
|
3058
|
+
let body;
|
|
3059
|
+
try {
|
|
3060
|
+
body = JSON.parse(raw);
|
|
3061
|
+
}
|
|
3062
|
+
catch {
|
|
3063
|
+
json(res, 400, { error: "invalid JSON body" });
|
|
3064
|
+
return;
|
|
3065
|
+
}
|
|
3066
|
+
const repoDir = body.cwd?.trim();
|
|
3067
|
+
if (!repoDir) {
|
|
3068
|
+
json(res, 400, { error: "cwd is required" });
|
|
3069
|
+
return;
|
|
3070
|
+
}
|
|
3071
|
+
try {
|
|
3072
|
+
const created = await createWorktree({
|
|
3073
|
+
repoDir,
|
|
3074
|
+
branch: body.branch?.trim() || undefined,
|
|
3075
|
+
baseRef: body.baseRef?.trim() || undefined,
|
|
3076
|
+
});
|
|
3077
|
+
json(res, 200, created);
|
|
3078
|
+
}
|
|
3079
|
+
catch (err) {
|
|
3080
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
3081
|
+
// Dirty tree / not-a-repo are the user's to resolve, not bugs.
|
|
3082
|
+
const expected = message.includes("uncommitted changes") || message.includes("Not a git repository");
|
|
3083
|
+
json(res, expected ? 409 : 500, { error: message });
|
|
3084
|
+
}
|
|
3085
|
+
});
|
|
3086
|
+
return;
|
|
3087
|
+
}
|
|
3088
|
+
// Every managed copy of a repo with the state the picker needs to decide
|
|
3089
|
+
// what to offer: branch, unsaved files, unmerged commits, and why a copy
|
|
3090
|
+
// cannot be reclaimed. `busy` paths come from the caller (the app knows its
|
|
3091
|
+
// own windows; the daemon does not), repeated as `busy=` params.
|
|
3092
|
+
if (method === "GET" && (url === "/worktrees" || url.startsWith("/worktrees?"))) {
|
|
3093
|
+
const params = new URL(url, `http://${host}`).searchParams;
|
|
3094
|
+
const target = params.get("cwd")?.trim() || cwd;
|
|
3095
|
+
void worktreeStatuses(target, params.getAll("busy"))
|
|
3096
|
+
.then((worktrees) => json(res, 200, { worktrees }))
|
|
3097
|
+
.catch((err) => {
|
|
3098
|
+
// Not-a-repo is the common case here, not a fault: answer "none".
|
|
3099
|
+
log("INFO", "app-sidecar", "worktreeStatuses failed", {
|
|
3100
|
+
message: err instanceof Error ? err.message : String(err),
|
|
3101
|
+
});
|
|
3102
|
+
json(res, 200, { worktrees: [] });
|
|
3103
|
+
});
|
|
3104
|
+
return;
|
|
3105
|
+
}
|
|
3106
|
+
// Remove one copy. `force` is the user having seen what would be lost and
|
|
3107
|
+
// said yes anyway, so it is never defaulted on.
|
|
3108
|
+
//
|
|
3109
|
+
// `path` arrives from the client, and a request naming a path is not
|
|
3110
|
+
// permission to delete it: `removeWorktree` re-derives the managed root
|
|
3111
|
+
// from the repo itself and refuses anything outside it, or reached through
|
|
3112
|
+
// a symlink. The check lives there so no caller can skip it.
|
|
3113
|
+
if (method === "DELETE" && url === "/worktree") {
|
|
3114
|
+
void readBody(req, res).then(async (raw) => {
|
|
3115
|
+
if (raw === null)
|
|
3116
|
+
return;
|
|
3117
|
+
let body;
|
|
3118
|
+
try {
|
|
3119
|
+
body = JSON.parse(raw);
|
|
3120
|
+
}
|
|
3121
|
+
catch {
|
|
3122
|
+
json(res, 400, { error: "invalid JSON body" });
|
|
3123
|
+
return;
|
|
3124
|
+
}
|
|
3125
|
+
const worktreePath = body.path?.trim();
|
|
3126
|
+
if (!worktreePath) {
|
|
3127
|
+
json(res, 400, { error: "path is required" });
|
|
3128
|
+
return;
|
|
3129
|
+
}
|
|
3130
|
+
const release = await removeWorktree({
|
|
3131
|
+
repoDir: body.cwd?.trim() || cwd,
|
|
3132
|
+
worktreePath,
|
|
3133
|
+
force: body.force === true,
|
|
3134
|
+
});
|
|
3135
|
+
// removeWorktree never throws; a refusal is a 409 the UI shows as
|
|
3136
|
+
// written, matching the dirty-checkout gate on POST above.
|
|
3137
|
+
json(res, release.freed ? 200 : 409, release);
|
|
3138
|
+
});
|
|
3139
|
+
return;
|
|
3140
|
+
}
|
|
3141
|
+
// Reclaim every copy that provably holds no work. Never forces: anything
|
|
3142
|
+
// with unsaved or unmerged changes comes back under `kept` with a reason
|
|
3143
|
+
// and waits for a human. Called on window close and on daemon startup.
|
|
3144
|
+
if (method === "POST" && url === "/worktrees/sweep") {
|
|
3145
|
+
void readBody(req, res).then(async (raw) => {
|
|
3146
|
+
if (raw === null)
|
|
3147
|
+
return;
|
|
3148
|
+
let body;
|
|
3149
|
+
try {
|
|
3150
|
+
body = JSON.parse(raw);
|
|
3151
|
+
}
|
|
3152
|
+
catch {
|
|
3153
|
+
json(res, 400, { error: "invalid JSON body" });
|
|
3154
|
+
return;
|
|
3155
|
+
}
|
|
3156
|
+
const busy = Array.isArray(body.busyPaths)
|
|
3157
|
+
? body.busyPaths.filter((p) => typeof p === "string")
|
|
3158
|
+
: [];
|
|
3159
|
+
json(res, 200, await sweepWorktrees(body.cwd?.trim() || cwd, busy));
|
|
3160
|
+
});
|
|
3161
|
+
return;
|
|
3162
|
+
}
|
|
3019
3163
|
// Dismiss a project from the picker (or restore it with hidden:false). The
|
|
3020
3164
|
// path is kept rather than the row: sessions in scratch dirs keep
|
|
3021
3165
|
// re-surfacing, so the user's decision has to outlive any one scan.
|
|
@@ -3626,7 +3770,7 @@ async function createSession(deps, opts) {
|
|
|
3626
3770
|
// while the webview keeps showing the short `/name`.
|
|
3627
3771
|
await session.prompt(text);
|
|
3628
3772
|
}
|
|
3629
|
-
});
|
|
3773
|
+
}, () => autopilot);
|
|
3630
3774
|
// After the user's run settles, kick off Nolan's auto-review loop — but
|
|
3631
3775
|
// only when the turn is actually reviewable (shouldStartAutopilotCycle):
|
|
3632
3776
|
// workflow commands (/compare, /expand, …) end with reports or
|
|
@@ -3655,7 +3799,8 @@ async function createSession(deps, opts) {
|
|
|
3655
3799
|
log("INFO", "app-sidecar", "autopilot cycle starting", { kind: decision.kind });
|
|
3656
3800
|
await runAutopilotCycle(text);
|
|
3657
3801
|
}
|
|
3658
|
-
else
|
|
3802
|
+
else {
|
|
3803
|
+
broadcast("autopilot_ignored", { reason: decision.reason });
|
|
3659
3804
|
log("INFO", "app-sidecar", "autopilot skipped", { reason: decision.reason });
|
|
3660
3805
|
}
|
|
3661
3806
|
// A prompt sent while Nolan was reviewing (build idle) queued but had no
|