@datalabrotterdam/nova-ai-cli 1.2.0-alpha.6 → 1.2.0-alpha.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +15 -0
- package/README.md +29 -3
- package/dist/{chunk-QSNU4H55.js → chunk-EOPM4RRE.js} +30 -4
- package/dist/{headless-C36E4XSQ.js → headless-QLCJRT3X.js} +1 -1
- package/dist/index.js +4 -10
- package/dist/{tui-TRADKND7.js → tui-HJQOYGHL.js} +249 -33
- package/package.json +3 -3
- package/dist/webui-L4GGMXJ3.js +0 -9
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,18 @@
|
|
|
1
|
+
# [@datalabrotterdam/nova-ai-cli-v1.2.0-alpha.7](https://github.com/Datalab-Rotterdam/nova-ai-cli/compare/@datalabrotterdam/nova-ai-cli@1.2.0-alpha.6...@datalabrotterdam/nova-ai-cli@1.2.0-alpha.7) (2026-10-24)
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
### Features
|
|
5
|
+
|
|
6
|
+
* **agent:** Claude Code compatible hooks ([227fe8b](https://github.com/Datalab-Rotterdam/nova-ai-cli/commit/227fe8b273ec2e211e9d57674e76af5c42a473bd))
|
|
7
|
+
* **agent:** install the shared Nova skills ([64139dd](https://github.com/Datalab-Rotterdam/nova-ai-cli/commit/64139dd612933343f04f165de1a9ab78832d6864))
|
|
8
|
+
* **agent:** mark and protect bundled skills ([6ef52eb](https://github.com/Datalab-Rotterdam/nova-ai-cli/commit/6ef52eb6d048f8a3b4e75686244aabcfd0f62d6e))
|
|
9
|
+
* **agent:** thinking effort for reasoning models ([8551fea](https://github.com/Datalab-Rotterdam/nova-ai-cli/commit/8551feacff3e1675f369030e20fb2b5071f0db7f))
|
|
10
|
+
* **agent:** web_search tool ([12985eb](https://github.com/Datalab-Rotterdam/nova-ai-cli/commit/12985eb9ca06d6c814d4e0162465c651889d9370))
|
|
11
|
+
* **cli:** /effort slider for thinking models ([8ca67f2](https://github.com/Datalab-Rotterdam/nova-ai-cli/commit/8ca67f2f5dcc8f6bff8c36409a2baba5d43c4a02))
|
|
12
|
+
* **cli:** remember the thinking level ([883196c](https://github.com/Datalab-Rotterdam/nova-ai-cli/commit/883196c9fdb089f4f26ac82b557cc87dfc0b1c0b))
|
|
13
|
+
* OAuth sign-in for MCP servers ([ba57c79](https://github.com/Datalab-Rotterdam/nova-ai-cli/commit/ba57c79642f902ab2bc20f59e4627fb2b774bb4a))
|
|
14
|
+
* the user's agent files as session modes ([3386de8](https://github.com/Datalab-Rotterdam/nova-ai-cli/commit/3386de88d07b0351bd1e44960fcacfef26e3d45b))
|
|
15
|
+
|
|
1
16
|
# [@datalabrotterdam/nova-ai-cli-v1.2.0-alpha.6](https://github.com/Datalab-Rotterdam/nova-ai-cli/compare/@datalabrotterdam/nova-ai-cli@1.2.0-alpha.5...@datalabrotterdam/nova-ai-cli@1.2.0-alpha.6) (2026-10-23)
|
|
2
17
|
|
|
3
18
|
|
package/README.md
CHANGED
|
@@ -79,6 +79,8 @@ row. To select text, hold Shift (Option in some macOS terminals) while dragging.
|
|
|
79
79
|
|
|
80
80
|
- **Modes** (Shift+Tab): `agent` does the work; `plan` reads the code, asks
|
|
81
81
|
questions and builds a task list but changes nothing; `ask` only answers.
|
|
82
|
+
Your own agents (`~/.nova-ai/agents/<name>.agent.md`, the format the VS Code
|
|
83
|
+
extension uses) are extra modes after these; `/mode` lists them.
|
|
82
84
|
- **Permissions**: before an edit or command you choose once, for this
|
|
83
85
|
session, always (saved as an exact rule) or deny. Ctrl+K switches between
|
|
84
86
|
asking, accepting edits, and bypass (never remembered).
|
|
@@ -95,12 +97,12 @@ row. To select text, hold Shift (Option in some macOS terminals) while dragging.
|
|
|
95
97
|
| Enter / Shift+Enter | Send / new line (Shift+Enter where the terminal supports the kitty keyboard protocol: VS Code, kitty, WezTerm, Ghostty, foot, Alacritty, recent iTerm2; Alt+Enter, Ctrl+J or a trailing `\` work everywhere) |
|
|
96
98
|
| Esc | Cancel the running turn |
|
|
97
99
|
| Ctrl+C twice | Exit; prints `nova-ai --resume <id>` |
|
|
98
|
-
| Shift+Tab | Cycle agent / ask / plan |
|
|
100
|
+
| Shift+Tab | Cycle agent / ask / plan and your own agents |
|
|
99
101
|
| Ctrl+R / Ctrl+P / Ctrl+K | Sessions / models / permission mode |
|
|
100
102
|
| Ctrl+O / Ctrl+T / Ctrl+B | Verbose tool output / tool inspector / background jobs |
|
|
101
103
|
| Alt+V | Paste a clipboard image |
|
|
102
104
|
|
|
103
|
-
Slash commands: `/help`, `/model`, `/mode`, `/permission`, `/session`,
|
|
105
|
+
Slash commands: `/help`, `/model`, `/effort` (thinking models), `/mode`, `/permission`, `/session`,
|
|
104
106
|
`/resume`, `/clear`, `/compact`, `/rewind [n]`, `/queue`, `/steer`, `/usage`,
|
|
105
107
|
`/tools`, `/mcp`, `/skills`, `/shell` and `/agent` (background jobs), `/trust`
|
|
106
108
|
and `/exit`. Details:
|
|
@@ -169,7 +171,10 @@ need ACP. Everything is documented in
|
|
|
169
171
|
and nested shells need an exact rule.
|
|
170
172
|
- **Web pages are fetched only with approval.** `fetch_url` asks every time
|
|
171
173
|
unless a rule allows that URL (a URL can carry data out), and a redirect to
|
|
172
|
-
another site needs its own approval.
|
|
174
|
+
another site needs its own approval. `web_search` asks the same way, since
|
|
175
|
+
the query goes to the search providers (You.com, DuckDuckGo; your own
|
|
176
|
+
SearXNG server first when `~/.nova-ai/settings.json` has
|
|
177
|
+
`"webSearch": { "searxngUrl": "https://…" }`).
|
|
173
178
|
- **File access stays in the workspace** (symlinks resolved); commands run in
|
|
174
179
|
their own process group with a timeout, and the whole tree is stopped on
|
|
175
180
|
cancel.
|
|
@@ -194,7 +199,22 @@ VS Code extension: settings, per-project trust, sessions and memory.
|
|
|
194
199
|
skills folders are read too). `/skills` lists them; `/skills off <name>`
|
|
195
200
|
switches one off in this project (`--global`: everywhere). Only names and
|
|
196
201
|
short descriptions go into the prompt; Nova loads a skill when it fits.
|
|
202
|
+
Nova installs its own skills for Word, Excel, PowerPoint and PDF files
|
|
203
|
+
(`nova-docx`, `nova-xlsx`, `nova-pptx`, `nova-pdf`, from
|
|
204
|
+
[nova-ai-skills](https://github.com/Datalab-Rotterdam/nova-ai-skills)) in
|
|
205
|
+
`~/.nova-ai/skills` and keeps them up to date; they need Python 3.
|
|
206
|
+
- **Hooks**: Claude Code's `hooks` format in the same settings files (a
|
|
207
|
+
repository's own only once you trust the workspace): `PreToolUse`,
|
|
208
|
+
`PostToolUse`, `UserPromptSubmit`, `SessionStart` and `Stop`, with Claude
|
|
209
|
+
Code's tool names as matchers, so hooks from `.claude/settings.json` work
|
|
210
|
+
when copied over.
|
|
197
211
|
- **MCP servers**: `.mcp.json` or `mcpServers` in `.nova-ai/settings.json`.
|
|
212
|
+
HTTP servers that use OAuth show "needs sign-in" in `/mcp`; `/mcp login
|
|
213
|
+
<server>` opens the sign-in page in your browser (tokens are kept in
|
|
214
|
+
`~/.nova-ai/mcp-auth`, refreshed automatically), `/mcp logout <server>`
|
|
215
|
+
forgets them. A server that wants a client registered by hand takes
|
|
216
|
+
`"oauth": { "clientId", "clientSecret", "scope", "callbackPort" }` in
|
|
217
|
+
`.mcp.json`.
|
|
198
218
|
|
|
199
219
|
The complete layout and rule syntax: [docs/NOVA_HOME.md](https://github.com/Datalab-Rotterdam/nova-ai-cli/blob/main/docs/NOVA_HOME.md).
|
|
200
220
|
|
|
@@ -207,6 +227,12 @@ to a text protocol for models that don't support it. When the context window
|
|
|
207
227
|
fills up, older messages are summarized automatically (`/compact` does it by
|
|
208
228
|
hand).
|
|
209
229
|
|
|
230
|
+
Thinking models are marked ◆ in the model list. For those with levels the
|
|
231
|
+
status line shows the current one (`· thinking medium`) and `/effort` opens a
|
|
232
|
+
slider (←/→, Enter), or sets one directly (`/effort high`, `/effort off`). The
|
|
233
|
+
choice is remembered like the model; a model without that level uses its own
|
|
234
|
+
default.
|
|
235
|
+
|
|
210
236
|
| Environment | |
|
|
211
237
|
|---|---|
|
|
212
238
|
| `NOVA_API_KEY`, `NOVA_MODEL` | Use this key / model instead of the stored ones |
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
// src/tui/session/tui-acp-client.ts
|
|
2
2
|
import { NOVA_NOTIFICATIONS } from "@datalabrotterdam/nova-ai-agent/acp/extensions.js";
|
|
3
|
+
import { openBrowser } from "@datalabrotterdam/nova-ai-agent/acp/auth-server.js";
|
|
3
4
|
import { readFile, writeFile, mkdir } from "fs/promises";
|
|
4
5
|
import { dirname } from "path";
|
|
5
6
|
import {
|
|
@@ -7,13 +8,11 @@ import {
|
|
|
7
8
|
terminateProcessTree
|
|
8
9
|
} from "@datalabrotterdam/nova-ai-agent/core/process-tree.js";
|
|
9
10
|
import { resolveWorkspaceFile } from "@datalabrotterdam/nova-ai-agent/core/tools/workspace-paths.js";
|
|
10
|
-
import {
|
|
11
|
-
isInteractionMode
|
|
12
|
-
} from "@datalabrotterdam/nova-ai-agent/core/interaction-modes.js";
|
|
13
11
|
import {
|
|
14
12
|
fromElicitationRequest,
|
|
15
13
|
toElicitationResponse
|
|
16
14
|
} from "@datalabrotterdam/nova-ai-agent/acp/user-questions.js";
|
|
15
|
+
import { INTERACTION_MODES } from "@datalabrotterdam/nova-ai-agent/core/interaction-modes.js";
|
|
17
16
|
var DEFAULT_OUTPUT_LIMIT = 1e5;
|
|
18
17
|
var nextMessageId = 0;
|
|
19
18
|
function uid() {
|
|
@@ -31,6 +30,8 @@ var TuiAcpClient = class {
|
|
|
31
30
|
cwd;
|
|
32
31
|
onBackgroundLifecycle;
|
|
33
32
|
onInteractionModeChange;
|
|
33
|
+
/** The modes the agent offers (built-in, then the user's agent files); set by the runner. */
|
|
34
|
+
modeIds = [...INTERACTION_MODES];
|
|
34
35
|
capabilities = {
|
|
35
36
|
fs: { readTextFile: true, writeTextFile: true },
|
|
36
37
|
terminal: true,
|
|
@@ -91,6 +92,29 @@ var TuiAcpClient = class {
|
|
|
91
92
|
appendUserMessage(text) {
|
|
92
93
|
this.appendMessage({ id: uid(), role: "user", text });
|
|
93
94
|
}
|
|
95
|
+
/** An MCP server's sign-in page: opened in the browser, and printed in case that fails. */
|
|
96
|
+
mcpAuthUrl(params) {
|
|
97
|
+
const { serverName, url } = params ?? {};
|
|
98
|
+
if (typeof serverName !== "string" || typeof url !== "string") return;
|
|
99
|
+
let page;
|
|
100
|
+
try {
|
|
101
|
+
page = new URL(url);
|
|
102
|
+
} catch {
|
|
103
|
+
return;
|
|
104
|
+
}
|
|
105
|
+
if (page.protocol !== "https:" && page.protocol !== "http:") {
|
|
106
|
+
this.appendError(`MCP server "${serverName}" sent a sign-in address that is not a web page; not opened.`);
|
|
107
|
+
return;
|
|
108
|
+
}
|
|
109
|
+
openBrowser(page.toString());
|
|
110
|
+
this.appendMessage({
|
|
111
|
+
id: uid(),
|
|
112
|
+
role: "assistant",
|
|
113
|
+
streaming: false,
|
|
114
|
+
text: `Sign in to MCP server "${serverName}" in your browser. If it did not open, visit:
|
|
115
|
+
${page}`
|
|
116
|
+
});
|
|
117
|
+
}
|
|
94
118
|
appendError(text) {
|
|
95
119
|
this.appendMessage({ id: uid(), role: "error", text });
|
|
96
120
|
}
|
|
@@ -295,7 +319,7 @@ ${err.message}`;
|
|
|
295
319
|
}
|
|
296
320
|
return;
|
|
297
321
|
case "current_mode_update":
|
|
298
|
-
if (
|
|
322
|
+
if (this.modeIds.includes(update.currentModeId)) {
|
|
299
323
|
this.onInteractionModeChange?.(update.currentModeId);
|
|
300
324
|
}
|
|
301
325
|
return;
|
|
@@ -415,6 +439,8 @@ ${err.message}`;
|
|
|
415
439
|
return this.backgroundUpdate(params);
|
|
416
440
|
case NOVA_NOTIFICATIONS.queueChanged:
|
|
417
441
|
return this.queueChanged(params);
|
|
442
|
+
case NOVA_NOTIFICATIONS.mcpAuthUrl:
|
|
443
|
+
return this.mcpAuthUrl(params);
|
|
418
444
|
default:
|
|
419
445
|
return;
|
|
420
446
|
}
|
package/dist/index.js
CHANGED
|
@@ -17,8 +17,7 @@ var MODE_FLAGS = {
|
|
|
17
17
|
"--headless": "print",
|
|
18
18
|
"--setup": "login",
|
|
19
19
|
"--version": "version",
|
|
20
|
-
"-v": "version"
|
|
21
|
-
"--web": "web"
|
|
20
|
+
"-v": "version"
|
|
22
21
|
};
|
|
23
22
|
function parseCli(argv) {
|
|
24
23
|
const first = argv[0];
|
|
@@ -34,7 +33,7 @@ function parseCli(argv) {
|
|
|
34
33
|
const mode = [...modes][0];
|
|
35
34
|
const wantsHelp = options.includes("--help") || options.includes("-h");
|
|
36
35
|
if (!mode) return wantsHelp ? { mode: "help", args: [] } : { mode: "tui", args: argv };
|
|
37
|
-
const drop = /* @__PURE__ */ new Set(["--acp", "--headless", "--setup", "--version", "-v"
|
|
36
|
+
const drop = /* @__PURE__ */ new Set(["--acp", "--headless", "--setup", "--version", "-v"]);
|
|
38
37
|
return { mode, args: argv.filter((arg, index) => !(drop.has(arg) && (end < 0 || index < end))) };
|
|
39
38
|
}
|
|
40
39
|
function versionText() {
|
|
@@ -140,7 +139,7 @@ ${MAIN_HELP}
|
|
|
140
139
|
}
|
|
141
140
|
case "print": {
|
|
142
141
|
const update = startUpdateCheck();
|
|
143
|
-
const { default: runHeadless } = await import("./headless-
|
|
142
|
+
const { default: runHeadless } = await import("./headless-QLCJRT3X.js");
|
|
144
143
|
const code = await runHeadless(command.args);
|
|
145
144
|
if (update) await noticeUpdate(update, 500);
|
|
146
145
|
return code;
|
|
@@ -153,11 +152,6 @@ ${MAIN_HELP}
|
|
|
153
152
|
const { runLogout } = await import("@datalabrotterdam/nova-ai-agent/commands/login.js");
|
|
154
153
|
return runLogout();
|
|
155
154
|
}
|
|
156
|
-
case "web": {
|
|
157
|
-
const { default: runWebUi } = await import("./webui-L4GGMXJ3.js");
|
|
158
|
-
await runWebUi();
|
|
159
|
-
return 0;
|
|
160
|
-
}
|
|
161
155
|
case "tui": {
|
|
162
156
|
let options;
|
|
163
157
|
try {
|
|
@@ -175,7 +169,7 @@ ${MAIN_HELP}
|
|
|
175
169
|
);
|
|
176
170
|
return 2;
|
|
177
171
|
}
|
|
178
|
-
const { default: runChat } = await import("./tui-
|
|
172
|
+
const { default: runChat } = await import("./tui-HJQOYGHL.js");
|
|
179
173
|
return runChat(options);
|
|
180
174
|
}
|
|
181
175
|
}
|
|
@@ -10,7 +10,7 @@ import {
|
|
|
10
10
|
import {
|
|
11
11
|
TuiAcpClient,
|
|
12
12
|
createStore
|
|
13
|
-
} from "./chunk-
|
|
13
|
+
} from "./chunk-EOPM4RRE.js";
|
|
14
14
|
|
|
15
15
|
// src/tui/index.ts
|
|
16
16
|
import { createInterface as createInterface2 } from "readline/promises";
|
|
@@ -122,11 +122,15 @@ import * as acp from "@agentclientprotocol/sdk";
|
|
|
122
122
|
import { NovaAgent } from "@datalabrotterdam/nova-ai-agent/acp/agent.js";
|
|
123
123
|
import { startInProcessAgent } from "@datalabrotterdam/nova-ai-agent/client/in-process.js";
|
|
124
124
|
import { replayHistory } from "@datalabrotterdam/nova-ai-agent/core/history.js";
|
|
125
|
-
import {
|
|
125
|
+
import {
|
|
126
|
+
saveDefaultEffort,
|
|
127
|
+
saveDefaultModel
|
|
128
|
+
} from "@datalabrotterdam/nova-ai-agent/core/credentials.js";
|
|
126
129
|
import {
|
|
127
130
|
savePermissionMode
|
|
128
131
|
} from "@datalabrotterdam/nova-ai-agent/core/policy/settings.js";
|
|
129
132
|
import { loadStoredSession } from "@datalabrotterdam/nova-ai-agent/core/sessions.js";
|
|
133
|
+
import { resolveEffort } from "@datalabrotterdam/nova-ai-agent/core/model-capabilities.js";
|
|
130
134
|
|
|
131
135
|
// src/tui/files/file-mentions.ts
|
|
132
136
|
import { statSync } from "fs";
|
|
@@ -286,6 +290,7 @@ var SessionRunner = class {
|
|
|
286
290
|
clientInfo: { name: "nova-ai-cli-tui", version: "1" }
|
|
287
291
|
});
|
|
288
292
|
this.model = credentials.defaultModel ?? process.env.NOVA_MODEL ?? "";
|
|
293
|
+
this.effort = credentials.defaultEffort ?? null;
|
|
289
294
|
this.sessionId = crypto.randomUUID();
|
|
290
295
|
this.sessionReady = Promise.resolve();
|
|
291
296
|
}
|
|
@@ -296,6 +301,10 @@ var SessionRunner = class {
|
|
|
296
301
|
title = null;
|
|
297
302
|
model;
|
|
298
303
|
interactionMode = "agent";
|
|
304
|
+
/** The modes the agent offers: the built-in ones, then the user's agent files. */
|
|
305
|
+
get modeIds() {
|
|
306
|
+
return this.acpClient.modeIds;
|
|
307
|
+
}
|
|
299
308
|
/** The agent, served in-process over a real ACP connection (tests may stub it). */
|
|
300
309
|
agent = new NovaAgent();
|
|
301
310
|
acpClient;
|
|
@@ -315,7 +324,10 @@ var SessionRunner = class {
|
|
|
315
324
|
handoffActive = false;
|
|
316
325
|
queuedAttachments = /* @__PURE__ */ new Map();
|
|
317
326
|
editingQueuedMessageId = null;
|
|
318
|
-
|
|
327
|
+
/** From the agent's model list: image input, thinking levels. */
|
|
328
|
+
modelInfo = /* @__PURE__ */ new Map();
|
|
329
|
+
/** The thinking level the user chose (saved); null: each model's default. */
|
|
330
|
+
effort;
|
|
319
331
|
contextUsageRequest = 0;
|
|
320
332
|
modelCheck = null;
|
|
321
333
|
resumeFrom(storedSessionId) {
|
|
@@ -353,6 +365,7 @@ var SessionRunner = class {
|
|
|
353
365
|
this.acpClient.muteSessionUpdates = false;
|
|
354
366
|
}
|
|
355
367
|
}).then((response) => {
|
|
368
|
+
this.applyModes(response.modes);
|
|
356
369
|
this.applyMcpStatus(response._meta);
|
|
357
370
|
this.applySkillStatus(response._meta);
|
|
358
371
|
this.agentSessionLoaded = true;
|
|
@@ -360,6 +373,7 @@ var SessionRunner = class {
|
|
|
360
373
|
void this.nova.setSessionMode({ sessionId: this.sessionId, modeId: this.interactionMode }).catch(() => {
|
|
361
374
|
});
|
|
362
375
|
this.applyPermissionMode(this.sessionId);
|
|
376
|
+
this.applyEffort(this.sessionId);
|
|
363
377
|
void this.updateContextUsage();
|
|
364
378
|
void this.refreshContextUsage().catch(() => {
|
|
365
379
|
});
|
|
@@ -482,6 +496,14 @@ var SessionRunner = class {
|
|
|
482
496
|
skillSessionStatus() {
|
|
483
497
|
return this.skills.map((skill) => ({ ...skill }));
|
|
484
498
|
}
|
|
499
|
+
/** Signs in to (or out of) an HTTP MCP server; the agent reconnects it and reports the new status. */
|
|
500
|
+
async mcpSignIn(serverName, signIn) {
|
|
501
|
+
await this.ensureSession();
|
|
502
|
+
const params = { sessionId: this.sessionId, serverName };
|
|
503
|
+
const meta = signIn ? await this.nova.mcpLogin(params) : await this.nova.mcpLogout(params);
|
|
504
|
+
this.applyMcpStatus(meta);
|
|
505
|
+
return this.mcpSessionStatus();
|
|
506
|
+
}
|
|
485
507
|
async trustWorkspace() {
|
|
486
508
|
await this.initialized;
|
|
487
509
|
await this.nova.workspaceTrust({ cwd: this.cwd, trusted: true });
|
|
@@ -581,8 +603,12 @@ var SessionRunner = class {
|
|
|
581
603
|
async listModels() {
|
|
582
604
|
await this.initialized;
|
|
583
605
|
const { models } = await this.nova.modelsList();
|
|
584
|
-
for (const model of models) this.
|
|
585
|
-
return models.map((model) => ({ id: model.id, name: model.name }));
|
|
606
|
+
for (const model of models) this.modelInfo.set(model.id, model);
|
|
607
|
+
return models.map((model) => ({ id: model.id, name: model.name, thinking: model.thinking }));
|
|
608
|
+
}
|
|
609
|
+
/** Sync, from the loaded model list: false until it has loaded. */
|
|
610
|
+
hasThinkingLevels(model = this.model) {
|
|
611
|
+
return (this.modelInfo.get(model)?.efforts.length ?? 0) > 0;
|
|
586
612
|
}
|
|
587
613
|
/**
|
|
588
614
|
* Takes the account's default model when none is set, and replaces a saved
|
|
@@ -597,7 +623,7 @@ var SessionRunner = class {
|
|
|
597
623
|
const { models, defaultModel } = await this.nova.modelsList(
|
|
598
624
|
this.model ? { preferred: this.model } : {}
|
|
599
625
|
);
|
|
600
|
-
for (const model of models) this.
|
|
626
|
+
for (const model of models) this.modelInfo.set(model.id, model);
|
|
601
627
|
if (!defaultModel || defaultModel === this.model) return;
|
|
602
628
|
const previous = this.model;
|
|
603
629
|
this.setModel(defaultModel);
|
|
@@ -610,8 +636,41 @@ var SessionRunner = class {
|
|
|
610
636
|
return this.modelCheck;
|
|
611
637
|
}
|
|
612
638
|
async supportsImageInput(model = this.model) {
|
|
613
|
-
|
|
614
|
-
|
|
639
|
+
return (await this.modelDetails(model))?.imageInput ?? false;
|
|
640
|
+
}
|
|
641
|
+
/** What the agent's model list says about `model`; undefined when it isn't listed. */
|
|
642
|
+
async modelDetails(model = this.model) {
|
|
643
|
+
if (!this.modelInfo.has(model)) await this.listModels();
|
|
644
|
+
return this.modelInfo.get(model);
|
|
645
|
+
}
|
|
646
|
+
/**
|
|
647
|
+
* The level the current model thinks at, as the agent picks it (the
|
|
648
|
+
* chosen one when the model has it); null for a model without levels
|
|
649
|
+
* or before the model list has loaded.
|
|
650
|
+
*/
|
|
651
|
+
currentEffort() {
|
|
652
|
+
const info = this.modelInfo.get(this.model);
|
|
653
|
+
if (!info?.efforts.length) return null;
|
|
654
|
+
return resolveEffort(
|
|
655
|
+
{ reasoning_efforts: info.efforts, reasoning_default: info.reasoningDefault },
|
|
656
|
+
this.effort
|
|
657
|
+
) ?? null;
|
|
658
|
+
}
|
|
659
|
+
/** The current model's levels from the loaded list; empty when it has none. */
|
|
660
|
+
modelEffortLevels() {
|
|
661
|
+
return this.modelInfo.get(this.model)?.efforts ?? [];
|
|
662
|
+
}
|
|
663
|
+
/** Saved and kept across models and sessions; a model without the level uses its default. */
|
|
664
|
+
setEffort(level) {
|
|
665
|
+
this.effort = level;
|
|
666
|
+
saveDefaultEffort(level);
|
|
667
|
+
if (this.agentSessionLoaded) this.applyEffort(this.sessionId);
|
|
668
|
+
}
|
|
669
|
+
applyEffort(sessionId) {
|
|
670
|
+
if (!this.effort) return;
|
|
671
|
+
void this.nova.setSessionConfigOption({ sessionId, configId: "thought_level", value: this.effort }).catch(() => {
|
|
672
|
+
this.acpClient.appendError("Could not change the thinking level.");
|
|
673
|
+
});
|
|
615
674
|
}
|
|
616
675
|
async compactContext() {
|
|
617
676
|
if (this.store.getState().busy || this.promptActive) {
|
|
@@ -890,11 +949,12 @@ var SessionRunner = class {
|
|
|
890
949
|
}
|
|
891
950
|
async createSession() {
|
|
892
951
|
await this.initialized;
|
|
893
|
-
const { sessionId, _meta } = await this.nova.newSession({
|
|
952
|
+
const { sessionId, _meta, modes } = await this.nova.newSession({
|
|
894
953
|
cwd: this.cwd,
|
|
895
954
|
mcpServers: []
|
|
896
955
|
});
|
|
897
956
|
this.sessionId = sessionId;
|
|
957
|
+
this.applyModes(modes);
|
|
898
958
|
this.applyMcpStatus(_meta);
|
|
899
959
|
this.applySkillStatus(_meta);
|
|
900
960
|
this.agentSessionLoaded = true;
|
|
@@ -902,6 +962,7 @@ var SessionRunner = class {
|
|
|
902
962
|
void this.nova.setSessionMode({ sessionId, modeId: this.interactionMode }).catch(() => {
|
|
903
963
|
});
|
|
904
964
|
this.applyPermissionMode(sessionId);
|
|
965
|
+
this.applyEffort(sessionId);
|
|
905
966
|
void this.updateContextUsage();
|
|
906
967
|
void this.refreshContextUsage().catch(() => {
|
|
907
968
|
});
|
|
@@ -937,7 +998,13 @@ var SessionRunner = class {
|
|
|
937
998
|
const failures = Array.isArray(record.failures) ? record.failures.flatMap((entry) => {
|
|
938
999
|
if (!entry || typeof entry !== "object") return [];
|
|
939
1000
|
const item = entry;
|
|
940
|
-
return typeof item.serverName === "string" && typeof item.message === "string" ? [
|
|
1001
|
+
return typeof item.serverName === "string" && typeof item.message === "string" ? [
|
|
1002
|
+
{
|
|
1003
|
+
serverName: item.serverName,
|
|
1004
|
+
message: item.message,
|
|
1005
|
+
...item.needsAuth === true ? { needsAuth: true } : {}
|
|
1006
|
+
}
|
|
1007
|
+
] : [];
|
|
941
1008
|
}) : [];
|
|
942
1009
|
this.mcpStatus = { configured, connected, failures };
|
|
943
1010
|
}
|
|
@@ -981,6 +1048,10 @@ var SessionRunner = class {
|
|
|
981
1048
|
if (this.sessionReady === ready) this.sessionReady = this.createSession();
|
|
982
1049
|
}
|
|
983
1050
|
}
|
|
1051
|
+
applyModes(modes) {
|
|
1052
|
+
if (modes?.availableModes.length) this.acpClient.modeIds = modes.availableModes.map((mode) => mode.id);
|
|
1053
|
+
if (!this.modeIds.includes(this.interactionMode)) this.applyInteractionMode("agent", "Mode: agent");
|
|
1054
|
+
}
|
|
984
1055
|
applyInteractionMode(mode, statusLine) {
|
|
985
1056
|
this.interactionMode = mode;
|
|
986
1057
|
this.store.setState({ interactionMode: mode, statusLine });
|
|
@@ -1450,6 +1521,7 @@ import { Fragment as Fragment2, jsx as jsx2, jsxs as jsxs2 } from "react/jsx-run
|
|
|
1450
1521
|
function StatusLine({
|
|
1451
1522
|
ui,
|
|
1452
1523
|
model,
|
|
1524
|
+
effort = null,
|
|
1453
1525
|
mcp,
|
|
1454
1526
|
scroll = 0
|
|
1455
1527
|
}) {
|
|
@@ -1460,6 +1532,7 @@ function StatusLine({
|
|
|
1460
1532
|
ui.statusLine ? /* @__PURE__ */ jsx2(Text2, { color: palette.muted, children: ui.statusLine }) : null,
|
|
1461
1533
|
/* @__PURE__ */ jsxs2(Text2, { wrap: "truncate-end", children: [
|
|
1462
1534
|
/* @__PURE__ */ jsx2(Text2, { color: palette.muted, children: model }),
|
|
1535
|
+
effort ? /* @__PURE__ */ jsx2(Text2, { color: palette.muted, children: ` \xB7 thinking ${effort.toLowerCase()}` }) : null,
|
|
1463
1536
|
separator,
|
|
1464
1537
|
ui.interactionMode,
|
|
1465
1538
|
separator,
|
|
@@ -1802,18 +1875,14 @@ function PlanView({
|
|
|
1802
1875
|
] });
|
|
1803
1876
|
}
|
|
1804
1877
|
|
|
1805
|
-
// src/tui/ink/controller.ts
|
|
1806
|
-
import { INTERACTION_MODES } from "@datalabrotterdam/nova-ai-agent/core/interaction-modes.js";
|
|
1807
|
-
|
|
1808
1878
|
// src/tui/commands/builtins.ts
|
|
1809
|
-
import { isInteractionMode } from "@datalabrotterdam/nova-ai-agent/core/interaction-modes.js";
|
|
1810
1879
|
var permissionModes = ["ask", "acceptEdits", "bypassAll"];
|
|
1811
1880
|
var builtinCommands = [
|
|
1812
1881
|
{
|
|
1813
1882
|
name: "help",
|
|
1814
1883
|
description: "Show available commands",
|
|
1815
1884
|
run(ctx) {
|
|
1816
|
-
const lines = builtinCommands.map((c) => `/${c.name} \u2014 ${c.description}`);
|
|
1885
|
+
const lines = builtinCommands.filter((c) => c.available?.(ctx) ?? true).map((c) => `/${c.name} \u2014 ${c.description}`);
|
|
1817
1886
|
ctx.print(lines.join("\n"));
|
|
1818
1887
|
}
|
|
1819
1888
|
},
|
|
@@ -1894,15 +1963,15 @@ var builtinCommands = [
|
|
|
1894
1963
|
},
|
|
1895
1964
|
{
|
|
1896
1965
|
name: "mode",
|
|
1897
|
-
description: "Switch interaction mode (/mode agent|ask|plan)",
|
|
1966
|
+
description: "Switch interaction mode (/mode agent|ask|plan|<agent file>)",
|
|
1898
1967
|
run(ctx, args) {
|
|
1899
1968
|
const mode = args.trim();
|
|
1900
1969
|
if (!mode) {
|
|
1901
|
-
ctx.print(`Current mode: ${ctx.getInteractionMode()}`);
|
|
1970
|
+
ctx.print(`Current mode: ${ctx.getInteractionMode()} (available: ${ctx.getInteractionModes().join(", ")})`);
|
|
1902
1971
|
return;
|
|
1903
1972
|
}
|
|
1904
|
-
if (!
|
|
1905
|
-
ctx.print(
|
|
1973
|
+
if (!ctx.getInteractionModes().includes(mode)) {
|
|
1974
|
+
ctx.print(`Usage: /mode ${ctx.getInteractionModes().join("|")}`);
|
|
1906
1975
|
return;
|
|
1907
1976
|
}
|
|
1908
1977
|
ctx.setInteractionMode(mode);
|
|
@@ -1944,6 +2013,16 @@ var builtinCommands = [
|
|
|
1944
2013
|
ctx.openModelPicker();
|
|
1945
2014
|
}
|
|
1946
2015
|
},
|
|
2016
|
+
{
|
|
2017
|
+
name: "effort",
|
|
2018
|
+
description: "Set how hard the model thinks (/effort <level>, or no args for a slider)",
|
|
2019
|
+
available: (ctx) => ctx.hasThinkingLevels(),
|
|
2020
|
+
async run(ctx, args) {
|
|
2021
|
+
const level = args.trim();
|
|
2022
|
+
if (level) ctx.print(await ctx.setEffort(level));
|
|
2023
|
+
else await ctx.openEffortPicker();
|
|
2024
|
+
}
|
|
2025
|
+
},
|
|
1947
2026
|
{
|
|
1948
2027
|
name: "tools",
|
|
1949
2028
|
description: "Inspect tool calls from this session",
|
|
@@ -1953,9 +2032,20 @@ var builtinCommands = [
|
|
|
1953
2032
|
},
|
|
1954
2033
|
{
|
|
1955
2034
|
name: "mcp",
|
|
1956
|
-
description: "Inspect
|
|
1957
|
-
run(ctx) {
|
|
1958
|
-
|
|
2035
|
+
description: "Inspect MCP servers, or sign in to one: /mcp login|logout <server>",
|
|
2036
|
+
async run(ctx, args) {
|
|
2037
|
+
const [action, ...rest] = args.trim().split(/\s+/).filter(Boolean);
|
|
2038
|
+
const name = rest.join(" ");
|
|
2039
|
+
if (!action) {
|
|
2040
|
+
await ctx.openMcpInspector();
|
|
2041
|
+
return;
|
|
2042
|
+
}
|
|
2043
|
+
if (action !== "login" && action !== "logout" || !name) {
|
|
2044
|
+
ctx.print("Usage: /mcp, or /mcp login|logout <server>");
|
|
2045
|
+
return;
|
|
2046
|
+
}
|
|
2047
|
+
if (action === "login") ctx.print(`Signing in to "${name}"\u2026 (waits up to 5 minutes for the browser)`);
|
|
2048
|
+
ctx.print(await ctx.mcpSignIn(name, action === "login"));
|
|
1959
2049
|
}
|
|
1960
2050
|
},
|
|
1961
2051
|
{
|
|
@@ -2142,8 +2232,8 @@ function formatJob(job) {
|
|
|
2142
2232
|
|
|
2143
2233
|
// src/tui/commands/index.ts
|
|
2144
2234
|
var COMMANDS = [...builtinCommands];
|
|
2145
|
-
function allCommands() {
|
|
2146
|
-
return COMMANDS;
|
|
2235
|
+
function allCommands(ctx) {
|
|
2236
|
+
return ctx ? COMMANDS.filter((c) => c.available?.(ctx) ?? true) : COMMANDS;
|
|
2147
2237
|
}
|
|
2148
2238
|
function findCommand(name) {
|
|
2149
2239
|
return COMMANDS.find((c) => c.name === name);
|
|
@@ -2361,7 +2451,8 @@ var WorkspaceAutocompleteProvider = class {
|
|
|
2361
2451
|
const mention = extractMentionPrefix(before);
|
|
2362
2452
|
if (mention) return this.mentionSuggestions(mention, options.signal);
|
|
2363
2453
|
if (cursorLine === 0 && before.startsWith("/") && !/\s/.test(before)) {
|
|
2364
|
-
const
|
|
2454
|
+
const commands = typeof this.commands === "function" ? this.commands() : this.commands;
|
|
2455
|
+
const items2 = filterCommands(commands, before.slice(1));
|
|
2365
2456
|
return items2.length ? { items: items2, prefix: before } : null;
|
|
2366
2457
|
}
|
|
2367
2458
|
if (!options.force) return null;
|
|
@@ -2884,6 +2975,40 @@ var SelectionState = class {
|
|
|
2884
2975
|
return "changed";
|
|
2885
2976
|
}
|
|
2886
2977
|
};
|
|
2978
|
+
var SliderState = class {
|
|
2979
|
+
constructor(levels, selected, onSelect, onCancel) {
|
|
2980
|
+
this.levels = levels;
|
|
2981
|
+
this.selected = selected;
|
|
2982
|
+
this.onSelect = onSelect;
|
|
2983
|
+
this.onCancel = onCancel;
|
|
2984
|
+
}
|
|
2985
|
+
levels;
|
|
2986
|
+
selected;
|
|
2987
|
+
onSelect;
|
|
2988
|
+
onCancel;
|
|
2989
|
+
handleKey(input, key) {
|
|
2990
|
+
if (isCancel(input, key)) {
|
|
2991
|
+
this.onCancel();
|
|
2992
|
+
return "closed";
|
|
2993
|
+
}
|
|
2994
|
+
if (key.return) {
|
|
2995
|
+
const level = this.levels[this.selected];
|
|
2996
|
+
if (!level) return "ignored";
|
|
2997
|
+
this.onSelect(level);
|
|
2998
|
+
return "closed";
|
|
2999
|
+
}
|
|
3000
|
+
const last = this.levels.length - 1;
|
|
3001
|
+
let next = this.selected;
|
|
3002
|
+
if (key.leftArrow || key.downArrow) next = Math.max(0, this.selected - 1);
|
|
3003
|
+
else if (key.rightArrow || key.upArrow) next = Math.min(last, this.selected + 1);
|
|
3004
|
+
else if (key.home) next = 0;
|
|
3005
|
+
else if (key.end) next = last;
|
|
3006
|
+
else return "ignored";
|
|
3007
|
+
if (next === this.selected) return "ignored";
|
|
3008
|
+
this.selected = next;
|
|
3009
|
+
return "changed";
|
|
3010
|
+
}
|
|
3011
|
+
};
|
|
2887
3012
|
var ScrollState = class {
|
|
2888
3013
|
constructor(onClose) {
|
|
2889
3014
|
this.onClose = onClose;
|
|
@@ -3132,6 +3257,9 @@ function permissionKey(request, input, key) {
|
|
|
3132
3257
|
return "closed";
|
|
3133
3258
|
}
|
|
3134
3259
|
|
|
3260
|
+
// src/tui/ink/controller.ts
|
|
3261
|
+
import { effortLabel } from "@datalabrotterdam/nova-ai-agent/core/model-capabilities.js";
|
|
3262
|
+
|
|
3135
3263
|
// src/tui/ink/editor/prompt-editor.ts
|
|
3136
3264
|
var MAX_HISTORY = 200;
|
|
3137
3265
|
var PromptEditor = class {
|
|
@@ -3965,8 +4093,8 @@ var TuiController = class {
|
|
|
3965
4093
|
"Models",
|
|
3966
4094
|
models.map((model) => ({
|
|
3967
4095
|
value: model.id,
|
|
3968
|
-
label: model.id,
|
|
3969
|
-
description: model.name
|
|
4096
|
+
label: model.thinking ? `${model.id} \u25C6` : model.id,
|
|
4097
|
+
description: [model.name, model.thinking ? "thinking" : null].filter(Boolean).join(" \xB7 ") || void 0
|
|
3970
4098
|
})),
|
|
3971
4099
|
"model-picker",
|
|
3972
4100
|
(model) => this.setModel(model)
|
|
@@ -3975,6 +4103,58 @@ var TuiController = class {
|
|
|
3975
4103
|
this.showText("Models", errorMessage(error, "Failed to list models."));
|
|
3976
4104
|
}
|
|
3977
4105
|
};
|
|
4106
|
+
effortName() {
|
|
4107
|
+
const level = this.runner.currentEffort();
|
|
4108
|
+
const efforts = level ? this.runner.modelEffortLevels() : [];
|
|
4109
|
+
return level ? effortLabel(level, efforts) : null;
|
|
4110
|
+
}
|
|
4111
|
+
/** The selected model's levels and current one, or why it has none. */
|
|
4112
|
+
async effortLevels() {
|
|
4113
|
+
let info;
|
|
4114
|
+
try {
|
|
4115
|
+
info = await this.runner.modelDetails();
|
|
4116
|
+
} catch (error) {
|
|
4117
|
+
return errorMessage(error, "Could not load the model list.");
|
|
4118
|
+
}
|
|
4119
|
+
const model = this.runner.model || "This model";
|
|
4120
|
+
if (!info?.efforts.length) {
|
|
4121
|
+
return info?.thinking ? `${model} thinks at a fixed depth; there is no level to choose.` : `${model} is not a thinking model; /model lists the ones marked \u25C6.`;
|
|
4122
|
+
}
|
|
4123
|
+
return { efforts: info.efforts, current: this.runner.currentEffort() ?? info.efforts[0] };
|
|
4124
|
+
}
|
|
4125
|
+
/** /effort: a slider over the selected model's levels. */
|
|
4126
|
+
openEffort = async () => {
|
|
4127
|
+
const levels = await this.effortLevels();
|
|
4128
|
+
if (typeof levels === "string") {
|
|
4129
|
+
this.showText("Thinking effort", levels);
|
|
4130
|
+
return;
|
|
4131
|
+
}
|
|
4132
|
+
const state = new SliderState(
|
|
4133
|
+
levels.efforts.map((value) => ({ value, label: effortLabel(value, levels.efforts) })),
|
|
4134
|
+
Math.max(0, levels.efforts.indexOf(levels.current)),
|
|
4135
|
+
(item) => {
|
|
4136
|
+
this.closeOverlay();
|
|
4137
|
+
this.runner.setEffort(item.value);
|
|
4138
|
+
this.store.setState({ statusLine: `Thinking effort: ${item.label}.` });
|
|
4139
|
+
},
|
|
4140
|
+
this.closeOverlay
|
|
4141
|
+
);
|
|
4142
|
+
this.showOverlay({ kind: "slider", title: `Thinking effort \xB7 ${this.runner.model}`, state });
|
|
4143
|
+
};
|
|
4144
|
+
/** /effort <level>: by id ("high") or name ("Extra high", "off"). */
|
|
4145
|
+
setEffort = async (level) => {
|
|
4146
|
+
const levels = await this.effortLevels();
|
|
4147
|
+
if (typeof levels === "string") return levels;
|
|
4148
|
+
const wanted = level.trim().toLowerCase();
|
|
4149
|
+
const value = levels.efforts.find(
|
|
4150
|
+
(candidate) => candidate === wanted || effortLabel(candidate, levels.efforts).toLowerCase() === wanted
|
|
4151
|
+
);
|
|
4152
|
+
const names = levels.efforts.map((candidate) => effortLabel(candidate, levels.efforts).toLowerCase());
|
|
4153
|
+
if (!value) return `${this.runner.model} has these levels: ${names.join(", ")}.`;
|
|
4154
|
+
this.runner.setEffort(value);
|
|
4155
|
+
this.refresh();
|
|
4156
|
+
return `Thinking effort: ${effortLabel(value, levels.efforts)}.`;
|
|
4157
|
+
};
|
|
3978
4158
|
openPermissionModes = () => {
|
|
3979
4159
|
const modes = [
|
|
3980
4160
|
{ value: "ask", label: "ask", description: "Prompt before tools make changes" },
|
|
@@ -4061,7 +4241,7 @@ status: failed - ${failure.message}`);
|
|
|
4061
4241
|
`Skills (${on} on, ${skills.length - on} off)`,
|
|
4062
4242
|
skills.length ? [
|
|
4063
4243
|
...skills.map(
|
|
4064
|
-
(skill) => `${skill.name} [${skill.source}]${skill.enabled ? "" : " (off)"}
|
|
4244
|
+
(skill) => `${skill.name} [${skill.bundledBy ? `bundled with ${skill.bundledBy}` : skill.source}]${skill.enabled ? "" : " (off)"}
|
|
4065
4245
|
${skill.description}
|
|
4066
4246
|
${skill.path}`
|
|
4067
4247
|
),
|
|
@@ -4132,8 +4312,8 @@ ${skill.path}`
|
|
|
4132
4312
|
this.runner.setInteractionMode(mode);
|
|
4133
4313
|
}
|
|
4134
4314
|
cycleInteractionMode() {
|
|
4135
|
-
const
|
|
4136
|
-
this.setInteractionMode(
|
|
4315
|
+
const modes = this.runner.modeIds;
|
|
4316
|
+
this.setInteractionMode(modes[(modes.indexOf(this.runner.interactionMode) + 1) % modes.length]);
|
|
4137
4317
|
}
|
|
4138
4318
|
appendAssistant(text) {
|
|
4139
4319
|
this.store.setState((state) => ({
|
|
@@ -4175,13 +4355,28 @@ ${skill.path}`
|
|
|
4175
4355
|
},
|
|
4176
4356
|
openSessionSwitcher: this.openSessions,
|
|
4177
4357
|
openModelPicker: () => void this.openModels(),
|
|
4358
|
+
hasThinkingLevels: () => this.runner.hasThinkingLevels(),
|
|
4359
|
+
openEffortPicker: () => this.openEffort(),
|
|
4360
|
+
setEffort: (level) => this.setEffort(level),
|
|
4178
4361
|
getModel: () => this.runner.model,
|
|
4179
4362
|
setModel: (model) => this.setModel(model),
|
|
4180
4363
|
getInteractionMode: () => this.runner.interactionMode,
|
|
4364
|
+
getInteractionModes: () => this.runner.modeIds,
|
|
4181
4365
|
setInteractionMode: (mode) => this.setInteractionMode(mode),
|
|
4182
4366
|
getPermissionMode: () => this.store.getState().permissionMode,
|
|
4183
4367
|
setPermissionMode: (mode) => this.setPermissionMode(mode),
|
|
4184
4368
|
openPermissionPicker: this.openPermissionModes,
|
|
4369
|
+
mcpSignIn: async (serverName, signIn) => {
|
|
4370
|
+
try {
|
|
4371
|
+
const status = await this.runner.mcpSignIn(serverName, signIn);
|
|
4372
|
+
this.refresh();
|
|
4373
|
+
const failure = status.failures.find((item) => item.serverName === serverName);
|
|
4374
|
+
if (!signIn) return `Signed out of "${serverName}".`;
|
|
4375
|
+
return failure ? `Signed in, but "${serverName}" did not connect: ${failure.message}` : `Signed in to "${serverName}"; its tools are available from the next message.`;
|
|
4376
|
+
} catch (error) {
|
|
4377
|
+
return errorMessage(error, signIn ? "Sign-in failed." : "Sign-out failed.");
|
|
4378
|
+
}
|
|
4379
|
+
},
|
|
4185
4380
|
trustWorkspace: async () => {
|
|
4186
4381
|
try {
|
|
4187
4382
|
await this.runner.trustWorkspace();
|
|
@@ -4288,7 +4483,7 @@ ${skill.path}`
|
|
|
4288
4483
|
const fdPath = resolveFdPath();
|
|
4289
4484
|
editor.setAutocompleteProvider(
|
|
4290
4485
|
new WorkspaceAutocompleteProvider(
|
|
4291
|
-
allCommands().map((command) => ({
|
|
4486
|
+
() => allCommands(this.commandContext).map((command) => ({
|
|
4292
4487
|
name: command.name,
|
|
4293
4488
|
description: command.description
|
|
4294
4489
|
})),
|
|
@@ -4329,6 +4524,7 @@ ${skill.path}`
|
|
|
4329
4524
|
overlay: this.overlay,
|
|
4330
4525
|
question: this.questionState(ui.pendingQuestion),
|
|
4331
4526
|
model: this.runner.model,
|
|
4527
|
+
effort: this.effortName(),
|
|
4332
4528
|
mcp: mcpCounts(this.runner),
|
|
4333
4529
|
expandedTools: this.expandedTools,
|
|
4334
4530
|
exiting: this.exiting,
|
|
@@ -4373,7 +4569,7 @@ function formatMcpServer(server, status) {
|
|
|
4373
4569
|
}
|
|
4374
4570
|
const failure = status.failures.find((item) => item.serverName === server.name);
|
|
4375
4571
|
lines.push(
|
|
4376
|
-
failure ? `status: failed - ${failure.message}` : status.connected.includes(server.name) ? "status: connected" : "status: configured; connection starts with the ACP session"
|
|
4572
|
+
failure?.needsAuth ? `status: needs sign-in - /mcp login ${server.name}` : failure ? `status: failed - ${failure.message}` : status.connected.includes(server.name) ? "status: connected" : "status: configured; connection starts with the ACP session"
|
|
4377
4573
|
);
|
|
4378
4574
|
return lines.join("\n");
|
|
4379
4575
|
}
|
|
@@ -4412,6 +4608,25 @@ function Frame({ title, children, footer }) {
|
|
|
4412
4608
|
footer ? /* @__PURE__ */ jsx5(Text5, { color: palette.faint, wrap: "truncate-end", children: footer }) : null
|
|
4413
4609
|
] });
|
|
4414
4610
|
}
|
|
4611
|
+
function SliderDialog({
|
|
4612
|
+
title,
|
|
4613
|
+
state,
|
|
4614
|
+
isActive = true
|
|
4615
|
+
}) {
|
|
4616
|
+
useDialogInput((input, key) => state.handleKey(input, key), isActive);
|
|
4617
|
+
const current = state.levels[state.selected];
|
|
4618
|
+
return /* @__PURE__ */ jsxs5(Frame, { title, footer: "\u2190\u2192 adjust \xB7 Enter apply \xB7 Esc cancel", children: [
|
|
4619
|
+
/* @__PURE__ */ jsx5(Text5, { wrap: "truncate-end", children: state.levels.map((level, index) => {
|
|
4620
|
+
const selected = index === state.selected;
|
|
4621
|
+
return /* @__PURE__ */ jsxs5(Text5, { children: [
|
|
4622
|
+
index > 0 ? /* @__PURE__ */ jsx5(Text5, { color: palette.faint, children: " \u2500\u2500 " }) : null,
|
|
4623
|
+
/* @__PURE__ */ jsx5(Text5, { color: selected ? palette.primary : palette.faint, children: selected ? "\u25CF " : "\u25CB " }),
|
|
4624
|
+
/* @__PURE__ */ jsx5(Text5, { bold: selected, color: selected ? void 0 : palette.muted, children: level.label })
|
|
4625
|
+
] }, level.value);
|
|
4626
|
+
}) }),
|
|
4627
|
+
current?.description ? /* @__PURE__ */ jsx5(Text5, { color: palette.muted, children: current.description }) : null
|
|
4628
|
+
] });
|
|
4629
|
+
}
|
|
4415
4630
|
function SelectionDialog({
|
|
4416
4631
|
title,
|
|
4417
4632
|
state,
|
|
@@ -4800,7 +5015,7 @@ function App({ controller }) {
|
|
|
4800
5015
|
),
|
|
4801
5016
|
/* @__PURE__ */ jsxs6(Box6, { ref: bottomRef, flexDirection: "column", flexShrink: 0, children: [
|
|
4802
5017
|
/* @__PURE__ */ jsx6(Surface, { controller, view, rows: Math.max(6, rows - 4), columns }),
|
|
4803
|
-
/* @__PURE__ */ jsx6(StatusLine, { ui: view.ui, model: view.model, mcp: view.mcp, scroll: view.scroll })
|
|
5018
|
+
/* @__PURE__ */ jsx6(StatusLine, { ui: view.ui, model: view.model, effort: view.effort, mcp: view.mcp, scroll: view.scroll })
|
|
4804
5019
|
] })
|
|
4805
5020
|
] });
|
|
4806
5021
|
}
|
|
@@ -4843,6 +5058,7 @@ function Surface({
|
|
|
4843
5058
|
const overlay = view.overlay;
|
|
4844
5059
|
if (overlay?.kind === "selection")
|
|
4845
5060
|
return /* @__PURE__ */ jsx6(SelectionDialog, { title: overlay.title, state: overlay.state, height: rows });
|
|
5061
|
+
if (overlay?.kind === "slider") return /* @__PURE__ */ jsx6(SliderDialog, { title: overlay.title, state: overlay.state });
|
|
4846
5062
|
if (overlay?.kind === "text")
|
|
4847
5063
|
return /* @__PURE__ */ jsx6(
|
|
4848
5064
|
ScrollPanel,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@datalabrotterdam/nova-ai-cli",
|
|
3
|
-
"version": "1.2.0-alpha.
|
|
3
|
+
"version": "1.2.0-alpha.7",
|
|
4
4
|
"description": "Agentic coding with DataLab Rotterdam's Nova AI in your terminal: interactive TUI, headless mode, and an ACP agent for editors",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"author": "DataLab Rotterdam",
|
|
@@ -39,7 +39,7 @@
|
|
|
39
39
|
},
|
|
40
40
|
"dependencies": {
|
|
41
41
|
"@agentclientprotocol/sdk": "^0.29.0",
|
|
42
|
-
"@datalabrotterdam/nova-ai-agent": "^1.0.0-alpha.
|
|
42
|
+
"@datalabrotterdam/nova-ai-agent": "^1.0.0-alpha.4",
|
|
43
43
|
"chalk": "^5.3.0",
|
|
44
44
|
"cli-highlight": "^2.1.11",
|
|
45
45
|
"diff": "^9.0.0",
|
|
@@ -76,6 +76,6 @@
|
|
|
76
76
|
]
|
|
77
77
|
},
|
|
78
78
|
"devDependencies": {
|
|
79
|
-
"@datalabrotterdam/nova-sdk": "
|
|
79
|
+
"@datalabrotterdam/nova-sdk": "1.4.0-k8s.3"
|
|
80
80
|
}
|
|
81
81
|
}
|
package/dist/webui-L4GGMXJ3.js
DELETED
|
@@ -1,9 +0,0 @@
|
|
|
1
|
-
// src/webui/index.ts
|
|
2
|
-
async function runWebUi() {
|
|
3
|
-
console.log("nova-ai --web is a work in progress \u2014 the browser UI isn't built yet.");
|
|
4
|
-
console.log("Use `nova-ai` for the terminal UI, `nova-ai -p` headless, or `nova-ai --acp` for editors.");
|
|
5
|
-
}
|
|
6
|
-
var webui_default = runWebUi;
|
|
7
|
-
export {
|
|
8
|
-
webui_default as default
|
|
9
|
-
};
|