@cruxy/cli 1.2.0 → 1.3.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/dist/agent/context.js +178 -0
- package/dist/agent/index.js +1 -0
- package/dist/agent/loop.js +41 -2
- package/dist/agent/mode.js +103 -0
- package/dist/agent/prompts.js +1 -1
- package/dist/agent/session.js +185 -72
- package/dist/approval/classify.js +204 -0
- package/dist/approval/policy.js +41 -3
- package/dist/approval/prompt.js +49 -22
- package/dist/checkpoint/gate.js +12 -0
- package/dist/cli/commands/run.js +374 -227
- package/dist/cli/commands/usage.js +45 -45
- package/dist/cli/onboard.js +2 -1
- package/dist/cli/program.js +60 -18
- package/dist/cli/repl.js +67 -249
- package/dist/cli/session-commands.js +755 -0
- package/dist/cli/session-factory.js +198 -76
- package/dist/cli/suggest.js +77 -0
- package/dist/components/fuzzy.js +3 -3
- package/dist/components/input.js +17 -2
- package/dist/components/keys.js +27 -3
- package/dist/components/select.js +3 -3
- package/dist/config/project.js +53 -1
- package/dist/config/schema.js +49 -16
- package/dist/jobs/log-renderer.js +47 -0
- package/dist/onboarding/steps.js +13 -22
- package/dist/plan/approve.js +36 -24
- package/dist/plan/execute.js +9 -7
- package/dist/plan/render.js +10 -23
- package/dist/plan/service.js +4 -1
- package/dist/render/capabilities.js +30 -1
- package/dist/render/context-view.js +106 -0
- package/dist/render/diff.js +198 -12
- package/dist/render/index.js +31 -5
- package/dist/render/plain-renderer.js +38 -2
- package/dist/render/plan-view.js +108 -0
- package/dist/render/resize.js +7 -2
- package/dist/render/status-view.js +66 -0
- package/dist/render/test-view.js +89 -0
- package/dist/render/tty-renderer.js +40 -0
- package/dist/routing/index.js +1 -0
- package/dist/routing/router.js +13 -4
- package/dist/routing/session-model.js +109 -0
- package/dist/routing/types.js +14 -0
- package/dist/session/export.js +88 -0
- package/dist/session/index.js +20 -0
- package/dist/session/list.js +137 -0
- package/dist/session/log.js +137 -0
- package/dist/session/paths.js +73 -0
- package/dist/session/replay.js +169 -0
- package/dist/session/resume.js +128 -0
- package/dist/session/types.js +223 -0
- package/dist/subagent/orchestrator.js +23 -0
- package/dist/testing/run-tests-tool.js +8 -0
- package/dist/tools/registry.js +3 -3
- package/dist/tui/app.js +385 -0
- package/dist/tui/approval-overlay.js +160 -0
- package/dist/tui/context-gauge.js +48 -0
- package/dist/tui/git-status.js +63 -0
- package/dist/tui/index.js +10 -0
- package/dist/tui/layout.js +269 -0
- package/dist/tui/overlay.js +105 -0
- package/dist/tui/palette.js +73 -0
- package/dist/tui/panels.js +235 -0
- package/dist/tui/renderer.js +776 -0
- package/dist/tui/supports.js +20 -0
- package/dist/tui/tool-versions.js +129 -0
- package/dist/usage/collect.js +21 -3
- package/dist/usage/index.js +10 -2
- package/dist/usage/report.js +76 -0
- package/dist/usage/store.js +7 -1
- package/dist/usage/summary.js +106 -17
- package/dist/usage/types.js +73 -4
- package/dist/usage/weighted.js +77 -0
- package/dist/utils/git.js +50 -4
- package/package.json +2 -2
- package/dist/usage/cost.js +0 -29
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
import { COMPACTION_MARKER } from "./prompts.js";
|
|
2
|
+
/** Characters a single content block contributes to the estimate. */
|
|
3
|
+
function blockChars(block) {
|
|
4
|
+
switch (block.type) {
|
|
5
|
+
case "text":
|
|
6
|
+
return block.text.length;
|
|
7
|
+
case "tool_use":
|
|
8
|
+
return block.name.length + JSON.stringify(block.input).length;
|
|
9
|
+
case "tool_result":
|
|
10
|
+
return block.content.length;
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
/** Characters one message contributes. Block structure and role labels are ignored. */
|
|
14
|
+
export function messageChars(message) {
|
|
15
|
+
if (typeof message.content === "string")
|
|
16
|
+
return message.content.length;
|
|
17
|
+
let chars = 0;
|
|
18
|
+
for (const block of message.content)
|
|
19
|
+
chars += blockChars(block);
|
|
20
|
+
return chars;
|
|
21
|
+
}
|
|
22
|
+
/** Chars → tokens, the one place the chars/4 heuristic is applied. */
|
|
23
|
+
export function charsToTokens(chars) {
|
|
24
|
+
return Math.ceil(chars / 4);
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Estimate the token footprint of a message list with a cheap chars/4 heuristic
|
|
28
|
+
* — no tokenizer dependency. Good enough to decide *when* to compact; exact
|
|
29
|
+
* counts are deferred to a later phase. Counts only textual payload (block
|
|
30
|
+
* structure and role labels are negligible and ignored).
|
|
31
|
+
*/
|
|
32
|
+
export function estimateTokens(messages) {
|
|
33
|
+
let chars = 0;
|
|
34
|
+
for (const message of messages)
|
|
35
|
+
chars += messageChars(message);
|
|
36
|
+
return charsToTokens(chars);
|
|
37
|
+
}
|
|
38
|
+
/** Compute a reading from a history — pure, and shared by the gauge and `/context`. */
|
|
39
|
+
export function readContext(messages, budget) {
|
|
40
|
+
// Deliberately the SAME expression `compactIfOverThreshold` branches on: the
|
|
41
|
+
// reserve covers the system prompt and tool schemas that `estimateTokens`
|
|
42
|
+
// never sees, and omitting it here would under-report by ~4.5k tokens and let
|
|
43
|
+
// the panel read "comfortable" while the seam was about to compact.
|
|
44
|
+
const used = estimateTokens(messages) + budget.reserveTokens;
|
|
45
|
+
const total = budget.maxTokens;
|
|
46
|
+
return {
|
|
47
|
+
used,
|
|
48
|
+
total,
|
|
49
|
+
fraction: total <= 0 ? 1 : Math.min(1, Math.max(0, used / total)),
|
|
50
|
+
compactAt: Math.round(budget.compactThreshold * total),
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Choose the boundary between the summarized prefix and the kept-recent tail.
|
|
55
|
+
*
|
|
56
|
+
* Tool-call integrity is the constraint: a `tool_use` (assistant) and its
|
|
57
|
+
* matching `tool_result` (the next user message) must never straddle the cut,
|
|
58
|
+
* or the next provider call breaks. A real user *prompt* (`role:"user"` with
|
|
59
|
+
* string content) only occurs at a completed turn boundary, where every prior
|
|
60
|
+
* tool exchange is already resolved — so the kept region must begin there. The
|
|
61
|
+
* synthetic compaction-summary user message is also string content, so a repeat
|
|
62
|
+
* compaction always finds at least the previous summary as a clean cut.
|
|
63
|
+
*
|
|
64
|
+
* Start from `length - keepRecentMessages` and walk *backwards* to the nearest
|
|
65
|
+
* such prompt: this keeps at least the recent floor and lands clean. Returns
|
|
66
|
+
* the cut index, or `null` if no safe boundary leaves a non-empty prefix (e.g.
|
|
67
|
+
* a single long in-progress turn — nothing safe to compact).
|
|
68
|
+
*
|
|
69
|
+
* Extracted from `Session` (P6 track 3) so `/context` can report what compaction
|
|
70
|
+
* would do by calling the function that decides what it does. A second
|
|
71
|
+
* implementation for the explanation would be free to be subtly wrong exactly
|
|
72
|
+
* where it mattered.
|
|
73
|
+
*/
|
|
74
|
+
export function findCut(messages, keepRecentMessages) {
|
|
75
|
+
const start = messages.length - keepRecentMessages;
|
|
76
|
+
for (let i = start; i >= 1; i--) {
|
|
77
|
+
const message = messages[i];
|
|
78
|
+
if (message.role === "user" && typeof message.content === "string")
|
|
79
|
+
return i;
|
|
80
|
+
}
|
|
81
|
+
return null;
|
|
82
|
+
}
|
|
83
|
+
/** Which part a message belongs to. */
|
|
84
|
+
function classify(message) {
|
|
85
|
+
if (typeof message.content === "string") {
|
|
86
|
+
// The synthetic pair a previous compaction spliced in. Worth its own bucket:
|
|
87
|
+
// "40% of your context is a summary of earlier context" is a different fact
|
|
88
|
+
// from "40% is your prompts", and it is the one that says compaction has
|
|
89
|
+
// already run.
|
|
90
|
+
return message.content.startsWith(COMPACTION_MARKER)
|
|
91
|
+
? "summaries"
|
|
92
|
+
: "prompts";
|
|
93
|
+
}
|
|
94
|
+
if (message.role === "user")
|
|
95
|
+
return "tool results";
|
|
96
|
+
return message.content.some((b) => b.type === "tool_use")
|
|
97
|
+
? "tool calls"
|
|
98
|
+
: "assistant";
|
|
99
|
+
}
|
|
100
|
+
/** A one-line excerpt of a message, for recognition rather than reading. */
|
|
101
|
+
function excerpt(message, max = 60) {
|
|
102
|
+
let text;
|
|
103
|
+
if (typeof message.content === "string") {
|
|
104
|
+
text = message.content;
|
|
105
|
+
}
|
|
106
|
+
else {
|
|
107
|
+
const first = message.content[0];
|
|
108
|
+
text =
|
|
109
|
+
first === undefined
|
|
110
|
+
? ""
|
|
111
|
+
: first.type === "text"
|
|
112
|
+
? first.text
|
|
113
|
+
: first.type === "tool_use"
|
|
114
|
+
? first.name
|
|
115
|
+
: first.content;
|
|
116
|
+
}
|
|
117
|
+
const flat = text.replace(/\s+/g, " ").trim();
|
|
118
|
+
return flat.length > max ? `${flat.slice(0, max - 1)}…` : flat;
|
|
119
|
+
}
|
|
120
|
+
/**
|
|
121
|
+
* Analyse a history against a budget. Pure; no I/O, no theme, no rendering.
|
|
122
|
+
*
|
|
123
|
+
* @param topN how many individual messages to name as the largest contributors.
|
|
124
|
+
*/
|
|
125
|
+
export function contextReport(messages, budget, topN = 5) {
|
|
126
|
+
const charsByPart = new Map();
|
|
127
|
+
const contributors = [];
|
|
128
|
+
for (const [index, message] of messages.entries()) {
|
|
129
|
+
const chars = messageChars(message);
|
|
130
|
+
const part = classify(message);
|
|
131
|
+
const bucket = charsByPart.get(part) ?? { chars: 0, messages: 0 };
|
|
132
|
+
bucket.chars += chars;
|
|
133
|
+
bucket.messages += 1;
|
|
134
|
+
charsByPart.set(part, bucket);
|
|
135
|
+
contributors.push({ chars, message, index });
|
|
136
|
+
}
|
|
137
|
+
const parts = [...charsByPart.entries()]
|
|
138
|
+
.map(([part, b]) => ({
|
|
139
|
+
part,
|
|
140
|
+
tokens: charsToTokens(b.chars),
|
|
141
|
+
messages: b.messages,
|
|
142
|
+
}))
|
|
143
|
+
.filter((p) => p.tokens > 0)
|
|
144
|
+
.sort((a, b) => b.tokens - a.tokens);
|
|
145
|
+
const largest = contributors
|
|
146
|
+
.filter((c) => c.chars > 0)
|
|
147
|
+
.sort((a, b) => b.chars - a.chars)
|
|
148
|
+
.slice(0, topN)
|
|
149
|
+
.map((c) => ({
|
|
150
|
+
label: classify(c.message),
|
|
151
|
+
excerpt: excerpt(c.message),
|
|
152
|
+
tokens: charsToTokens(c.chars),
|
|
153
|
+
position: c.index + 1,
|
|
154
|
+
}));
|
|
155
|
+
const cut = findCut(messages, budget.keepRecentMessages);
|
|
156
|
+
const reading = readContext(messages, budget);
|
|
157
|
+
return {
|
|
158
|
+
reading,
|
|
159
|
+
messages: messages.length,
|
|
160
|
+
reserveTokens: budget.reserveTokens,
|
|
161
|
+
parts,
|
|
162
|
+
largest,
|
|
163
|
+
compaction: {
|
|
164
|
+
cut,
|
|
165
|
+
droppedMessages: cut ?? 0,
|
|
166
|
+
droppedTokens: cut === null ? 0 : estimateTokens(messages.slice(0, cut)),
|
|
167
|
+
keptMessages: cut === null ? messages.length : messages.length - cut,
|
|
168
|
+
keptTokens: cut === null
|
|
169
|
+
? estimateTokens(messages)
|
|
170
|
+
: estimateTokens(messages.slice(cut)),
|
|
171
|
+
// Compared against the UNROUNDED product, exactly as
|
|
172
|
+
// `compactIfOverThreshold` does — `compactAt` is rounded for display, and
|
|
173
|
+
// a report claiming to say what compaction would do must not disagree
|
|
174
|
+
// with the seam over a fraction of a token.
|
|
175
|
+
overThreshold: reading.used > budget.compactThreshold * budget.maxTokens,
|
|
176
|
+
},
|
|
177
|
+
};
|
|
178
|
+
}
|
package/dist/agent/index.js
CHANGED
package/dist/agent/loop.js
CHANGED
|
@@ -24,6 +24,11 @@ export async function runAgent(args) {
|
|
|
24
24
|
// gateway does not offer throws CRUXY_E_ROUTING_TIER_UNAVAILABLE here — so a
|
|
25
25
|
// misrouted run never reaches provider.stream (no request sent with the wrong
|
|
26
26
|
// tier), and never silently falls back to a different one.
|
|
27
|
+
//
|
|
28
|
+
// Resolving per RUN, not per iteration, is still right after P6 track 1 made
|
|
29
|
+
// the choice mutable: a `/model` mid-turn is impossible (the input loop has
|
|
30
|
+
// released stdin and is awaiting `session.send`), and a run that changed model
|
|
31
|
+
// between its own iterations would attribute one history to two tiers.
|
|
27
32
|
const routed = args.router
|
|
28
33
|
? resolveTaskModel(args.router, args.taskClass ?? "main-turn")
|
|
29
34
|
: null;
|
|
@@ -38,7 +43,11 @@ export async function runAgent(args) {
|
|
|
38
43
|
}
|
|
39
44
|
}
|
|
40
45
|
/** The body of {@link runAgent}, split out so turn cleanup lives in one finally. */
|
|
41
|
-
async function driveLoop(args, renderer,
|
|
46
|
+
async function driveLoop(args, renderer,
|
|
47
|
+
// `tier` is optional because a router may decline and send the request as
|
|
48
|
+
// `auto` — no tier was chosen client-side, so there is none to report until
|
|
49
|
+
// the gateway's routing frame arrives. See `resolveTaskModel`.
|
|
50
|
+
routed) {
|
|
42
51
|
const { provider, registry, config, ctx } = args;
|
|
43
52
|
const { logger } = ctx;
|
|
44
53
|
// Work on a copy so we never mutate the caller's array as a side effect; the
|
|
@@ -116,6 +125,12 @@ async function driveLoop(args, renderer, routed) {
|
|
|
116
125
|
// true and is recorded as a real 0.
|
|
117
126
|
let sawUsage = false;
|
|
118
127
|
const reqUsage = { input_tokens: 0, output_tokens: 0 };
|
|
128
|
+
// The served tier arrives on the stream's OPENING frame, but usage is only
|
|
129
|
+
// reported once the stream has closed — so it is held here across the whole
|
|
130
|
+
// request, exactly like `sawUsage`. Absent for backends that report no
|
|
131
|
+
// routing, which is why the fallback below still exists.
|
|
132
|
+
let servedTier;
|
|
133
|
+
let routingMode;
|
|
119
134
|
// Live progress while waiting on the model; dismissed by the first delta.
|
|
120
135
|
// Token context is whatever the loop has actually accumulated (U.4): zero
|
|
121
136
|
// on the first turn → no figure shown, never a fabricated number.
|
|
@@ -133,6 +148,20 @@ async function driveLoop(args, renderer, routed) {
|
|
|
133
148
|
...(routed ? { model: routed.model } : {}),
|
|
134
149
|
})) {
|
|
135
150
|
switch (ev.type) {
|
|
151
|
+
case "routing":
|
|
152
|
+
servedTier = ev.routing.tier;
|
|
153
|
+
routingMode = ev.routing.mode;
|
|
154
|
+
// Publish to the renderer HERE, not at the `setPhase` above. That one
|
|
155
|
+
// fires before `provider.stream` is called, so the served tier is not
|
|
156
|
+
// knowable yet and it can only carry `routed?.tier` — what this run
|
|
157
|
+
// ASKED for. This frame is the first moment the backend's answer
|
|
158
|
+
// exists, and it is the answer worth showing: it resolves `auto`, and
|
|
159
|
+
// it reflects a downgrade the client never chose.
|
|
160
|
+
renderer?.servedRouting({
|
|
161
|
+
tier: ev.routing.tier,
|
|
162
|
+
...(ev.routing.mode !== undefined ? { mode: ev.routing.mode } : {}),
|
|
163
|
+
});
|
|
164
|
+
break;
|
|
136
165
|
case "text_delta":
|
|
137
166
|
turnText += ev.text;
|
|
138
167
|
renderer?.write(ev.text);
|
|
@@ -178,8 +207,18 @@ async function driveLoop(args, renderer, routed) {
|
|
|
178
207
|
// event arrived, or `undefined` (unknown) when the provider reported none. A
|
|
179
208
|
// stream that threw above never reaches here, so failed requests aren't
|
|
180
209
|
// recorded with a misleading zero.
|
|
210
|
+
// THE BACKEND WINS on tier. `routed?.tier` is what this run ASKED for;
|
|
211
|
+
// `servedTier` is what the gateway says actually ran, which is not the same
|
|
212
|
+
// claim — a "auto" request is resolved server-side, and any request can be
|
|
213
|
+
// downgraded under budget pressure. Attributing tokens to the tier we asked
|
|
214
|
+
// for would misreport exactly the case worth knowing about. The client
|
|
215
|
+
// choice remains the fallback for backends that report no routing at all.
|
|
216
|
+
// `routingMode` travels alongside so a difference between the two is
|
|
217
|
+
// explainable after the fact ("auto_degraded") rather than an unexplained
|
|
218
|
+
// tier nobody selected.
|
|
181
219
|
args.onRequestUsage?.({
|
|
182
|
-
tier: routed?.tier,
|
|
220
|
+
tier: servedTier ?? routed?.tier,
|
|
221
|
+
...(routingMode !== undefined ? { routingMode } : {}),
|
|
183
222
|
usage: sawUsage ? { ...reqUsage } : undefined,
|
|
184
223
|
});
|
|
185
224
|
// ── Record the assistant turn ───────────────────────────────────────────
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Session modes (P5 track 3) — the one piece of state that says how much a turn
|
|
3
|
+
* does without asking.
|
|
4
|
+
*
|
|
5
|
+
* Two facts govern that, and before P5 they were kept in different places and at
|
|
6
|
+
* different lifetimes: plan mode lived on the `Session` (runtime, journaled,
|
|
7
|
+
* restored on resume) while auto-approve was a config key read from disk. The
|
|
8
|
+
* config key was also dead — nothing consumed `agent.autoApprove`, and
|
|
9
|
+
* `ApprovalConfigSchema` in the same file stated flatly that no auto-approve
|
|
10
|
+
* mode exists "not behind a footgun flag". One file promised something another
|
|
11
|
+
* refused to do.
|
|
12
|
+
*
|
|
13
|
+
* A mode is the cross product of the two, named. That is what makes it a
|
|
14
|
+
* SUBSUMPTION rather than a third source of truth: `Session` holds exactly one
|
|
15
|
+
* mode and derives both booleans from it, so there is no state in which plan
|
|
16
|
+
* mode and the mode indicator can disagree.
|
|
17
|
+
*
|
|
18
|
+
* Auto-approve is deliberately a RUNTIME mode and only that — never a config
|
|
19
|
+
* key. A flag on disk silently disarms every approval in every session that
|
|
20
|
+
* loads it, with nothing on screen to say so; a mode is chosen in the session it
|
|
21
|
+
* affects, shown while it is active, and gone when the session ends.
|
|
22
|
+
*/
|
|
23
|
+
/**
|
|
24
|
+
* Cycle order, and the order every list of modes uses. Shift+Tab walks this
|
|
25
|
+
* ring; the last entry wraps to the first.
|
|
26
|
+
*/
|
|
27
|
+
export const SESSION_MODES = [
|
|
28
|
+
"manual",
|
|
29
|
+
"auto-approve",
|
|
30
|
+
"plan",
|
|
31
|
+
"full-auto",
|
|
32
|
+
];
|
|
33
|
+
/** The default for a session that says nothing — the pre-P5 behaviour exactly. */
|
|
34
|
+
export const DEFAULT_MODE = "manual";
|
|
35
|
+
/** The next mode in the ring. Total: every mode has a successor. */
|
|
36
|
+
export function nextMode(mode) {
|
|
37
|
+
const i = SESSION_MODES.indexOf(mode);
|
|
38
|
+
return SESSION_MODES[(i + 1) % SESSION_MODES.length];
|
|
39
|
+
}
|
|
40
|
+
/** Whether this mode proposes a plan before executing (C.31). */
|
|
41
|
+
export function modePlans(mode) {
|
|
42
|
+
return mode === "plan" || mode === "full-auto";
|
|
43
|
+
}
|
|
44
|
+
/** Whether this mode allows gated actions without prompting. */
|
|
45
|
+
export function modeAutoApproves(mode) {
|
|
46
|
+
return mode === "auto-approve" || mode === "full-auto";
|
|
47
|
+
}
|
|
48
|
+
/** Short label for the status line and the mode indicator. */
|
|
49
|
+
export const MODE_LABELS = {
|
|
50
|
+
manual: "manual",
|
|
51
|
+
"auto-approve": "auto-approve",
|
|
52
|
+
plan: "plan",
|
|
53
|
+
"full-auto": "full-auto",
|
|
54
|
+
};
|
|
55
|
+
/**
|
|
56
|
+
* One line saying what the mode will actually do, shown when it changes.
|
|
57
|
+
*
|
|
58
|
+
* A mode that skips approvals has to SAY it skips approvals at the moment it is
|
|
59
|
+
* switched on. The whole objection to the config flag was that it disarmed the
|
|
60
|
+
* gate with nothing on screen; a runtime mode that announced itself as vaguely
|
|
61
|
+
* as "auto-approve on" would reproduce the same problem more slowly.
|
|
62
|
+
*
|
|
63
|
+
* These lines must not overstate the mode either. Saying "including destructive
|
|
64
|
+
* ones" when the ceiling stops exactly those would teach the user to expect a
|
|
65
|
+
* silence that never comes — and, worse, teach them the gate is gone when it is
|
|
66
|
+
* not. Each auto line names the suppression AND its limit.
|
|
67
|
+
*/
|
|
68
|
+
export function modeDescription(mode) {
|
|
69
|
+
switch (mode) {
|
|
70
|
+
case "manual":
|
|
71
|
+
return "every action asks first";
|
|
72
|
+
case "auto-approve":
|
|
73
|
+
return "reversible actions run WITHOUT asking — irreversible ones still ask";
|
|
74
|
+
case "plan":
|
|
75
|
+
return "propose a plan for approval, then ask before each action";
|
|
76
|
+
case "full-auto":
|
|
77
|
+
return "propose a plan, then run it WITHOUT asking — irreversible actions still ask";
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* Parse a mode from user input (`/mode <name>`), or null when it names nothing.
|
|
82
|
+
* Never guesses: a typo must not silently arm auto-approve.
|
|
83
|
+
*/
|
|
84
|
+
export function parseMode(text) {
|
|
85
|
+
const want = text.trim().toLowerCase();
|
|
86
|
+
return SESSION_MODES.find((m) => m === want) ?? null;
|
|
87
|
+
}
|
|
88
|
+
/**
|
|
89
|
+
* Recover a mode from the two booleans an older session journaled.
|
|
90
|
+
*
|
|
91
|
+
* Sessions recorded before P5 carry `plan-mode` events and nothing else, so
|
|
92
|
+
* auto-approve reads false — which is right: it was not a thing that could be
|
|
93
|
+
* on. This is what lets a pre-P5 session resume into the mode it actually had.
|
|
94
|
+
*/
|
|
95
|
+
export function modeFromFlags(plans, autoApproves) {
|
|
96
|
+
if (plans && autoApproves)
|
|
97
|
+
return "full-auto";
|
|
98
|
+
if (plans)
|
|
99
|
+
return "plan";
|
|
100
|
+
if (autoApproves)
|
|
101
|
+
return "auto-approve";
|
|
102
|
+
return "manual";
|
|
103
|
+
}
|
package/dist/agent/prompts.js
CHANGED