@xingjisoft/neutrino 0.6.2 → 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 +19 -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/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 +77 -4
- 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 +4 -2
- 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/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 +139 -21
- 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 +153 -19
- 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 +27 -6
- package/web/app.js +201 -32
- package/web/i18n.js +15 -0
- package/web/index.html +3 -1
- package/web/plugins.css +10 -0
- package/web/plugins.js +230 -0
- package/web/style.css +11 -2
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";
|
|
@@ -21,9 +27,7 @@ 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
32
|
* A finished turn folds its *work* away — the thinking and the tool calls —
|
|
29
33
|
* while the full user prompt and final reply always stay on screen. Doing
|
|
@@ -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,6 +819,7 @@ 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")),
|
|
796
825
|
budgetScope: taskBudgetScope(loadConfig("standard")),
|
|
@@ -800,7 +829,16 @@ export class ChatScreen extends Screen {
|
|
|
800
829
|
this.taskOutcome = "completed";
|
|
801
830
|
let failed = false;
|
|
802
831
|
try {
|
|
803
|
-
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
|
+
});
|
|
804
842
|
}
|
|
805
843
|
catch (error) {
|
|
806
844
|
failed = true;
|
|
@@ -834,6 +872,8 @@ export class ChatScreen extends Screen {
|
|
|
834
872
|
await this.autoCompress(token);
|
|
835
873
|
if (token !== this.turnToken)
|
|
836
874
|
return;
|
|
875
|
+
if (this.activeAgentMode === FAST_MODE)
|
|
876
|
+
return this.runFastTurn(token, turnMessages, approvalContext);
|
|
837
877
|
if (this.activeAgentMode !== "standard")
|
|
838
878
|
this.plan = null;
|
|
839
879
|
const plan = this.activeAgentMode === "standard" ? await this.makePlan(token) : null;
|
|
@@ -906,6 +946,7 @@ export class ChatScreen extends Screen {
|
|
|
906
946
|
this.publishLive();
|
|
907
947
|
if (this.busy && !this.paused) {
|
|
908
948
|
this.queued = trimmed;
|
|
949
|
+
this.queuedAgentMode = this.agentMode;
|
|
909
950
|
this.app.render();
|
|
910
951
|
return;
|
|
911
952
|
}
|
|
@@ -938,7 +979,8 @@ export class ChatScreen extends Screen {
|
|
|
938
979
|
this.busy = true;
|
|
939
980
|
this.paused = false;
|
|
940
981
|
this.app.state.isThinking = true;
|
|
941
|
-
this.activeAgentMode = this.agentMode;
|
|
982
|
+
this.activeAgentMode = this.queuedAgentMode ?? this.agentMode;
|
|
983
|
+
this.queuedAgentMode = null;
|
|
942
984
|
this.turnAbort?.abort();
|
|
943
985
|
this.turnAbort = new AbortController();
|
|
944
986
|
const token = ++this.turnToken;
|
|
@@ -982,6 +1024,67 @@ export class ChatScreen extends Screen {
|
|
|
982
1024
|
}
|
|
983
1025
|
}
|
|
984
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
|
+
}
|
|
985
1088
|
buildApiMessages(messages = this.messages, directive = "") {
|
|
986
1089
|
// A step directive extends the system prompt rather than adding a user
|
|
987
1090
|
// message: the plan is an instruction, not something the user just said.
|
|
@@ -1010,7 +1113,7 @@ export class ChatScreen extends Screen {
|
|
|
1010
1113
|
*/
|
|
1011
1114
|
async agentLoop(apiMessages, token, approvalContext) {
|
|
1012
1115
|
const tools = this.activeAgentMode === "ptc"
|
|
1013
|
-
? [...
|
|
1116
|
+
? [...getToolDefinitions(), (await import("../llm/codemode.js")).getCodeToolDefinition()] : getToolDefinitions();
|
|
1014
1117
|
let emptyReplies = 0;
|
|
1015
1118
|
try {
|
|
1016
1119
|
while (true) {
|
|
@@ -1235,11 +1338,15 @@ export class ChatScreen extends Screen {
|
|
|
1235
1338
|
}
|
|
1236
1339
|
// ---- approval gate ---------------------------------------------------
|
|
1237
1340
|
/** Classify a tool call and, when it is risky, run it past the gate. */
|
|
1238
|
-
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);
|
|
1239
1343
|
const linked = linkSignals(signal, currentTask()?.signal);
|
|
1240
1344
|
signal = linked.signal;
|
|
1241
1345
|
try {
|
|
1242
1346
|
currentTask()?.runtime.assertTool(tool, args);
|
|
1347
|
+
const plugins = currentTask()?.runtime.plugins;
|
|
1348
|
+
if (plugins)
|
|
1349
|
+
await plugins.guardTool(tool, args);
|
|
1243
1350
|
}
|
|
1244
1351
|
catch (error) {
|
|
1245
1352
|
linked.dispose();
|
|
@@ -1254,8 +1361,9 @@ export class ChatScreen extends Screen {
|
|
|
1254
1361
|
const cancelled = () => approvalToken !== this.turnToken || this.paused || signal?.aborted === true;
|
|
1255
1362
|
const gate = new ApprovalGate({
|
|
1256
1363
|
cancelled,
|
|
1257
|
-
askHuman: () =>
|
|
1364
|
+
askHuman: () => this.askHuman(tool, args, risk, cancelled, signal),
|
|
1258
1365
|
reviewer: () => this.reviewerModel(),
|
|
1366
|
+
...(createReviewerClient ? { createReviewerClient } : {}),
|
|
1259
1367
|
autoApprovalEnabled: () => this.approvalMode === "auto",
|
|
1260
1368
|
extremeAutoApprovalEnabled: () => loadConfig().auto_approve_extreme_risk === true,
|
|
1261
1369
|
// Reviews happen before any card is shown, so without this the app looks
|
|
@@ -1293,9 +1401,18 @@ export class ChatScreen extends Screen {
|
|
|
1293
1401
|
}
|
|
1294
1402
|
}
|
|
1295
1403
|
/** Park the turn until the user answers; the header turns orange meanwhile. */
|
|
1296
|
-
askHuman(tool, args, risk) {
|
|
1297
|
-
|
|
1298
|
-
|
|
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 };
|
|
1299
1416
|
this.app.state.approval = { tool, level: risk.level };
|
|
1300
1417
|
// The card floats over the same corner the approval card needs on a short
|
|
1301
1418
|
// terminal, and a decision the user cannot see is not a decision. The plan
|
|
@@ -1304,7 +1421,7 @@ export class ChatScreen extends Screen {
|
|
|
1304
1421
|
this.app.bell?.("approval");
|
|
1305
1422
|
this.app.render();
|
|
1306
1423
|
this.publishLive();
|
|
1307
|
-
});
|
|
1424
|
+
}), signal);
|
|
1308
1425
|
}
|
|
1309
1426
|
/** Answer the pending request (keyboard, mouse, or the web UI). */
|
|
1310
1427
|
settleApproval(approved) {
|
|
@@ -1498,7 +1615,7 @@ export class ChatScreen extends Screen {
|
|
|
1498
1615
|
createClient: () => new LLMClient(this.model, this.modelProvider, this.intensity),
|
|
1499
1616
|
system: loadSubAgentPrompt(),
|
|
1500
1617
|
task: buildSubAgentTask({ task, context, cwd: this.liveDir, rounds: maxRounds }),
|
|
1501
|
-
tools:
|
|
1618
|
+
tools: getSubAgentToolDefinitions(),
|
|
1502
1619
|
maxRounds,
|
|
1503
1620
|
approve: (name, callArgs) => this.requestApproval(name, callArgs, approval, signal),
|
|
1504
1621
|
execute: (name, callArgs) => name === DELEGATE_TOOL
|
|
@@ -1791,7 +1908,7 @@ export class ChatScreen extends Screen {
|
|
|
1791
1908
|
if (chatMessages.length === 0)
|
|
1792
1909
|
return true;
|
|
1793
1910
|
try {
|
|
1794
|
-
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);
|
|
1795
1912
|
this.saveError = null;
|
|
1796
1913
|
return true;
|
|
1797
1914
|
}
|
|
@@ -1850,7 +1967,7 @@ export class ChatScreen extends Screen {
|
|
|
1850
1967
|
}
|
|
1851
1968
|
else if (m.role === "user") {
|
|
1852
1969
|
add({ spans: [{ text: "You:", style: { fg: C.accent, bold: true } }] });
|
|
1853
|
-
pushMarkdown(sanitizeText(m.content), { fg: C.user });
|
|
1970
|
+
pushMarkdown(sanitizeText(terminalTemplate("chat.message.renderer", { content: m.content, role: "user" }) ?? m.content), { fg: C.user });
|
|
1854
1971
|
add({ spans: [] });
|
|
1855
1972
|
// The fold control follows the complete prompt, which stays visible
|
|
1856
1973
|
// even when the turn or the earlier conversation is collapsed.
|
|
@@ -1864,7 +1981,7 @@ export class ChatScreen extends Screen {
|
|
|
1864
1981
|
}
|
|
1865
1982
|
else if (m.role === "assistant") {
|
|
1866
1983
|
add({ spans: [{ text: "Neutrino:", style: { fg: C.accent, bold: true } }] });
|
|
1867
|
-
pushMarkdown(sanitizeText(m.content), { fg: C.assistant });
|
|
1984
|
+
pushMarkdown(sanitizeText(terminalTemplate("chat.message.renderer", { content: m.content, role: "assistant" }) ?? m.content), { fg: C.assistant });
|
|
1868
1985
|
add({ spans: [] });
|
|
1869
1986
|
}
|
|
1870
1987
|
else if (m.role === "summary") {
|
|
@@ -1902,7 +2019,7 @@ export class ChatScreen extends Screen {
|
|
|
1902
2019
|
}
|
|
1903
2020
|
// Command output routinely carries ANSI colours and tabs; clean it once
|
|
1904
2021
|
// so the collapsed line count and the expanded body agree.
|
|
1905
|
-
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);
|
|
1906
2023
|
if (this.expandedTools.has(mi)) {
|
|
1907
2024
|
pushText(body || "(no output)", { fg: C.dim }, mi);
|
|
1908
2025
|
add({ spans: [{ text: " ▾ click to collapse", style: { fg: C.toolHeader } }] }, mi);
|
|
@@ -2024,7 +2141,7 @@ export class ChatScreen extends Screen {
|
|
|
2024
2141
|
active: true,
|
|
2025
2142
|
dimmed: this.busy,
|
|
2026
2143
|
cursorOn: this.app.state.cursorOn,
|
|
2027
|
-
placeholder: "Type a message...",
|
|
2144
|
+
placeholder: sanitizeText(terminalTemplate("chat.composer", { text: this.inputValue }) ?? "Type a message...", { singleLine: true }),
|
|
2028
2145
|
});
|
|
2029
2146
|
const hintY = h - 1;
|
|
2030
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";
|
|
@@ -2086,7 +2203,8 @@ export class ChatScreen extends Screen {
|
|
|
2086
2203
|
{ kind: "line", text: `Total: ${total}` },
|
|
2087
2204
|
];
|
|
2088
2205
|
}
|
|
2089
|
-
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) });
|
|
2090
2208
|
rows.push({ kind: "line", text: `Tokens ${this.tokens.prompt} → ${this.tokens.completion}` });
|
|
2091
2209
|
return rows;
|
|
2092
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
|
+
}
|
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
import { Screen } from "../terminal/screen.js";
|
|
2
|
+
import { C } from "../colors.js";
|
|
3
|
+
import { drawHeader } from "../header.js";
|
|
4
|
+
import { drawHint } from "../ui.js";
|
|
5
|
+
import { configurePlugin, installPlugin, listPlugins, removePlugin, rollbackPlugin, selectProvider } from "../plugins/manager.js";
|
|
6
|
+
import { invalidateTuiPlugins, terminalUI } from "../plugins/terminal.js";
|
|
7
|
+
import { sanitizeText } from "../sanitize.js";
|
|
8
|
+
export class PluginViewScreen extends Screen {
|
|
9
|
+
item;
|
|
10
|
+
offset = 0;
|
|
11
|
+
constructor(item) {
|
|
12
|
+
super();
|
|
13
|
+
this.item = item;
|
|
14
|
+
}
|
|
15
|
+
onKey(key) { if (key.name === "escape" || key.name === "backspace") {
|
|
16
|
+
this.app.popScreen();
|
|
17
|
+
return;
|
|
18
|
+
} if (key.name === "down" || key.name === "pagedown")
|
|
19
|
+
this.offset++; if (key.name === "up" || key.name === "pageup")
|
|
20
|
+
this.offset = Math.max(0, this.offset - 1); this.app.render(); }
|
|
21
|
+
onMouse(ev) { if (ev.type === "click" && ev.button === 2)
|
|
22
|
+
this.app.popScreen(); }
|
|
23
|
+
render(buf) { drawHeader(buf, this.app.state); buf.writeText(2, 3, this.item.title, { fg: C.accent, bold: true }); const text = sanitizeText(this.item.text ?? "This view is available in the browser. / 此视图可在浏览器中打开。"); text.split("\n").slice(this.offset, this.offset + Math.max(1, buf.height - 8)).forEach((line, i) => buf.writeText(2, 5 + i, line.slice(0, Math.max(1, buf.width - 4)), { fg: C.white })); drawHint(buf, buf.height - 2, "Esc back · ↑/↓ scroll"); }
|
|
24
|
+
}
|
|
25
|
+
export class PluginsScreen extends Screen {
|
|
26
|
+
profile = "standard";
|
|
27
|
+
plugins = [];
|
|
28
|
+
index = 0;
|
|
29
|
+
detail = false;
|
|
30
|
+
editing = null;
|
|
31
|
+
draft = "";
|
|
32
|
+
message = "";
|
|
33
|
+
trustPending = false;
|
|
34
|
+
onActivate() { this.refresh(); }
|
|
35
|
+
refresh() { try {
|
|
36
|
+
this.plugins = listPlugins(this.profile).plugins;
|
|
37
|
+
this.index = Math.min(this.index, Math.max(0, this.plugins.length - 1));
|
|
38
|
+
invalidateTuiPlugins();
|
|
39
|
+
}
|
|
40
|
+
catch (e) {
|
|
41
|
+
this.message = e.message;
|
|
42
|
+
} }
|
|
43
|
+
action(run) { try {
|
|
44
|
+
run();
|
|
45
|
+
this.message = "Saved / 已保存";
|
|
46
|
+
this.refresh();
|
|
47
|
+
}
|
|
48
|
+
catch (e) {
|
|
49
|
+
this.message = e.message;
|
|
50
|
+
} this.app.render(); }
|
|
51
|
+
onKey(key) {
|
|
52
|
+
if (this.editing) {
|
|
53
|
+
if (key.name === "escape") {
|
|
54
|
+
this.editing = null;
|
|
55
|
+
this.app.render();
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
58
|
+
if (key.name === "return") {
|
|
59
|
+
const type = this.editing;
|
|
60
|
+
this.editing = null;
|
|
61
|
+
this.action(() => { if (type === "install")
|
|
62
|
+
installPlugin(this.draft);
|
|
63
|
+
else
|
|
64
|
+
configurePlugin(this.plugins[this.index].manifest.id, { config: JSON.parse(this.draft) }, this.profile); });
|
|
65
|
+
return;
|
|
66
|
+
}
|
|
67
|
+
if (key.name === "backspace")
|
|
68
|
+
this.draft = this.draft.slice(0, -1);
|
|
69
|
+
else if (!key.ctrl && !key.meta && key.name.length === 1)
|
|
70
|
+
this.draft += key.name;
|
|
71
|
+
this.app.render();
|
|
72
|
+
return;
|
|
73
|
+
}
|
|
74
|
+
if (key.name === "escape" || key.name === "backspace") {
|
|
75
|
+
if (this.detail) {
|
|
76
|
+
this.detail = false;
|
|
77
|
+
this.trustPending = false;
|
|
78
|
+
this.app.render();
|
|
79
|
+
}
|
|
80
|
+
else
|
|
81
|
+
this.app.popScreen();
|
|
82
|
+
return;
|
|
83
|
+
}
|
|
84
|
+
if (key.name === "tab") {
|
|
85
|
+
this.profile = this.profile === "standard" ? "pro" : "standard";
|
|
86
|
+
this.refresh();
|
|
87
|
+
this.app.render();
|
|
88
|
+
return;
|
|
89
|
+
}
|
|
90
|
+
if (key.name === "up") {
|
|
91
|
+
this.index = Math.max(0, this.index - 1);
|
|
92
|
+
this.app.render();
|
|
93
|
+
return;
|
|
94
|
+
}
|
|
95
|
+
if (key.name === "down") {
|
|
96
|
+
this.index = Math.min(this.plugins.length - 1, this.index + 1);
|
|
97
|
+
this.app.render();
|
|
98
|
+
return;
|
|
99
|
+
}
|
|
100
|
+
if (key.name === "i") {
|
|
101
|
+
this.editing = "install";
|
|
102
|
+
this.draft = "";
|
|
103
|
+
this.app.render();
|
|
104
|
+
return;
|
|
105
|
+
}
|
|
106
|
+
if (key.name === "m") {
|
|
107
|
+
const items = terminalUI().filter(u => ["app.menu", "workspace.tabs", "settings.sections"].includes(u.slot));
|
|
108
|
+
if (items.length)
|
|
109
|
+
this.app.pushScreen(new PluginViewScreen({ ...items[0], text: items.map(i => `${i.title}\n${i.text ?? ""}`).join("\n\n") }));
|
|
110
|
+
return;
|
|
111
|
+
}
|
|
112
|
+
const p = this.plugins[this.index];
|
|
113
|
+
if (!p)
|
|
114
|
+
return;
|
|
115
|
+
if (key.name === "return") {
|
|
116
|
+
this.detail = !this.detail;
|
|
117
|
+
this.app.render();
|
|
118
|
+
return;
|
|
119
|
+
}
|
|
120
|
+
if (key.name === "e") {
|
|
121
|
+
if (!p.enabled && p.manifest.runtime === "native" && p.manifest.entries?.agent) {
|
|
122
|
+
this.trustPending = true;
|
|
123
|
+
this.message = "Native plugin can execute unrestricted local code. Press Y to trust this exact revision, Esc to cancel.";
|
|
124
|
+
this.app.render();
|
|
125
|
+
return;
|
|
126
|
+
}
|
|
127
|
+
this.action(() => configurePlugin(p.manifest.id, { enabled: !p.enabled }, this.profile));
|
|
128
|
+
return;
|
|
129
|
+
}
|
|
130
|
+
if (key.name === "y" && this.trustPending) {
|
|
131
|
+
this.trustPending = false;
|
|
132
|
+
this.action(() => configurePlugin(p.manifest.id, { enabled: true, trustNative: true }, this.profile));
|
|
133
|
+
return;
|
|
134
|
+
}
|
|
135
|
+
if (key.name === "c") {
|
|
136
|
+
this.editing = "config";
|
|
137
|
+
this.draft = JSON.stringify(p.config);
|
|
138
|
+
this.app.render();
|
|
139
|
+
return;
|
|
140
|
+
}
|
|
141
|
+
if (key.name === "u") {
|
|
142
|
+
this.action(() => { installPlugin(p.source); });
|
|
143
|
+
return;
|
|
144
|
+
}
|
|
145
|
+
if (key.name === "r") {
|
|
146
|
+
this.action(() => rollbackPlugin(p.manifest.id));
|
|
147
|
+
return;
|
|
148
|
+
}
|
|
149
|
+
if (key.name === "d") {
|
|
150
|
+
this.action(() => removePlugin(p.manifest.id));
|
|
151
|
+
return;
|
|
152
|
+
}
|
|
153
|
+
if (key.name === "v") {
|
|
154
|
+
const replacements = (p.manifest.contributes?.ui ?? []).filter(u => ["chat.composer", "chat.message.renderer", "chat.card.plan", "chat.card.tool"].includes(u.slot));
|
|
155
|
+
this.action(() => { for (const u of replacements)
|
|
156
|
+
selectProvider(u.slot, `${p.manifest.id}/${u.id}`, this.profile); for (const prompt of p.manifest.contributes?.prompts ?? [])
|
|
157
|
+
if (prompt.replace)
|
|
158
|
+
selectProvider("persona", `${p.manifest.id}/${prompt.id}`, this.profile); });
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
onPaste(text) { if (this.editing) {
|
|
162
|
+
this.draft += text;
|
|
163
|
+
this.app.render();
|
|
164
|
+
} }
|
|
165
|
+
onMouse(ev) { if (ev.type === "click" && ev.button === 2)
|
|
166
|
+
this.app.popScreen();
|
|
167
|
+
else if (ev.type === "click" && ev.button === 0 && ev.y >= 5) {
|
|
168
|
+
this.index = Math.min(this.plugins.length - 1, Math.max(0, ev.y - 5));
|
|
169
|
+
this.app.render();
|
|
170
|
+
} }
|
|
171
|
+
render(buf) {
|
|
172
|
+
drawHeader(buf, this.app.state);
|
|
173
|
+
buf.writeText(2, 2, `Plugins / 插件 · ${this.profile}`, { fg: C.accent, bold: true });
|
|
174
|
+
let y = 4;
|
|
175
|
+
if (this.editing) {
|
|
176
|
+
buf.writeText(2, y++, this.editing === "install" ? "Local plugin directory / 本机插件目录" : "Configuration JSON / 配置 JSON", { fg: C.white });
|
|
177
|
+
buf.writeText(2, y++, this.draft.slice(-Math.max(1, buf.width - 5)), { fg: C.accent });
|
|
178
|
+
}
|
|
179
|
+
else if (this.detail && this.plugins[this.index]) {
|
|
180
|
+
const p = this.plugins[this.index];
|
|
181
|
+
const lines = [p.manifest.name, `${p.manifest.id} @ ${p.manifest.version}`, p.manifest.description ?? "", `Runtime: ${p.manifest.runtime ?? "sandbox"}`, `Permissions: ${(p.manifest.permissions ?? []).join(", ")}`, `Source: ${p.source}`, `Integrity: ${p.integrity}`, p.error ?? "", JSON.stringify(p.config)];
|
|
182
|
+
for (const line of lines)
|
|
183
|
+
buf.writeText(2, y++, sanitizeText(line, { singleLine: true }).slice(0, buf.width - 4), { fg: C.white });
|
|
184
|
+
}
|
|
185
|
+
else {
|
|
186
|
+
if (!this.plugins.length)
|
|
187
|
+
buf.writeText(2, 5, "No plugins. Press I to install a local directory. / 按 I 安装本机插件。", { fg: C.dim });
|
|
188
|
+
const start = Math.max(0, this.index - Math.max(1, buf.height - 12) + 1);
|
|
189
|
+
this.plugins.slice(start, start + Math.max(1, buf.height - 12)).forEach((p, i) => buf.writeText(2, 5 + i, `${i + start === this.index ? "›" : " "} ${p.enabled ? "●" : "○"} ${p.manifest.name} ${p.manifest.version}${p.error ? " !" : ""}`.slice(0, buf.width - 4), { fg: i + start === this.index ? C.accent : C.white, bold: i + start === this.index }));
|
|
190
|
+
}
|
|
191
|
+
if (this.message)
|
|
192
|
+
buf.writeText(2, buf.height - 4, sanitizeText(this.message, { singleLine: true }).slice(0, buf.width - 4), { fg: C.amber });
|
|
193
|
+
drawHint(buf, buf.height - 3, "↑/↓ select · Enter details · E enable/disable · I install · C config · U update");
|
|
194
|
+
drawHint(buf, buf.height - 2, "R rollback · D remove · V select providers · M extensions · Tab profile · Esc back");
|
|
195
|
+
}
|
|
196
|
+
}
|