chivgent 1.1.1 → 1.1.3
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 +31 -8
- package/README.zh-CN.md +31 -8
- package/dist/agent.d.ts.map +1 -1
- package/dist/agent.js +15 -1
- package/dist/agent.js.map +1 -1
- package/dist/cli-options.d.ts +6 -0
- package/dist/cli-options.d.ts.map +1 -1
- package/dist/cli-options.js +22 -0
- package/dist/cli-options.js.map +1 -1
- package/dist/cli-runtime.d.ts.map +1 -1
- package/dist/cli-runtime.js +30 -8
- package/dist/cli-runtime.js.map +1 -1
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +11 -3
- package/dist/cli.js.map +1 -1
- package/dist/context/compaction.d.ts +10 -0
- package/dist/context/compaction.d.ts.map +1 -1
- package/dist/context/compaction.js +50 -10
- package/dist/context/compaction.js.map +1 -1
- package/dist/context/context-manager.d.ts +2 -5
- package/dist/context/context-manager.d.ts.map +1 -1
- package/dist/context/context-manager.js +47 -19
- package/dist/context/context-manager.js.map +1 -1
- package/dist/context/errors.d.ts +12 -0
- package/dist/context/errors.d.ts.map +1 -0
- package/dist/context/errors.js +19 -0
- package/dist/context/errors.js.map +1 -0
- package/dist/evals/report.d.ts.map +1 -1
- package/dist/evals/report.js +3 -0
- package/dist/evals/report.js.map +1 -1
- package/dist/evals/runner.d.ts +2 -0
- package/dist/evals/runner.d.ts.map +1 -1
- package/dist/evals/runner.js +13 -1
- package/dist/evals/runner.js.map +1 -1
- package/dist/evals/task.d.ts +2 -0
- package/dist/evals/task.d.ts.map +1 -1
- package/dist/evals/task.js +7 -0
- package/dist/evals/task.js.map +1 -1
- package/dist/events.d.ts +2 -0
- package/dist/events.d.ts.map +1 -1
- package/dist/events.js.map +1 -1
- package/dist/llm.d.ts +2 -0
- package/dist/llm.d.ts.map +1 -1
- package/dist/llm.js.map +1 -1
- package/dist/permissions.d.ts +6 -1
- package/dist/permissions.d.ts.map +1 -1
- package/dist/permissions.js +21 -4
- package/dist/permissions.js.map +1 -1
- package/dist/prompts.d.ts +4 -9
- package/dist/prompts.d.ts.map +1 -1
- package/dist/prompts.js +37 -9
- package/dist/prompts.js.map +1 -1
- package/dist/providers/definitions.js +1 -1
- package/dist/providers/definitions.js.map +1 -1
- package/dist/providers/openai-compatible-chat.d.ts.map +1 -1
- package/dist/providers/openai-compatible-chat.js +11 -4
- package/dist/providers/openai-compatible-chat.js.map +1 -1
- package/dist/providers/openai.js +2 -0
- package/dist/providers/openai.js.map +1 -1
- package/dist/repl.d.ts +12 -1
- package/dist/repl.d.ts.map +1 -1
- package/dist/repl.js +234 -60
- package/dist/repl.js.map +1 -1
- package/dist/session-store.d.ts +3 -0
- package/dist/session-store.d.ts.map +1 -1
- package/dist/session-store.js +5 -0
- package/dist/session-store.js.map +1 -1
- package/dist/session.d.ts +1 -0
- package/dist/session.d.ts.map +1 -1
- package/dist/session.js +33 -3
- package/dist/session.js.map +1 -1
- package/dist/shell/docker.d.ts.map +1 -1
- package/dist/shell/docker.js +1 -0
- package/dist/shell/docker.js.map +1 -1
- package/dist/shell/native.d.ts.map +1 -1
- package/dist/shell/native.js +1 -0
- package/dist/shell/native.js.map +1 -1
- package/dist/shell/types.d.ts +2 -0
- package/dist/shell/types.d.ts.map +1 -1
- package/dist/tools/bash.d.ts +16 -24
- package/dist/tools/bash.d.ts.map +1 -1
- package/dist/tools/bash.js +94 -30
- package/dist/tools/bash.js.map +1 -1
- package/dist/tools/target.d.ts.map +1 -1
- package/dist/tools/target.js +3 -1
- package/dist/tools/target.js.map +1 -1
- package/dist/tools/web-search.d.ts +42 -0
- package/dist/tools/web-search.d.ts.map +1 -0
- package/dist/tools/web-search.js +154 -0
- package/dist/tools/web-search.js.map +1 -0
- package/dist/tui/activity.d.ts +3 -1
- package/dist/tui/activity.d.ts.map +1 -1
- package/dist/tui/activity.js +46 -6
- package/dist/tui/activity.js.map +1 -1
- package/dist/tui/editor-input.d.ts +10 -0
- package/dist/tui/editor-input.d.ts.map +1 -1
- package/dist/tui/editor-input.js +126 -33
- package/dist/tui/editor-input.js.map +1 -1
- package/dist/tui/editor.d.ts +1 -1
- package/dist/tui/editor.d.ts.map +1 -1
- package/dist/tui/editor.js +31 -5
- package/dist/tui/editor.js.map +1 -1
- package/dist/tui/live.d.ts.map +1 -1
- package/dist/tui/live.js +55 -4
- package/dist/tui/live.js.map +1 -1
- package/dist/tui/markdown.d.ts +13 -0
- package/dist/tui/markdown.d.ts.map +1 -1
- package/dist/tui/markdown.js +35 -26
- package/dist/tui/markdown.js.map +1 -1
- package/dist/tui/provider-picker.d.ts +28 -0
- package/dist/tui/provider-picker.d.ts.map +1 -0
- package/dist/tui/provider-picker.js +131 -0
- package/dist/tui/provider-picker.js.map +1 -0
- package/dist/tui/welcome.d.ts +5 -4
- package/dist/tui/welcome.d.ts.map +1 -1
- package/dist/tui/welcome.js +8 -9
- package/dist/tui/welcome.js.map +1 -1
- package/dist/websearch/backend.d.ts +83 -0
- package/dist/websearch/backend.d.ts.map +1 -0
- package/dist/websearch/backend.js +238 -0
- package/dist/websearch/backend.js.map +1 -0
- package/package.json +1 -1
package/dist/repl.js
CHANGED
|
@@ -4,63 +4,74 @@ import { readLiveGitDiff } from "./tui/git-diff.js";
|
|
|
4
4
|
import { terminalText } from "./tui/text.js";
|
|
5
5
|
import { terminalColorEnabled } from "./tui/selection.js";
|
|
6
6
|
import { PatchReviewController } from "./tui/review.js";
|
|
7
|
+
import { ProviderPickerController } from "./tui/provider-picker.js";
|
|
7
8
|
import { InputMenu } from "./tui/menu.js";
|
|
8
9
|
import { createInterface, emitKeypressEvents } from "node:readline";
|
|
9
10
|
import { formatTokens } from "./providers/usage.js";
|
|
10
11
|
import { approvalPreview, commandReviewable } from "./tui/activity.js";
|
|
11
12
|
import { terminalWidth } from "./tui/live.js";
|
|
12
13
|
import { formatDiffPage, mostRecentSuccessfulPatch } from "./tui/diff.js";
|
|
13
|
-
import { describePermissionMode, parsePermissionMode } from "./permissions.js";
|
|
14
|
+
import { describePermissionMode, parsePermissionMode, WEB_SEARCH_APPROVAL_PREFIX } from "./permissions.js";
|
|
14
15
|
import { HeuristicTokenEstimator } from "./context/token-estimator.js";
|
|
15
16
|
export const REPL_PROMPT = "› ";
|
|
16
17
|
export const BUILT_IN_COMMANDS = [
|
|
17
18
|
"help",
|
|
18
|
-
"session",
|
|
19
19
|
"status",
|
|
20
|
-
"compact",
|
|
21
|
-
"plan",
|
|
22
|
-
"permissions",
|
|
23
20
|
"new",
|
|
24
21
|
"resume",
|
|
25
22
|
"rename",
|
|
26
|
-
"tools",
|
|
27
23
|
"diff",
|
|
24
|
+
"clear",
|
|
25
|
+
"compact",
|
|
26
|
+
"permissions",
|
|
27
|
+
"provider",
|
|
28
|
+
"model",
|
|
29
|
+
"endpoint",
|
|
30
|
+
"login",
|
|
31
|
+
"exit",
|
|
32
|
+
];
|
|
33
|
+
export const LEGACY_COMMAND_ALIASES = [
|
|
34
|
+
"session",
|
|
35
|
+
"plan",
|
|
36
|
+
"tools",
|
|
28
37
|
"compose",
|
|
29
38
|
"editor",
|
|
30
39
|
"review",
|
|
31
40
|
"gitdiff",
|
|
32
|
-
"clear",
|
|
33
|
-
"exit",
|
|
34
41
|
"quit",
|
|
35
|
-
"login",
|
|
36
42
|
"logout",
|
|
37
|
-
"provider",
|
|
38
|
-
"model",
|
|
39
|
-
"endpoint",
|
|
40
43
|
];
|
|
44
|
+
export const BUILT_IN_DESCRIPTIONS = {
|
|
45
|
+
help: "show this help",
|
|
46
|
+
status: "show session, model, tokens & permissions",
|
|
47
|
+
new: "save and start a fresh session",
|
|
48
|
+
resume: "switch to a saved session in workspace",
|
|
49
|
+
rename: "set current session title",
|
|
50
|
+
diff: "review workspace Git changes (/diff patch for patch)",
|
|
51
|
+
clear: "clear transcript in current session",
|
|
52
|
+
compact: "summarise older history for the model",
|
|
53
|
+
permissions: "inspect or change permission policy",
|
|
54
|
+
provider: "choose a Provider",
|
|
55
|
+
model: "show or change current model",
|
|
56
|
+
endpoint: "set URL for OpenAI-compatible Provider",
|
|
57
|
+
login: "store API key or sign in with subscription",
|
|
58
|
+
exit: "leave chivgent",
|
|
59
|
+
};
|
|
41
60
|
const HELP = `Commands:
|
|
42
61
|
/help Show this help
|
|
43
|
-
/
|
|
44
|
-
/status Show live session, model, permissions, plan state, and usage
|
|
62
|
+
/status Show live session, model, permissions, plan state, and usage (alias: /session)
|
|
45
63
|
/compact Summarise older history for the model; keeps the full transcript
|
|
46
64
|
/plan [on|off] Toggle restricted read-only Plan Mode
|
|
47
|
-
/permissions [ask-first|workspace|unrestricted] Inspect or change this process's policy
|
|
65
|
+
/permissions [ask-first|workspace|unrestricted|plan] Inspect or change this process's policy
|
|
48
66
|
/new [title] Save this session and start a fresh session
|
|
49
67
|
/resume [id] Switch to a saved session in this workspace
|
|
50
68
|
/rename TITLE Set the current session title
|
|
51
|
-
/
|
|
52
|
-
/diff [N] Review the latest successful apply_patch, page N
|
|
53
|
-
/compose Write a multi-line prompt (/send to submit, /cancel to discard)
|
|
54
|
-
/editor Open the cursor-editable multiline composer (Ctrl+S sends)
|
|
55
|
-
/review Browse last applied patch with arrows, q to close
|
|
56
|
-
/gitdiff [--staged] Review actual workspace Git changes after explicit approval
|
|
69
|
+
/diff [--staged|patch] Review workspace Git changes (or latest patch)
|
|
57
70
|
/clear Start a new transcript in the same session
|
|
58
71
|
/provider Choose a Provider (↑↓ and Enter in the TUI)
|
|
59
72
|
/model Show or change the current model: /model MODEL
|
|
60
73
|
/endpoint Set the URL for an OpenAI-compatible Provider
|
|
61
|
-
/login Store
|
|
62
|
-
/login oauth [openai|xai] Sign in with a subscription
|
|
63
|
-
/logout Sign out of the current OAuth subscription
|
|
74
|
+
/login Store API key or sign in with subscription (/login logout to sign out)
|
|
64
75
|
/exit Leave chivgent
|
|
65
76
|
|
|
66
77
|
Anything else is sent to the model. Ctrl+C stops a running answer.
|
|
@@ -95,8 +106,6 @@ export function handleSlashCommand(line, context) {
|
|
|
95
106
|
context.write(helpText(context));
|
|
96
107
|
return "handled";
|
|
97
108
|
case "/session":
|
|
98
|
-
context.write(describeSession(context));
|
|
99
|
-
return "handled";
|
|
100
109
|
case "/status":
|
|
101
110
|
context.write(describeStatus(context));
|
|
102
111
|
return "handled";
|
|
@@ -123,6 +132,12 @@ export function handleSlashCommand(line, context) {
|
|
|
123
132
|
}
|
|
124
133
|
case "/permissions": {
|
|
125
134
|
const argument = trimmed.slice("/permissions".length).trim().toLowerCase();
|
|
135
|
+
if (argument === "plan" || argument === "plan on") {
|
|
136
|
+
return { kind: "plan", enabled: true };
|
|
137
|
+
}
|
|
138
|
+
if (argument === "plan off") {
|
|
139
|
+
return { kind: "plan", enabled: false };
|
|
140
|
+
}
|
|
126
141
|
if (context.permissionPolicy === undefined) {
|
|
127
142
|
context.write("Named permission modes are unavailable because this session uses legacy capability flags. Start with --permission-mode to enable them.\n");
|
|
128
143
|
}
|
|
@@ -134,7 +149,7 @@ export function handleSlashCommand(line, context) {
|
|
|
134
149
|
return { kind: "permission-mode", mode: parsePermissionMode(argument) };
|
|
135
150
|
}
|
|
136
151
|
catch {
|
|
137
|
-
context.write("Usage: /permissions [ask-first|workspace|unrestricted]\n");
|
|
152
|
+
context.write("Usage: /permissions [ask-first|workspace|unrestricted|plan]\n");
|
|
138
153
|
}
|
|
139
154
|
}
|
|
140
155
|
return "handled";
|
|
@@ -162,13 +177,28 @@ export function handleSlashCommand(line, context) {
|
|
|
162
177
|
return "handled";
|
|
163
178
|
case "/diff": {
|
|
164
179
|
const argument = trimmed.slice("/diff".length).trim();
|
|
165
|
-
if (argument
|
|
166
|
-
|
|
180
|
+
if (argument === "patch" || argument.startsWith("patch ")) {
|
|
181
|
+
const pageArg = argument.slice("patch".length).trim();
|
|
182
|
+
if (pageArg.length > 0 && !/^[1-9]\d*$/.test(pageArg)) {
|
|
183
|
+
context.write("Usage: /diff patch [positive page number]\n");
|
|
184
|
+
return "handled";
|
|
185
|
+
}
|
|
186
|
+
const patch = mostRecentSuccessfulPatch(context.session.messages);
|
|
187
|
+
if (patch === undefined) {
|
|
188
|
+
context.write("No successfully applied patch found in this session.\n");
|
|
189
|
+
return "handled";
|
|
190
|
+
}
|
|
191
|
+
const page = pageArg.length === 0 ? 1 : Number(pageArg);
|
|
192
|
+
context.write(formatDiffPage(patch, page, context.width ?? 80, context.color === true).text);
|
|
193
|
+
return "handled";
|
|
194
|
+
}
|
|
195
|
+
if (argument.length > 0 && !/^[1-9]\d*$/.test(argument) && argument !== "--staged") {
|
|
196
|
+
context.write("Usage: /diff [--staged|patch [positive page number]]\n");
|
|
167
197
|
return "handled";
|
|
168
198
|
}
|
|
169
199
|
const patch = mostRecentSuccessfulPatch(context.session.messages);
|
|
170
200
|
if (patch === undefined) {
|
|
171
|
-
context.write("No successfully applied patch found in this session.\n");
|
|
201
|
+
context.write("No successfully applied patch found in this session. In interactive TUI, use /diff to review workspace Git changes.\n");
|
|
172
202
|
return "handled";
|
|
173
203
|
}
|
|
174
204
|
const page = argument.length === 0 ? 1 : Number(argument);
|
|
@@ -176,10 +206,14 @@ export function handleSlashCommand(line, context) {
|
|
|
176
206
|
return "handled";
|
|
177
207
|
}
|
|
178
208
|
case "/compose":
|
|
179
|
-
context.write(
|
|
209
|
+
context.write(context.inlineComposer
|
|
210
|
+
? "The multiline editor is already active in this prompt. Enter sends; Ctrl+O inserts a newline.\n"
|
|
211
|
+
: "Use /compose at the interactive prompt to enter multi-line mode.\n");
|
|
180
212
|
return "handled";
|
|
181
213
|
case "/editor":
|
|
182
|
-
context.write(
|
|
214
|
+
context.write(context.inlineComposer
|
|
215
|
+
? "The cursor-addressable multiline editor is already active in this prompt. Enter sends; Ctrl+O inserts a newline.\n"
|
|
216
|
+
: "Use /editor in the rich interactive TUI to open the multiline editor.\n");
|
|
183
217
|
return "handled";
|
|
184
218
|
case "/review":
|
|
185
219
|
context.write("Use /review in an interactive TUI to browse the most recent successful patch.\n");
|
|
@@ -197,6 +231,9 @@ export function handleSlashCommand(line, context) {
|
|
|
197
231
|
return "handled";
|
|
198
232
|
}
|
|
199
233
|
if (trimmed === "/provider") {
|
|
234
|
+
if (context.interactivePicker === true) {
|
|
235
|
+
return { kind: "provider", argument: "" };
|
|
236
|
+
}
|
|
200
237
|
context.write(context.providers.describe());
|
|
201
238
|
return "handled";
|
|
202
239
|
}
|
|
@@ -221,12 +258,21 @@ export function handleSlashCommand(line, context) {
|
|
|
221
258
|
return "handled";
|
|
222
259
|
}
|
|
223
260
|
return { kind: "endpoint", argument: trimmed.slice("/endpoint".length).trim() };
|
|
224
|
-
case "/login":
|
|
261
|
+
case "/login": {
|
|
262
|
+
const argument = trimmed.slice("/login".length).trim();
|
|
263
|
+
if (argument === "logout" || argument === "--logout") {
|
|
264
|
+
if (context.signIn?.signOut === undefined) {
|
|
265
|
+
context.write("OAuth sign-out is unavailable in this session.\n");
|
|
266
|
+
return "handled";
|
|
267
|
+
}
|
|
268
|
+
return { kind: "login", providerArgument: "__logout__" };
|
|
269
|
+
}
|
|
225
270
|
if (context.signIn === undefined) {
|
|
226
271
|
context.write("This session cannot store a key; it is attached to a server that already has one.\n");
|
|
227
272
|
return "handled";
|
|
228
273
|
}
|
|
229
|
-
return { kind: "login", providerArgument:
|
|
274
|
+
return { kind: "login", providerArgument: argument };
|
|
275
|
+
}
|
|
230
276
|
case "/logout":
|
|
231
277
|
if (context.signIn?.signOut === undefined) {
|
|
232
278
|
context.write("OAuth sign-out is unavailable in this session.\n");
|
|
@@ -266,20 +312,6 @@ function describeUsage(context) {
|
|
|
266
312
|
`tokens: ${parts.join(", ")}${usage.complete ? "" : " (a turn reported none)"}`,
|
|
267
313
|
];
|
|
268
314
|
}
|
|
269
|
-
function describeSession(context) {
|
|
270
|
-
const lines = [
|
|
271
|
-
`id: ${context.session.id}`,
|
|
272
|
-
`workspace: ${context.session.cwd}`,
|
|
273
|
-
`prompts: ${context.session.turns}`,
|
|
274
|
-
`messages: ${context.session.messages.length}`,
|
|
275
|
-
...describeUsage(context),
|
|
276
|
-
];
|
|
277
|
-
const sessionFile = context.sessionFileForSession?.(context.session) ?? context.sessionFile;
|
|
278
|
-
if (sessionFile !== undefined) {
|
|
279
|
-
lines.push(`log: ${sessionFile}`);
|
|
280
|
-
}
|
|
281
|
-
return `${lines.join("\n")}\n`;
|
|
282
|
-
}
|
|
283
315
|
function describeStatus(context) {
|
|
284
316
|
const usage = describeUsage(context);
|
|
285
317
|
let contextEstimate = "unavailable";
|
|
@@ -287,13 +319,19 @@ function describeStatus(context) {
|
|
|
287
319
|
contextEstimate = `approximately ${new HeuristicTokenEstimator().estimateMessages(context.session.messages)} tokens (transcript only; excludes system prompt and tool schemas)`;
|
|
288
320
|
}
|
|
289
321
|
catch { /* inaccessible estimates are reported explicitly */ }
|
|
322
|
+
const sessionFile = context.sessionFileForSession?.(context.session) ?? context.sessionFile;
|
|
290
323
|
const lines = [
|
|
291
324
|
`provider: ${context.providers?.provider ?? context.providerName ?? "unavailable"}`,
|
|
292
325
|
`model: ${context.providers === undefined ? context.modelName ?? "unavailable" : context.providers.model ?? "unavailable"}`,
|
|
293
|
-
`
|
|
294
|
-
`
|
|
326
|
+
`workspace: ${context.session.cwd}`,
|
|
327
|
+
`id: ${context.session.id}${context.session.title ? ` · ${context.session.title}` : ""}`,
|
|
328
|
+
`prompts: ${context.session.turns}`,
|
|
329
|
+
`messages: ${context.session.messages.length}`,
|
|
295
330
|
`plan: ${context.session.planMode ? "on (writes and Shell blocked for this session)" : "off"}`,
|
|
296
331
|
...(context.permissionPolicy === undefined ? [] : [`permissions: ${context.permissionPolicy.mode} (${describePermissionMode(context.permissionPolicy.mode)})`]),
|
|
332
|
+
...(context.webSearch === undefined ? [] : [
|
|
333
|
+
`web search: ${context.webSearch.provider}${context.permissionPolicy === undefined || context.permissionPolicy.mode === "unrestricted" ? "" : " (each query asks for approval)"}`,
|
|
334
|
+
]),
|
|
297
335
|
...(context.permissionPolicy === undefined ? [
|
|
298
336
|
`file write grant: ${context.permissions === undefined ? "unavailable" : context.permissions.includes("file-write") ? "enabled" : "disabled"}`,
|
|
299
337
|
`Shell grant: ${context.permissions === undefined ? "unavailable" : context.permissions.includes("shell") ? "enabled" : "disabled"}`,
|
|
@@ -301,6 +339,7 @@ function describeStatus(context) {
|
|
|
301
339
|
] : []),
|
|
302
340
|
...usage,
|
|
303
341
|
`context: ${contextEstimate}`,
|
|
342
|
+
...(sessionFile !== undefined ? [`log: ${sessionFile}`] : []),
|
|
304
343
|
];
|
|
305
344
|
return `${lines.join("\n")}\n`;
|
|
306
345
|
}
|
|
@@ -325,6 +364,25 @@ async function withoutEcho(readline, read) {
|
|
|
325
364
|
internals._writeToOutput = original;
|
|
326
365
|
}
|
|
327
366
|
}
|
|
367
|
+
/** Plain-REPL Provider chooser; only the API key itself is hidden. */
|
|
368
|
+
async function readProviderChoice(readNextLine, write, providerIds, active) {
|
|
369
|
+
write("Select a Provider for /login:\n");
|
|
370
|
+
providerIds.forEach((id, index) => {
|
|
371
|
+
write(` ${index + 1}. ${id}${id === active ? " (current)" : ""}\n`);
|
|
372
|
+
});
|
|
373
|
+
write("Provider (number or name, Enter cancels): ");
|
|
374
|
+
const answer = await readNextLine();
|
|
375
|
+
if (answer === undefined)
|
|
376
|
+
return undefined;
|
|
377
|
+
const choice = answer.trim();
|
|
378
|
+
if (choice.length === 0)
|
|
379
|
+
return undefined;
|
|
380
|
+
const number = Number(choice);
|
|
381
|
+
if (Number.isInteger(number) && number >= 1 && number <= providerIds.length) {
|
|
382
|
+
return providerIds[number - 1];
|
|
383
|
+
}
|
|
384
|
+
return providerIds.find((id) => id.toLowerCase() === choice.toLowerCase());
|
|
385
|
+
}
|
|
328
386
|
async function runSignIn(readline, readNextLine, write, signIn) {
|
|
329
387
|
write(`API key for ${signIn.provider} (hidden, saved locally; Enter skips): `);
|
|
330
388
|
// The key is read from the same line source the loop uses. readline's
|
|
@@ -373,7 +431,7 @@ export async function runRepl(options) {
|
|
|
373
431
|
const menu = tuiInput === undefined ? undefined : new InputMenu(readline, options.output);
|
|
374
432
|
let dismissedLine;
|
|
375
433
|
let refreshQueued = false;
|
|
376
|
-
const menuPriority = ["provider", "model", "
|
|
434
|
+
const menuPriority = ["status", "diff", "provider", "model", "new", "resume", "help"];
|
|
377
435
|
const commands = [...BUILT_IN_COMMANDS, ...(options.extensionCommands ?? []).map((command) => command.name)]
|
|
378
436
|
.filter((name, index, all) => all.indexOf(name) === index)
|
|
379
437
|
.sort((a, b) => {
|
|
@@ -384,10 +442,9 @@ export async function runRepl(options) {
|
|
|
384
442
|
return rank(a) - rank(b);
|
|
385
443
|
})
|
|
386
444
|
.map((name) => {
|
|
387
|
-
const description =
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
: description === undefined ? "" : ` · ${description}`;
|
|
445
|
+
const description = BUILT_IN_DESCRIPTIONS[name]
|
|
446
|
+
?? options.extensionCommands?.find(command => command.name === name)?.description;
|
|
447
|
+
const suffix = description === undefined ? "" : ` · ${description}`;
|
|
391
448
|
return { value: name, label: `/${name}${suffix}` };
|
|
392
449
|
});
|
|
393
450
|
const menuItems = (line) => {
|
|
@@ -541,8 +598,9 @@ export async function runRepl(options) {
|
|
|
541
598
|
tuiInput?.setBusy(false);
|
|
542
599
|
try {
|
|
543
600
|
const width = terminalWidth({ columns: options.output.columns });
|
|
544
|
-
|
|
545
|
-
|
|
601
|
+
const wraps = context?.startsWith(WEB_SEARCH_APPROVAL_PREFIX) === true;
|
|
602
|
+
write(approvalPreview(command, width, context ?? "Shell command", wraps ? { wrap: true } : {}));
|
|
603
|
+
if (!wraps && !commandReviewable(command, width)) {
|
|
546
604
|
write(" Denied: command cannot be fully reviewed at this width. Ask for a shorter command.\n");
|
|
547
605
|
return false;
|
|
548
606
|
}
|
|
@@ -637,12 +695,22 @@ export async function runRepl(options) {
|
|
|
637
695
|
.map(message => message.content)
|
|
638
696
|
.filter(message => message.length > 0)
|
|
639
697
|
.slice(-100);
|
|
698
|
+
const footerIdentity = () => {
|
|
699
|
+
const provider = options.providers?.provider ?? options.providerName;
|
|
700
|
+
// A live Provider wins. Do not fall back to the startup model, and do
|
|
701
|
+
// not invent a name when the model or provider is still unset.
|
|
702
|
+
const model = options.providers === undefined ? options.modelName : options.providers.model;
|
|
703
|
+
if (provider === undefined || provider.length === 0 || model === undefined || model.length === 0)
|
|
704
|
+
return undefined;
|
|
705
|
+
return `${provider} · ${model}`;
|
|
706
|
+
};
|
|
640
707
|
const editor = new EditorController(output, resolveDraft, {
|
|
641
708
|
submitOnEnter: isDefault,
|
|
642
709
|
history: recent,
|
|
643
710
|
slashCommands: commands.map(item => ({ value: item.value, label: item.label })),
|
|
644
711
|
mouse: isDefault && options.tui === true && output.isTTY === true,
|
|
645
712
|
color: isDefault && terminalColorEnabled(output.isTTY === true, process.env),
|
|
713
|
+
...(isDefault && options.tui ? { footerIdentity } : {}),
|
|
646
714
|
...(isDefault && options.tui && options.permissionPolicy !== undefined ? {
|
|
647
715
|
footerStatus: () => {
|
|
648
716
|
if (activeSession.planMode)
|
|
@@ -766,6 +834,40 @@ export async function runRepl(options) {
|
|
|
766
834
|
write(`${error instanceof Error ? error.message : "Git diff unavailable"}\n`);
|
|
767
835
|
}
|
|
768
836
|
};
|
|
837
|
+
/** `/login` must name its target before any hidden key prompt. */
|
|
838
|
+
const chooseLoginProvider = async () => {
|
|
839
|
+
const providers = options.providers;
|
|
840
|
+
if (providers === undefined)
|
|
841
|
+
return undefined;
|
|
842
|
+
const ids = [...providers.providerIds()];
|
|
843
|
+
if (ids.length === 0)
|
|
844
|
+
return undefined;
|
|
845
|
+
if (tuiInput === undefined) {
|
|
846
|
+
return readProviderChoice(readNextLine, write, ids, providers.provider);
|
|
847
|
+
}
|
|
848
|
+
menu?.clear();
|
|
849
|
+
dismissedLine = undefined;
|
|
850
|
+
let resolveChoice;
|
|
851
|
+
const pending = new Promise(resolve => { resolveChoice = resolve; });
|
|
852
|
+
const output = options.output;
|
|
853
|
+
const picker = new ProviderPickerController(output, ids, providers.provider, resolveChoice);
|
|
854
|
+
const resized = () => { picker.resized(); };
|
|
855
|
+
const closed = () => { picker.cancel(); };
|
|
856
|
+
tuiInput.setRawHandler(chunk => { picker.receive(chunk); });
|
|
857
|
+
output.on("resize", resized);
|
|
858
|
+
readline.once("close", closed);
|
|
859
|
+
options.input.once("end", closed);
|
|
860
|
+
try {
|
|
861
|
+
picker.begin();
|
|
862
|
+
return await pending;
|
|
863
|
+
}
|
|
864
|
+
finally {
|
|
865
|
+
tuiInput.setRawHandler(undefined);
|
|
866
|
+
output.off("resize", resized);
|
|
867
|
+
readline.off("close", closed);
|
|
868
|
+
options.input.off("end", closed);
|
|
869
|
+
}
|
|
870
|
+
};
|
|
769
871
|
const signInAfterProviderSelection = async () => {
|
|
770
872
|
if (!options.tui || options.signIn === undefined)
|
|
771
873
|
return;
|
|
@@ -805,21 +907,31 @@ export async function runRepl(options) {
|
|
|
805
907
|
}
|
|
806
908
|
const fromComposer = line.trim() === "/compose";
|
|
807
909
|
const fromEditor = line.trim() === "/editor";
|
|
808
|
-
if (line.trim() === "/review") {
|
|
910
|
+
if (line.trim() === "/review" || line.trim() === "/diff patch") {
|
|
809
911
|
await inspectPatch();
|
|
810
912
|
if (inputClosed)
|
|
811
913
|
break;
|
|
812
914
|
showPrompt();
|
|
813
915
|
continue;
|
|
814
916
|
}
|
|
815
|
-
if (line.trim().startsWith("/gitdiff")) {
|
|
816
|
-
|
|
917
|
+
if (line.trim() === "/diff" || line.trim() === "/diff --staged" || line.trim().startsWith("/gitdiff")) {
|
|
918
|
+
const gitDiffLine = line.trim().startsWith("/gitdiff")
|
|
919
|
+
? line.trim()
|
|
920
|
+
: `/gitdiff${line.trim().slice("/diff".length)}`;
|
|
921
|
+
await inspectLiveGitDiff(gitDiffLine);
|
|
817
922
|
if (inputClosed)
|
|
818
923
|
break;
|
|
819
924
|
showPrompt();
|
|
820
925
|
continue;
|
|
821
926
|
}
|
|
822
927
|
if (fromComposer || fromEditor) {
|
|
928
|
+
if (options.inlineComposer && tuiInput !== undefined) {
|
|
929
|
+
write("The cursor-addressable multiline editor is already active in this prompt. Enter sends; Ctrl+O inserts a newline.\n");
|
|
930
|
+
if (inputClosed)
|
|
931
|
+
break;
|
|
932
|
+
showPrompt();
|
|
933
|
+
continue;
|
|
934
|
+
}
|
|
823
935
|
const draft = fromEditor ? await collectEditor() : await collectMultiline();
|
|
824
936
|
if (draft === undefined) {
|
|
825
937
|
if (inputClosed)
|
|
@@ -829,6 +941,14 @@ export async function runRepl(options) {
|
|
|
829
941
|
}
|
|
830
942
|
line = draft;
|
|
831
943
|
}
|
|
944
|
+
if (options.inlineComposer && tuiInput !== undefined) {
|
|
945
|
+
const isOutputTTY = typeof options.output === "object" && options.output !== null && "isTTY" in options.output && options.output.isTTY === true;
|
|
946
|
+
const useColor = options.tui === true && terminalColorEnabled(isOutputTTY, process.env);
|
|
947
|
+
const prefix = useColor ? "\u001b[1m\u001b[2m›\u001b[0m " : "› ";
|
|
948
|
+
const lines = line.split("\n");
|
|
949
|
+
const formattedPrompt = lines.map((l, i) => `${i === 0 ? prefix : " "}${terminalText(l)}`).join("\n");
|
|
950
|
+
write(`\n${formattedPrompt}\n\n`);
|
|
951
|
+
}
|
|
832
952
|
const outcome = fromComposer || fromEditor ? "not-a-command" : handleSlashCommand(line, {
|
|
833
953
|
session: activeSession,
|
|
834
954
|
write,
|
|
@@ -846,9 +966,11 @@ export async function runRepl(options) {
|
|
|
846
966
|
...(options.compactionEnabled === undefined ? {} : { compactionEnabled: options.compactionEnabled }),
|
|
847
967
|
...(options.providerName === undefined ? {} : { providerName: options.providerName }),
|
|
848
968
|
...(options.modelName === undefined ? {} : { modelName: options.modelName }),
|
|
969
|
+
...(options.webSearch === undefined ? {} : { webSearch: options.webSearch }),
|
|
849
970
|
...(options.permissions === undefined ? {} : { permissions: options.permissions }),
|
|
850
971
|
...(options.permissionPolicy === undefined ? {} : { permissionPolicy: options.permissionPolicy }),
|
|
851
972
|
...(options.inlineComposer === true ? { inlineComposer: true } : {}),
|
|
973
|
+
...(tuiInput !== undefined ? { interactivePicker: true } : {}),
|
|
852
974
|
...(options.sessionOptions === undefined ? {} : { sessionOptions: options.sessionOptions }),
|
|
853
975
|
});
|
|
854
976
|
if (outcome === "exit") {
|
|
@@ -906,6 +1028,31 @@ export async function runRepl(options) {
|
|
|
906
1028
|
continue;
|
|
907
1029
|
}
|
|
908
1030
|
}
|
|
1031
|
+
else if (options.providers !== undefined) {
|
|
1032
|
+
signingIn = true;
|
|
1033
|
+
let chosen;
|
|
1034
|
+
try {
|
|
1035
|
+
chosen = await chooseLoginProvider();
|
|
1036
|
+
}
|
|
1037
|
+
finally {
|
|
1038
|
+
signingIn = false;
|
|
1039
|
+
}
|
|
1040
|
+
if (chosen === undefined) {
|
|
1041
|
+
write("No Provider selected; no key was stored.\n");
|
|
1042
|
+
if (inputClosed)
|
|
1043
|
+
break;
|
|
1044
|
+
showPrompt();
|
|
1045
|
+
continue;
|
|
1046
|
+
}
|
|
1047
|
+
const selected = await options.providers.select(chosen);
|
|
1048
|
+
write(selected.message);
|
|
1049
|
+
if (!selected.ok) {
|
|
1050
|
+
if (inputClosed)
|
|
1051
|
+
break;
|
|
1052
|
+
showPrompt();
|
|
1053
|
+
continue;
|
|
1054
|
+
}
|
|
1055
|
+
}
|
|
909
1056
|
if (options.signIn !== undefined) {
|
|
910
1057
|
signingIn = true;
|
|
911
1058
|
try {
|
|
@@ -915,11 +1062,38 @@ export async function runRepl(options) {
|
|
|
915
1062
|
signingIn = false;
|
|
916
1063
|
}
|
|
917
1064
|
}
|
|
1065
|
+
if (inputClosed)
|
|
1066
|
+
break;
|
|
918
1067
|
showPrompt();
|
|
919
1068
|
continue;
|
|
920
1069
|
}
|
|
921
1070
|
if (typeof outcome === "object" && (outcome.kind === "provider" || outcome.kind === "model" || outcome.kind === "endpoint")) {
|
|
922
1071
|
if (options.providers !== undefined) {
|
|
1072
|
+
if (outcome.kind === "provider" && outcome.argument.length === 0 && tuiInput !== undefined) {
|
|
1073
|
+
signingIn = true;
|
|
1074
|
+
let chosen;
|
|
1075
|
+
try {
|
|
1076
|
+
chosen = await chooseLoginProvider();
|
|
1077
|
+
}
|
|
1078
|
+
finally {
|
|
1079
|
+
signingIn = false;
|
|
1080
|
+
}
|
|
1081
|
+
if (chosen === undefined) {
|
|
1082
|
+
write("No Provider selected; current Provider retained.\n");
|
|
1083
|
+
if (inputClosed)
|
|
1084
|
+
break;
|
|
1085
|
+
showPrompt();
|
|
1086
|
+
continue;
|
|
1087
|
+
}
|
|
1088
|
+
const selected = await options.providers.select(chosen);
|
|
1089
|
+
write(selected.message);
|
|
1090
|
+
if (selected.ok)
|
|
1091
|
+
await signInAfterProviderSelection();
|
|
1092
|
+
if (inputClosed)
|
|
1093
|
+
break;
|
|
1094
|
+
showPrompt();
|
|
1095
|
+
continue;
|
|
1096
|
+
}
|
|
923
1097
|
const selected = outcome.kind === "provider"
|
|
924
1098
|
? await options.providers.select(outcome.argument)
|
|
925
1099
|
: outcome.kind === "model" ? await options.providers.changeModel(outcome.argument)
|