@xingjisoft/neutrino 0.6.0 → 0.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +6 -1
- package/assets/fast-planner.txt +57 -0
- package/assets/fast-replanner.txt +28 -0
- package/dist/agents.d.ts +25 -0
- package/dist/app.d.ts +206 -0
- package/dist/app.js +15 -0
- package/dist/background.d.ts +96 -0
- package/dist/bin.d.ts +2 -0
- package/dist/bin.js +40 -3
- package/dist/browser.d.ts +18 -0
- package/dist/clipboard.d.ts +16 -0
- package/dist/colors.d.ts +29 -0
- package/dist/compact.d.ts +52 -0
- package/dist/config.d.ts +221 -0
- package/dist/config.js +22 -7
- package/dist/cwd.d.ts +19 -0
- package/dist/fast/artifacts.d.ts +26 -0
- package/dist/fast/artifacts.js +109 -0
- package/dist/fast/engine.d.ts +78 -0
- package/dist/fast/engine.js +657 -0
- package/dist/fast/evidence.d.ts +21 -0
- package/dist/fast/evidence.js +68 -0
- package/dist/fast/planner.d.ts +10 -0
- package/dist/fast/planner.js +51 -0
- package/dist/fast/pool.d.ts +17 -0
- package/dist/fast/pool.js +78 -0
- package/dist/fast/scheduler.d.ts +54 -0
- package/dist/fast/scheduler.js +383 -0
- package/dist/fast/types.d.ts +100 -0
- package/dist/fast/types.js +21 -0
- package/dist/fast/worker.d.ts +23 -0
- package/dist/fast/worker.js +185 -0
- package/dist/header.d.ts +3 -0
- package/dist/index.d.ts +5 -0
- package/dist/llm/approval.d.ts +204 -0
- package/dist/llm/approval.js +14 -3
- package/dist/llm/client.d.ts +118 -0
- package/dist/llm/client.js +57 -8
- package/dist/llm/codeExecutor.d.ts +14 -0
- package/dist/llm/codemode.d.ts +15 -0
- package/dist/llm/context.d.ts +29 -0
- package/dist/llm/context.js +17 -4
- package/dist/llm/diagnostics.d.ts +91 -0
- package/dist/llm/diagnostics.js +242 -0
- package/dist/llm/plan.d.ts +148 -0
- package/dist/llm/retry.d.ts +80 -0
- package/dist/llm/risk.d.ts +21 -0
- package/dist/llm/risk.js +7 -0
- package/dist/llm/runtime.d.ts +189 -0
- package/dist/llm/runtime.js +178 -24
- package/dist/llm/search.d.ts +2 -0
- package/dist/llm/searchNetwork.d.ts +8 -0
- package/dist/llm/subagent.d.ts +112 -0
- package/dist/llm/subagent.js +25 -12
- package/dist/llm/tools.d.ts +20 -0
- package/dist/llm/tools.js +4 -1
- package/dist/llm/workspace.d.ts +6 -0
- package/dist/llm/workspace.js +271 -0
- package/dist/markdown.d.ts +58 -0
- package/dist/migration.d.ts +8 -0
- package/dist/persistence.d.ts +14 -0
- package/dist/plugins/catalog.d.ts +20 -0
- package/dist/plugins/catalog.js +45 -0
- package/dist/plugins/cli.d.ts +1 -0
- package/dist/plugins/cli.js +106 -0
- package/dist/plugins/host.d.ts +121 -0
- package/dist/plugins/host.js +450 -0
- package/dist/plugins/manager.d.ts +24 -0
- package/dist/plugins/manager.js +288 -0
- package/dist/plugins/package.d.ts +5 -0
- package/dist/plugins/package.js +53 -0
- package/dist/plugins/runner.d.ts +1 -0
- package/dist/plugins/runner.js +227 -0
- package/dist/plugins/session.d.ts +12 -0
- package/dist/plugins/session.js +97 -0
- package/dist/plugins/terminal.d.ts +22 -0
- package/dist/plugins/terminal.js +33 -0
- package/dist/plugins/types.d.ts +178 -0
- package/dist/plugins/types.js +6 -0
- package/dist/plugins/validation.d.ts +14 -0
- package/dist/plugins/validation.js +174 -0
- package/dist/plugins/web-sdk.d.ts +58 -0
- package/dist/plugins/web-sdk.js +1 -0
- package/dist/pro/agents.d.ts +34 -0
- package/dist/pro/agents.js +1 -1
- package/dist/pro/config.d.ts +75 -0
- package/dist/pro/config.js +3 -2
- package/dist/pro/preflight.d.ts +21 -0
- package/dist/sanitize.d.ts +34 -0
- package/dist/screens/agentConversation.d.ts +28 -0
- package/dist/screens/agentHistory.d.ts +25 -0
- package/dist/screens/agentSelect.d.ts +16 -0
- package/dist/screens/chat.d.ts +292 -0
- package/dist/screens/chat.js +179 -65
- package/dist/screens/history.d.ts +23 -0
- package/dist/screens/modelSelect.d.ts +29 -0
- package/dist/screens/plugins.d.ts +29 -0
- package/dist/screens/plugins.js +196 -0
- package/dist/screens/settings.d.ts +42 -0
- package/dist/screens/start.d.ts +43 -0
- package/dist/screens/start.js +25 -17
- package/dist/screens/updatePrompt.d.ts +41 -0
- package/dist/selection.d.ts +75 -0
- package/dist/state.d.ts +26 -0
- package/dist/terminal/buffer.d.ts +47 -0
- package/dist/terminal/input.d.ts +70 -0
- package/dist/terminal/renderer.d.ts +43 -0
- package/dist/terminal/restore.d.ts +41 -0
- package/dist/terminal/screen.d.ts +57 -0
- package/dist/tray.d.ts +14 -0
- package/dist/ui.d.ts +123 -0
- package/dist/update.d.ts +71 -0
- package/dist/updateWatch.d.ts +43 -0
- package/dist/web/approvalQueue.d.ts +8 -0
- package/dist/web/approvalQueue.js +59 -0
- package/dist/web/auth.d.ts +14 -0
- package/dist/web/clientState.d.ts +16 -0
- package/dist/web/folderPicker.d.ts +34 -0
- package/dist/web/folderPicker.js +544 -0
- package/dist/web/hub.d.ts +213 -0
- package/dist/web/hub.js +14 -2
- package/dist/web/localApi.d.ts +11 -0
- package/dist/web/localApi.js +260 -0
- package/dist/web/plugins.d.ts +8 -0
- package/dist/web/plugins.js +64 -0
- package/dist/web/server.d.ts +81 -0
- package/dist/web/server.js +127 -19
- package/dist/web/service.d.ts +7 -0
- package/dist/web/service.js +1 -0
- package/dist/web/session.d.ts +176 -0
- package/dist/web/session.js +170 -21
- package/dist/web/settings.d.ts +65 -0
- package/dist/web/terminalBridge.d.ts +15 -0
- package/dist/web/terminalBridge.js +8 -2
- package/dist/width.d.ts +6 -0
- package/docs/plugins/README.md +126 -0
- package/docs/plugins/agent-sdk.md +92 -0
- package/docs/plugins/architecture.md +64 -0
- package/docs/plugins/compatibility.md +34 -0
- package/docs/plugins/development.md +55 -0
- package/docs/plugins/manifest.md +59 -0
- package/docs/plugins/manifest.schema.json +286 -0
- package/docs/plugins/security.md +53 -0
- package/docs/plugins/skills-prompts.md +69 -0
- package/docs/plugins/ui-sdk.md +109 -0
- package/examples/local-api.py +105 -0
- package/examples/plugins/cards/card.html +6 -0
- package/examples/plugins/cards/neutrino.plugin.json +42 -0
- package/examples/plugins/composer/editor.html +10 -0
- package/examples/plugins/composer/neutrino.plugin.json +10 -0
- package/examples/plugins/function-tools/agent.js +32 -0
- package/examples/plugins/function-tools/neutrino.plugin.json +15 -0
- package/examples/plugins/menus-tabs/neutrino.plugin.json +15 -0
- package/examples/plugins/menus-tabs/panel.html +6 -0
- package/examples/plugins/skills-prompts/neutrino.plugin.json +13 -0
- package/examples/plugins/skills-prompts/prompts/evidence.md +1 -0
- package/examples/plugins/skills-prompts/prompts/reviewer.md +1 -0
- package/examples/plugins/skills-prompts/skills/review/SKILL.md +8 -0
- package/examples/plugins/theme/neutrino.plugin.json +12 -0
- package/package.json +66 -42
- package/web/LOCAL-API.md +65 -0
- package/web/README.md +30 -8
- package/web/app.js +256 -67
- package/web/i18n.js +18 -3
- package/web/index.html +3 -1
- package/web/plugins.css +10 -0
- package/web/plugins.js +230 -0
- package/web/style.css +12 -3
- package/web/turns.js +7 -8
package/dist/screens/chat.js
CHANGED
|
@@ -1,4 +1,10 @@
|
|
|
1
|
+
import { terminalTemplate } from "../plugins/terminal.js";
|
|
2
|
+
import { runPluginTurn, pluginCommand, pluginApprovalContext } from "../plugins/session.js";
|
|
1
3
|
import path from "node:path";
|
|
4
|
+
import { ApprovalQueue } from "../web/approvalQueue.js";
|
|
5
|
+
import { randomUUID } from "node:crypto";
|
|
6
|
+
import { FastAgentEngine } from "../fast/engine.js";
|
|
7
|
+
import { FAST_MODE, assertFastAvailable, fastSettings } from "../fast/types.js";
|
|
2
8
|
import { Screen } from "../terminal/screen.js";
|
|
3
9
|
import { C } from "../colors.js";
|
|
4
10
|
import { findModel, findConfiguredModel, intensityForModel, contextCompressionThreshold, modelContextWindow, sessionDir, loadConfig, loadPlannerPrompt, loadStepPrompt, loadSubAgentPrompt, normalizeApprovalMode, planFirstEnabled, loadAgentPrompt, normalizeAgentMode, saveSession, } from "../config.js";
|
|
@@ -7,7 +13,7 @@ import { computeInputWindow, cursorFromColumn, drawInputBox, drawScrollbar, wrap
|
|
|
7
13
|
import { drawSpans, renderMarkdownText } from "../markdown.js";
|
|
8
14
|
import { ellipsize, displayWidth } from "../width.js";
|
|
9
15
|
import { sanitizeText } from "../sanitize.js";
|
|
10
|
-
import { TaskRuntime, currentTask, linkSignals, taskLimits, resumeTaskSnapshot } from "../llm/runtime.js";
|
|
16
|
+
import { TaskRuntime, TaskControlError, currentTask, linkSignals, taskLimits, taskBudgetScope, resumeTaskSnapshot } from "../llm/runtime.js";
|
|
11
17
|
import { prepareContext, outputAllowance, finalTaskReport } from "../llm/context.js";
|
|
12
18
|
import { settlePlan } from "../llm/plan.js";
|
|
13
19
|
import { LLMClient, describeError } from "../llm/client.js";
|
|
@@ -21,12 +27,10 @@ import { COMPACT_KEEP, COMPACT_MAX_CHARS_PER_MESSAGE, planCompaction, transcript
|
|
|
21
27
|
import { hub, liveApprovalDetails } from "../web/hub.js";
|
|
22
28
|
import { SessionCheckpoint } from "../persistence.js";
|
|
23
29
|
import { writeCrashLog } from "../terminal/restore.js";
|
|
24
|
-
const TOOLS = getToolDefinitions();
|
|
25
30
|
/** A sub-agent gets the same tools minus delegation: one level, never a tree. */
|
|
26
|
-
const SUB_AGENT_TOOLS = getSubAgentToolDefinitions();
|
|
27
31
|
/**
|
|
28
|
-
* A finished turn folds its *work* away — the
|
|
29
|
-
*
|
|
32
|
+
* A finished turn folds its *work* away — the thinking and the tool calls —
|
|
33
|
+
* while the full user prompt and final reply always stay on screen. Doing
|
|
30
34
|
* that is only worth a click when the work is substantial: four or more blocks,
|
|
31
35
|
* or roughly six wrapped lines of text. Short exchanges stay readable without a
|
|
32
36
|
* click. `web/turns.js` applies the same rule in the browser, so both UIs fold
|
|
@@ -84,6 +88,7 @@ export class ChatScreen extends Screen {
|
|
|
84
88
|
loadedApprovalMode = null;
|
|
85
89
|
loadedPlan = null;
|
|
86
90
|
loadedTask;
|
|
91
|
+
loadedFast;
|
|
87
92
|
loadedAgentMode = null;
|
|
88
93
|
approvalMode = "request";
|
|
89
94
|
modelProvider = "DeepSeek";
|
|
@@ -117,6 +122,8 @@ export class ChatScreen extends Screen {
|
|
|
117
122
|
turnToken = 0;
|
|
118
123
|
agentMode = "standard";
|
|
119
124
|
activeAgentMode = "standard";
|
|
125
|
+
fastState;
|
|
126
|
+
queuedAgentMode = null;
|
|
120
127
|
turnAbort = null;
|
|
121
128
|
inputValue = "";
|
|
122
129
|
cursor = 0;
|
|
@@ -161,6 +168,7 @@ export class ChatScreen extends Screen {
|
|
|
161
168
|
liveDir = process.cwd();
|
|
162
169
|
// ---- approval gate ---------------------------------------------------
|
|
163
170
|
/** Non-null while a tool call is waiting for a human decision. */
|
|
171
|
+
approvalQueue = new ApprovalQueue();
|
|
164
172
|
pendingApproval = null;
|
|
165
173
|
reviewing = null;
|
|
166
174
|
/** Decisions taken by the gate, keyed by the tool message (survives
|
|
@@ -215,6 +223,11 @@ export class ChatScreen extends Screen {
|
|
|
215
223
|
this.finishedTurns.add(start);
|
|
216
224
|
this.plan = resumePlan(this.loadedPlan);
|
|
217
225
|
this.lastTask = resumeTaskSnapshot(this.loadedTask);
|
|
226
|
+
this.fastState = this.loadedFast ? structuredClone(this.loadedFast) : undefined;
|
|
227
|
+
if (this.fastState && !["completed", "failed", "cancelled"].includes(this.fastState.phase)) {
|
|
228
|
+
this.fastState.phase = "failed";
|
|
229
|
+
this.fastState.error = "Process restarted; review saved artifacts before resuming. Tools are never replayed automatically.";
|
|
230
|
+
}
|
|
218
231
|
this.context = normalizeContext(this.loadedContext, this.messages.filter((m) => m.role !== "system").length);
|
|
219
232
|
this.liveDir = this.loadedCwd ?? process.cwd();
|
|
220
233
|
this.checkpoint.register();
|
|
@@ -228,6 +241,7 @@ export class ChatScreen extends Screen {
|
|
|
228
241
|
}
|
|
229
242
|
}
|
|
230
243
|
onDeactivate() {
|
|
244
|
+
this.approvalQueue.cancelWaiting();
|
|
231
245
|
// Never leave the agent awaiting a decision nobody can give.
|
|
232
246
|
if (this.pendingApproval)
|
|
233
247
|
this.settleApproval(false);
|
|
@@ -274,6 +288,7 @@ export class ChatScreen extends Screen {
|
|
|
274
288
|
approvalMode: this.approvalMode,
|
|
275
289
|
agentMode: this.agentMode,
|
|
276
290
|
activeAgentMode: this.busy ? this.activeAgentMode : this.agentMode,
|
|
291
|
+
...(this.fastState ? { fast: this.fastState } : {}),
|
|
277
292
|
task: this.taskRuntime?.snapshot() ?? this.lastTask,
|
|
278
293
|
busy: this.busy,
|
|
279
294
|
paused: this.paused,
|
|
@@ -283,7 +298,7 @@ export class ChatScreen extends Screen {
|
|
|
283
298
|
tokens: { prompt: this.tokens.prompt, completion: this.tokens.completion },
|
|
284
299
|
subagent: this.subagent,
|
|
285
300
|
approval: this.pendingApproval
|
|
286
|
-
? liveApprovalDetails(this.pendingApproval.tool, this.pendingApproval.args, this.pendingApproval.risk, this.liveDir)
|
|
301
|
+
? { ...liveApprovalDetails(this.pendingApproval.tool, this.pendingApproval.args, this.pendingApproval.risk, this.liveDir), id: this.pendingApproval.id }
|
|
287
302
|
: null,
|
|
288
303
|
reviewing: this.reviewing,
|
|
289
304
|
};
|
|
@@ -316,6 +331,8 @@ export class ChatScreen extends Screen {
|
|
|
316
331
|
livePause() {
|
|
317
332
|
if (this.paused || (!this.busy && !this.queued))
|
|
318
333
|
return;
|
|
334
|
+
if (this.fastState && this.activeAgentMode === FAST_MODE && this.busy)
|
|
335
|
+
this.fastState.phase = "cancelling";
|
|
319
336
|
this.paused = true;
|
|
320
337
|
this.subagent = null;
|
|
321
338
|
this.retryNotice = null;
|
|
@@ -339,13 +356,21 @@ export class ChatScreen extends Screen {
|
|
|
339
356
|
this.togglePause();
|
|
340
357
|
}
|
|
341
358
|
/** Answer the pending call from the web UI. */
|
|
342
|
-
liveApprove(approved) {
|
|
343
|
-
if (!this.pendingApproval)
|
|
359
|
+
liveApprove(approved, approvalId) {
|
|
360
|
+
if (!this.pendingApproval || (approvalId !== undefined && this.pendingApproval.id !== approvalId))
|
|
344
361
|
return false;
|
|
345
362
|
this.settleApproval(approved);
|
|
346
363
|
return true;
|
|
347
364
|
}
|
|
348
365
|
liveSetAgentMode(mode) {
|
|
366
|
+
if (mode === FAST_MODE) {
|
|
367
|
+
try {
|
|
368
|
+
assertFastAvailable(loadConfig());
|
|
369
|
+
}
|
|
370
|
+
catch (error) {
|
|
371
|
+
return { ok: false, error: describeError(error) };
|
|
372
|
+
}
|
|
373
|
+
}
|
|
349
374
|
this.agentMode = mode;
|
|
350
375
|
this.save();
|
|
351
376
|
this.publishLive();
|
|
@@ -376,6 +401,8 @@ export class ChatScreen extends Screen {
|
|
|
376
401
|
this.turnToken++;
|
|
377
402
|
this.taskRuntime = null;
|
|
378
403
|
this.lastTask = undefined;
|
|
404
|
+
this.fastState = undefined;
|
|
405
|
+
this.queuedAgentMode = null;
|
|
379
406
|
if (this.pendingApproval)
|
|
380
407
|
this.settleApproval(false);
|
|
381
408
|
this.busy = false;
|
|
@@ -738,6 +765,7 @@ export class ChatScreen extends Screen {
|
|
|
738
765
|
try {
|
|
739
766
|
const client = new LLMClient(this.model, this.modelProvider, this.intensity);
|
|
740
767
|
const answer = await client.complete(loadPlannerPrompt(), buildPlannerInput({ request, cwd: this.liveDir, transcript: planTranscript(this.messages) }), {
|
|
768
|
+
...(currentTask()?.runtime.diagnostics ? { diagnostic: { role: "planning" } } : {}),
|
|
741
769
|
onRetry: (notice) => {
|
|
742
770
|
this.retryNotice =
|
|
743
771
|
`Planning — network hiccup, retrying in ${formatDelay(notice.delayMs)} ` +
|
|
@@ -791,15 +819,26 @@ export class ChatScreen extends Screen {
|
|
|
791
819
|
*/
|
|
792
820
|
async runTurn(token, turnMessages, approvalContext) {
|
|
793
821
|
const runtime = new TaskRuntime({
|
|
822
|
+
executionMode: this.activeAgentMode,
|
|
794
823
|
request: approvalContext.prompt, priorUserRequests: approvalContext.priorUserRequests,
|
|
795
824
|
cwd: this.liveDir, signal: this.turnAbort?.signal, limits: taskLimits(loadConfig("standard")),
|
|
825
|
+
budgetScope: taskBudgetScope(loadConfig("standard")),
|
|
796
826
|
journalDir: path.join(sessionDir("standard"), "tasks", path.basename(this.sessionId || "unsaved")),
|
|
797
827
|
});
|
|
798
828
|
this.taskRuntime = runtime;
|
|
799
829
|
this.taskOutcome = "completed";
|
|
800
830
|
let failed = false;
|
|
801
831
|
try {
|
|
802
|
-
await runtime.
|
|
832
|
+
await runPluginTurn(runtime, { profile: "standard", cwd: this.liveDir, sessionId: this.sessionId ?? undefined, role: "main", request: approvalContext.prompt }, (name, args, signal) => this.requestApproval(name, args, approvalContext, signal), async () => {
|
|
833
|
+
const command = pluginCommand(approvalContext.prompt);
|
|
834
|
+
if (command) {
|
|
835
|
+
const result = await runtime.plugins.runCommand(command.name, command.args);
|
|
836
|
+
this.messages.push({ role: "assistant", content: typeof result === "string" ? result : JSON.stringify(result ?? null, null, 2) });
|
|
837
|
+
this.onTurnFinished();
|
|
838
|
+
return;
|
|
839
|
+
}
|
|
840
|
+
await this.runTaskTurn(token, turnMessages, approvalContext);
|
|
841
|
+
});
|
|
803
842
|
}
|
|
804
843
|
catch (error) {
|
|
805
844
|
failed = true;
|
|
@@ -833,6 +872,8 @@ export class ChatScreen extends Screen {
|
|
|
833
872
|
await this.autoCompress(token);
|
|
834
873
|
if (token !== this.turnToken)
|
|
835
874
|
return;
|
|
875
|
+
if (this.activeAgentMode === FAST_MODE)
|
|
876
|
+
return this.runFastTurn(token, turnMessages, approvalContext);
|
|
836
877
|
if (this.activeAgentMode !== "standard")
|
|
837
878
|
this.plan = null;
|
|
838
879
|
const plan = this.activeAgentMode === "standard" ? await this.makePlan(token) : null;
|
|
@@ -905,6 +946,7 @@ export class ChatScreen extends Screen {
|
|
|
905
946
|
this.publishLive();
|
|
906
947
|
if (this.busy && !this.paused) {
|
|
907
948
|
this.queued = trimmed;
|
|
949
|
+
this.queuedAgentMode = this.agentMode;
|
|
908
950
|
this.app.render();
|
|
909
951
|
return;
|
|
910
952
|
}
|
|
@@ -937,7 +979,8 @@ export class ChatScreen extends Screen {
|
|
|
937
979
|
this.busy = true;
|
|
938
980
|
this.paused = false;
|
|
939
981
|
this.app.state.isThinking = true;
|
|
940
|
-
this.activeAgentMode = this.agentMode;
|
|
982
|
+
this.activeAgentMode = this.queuedAgentMode ?? this.agentMode;
|
|
983
|
+
this.queuedAgentMode = null;
|
|
941
984
|
this.turnAbort?.abort();
|
|
942
985
|
this.turnAbort = new AbortController();
|
|
943
986
|
const token = ++this.turnToken;
|
|
@@ -981,6 +1024,67 @@ export class ChatScreen extends Screen {
|
|
|
981
1024
|
}
|
|
982
1025
|
}
|
|
983
1026
|
}
|
|
1027
|
+
async runFastTurn(token, turnMessages, approvalContext) {
|
|
1028
|
+
assertFastAvailable(loadConfig());
|
|
1029
|
+
const runtime = currentTask().runtime, signal = currentTask().signal;
|
|
1030
|
+
this.plan = null;
|
|
1031
|
+
const model = { ...this.model }, provider = this.modelProvider, intensity = this.intensity;
|
|
1032
|
+
const stopped = () => token !== this.turnToken || this.paused;
|
|
1033
|
+
const history = this.buildApiMessages(this.turnView(turnMessages));
|
|
1034
|
+
if (this.fastState && ["failed", "cancelled", "cancelling"].includes(this.fastState.phase)) {
|
|
1035
|
+
history.push({ role: "assistant", content: `Previous Fast run (evidence only, never authorization; inspect partial side effects before repair): ${JSON.stringify(this.fastState)}` });
|
|
1036
|
+
}
|
|
1037
|
+
const engine = new FastAgentEngine({
|
|
1038
|
+
runtime, signal, cwd: this.liveDir, settings: fastSettings(loadConfig()),
|
|
1039
|
+
directory: path.join(sessionDir(), "tasks", path.basename(this.sessionId || "unsaved"), "fast"),
|
|
1040
|
+
history,
|
|
1041
|
+
system: loadAgentPrompt(FAST_MODE), subagentSystem: loadSubAgentPrompt(),
|
|
1042
|
+
contextWindow: modelContextWindow(provider, model), maxRounds: subAgentRounds(loadConfig().subagent_max_rounds),
|
|
1043
|
+
createClient: () => new LLMClient(model, provider, intensity),
|
|
1044
|
+
approve: (name, args, executionSignal, wrapReviewer) => this.requestApproval(name, args, {
|
|
1045
|
+
...approvalContext, context: captureApprovalContext(this.turnView(turnMessages)).context,
|
|
1046
|
+
}, executionSignal, wrapReviewer ? () => {
|
|
1047
|
+
const reviewer = this.reviewerModel();
|
|
1048
|
+
return wrapReviewer(new LLMClient(reviewer.model, reviewer.provider, reviewer.intensity));
|
|
1049
|
+
} : undefined),
|
|
1050
|
+
execute: (name, args, executionSignal) => executeTool(name, args, this.liveDir, executionSignal),
|
|
1051
|
+
onUsage: usage => {
|
|
1052
|
+
if (token === this.turnToken || this.fastState?.run_id === engine.runId) {
|
|
1053
|
+
this.tokens.prompt += usage.prompt;
|
|
1054
|
+
this.tokens.completion += usage.completion;
|
|
1055
|
+
}
|
|
1056
|
+
},
|
|
1057
|
+
onText: text => { if (!stopped()) {
|
|
1058
|
+
this.streamingContent = text;
|
|
1059
|
+
this.checkpoint.schedule();
|
|
1060
|
+
this.publishLive();
|
|
1061
|
+
this.app.render();
|
|
1062
|
+
} },
|
|
1063
|
+
onSnapshot: snapshot => {
|
|
1064
|
+
if (!stopped() || (this.fastState?.run_id === snapshot.run_id && this.fastState.phase === "cancelling")) {
|
|
1065
|
+
this.fastState = snapshot;
|
|
1066
|
+
this.checkpoint.schedule();
|
|
1067
|
+
this.publishLive();
|
|
1068
|
+
this.app.render();
|
|
1069
|
+
}
|
|
1070
|
+
},
|
|
1071
|
+
onTool: (name, args, result) => {
|
|
1072
|
+
if (!stopped()) {
|
|
1073
|
+
this.messages.push({ role: "tool", name, arguments: args, result });
|
|
1074
|
+
this.save();
|
|
1075
|
+
this.publishLive();
|
|
1076
|
+
}
|
|
1077
|
+
},
|
|
1078
|
+
});
|
|
1079
|
+
const result = await engine.run();
|
|
1080
|
+
if (stopped())
|
|
1081
|
+
return;
|
|
1082
|
+
this.fastState = result.snapshot;
|
|
1083
|
+
this.taskOutcome = result.status;
|
|
1084
|
+
this.messages.push({ role: "assistant", content: result.report });
|
|
1085
|
+
this.streamingContent = null;
|
|
1086
|
+
this.onTurnFinished();
|
|
1087
|
+
}
|
|
984
1088
|
buildApiMessages(messages = this.messages, directive = "") {
|
|
985
1089
|
// A step directive extends the system prompt rather than adding a user
|
|
986
1090
|
// message: the plan is an instruction, not something the user just said.
|
|
@@ -1009,7 +1113,7 @@ export class ChatScreen extends Screen {
|
|
|
1009
1113
|
*/
|
|
1010
1114
|
async agentLoop(apiMessages, token, approvalContext) {
|
|
1011
1115
|
const tools = this.activeAgentMode === "ptc"
|
|
1012
|
-
? [...
|
|
1116
|
+
? [...getToolDefinitions(), (await import("../llm/codemode.js")).getCodeToolDefinition()] : getToolDefinitions();
|
|
1013
1117
|
let emptyReplies = 0;
|
|
1014
1118
|
try {
|
|
1015
1119
|
while (true) {
|
|
@@ -1082,7 +1186,7 @@ export class ChatScreen extends Screen {
|
|
|
1082
1186
|
return false;
|
|
1083
1187
|
}
|
|
1084
1188
|
if (client.outcome?.error?.code.endsWith("_budget") && currentTask()) {
|
|
1085
|
-
const report = await finalTaskReport(currentTask().runtime, () => new LLMClient(this.model, this.modelProvider, this.intensity), usage => { this.tokens.prompt += usage.prompt; this.tokens.completion += usage.completion; });
|
|
1189
|
+
const report = await finalTaskReport(currentTask().runtime, () => new LLMClient(this.model, this.modelProvider, this.intensity), usage => { this.tokens.prompt += usage.prompt; this.tokens.completion += usage.completion; }, apiMessages);
|
|
1086
1190
|
if (token !== this.turnToken || this.paused)
|
|
1087
1191
|
return false;
|
|
1088
1192
|
this.taskOutcome = "partial";
|
|
@@ -1167,7 +1271,15 @@ export class ChatScreen extends Screen {
|
|
|
1167
1271
|
this.app.render();
|
|
1168
1272
|
continue;
|
|
1169
1273
|
}
|
|
1170
|
-
|
|
1274
|
+
let result;
|
|
1275
|
+
try {
|
|
1276
|
+
result = await this.runTool(tc.name, args, toolMsg, token, apiMessages, approvalContext);
|
|
1277
|
+
}
|
|
1278
|
+
catch (error) {
|
|
1279
|
+
toolMsg.result = `[Not executed: ${describeError(error)}]`;
|
|
1280
|
+
this.save();
|
|
1281
|
+
throw error;
|
|
1282
|
+
}
|
|
1171
1283
|
// A clear starts a different transcript while an old tool may finish.
|
|
1172
1284
|
if (!this.messages.includes(toolMsg))
|
|
1173
1285
|
return false;
|
|
@@ -1185,6 +1297,15 @@ export class ChatScreen extends Screen {
|
|
|
1185
1297
|
catch (e) {
|
|
1186
1298
|
if (token !== this.turnToken)
|
|
1187
1299
|
return false;
|
|
1300
|
+
if (e instanceof TaskControlError && e.code.endsWith("_budget") && currentTask()) {
|
|
1301
|
+
const report = await finalTaskReport(currentTask().runtime, () => new LLMClient(this.model, this.modelProvider, this.intensity), usage => { this.tokens.prompt += usage.prompt; this.tokens.completion += usage.completion; }, apiMessages);
|
|
1302
|
+
if (token !== this.turnToken || this.paused)
|
|
1303
|
+
return false;
|
|
1304
|
+
this.taskOutcome = "partial";
|
|
1305
|
+
this.streamingContent = null;
|
|
1306
|
+
this.messages.push({ role: "assistant", content: report });
|
|
1307
|
+
return false;
|
|
1308
|
+
}
|
|
1188
1309
|
this.taskOutcome = "failed";
|
|
1189
1310
|
const err = describeError(e);
|
|
1190
1311
|
this.messages.push({ role: "assistant", content: (this.streamingContent ?? "") + `\n[Error: ${err}]` });
|
|
@@ -1217,11 +1338,15 @@ export class ChatScreen extends Screen {
|
|
|
1217
1338
|
}
|
|
1218
1339
|
// ---- approval gate ---------------------------------------------------
|
|
1219
1340
|
/** Classify a tool call and, when it is risky, run it past the gate. */
|
|
1220
|
-
async requestApproval(tool, args, context = captureApprovalContext(this.messages), signal = currentTask()?.runtime.signal ?? this.turnAbort?.signal) {
|
|
1341
|
+
async requestApproval(tool, args, context = captureApprovalContext(this.messages), signal = currentTask()?.runtime.signal ?? this.turnAbort?.signal, createReviewerClient) {
|
|
1342
|
+
context = pluginApprovalContext(context);
|
|
1221
1343
|
const linked = linkSignals(signal, currentTask()?.signal);
|
|
1222
1344
|
signal = linked.signal;
|
|
1223
1345
|
try {
|
|
1224
1346
|
currentTask()?.runtime.assertTool(tool, args);
|
|
1347
|
+
const plugins = currentTask()?.runtime.plugins;
|
|
1348
|
+
if (plugins)
|
|
1349
|
+
await plugins.guardTool(tool, args);
|
|
1225
1350
|
}
|
|
1226
1351
|
catch (error) {
|
|
1227
1352
|
linked.dispose();
|
|
@@ -1236,8 +1361,9 @@ export class ChatScreen extends Screen {
|
|
|
1236
1361
|
const cancelled = () => approvalToken !== this.turnToken || this.paused || signal?.aborted === true;
|
|
1237
1362
|
const gate = new ApprovalGate({
|
|
1238
1363
|
cancelled,
|
|
1239
|
-
askHuman: () =>
|
|
1364
|
+
askHuman: () => this.askHuman(tool, args, risk, cancelled, signal),
|
|
1240
1365
|
reviewer: () => this.reviewerModel(),
|
|
1366
|
+
...(createReviewerClient ? { createReviewerClient } : {}),
|
|
1241
1367
|
autoApprovalEnabled: () => this.approvalMode === "auto",
|
|
1242
1368
|
extremeAutoApprovalEnabled: () => loadConfig().auto_approve_extreme_risk === true,
|
|
1243
1369
|
// Reviews happen before any card is shown, so without this the app looks
|
|
@@ -1275,9 +1401,18 @@ export class ChatScreen extends Screen {
|
|
|
1275
1401
|
}
|
|
1276
1402
|
}
|
|
1277
1403
|
/** Park the turn until the user answers; the header turns orange meanwhile. */
|
|
1278
|
-
askHuman(tool, args, risk) {
|
|
1279
|
-
|
|
1280
|
-
|
|
1404
|
+
askHuman(tool, args, risk, cancelled, signal) {
|
|
1405
|
+
const requestedMode = this.approvalMode;
|
|
1406
|
+
return this.approvalQueue.run(() => new Promise((resolve) => {
|
|
1407
|
+
if (cancelled()) {
|
|
1408
|
+
resolve(false);
|
|
1409
|
+
return;
|
|
1410
|
+
}
|
|
1411
|
+
if (requestedMode !== this.approvalMode) {
|
|
1412
|
+
resolve(null);
|
|
1413
|
+
return;
|
|
1414
|
+
}
|
|
1415
|
+
this.pendingApproval = { id: randomUUID(), tool, args, risk, resolve };
|
|
1281
1416
|
this.app.state.approval = { tool, level: risk.level };
|
|
1282
1417
|
// The card floats over the same corner the approval card needs on a short
|
|
1283
1418
|
// terminal, and a decision the user cannot see is not a decision. The plan
|
|
@@ -1286,7 +1421,7 @@ export class ChatScreen extends Screen {
|
|
|
1286
1421
|
this.app.bell?.("approval");
|
|
1287
1422
|
this.app.render();
|
|
1288
1423
|
this.publishLive();
|
|
1289
|
-
});
|
|
1424
|
+
}), signal);
|
|
1290
1425
|
}
|
|
1291
1426
|
/** Answer the pending request (keyboard, mouse, or the web UI). */
|
|
1292
1427
|
settleApproval(approved) {
|
|
@@ -1480,7 +1615,7 @@ export class ChatScreen extends Screen {
|
|
|
1480
1615
|
createClient: () => new LLMClient(this.model, this.modelProvider, this.intensity),
|
|
1481
1616
|
system: loadSubAgentPrompt(),
|
|
1482
1617
|
task: buildSubAgentTask({ task, context, cwd: this.liveDir, rounds: maxRounds }),
|
|
1483
|
-
tools:
|
|
1618
|
+
tools: getSubAgentToolDefinitions(),
|
|
1484
1619
|
maxRounds,
|
|
1485
1620
|
approve: (name, callArgs) => this.requestApproval(name, callArgs, approval, signal),
|
|
1486
1621
|
execute: (name, callArgs) => name === DELEGATE_TOOL
|
|
@@ -1544,7 +1679,7 @@ export class ChatScreen extends Screen {
|
|
|
1544
1679
|
/**
|
|
1545
1680
|
* A finished turn is only worth folding when the work it hides is substantial.
|
|
1546
1681
|
* Whether it is folded right now is a separate question (`expandedTurns`), and
|
|
1547
|
-
* its reply
|
|
1682
|
+
* its user input and reply are never part of what it hides.
|
|
1548
1683
|
*/
|
|
1549
1684
|
turnHides(start, end) {
|
|
1550
1685
|
if (!this.finishedTurns.has(start))
|
|
@@ -1553,12 +1688,12 @@ export class ChatScreen extends Screen {
|
|
|
1553
1688
|
let blocks = 0;
|
|
1554
1689
|
let chars = 0;
|
|
1555
1690
|
for (let i = start; i < end; i++) {
|
|
1556
|
-
//
|
|
1557
|
-
//
|
|
1691
|
+
// User input and the reply stay on screen, so neither counts toward the
|
|
1692
|
+
// folding threshold, however long either one is.
|
|
1558
1693
|
if (i === answer)
|
|
1559
1694
|
continue;
|
|
1560
1695
|
const m = this.messages[i];
|
|
1561
|
-
if (m.role === "
|
|
1696
|
+
if (m.role === "assistant") {
|
|
1562
1697
|
blocks++;
|
|
1563
1698
|
chars += m.content.length;
|
|
1564
1699
|
}
|
|
@@ -1613,23 +1748,6 @@ export class ChatScreen extends Screen {
|
|
|
1613
1748
|
}
|
|
1614
1749
|
return map;
|
|
1615
1750
|
}
|
|
1616
|
-
/**
|
|
1617
|
-
* The first line of the first message in `role`. Only one line: the header is
|
|
1618
|
-
* a signpost, and the full text is one click away.
|
|
1619
|
-
*/
|
|
1620
|
-
turnPreview(start, end, role) {
|
|
1621
|
-
for (const m of this.messages.slice(start, end)) {
|
|
1622
|
-
if (m.role !== role)
|
|
1623
|
-
continue;
|
|
1624
|
-
const line = sanitizeText(m.content)
|
|
1625
|
-
.split("\n")
|
|
1626
|
-
.map((part) => part.trim())
|
|
1627
|
-
.find((part) => part.length > 0);
|
|
1628
|
-
if (line)
|
|
1629
|
-
return line;
|
|
1630
|
-
}
|
|
1631
|
-
return "";
|
|
1632
|
-
}
|
|
1633
1751
|
/** The header standing in for a folded turn, or the way back into one. */
|
|
1634
1752
|
pushTurnHeader(start, end, width, add) {
|
|
1635
1753
|
const count = end - start;
|
|
@@ -1646,13 +1764,10 @@ export class ChatScreen extends Screen {
|
|
|
1646
1764
|
}, -1, start);
|
|
1647
1765
|
return;
|
|
1648
1766
|
}
|
|
1649
|
-
const
|
|
1650
|
-
|
|
1651
|
-
// The prompt and the reply sit either side of the work this fold hides, so
|
|
1652
|
-
// the control line goes between them and the answer stays on screen below.
|
|
1653
|
-
const hidden = this.turnAnswer(start, end) < 0 ? count : count - 1;
|
|
1767
|
+
const answer = this.turnAnswer(start, end);
|
|
1768
|
+
const hidden = this.messages.slice(start, end).filter((message, offset) => message.role !== "user" && message.role !== "system" && start + offset !== answer).length;
|
|
1654
1769
|
const meta = `${hidden} of ${count} message${count === 1 ? "" : "s"} folded`;
|
|
1655
|
-
add({ spans: [{ text: ellipsize(
|
|
1770
|
+
add({ spans: [{ text: ellipsize(`▸ ${meta} — click to expand`, width), style: { fg: C.dim } }] }, -1, start);
|
|
1656
1771
|
}
|
|
1657
1772
|
/** Fold or unfold one turn. */
|
|
1658
1773
|
toggleTurn(start) {
|
|
@@ -1793,7 +1908,7 @@ export class ChatScreen extends Screen {
|
|
|
1793
1908
|
if (chatMessages.length === 0)
|
|
1794
1909
|
return true;
|
|
1795
1910
|
try {
|
|
1796
|
-
this.sessionId = saveSession(chatMessages, this.model.name, this.modelProvider, this.intensity, this.liveDir, this.sessionId ?? undefined, this.tokens, this.context, this.approvalMode, this.plan, "standard", undefined, this.agentMode, this.taskRuntime?.snapshot() ?? this.lastTask);
|
|
1911
|
+
this.sessionId = saveSession(chatMessages, this.model.name, this.modelProvider, this.intensity, this.liveDir, this.sessionId ?? undefined, this.tokens, this.context, this.approvalMode, this.plan, "standard", undefined, this.agentMode, this.taskRuntime?.snapshot() ?? this.lastTask, this.fastState);
|
|
1797
1912
|
this.saveError = null;
|
|
1798
1913
|
return true;
|
|
1799
1914
|
}
|
|
@@ -1845,30 +1960,28 @@ export class ChatScreen extends Screen {
|
|
|
1845
1960
|
archived = context !== null && recordIndex < context.end;
|
|
1846
1961
|
recordIndex++;
|
|
1847
1962
|
}
|
|
1848
|
-
// A foldable turn always shows its control lines first; its work then
|
|
1849
|
-
// renders when the reader has opened it, and its reply always does.
|
|
1850
|
-
if (m.role !== "system" && turn === mi && !(archived && !this.expandedContext)) {
|
|
1851
|
-
this.pushTurnHeader(mi, this.turnEnd(mi), width, add);
|
|
1852
|
-
}
|
|
1853
1963
|
const folded = turn >= 0 && !this.expandedTurns.has(turn);
|
|
1854
1964
|
const reply = folded && answers[mi] === mi;
|
|
1855
1965
|
if (m.role === "system") {
|
|
1856
1966
|
pushText(sanitizeText(m.content), { fg: C.dim });
|
|
1857
1967
|
}
|
|
1858
|
-
else if ((archived && !this.expandedContext) || (folded && !reply)) {
|
|
1859
|
-
// Hidden: by the collapsed "Earlier conversation" fold, or by the turn's
|
|
1860
|
-
// own fold, which hides the prompt body and the work but never the
|
|
1861
|
-
// reply. Either way the message stays in the transcript, on disk and in
|
|
1862
|
-
// the request; the header above says how to get it back on screen.
|
|
1863
|
-
}
|
|
1864
1968
|
else if (m.role === "user") {
|
|
1865
1969
|
add({ spans: [{ text: "You:", style: { fg: C.accent, bold: true } }] });
|
|
1866
|
-
pushMarkdown(sanitizeText(m.content), { fg: C.user });
|
|
1970
|
+
pushMarkdown(sanitizeText(terminalTemplate("chat.message.renderer", { content: m.content, role: "user" }) ?? m.content), { fg: C.user });
|
|
1867
1971
|
add({ spans: [] });
|
|
1972
|
+
// The fold control follows the complete prompt, which stays visible
|
|
1973
|
+
// even when the turn or the earlier conversation is collapsed.
|
|
1974
|
+
if (turn === mi && !(archived && !this.expandedContext)) {
|
|
1975
|
+
this.pushTurnHeader(mi, this.turnEnd(mi), width, add);
|
|
1976
|
+
}
|
|
1977
|
+
}
|
|
1978
|
+
else if ((archived && !this.expandedContext) || (folded && !reply)) {
|
|
1979
|
+
// Only AI output is hidden. Every message remains in the transcript,
|
|
1980
|
+
// on disk and in the request; the controls reveal it again.
|
|
1868
1981
|
}
|
|
1869
1982
|
else if (m.role === "assistant") {
|
|
1870
1983
|
add({ spans: [{ text: "Neutrino:", style: { fg: C.accent, bold: true } }] });
|
|
1871
|
-
pushMarkdown(sanitizeText(m.content), { fg: C.assistant });
|
|
1984
|
+
pushMarkdown(sanitizeText(terminalTemplate("chat.message.renderer", { content: m.content, role: "assistant" }) ?? m.content), { fg: C.assistant });
|
|
1872
1985
|
add({ spans: [] });
|
|
1873
1986
|
}
|
|
1874
1987
|
else if (m.role === "summary") {
|
|
@@ -1906,7 +2019,7 @@ export class ChatScreen extends Screen {
|
|
|
1906
2019
|
}
|
|
1907
2020
|
// Command output routinely carries ANSI colours and tabs; clean it once
|
|
1908
2021
|
// so the collapsed line count and the expanded body agree.
|
|
1909
|
-
const body = sanitizeText(m.result
|
|
2022
|
+
const body = sanitizeText(terminalTemplate("chat.card.tool", { name: m.name, result: m.result, role: "tool", arguments: JSON.stringify(m.arguments) }) ?? m.result);
|
|
1910
2023
|
if (this.expandedTools.has(mi)) {
|
|
1911
2024
|
pushText(body || "(no output)", { fg: C.dim }, mi);
|
|
1912
2025
|
add({ spans: [{ text: " ▾ click to collapse", style: { fg: C.toolHeader } }] }, mi);
|
|
@@ -1918,7 +2031,7 @@ export class ChatScreen extends Screen {
|
|
|
1918
2031
|
}
|
|
1919
2032
|
add({ spans: [] });
|
|
1920
2033
|
}
|
|
1921
|
-
if (context && archived && recordIndex === context.end) {
|
|
2034
|
+
if (context && archived && recordIndex === context.end && this.expandedContext) {
|
|
1922
2035
|
this.contextLines.add(lines.length);
|
|
1923
2036
|
add({ spans: [{ text: "▾ Collapse earlier conversation", style: { fg: C.amber } }] });
|
|
1924
2037
|
add({ spans: [] });
|
|
@@ -2028,7 +2141,7 @@ export class ChatScreen extends Screen {
|
|
|
2028
2141
|
active: true,
|
|
2029
2142
|
dimmed: this.busy,
|
|
2030
2143
|
cursorOn: this.app.state.cursorOn,
|
|
2031
|
-
placeholder: "Type a message...",
|
|
2144
|
+
placeholder: sanitizeText(terminalTemplate("chat.composer", { text: this.inputValue }) ?? "Type a message...", { singleLine: true }),
|
|
2032
2145
|
});
|
|
2033
2146
|
const hintY = h - 1;
|
|
2034
2147
|
const hints = "Enter send · Ctrl+P plan · Ctrl+C pause · ESC ESC pause · Ctrl+A approval · Ctrl+B menu · Ctrl+L clear · drag to copy";
|
|
@@ -2090,7 +2203,8 @@ export class ChatScreen extends Screen {
|
|
|
2090
2203
|
{ kind: "line", text: `Total: ${total}` },
|
|
2091
2204
|
];
|
|
2092
2205
|
}
|
|
2093
|
-
const
|
|
2206
|
+
const custom = terminalTemplate("chat.card.plan", { plan: JSON.stringify(plan), role: "main" });
|
|
2207
|
+
const rows = custom ? wrapText(sanitizeText(custom), width).slice(0, Math.max(1, maxRows - 1)).map(text => ({ kind: "line", text })) : planCardRows(plan, { width, maxRows: Math.max(1, maxRows - 1) });
|
|
2094
2208
|
rows.push({ kind: "line", text: `Tokens ${this.tokens.prompt} → ${this.tokens.completion}` });
|
|
2095
2209
|
return rows;
|
|
2096
2210
|
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import type { Buffer } from "../terminal/buffer.js";
|
|
2
|
+
import { Screen } from "../terminal/screen.js";
|
|
3
|
+
import type { Key, MouseEvent } from "../terminal/input.js";
|
|
4
|
+
export declare class HistoryScreen extends Screen {
|
|
5
|
+
private sessions;
|
|
6
|
+
private selectedIdx;
|
|
7
|
+
private scrollTop;
|
|
8
|
+
private lastHoverX;
|
|
9
|
+
private lastHoverY;
|
|
10
|
+
private rows;
|
|
11
|
+
private itemBounds;
|
|
12
|
+
private cwd;
|
|
13
|
+
onActivate(): void;
|
|
14
|
+
private move;
|
|
15
|
+
onKey(key: Key): void;
|
|
16
|
+
onMouse(ev: MouseEvent): void;
|
|
17
|
+
/** Map a terminal row to the history item index (0 = "Open other Agent's history"). */
|
|
18
|
+
private itemIndexAtRow;
|
|
19
|
+
private loadSelected;
|
|
20
|
+
private deleteSelected;
|
|
21
|
+
private buildRows;
|
|
22
|
+
render(buf: Buffer): void;
|
|
23
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import type { Buffer } from "../terminal/buffer.js";
|
|
2
|
+
import { Screen } from "../terminal/screen.js";
|
|
3
|
+
import type { Key, MouseEvent } from "../terminal/input.js";
|
|
4
|
+
export declare class ModelSelectScreen extends Screen {
|
|
5
|
+
private level;
|
|
6
|
+
private providers;
|
|
7
|
+
private modelMap;
|
|
8
|
+
private selectedProviderIdx;
|
|
9
|
+
private selectedModelIdx;
|
|
10
|
+
private selectedIntensityIdx;
|
|
11
|
+
private scrollBtn;
|
|
12
|
+
private lastHoverX;
|
|
13
|
+
private lastHoverY;
|
|
14
|
+
onActivate(): void;
|
|
15
|
+
private currentList;
|
|
16
|
+
/** Only the intensity names this exact model accepts. */
|
|
17
|
+
private currentIntensities;
|
|
18
|
+
private currentIdx;
|
|
19
|
+
private setIdx;
|
|
20
|
+
private move;
|
|
21
|
+
private confirm;
|
|
22
|
+
private goBack;
|
|
23
|
+
/** Map a terminal row to the list index rendered there, if any. */
|
|
24
|
+
private indexAtRow;
|
|
25
|
+
onMouse(ev: MouseEvent): void;
|
|
26
|
+
onKey(key: Key): void;
|
|
27
|
+
render(buf: Buffer): void;
|
|
28
|
+
private currentModelName;
|
|
29
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { Screen } from "../terminal/screen.js";
|
|
2
|
+
import type { Buffer } from "../terminal/buffer.js";
|
|
3
|
+
import type { Key, MouseEvent } from "../terminal/input.js";
|
|
4
|
+
import type { OwnedUI } from "../plugins/host.js";
|
|
5
|
+
export declare class PluginViewScreen extends Screen {
|
|
6
|
+
private item;
|
|
7
|
+
private offset;
|
|
8
|
+
constructor(item: OwnedUI);
|
|
9
|
+
onKey(key: Key): void;
|
|
10
|
+
onMouse(ev: MouseEvent): void;
|
|
11
|
+
render(buf: Buffer): void;
|
|
12
|
+
}
|
|
13
|
+
export declare class PluginsScreen extends Screen {
|
|
14
|
+
private profile;
|
|
15
|
+
private plugins;
|
|
16
|
+
private index;
|
|
17
|
+
private detail;
|
|
18
|
+
private editing;
|
|
19
|
+
private draft;
|
|
20
|
+
private message;
|
|
21
|
+
private trustPending;
|
|
22
|
+
onActivate(): void;
|
|
23
|
+
private refresh;
|
|
24
|
+
private action;
|
|
25
|
+
onKey(key: Key): void;
|
|
26
|
+
onPaste(text: string): void;
|
|
27
|
+
onMouse(ev: MouseEvent): void;
|
|
28
|
+
render(buf: Buffer): void;
|
|
29
|
+
}
|