@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/web/session.js
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
|
+
import { runPluginTurn, pluginCommand, pluginApprovalContext } from "../plugins/session.js";
|
|
1
2
|
import path from "node:path";
|
|
3
|
+
import { ApprovalQueue } from "./approvalQueue.js";
|
|
2
4
|
/**
|
|
3
5
|
* Headless conversation runner for the web UI.
|
|
4
6
|
*
|
|
@@ -12,9 +14,11 @@ import path from "node:path";
|
|
|
12
14
|
* (snapshot, stream, pause, resume, message) work unchanged.
|
|
13
15
|
*/
|
|
14
16
|
import { randomUUID } from "node:crypto";
|
|
17
|
+
import { FastAgentEngine } from "../fast/engine.js";
|
|
18
|
+
import { FAST_MODE, assertFastAvailable, fastSettings } from "../fast/types.js";
|
|
15
19
|
import { findModel, contextCompressionThreshold, modelContextWindow, sessionDir, intensityForModel, loadConfig, loadPlannerPrompt, loadStepPrompt, loadSubAgentPrompt, normalizeApprovalMode, planFirstEnabled, loadModels, loadAgentPrompt, normalizeAgentMode, saveSession, } from "../config.js";
|
|
16
20
|
import { COMPACT_KEEP, planCompaction, transcriptFor, modelHistory, normalizeContext, } from "../compact.js";
|
|
17
|
-
import { TaskRuntime, currentTask, linkSignals, taskLimits, resumeTaskSnapshot } from "../llm/runtime.js";
|
|
21
|
+
import { TaskRuntime, TaskControlError, currentTask, linkSignals, taskLimits, taskBudgetScope, resumeTaskSnapshot } from "../llm/runtime.js";
|
|
18
22
|
import { prepareContext, outputAllowance, finalTaskReport } from "../llm/context.js";
|
|
19
23
|
import { FINISH_STEP_TOOL, FINISH_STEP_DEFINITION, finishPlanStep, preparePlan, settlePlan } from "../llm/plan.js";
|
|
20
24
|
import { LLMClient, describeError } from "../llm/client.js";
|
|
@@ -34,7 +38,6 @@ const IDLE_DISPOSE_MS = 30 * 60 * 1000;
|
|
|
34
38
|
/** Placeholder shown while a tool is still running. */
|
|
35
39
|
const TOOL_PENDING = "…";
|
|
36
40
|
/** A sub-agent gets the same tools minus delegation: one level, never a tree. */
|
|
37
|
-
const SUB_AGENT_TOOLS = getSubAgentToolDefinitions();
|
|
38
41
|
/** Marker for the model-only summary, separate from the original transcript. */
|
|
39
42
|
const SUMMARY_PREFIX = "Context was compressed. Summary of the earlier part:";
|
|
40
43
|
/**
|
|
@@ -50,6 +53,8 @@ export function newSessionId(now = new Date()) {
|
|
|
50
53
|
* cannot litter the history with empty transcripts.
|
|
51
54
|
*/
|
|
52
55
|
export function createWebSession(cwd, selection, approvalMode, mode = "standard", proPreset, agentMode = "standard") {
|
|
56
|
+
if (agentMode === FAST_MODE)
|
|
57
|
+
assertFastAvailable(loadConfig(mode));
|
|
53
58
|
const now = new Date();
|
|
54
59
|
const source = {
|
|
55
60
|
id: newSessionId(now),
|
|
@@ -170,13 +175,19 @@ export class WebSession {
|
|
|
170
175
|
lastTask;
|
|
171
176
|
taskOutcome = "completed";
|
|
172
177
|
turnToken = 0;
|
|
178
|
+
/** Set by a resume click: the next turn clears its tool calls without asking. */
|
|
179
|
+
autoApproveNextTurn = false;
|
|
180
|
+
autoApproveToken = null;
|
|
173
181
|
agentMode = "standard";
|
|
174
182
|
activeAgentMode = "standard";
|
|
183
|
+
fastState;
|
|
184
|
+
queuedAgentMode = null;
|
|
175
185
|
turnAbort = null;
|
|
176
186
|
lastPromptTokens = 0;
|
|
177
187
|
disposed = false;
|
|
178
188
|
idleTimer = null;
|
|
179
189
|
/** Non-null while a tool call waits for the browser to answer. */
|
|
190
|
+
approvalQueue = new ApprovalQueue();
|
|
180
191
|
pendingApproval = null;
|
|
181
192
|
/** Reviewer count while an automated review runs, null otherwise. */
|
|
182
193
|
reviewing = null;
|
|
@@ -197,6 +208,11 @@ export class WebSession {
|
|
|
197
208
|
this.context = normalizeContext(source.context, this.messages.filter((m) => m.role !== "system").length);
|
|
198
209
|
this.plan = resumePlan(source.plan ?? null);
|
|
199
210
|
this.lastTask = resumeTaskSnapshot(source.task_run);
|
|
211
|
+
this.fastState = source.fast_run ? structuredClone(source.fast_run) : undefined;
|
|
212
|
+
if (this.fastState && !["completed", "failed", "cancelled"].includes(this.fastState.phase)) {
|
|
213
|
+
this.fastState.phase = "failed";
|
|
214
|
+
this.fastState.error = "Process restarted; artifacts preserved. Review partial state before resuming; tools are never replayed automatically.";
|
|
215
|
+
}
|
|
200
216
|
this.tokens = {
|
|
201
217
|
prompt: source.tokens?.prompt ?? 0,
|
|
202
218
|
completion: source.tokens?.completion ?? 0,
|
|
@@ -216,6 +232,7 @@ export class WebSession {
|
|
|
216
232
|
if (this.disposed)
|
|
217
233
|
return;
|
|
218
234
|
this.disposed = true;
|
|
235
|
+
this.approvalQueue.cancelWaiting();
|
|
219
236
|
this.proAbort?.abort();
|
|
220
237
|
this.turnAbort?.abort();
|
|
221
238
|
this.turnToken++;
|
|
@@ -283,6 +300,7 @@ export class WebSession {
|
|
|
283
300
|
approvalMode: this.approvalMode,
|
|
284
301
|
agentMode: this.agentMode,
|
|
285
302
|
activeAgentMode: this.busy ? this.activeAgentMode : this.agentMode,
|
|
303
|
+
...(this.fastState ? { fast: this.fastState } : {}),
|
|
286
304
|
task: this.taskRuntime?.snapshot() ?? this.lastTask,
|
|
287
305
|
busy: this.busy,
|
|
288
306
|
paused: this.paused,
|
|
@@ -295,7 +313,7 @@ export class WebSession {
|
|
|
295
313
|
tokens: { prompt: this.tokens.prompt, completion: this.tokens.completion },
|
|
296
314
|
subagent: this.subagent,
|
|
297
315
|
approval: this.pendingApproval
|
|
298
|
-
? liveApprovalDetails(this.pendingApproval.tool, this.pendingApproval.args, this.pendingApproval.risk, this.dir)
|
|
316
|
+
? { ...liveApprovalDetails(this.pendingApproval.tool, this.pendingApproval.args, this.pendingApproval.risk, this.dir), id: this.pendingApproval.id }
|
|
299
317
|
: null,
|
|
300
318
|
reviewing: this.reviewing,
|
|
301
319
|
};
|
|
@@ -331,6 +349,8 @@ export class WebSession {
|
|
|
331
349
|
livePause() {
|
|
332
350
|
if (this.paused || (!this.busy && !this.queued))
|
|
333
351
|
return;
|
|
352
|
+
if (this.fastState && this.activeAgentMode === FAST_MODE && this.busy)
|
|
353
|
+
this.fastState.phase = "cancelling";
|
|
334
354
|
this.paused = true;
|
|
335
355
|
this.proAbort?.abort();
|
|
336
356
|
this.turnAbort?.abort();
|
|
@@ -358,14 +378,27 @@ export class WebSession {
|
|
|
358
378
|
return;
|
|
359
379
|
this.paused = false;
|
|
360
380
|
this.publishLive();
|
|
361
|
-
this.
|
|
381
|
+
if (this.busy)
|
|
382
|
+
return;
|
|
383
|
+
// Continue answers the approval the pause threw away: the turn that follows
|
|
384
|
+
// runs its tool calls through the gate instead of asking the human again.
|
|
385
|
+
const restart = !this.queued && lastRequestText(this.messages) !== "";
|
|
386
|
+
if (!this.queued && !restart)
|
|
387
|
+
return;
|
|
388
|
+
this.autoApproveNextTurn = true;
|
|
389
|
+
// A queued message starts the next turn on its own; otherwise the paused turn
|
|
390
|
+
// is restarted from the transcript the pause kept, partial answer included.
|
|
391
|
+
if (this.queued)
|
|
392
|
+
this.maybeFlushQueue();
|
|
393
|
+
else
|
|
394
|
+
this.startTurn();
|
|
362
395
|
}
|
|
363
396
|
liveSend(text) {
|
|
364
397
|
this.send(text);
|
|
365
398
|
}
|
|
366
|
-
liveApprove(approved) {
|
|
399
|
+
liveApprove(approved, approvalId) {
|
|
367
400
|
const pending = this.pendingApproval;
|
|
368
|
-
if (!pending)
|
|
401
|
+
if (!pending || (approvalId !== undefined && pending.id !== approvalId))
|
|
369
402
|
return false;
|
|
370
403
|
this.pendingApproval = null;
|
|
371
404
|
pending.resolve(approved);
|
|
@@ -375,6 +408,14 @@ export class WebSession {
|
|
|
375
408
|
liveSetAgentMode(mode) {
|
|
376
409
|
if (this.mode === "pro" || this.disposed)
|
|
377
410
|
return { ok: false, error: "this session cannot change agent mode" };
|
|
411
|
+
if (mode === FAST_MODE) {
|
|
412
|
+
try {
|
|
413
|
+
assertFastAvailable(loadConfig(this.mode));
|
|
414
|
+
}
|
|
415
|
+
catch (error) {
|
|
416
|
+
return { ok: false, error: describeError(error) };
|
|
417
|
+
}
|
|
418
|
+
}
|
|
378
419
|
this.agentMode = mode;
|
|
379
420
|
this.save();
|
|
380
421
|
this.publishLive();
|
|
@@ -410,6 +451,8 @@ export class WebSession {
|
|
|
410
451
|
this.turnToken++;
|
|
411
452
|
this.taskRuntime = null;
|
|
412
453
|
this.lastTask = undefined;
|
|
454
|
+
this.fastState = undefined;
|
|
455
|
+
this.queuedAgentMode = null;
|
|
413
456
|
if (this.pendingApproval) {
|
|
414
457
|
const pending = this.pendingApproval;
|
|
415
458
|
this.pendingApproval = null;
|
|
@@ -443,6 +486,7 @@ export class WebSession {
|
|
|
443
486
|
this.save();
|
|
444
487
|
if (this.busy || this.paused) {
|
|
445
488
|
this.queued = trimmed;
|
|
489
|
+
this.queuedAgentMode = this.agentMode;
|
|
446
490
|
this.publishLive();
|
|
447
491
|
return;
|
|
448
492
|
}
|
|
@@ -472,10 +516,13 @@ export class WebSession {
|
|
|
472
516
|
const turnMessages = this.messages.slice();
|
|
473
517
|
const approvalContext = captureApprovalContext(turnMessages);
|
|
474
518
|
this.busy = true;
|
|
475
|
-
this.activeAgentMode = this.agentMode;
|
|
519
|
+
this.activeAgentMode = this.queuedAgentMode ?? this.agentMode;
|
|
520
|
+
this.queuedAgentMode = null;
|
|
476
521
|
this.turnAbort?.abort();
|
|
477
522
|
this.turnAbort = new AbortController();
|
|
478
523
|
const token = ++this.turnToken;
|
|
524
|
+
this.autoApproveToken = this.autoApproveNextTurn ? token : null;
|
|
525
|
+
this.autoApproveNextTurn = false;
|
|
479
526
|
this.streamingContent = "";
|
|
480
527
|
this.publishLive();
|
|
481
528
|
void (async () => {
|
|
@@ -492,15 +539,26 @@ export class WebSession {
|
|
|
492
539
|
*/
|
|
493
540
|
async runTurn(token, turnMessages, approvalContext) {
|
|
494
541
|
const runtime = new TaskRuntime({
|
|
542
|
+
executionMode: this.mode === "pro" ? "pro" : this.activeAgentMode,
|
|
495
543
|
request: approvalContext.prompt, priorUserRequests: approvalContext.priorUserRequests,
|
|
496
544
|
cwd: this.dir, signal: this.turnAbort?.signal, limits: taskLimits(loadConfig(this.mode)),
|
|
545
|
+
budgetScope: taskBudgetScope(loadConfig(this.mode)),
|
|
497
546
|
journalDir: path.join(sessionDir(this.mode), "tasks", path.basename(this.sessionId || "unsaved")),
|
|
498
547
|
});
|
|
499
548
|
this.taskRuntime = runtime;
|
|
500
549
|
this.taskOutcome = "completed";
|
|
501
550
|
let failed = false;
|
|
502
551
|
try {
|
|
503
|
-
await runtime.
|
|
552
|
+
await runPluginTurn(runtime, { profile: this.mode, cwd: this.dir, sessionId: this.sessionId ?? undefined, role: "main", request: approvalContext.prompt }, (name, args, signal) => this.requestApproval(name, args, approvalContext, signal), async () => {
|
|
553
|
+
const command = pluginCommand(approvalContext.prompt);
|
|
554
|
+
if (command) {
|
|
555
|
+
const result = await runtime.plugins.runCommand(command.name, command.args);
|
|
556
|
+
this.messages.push({ role: "assistant", content: typeof result === "string" ? result : JSON.stringify(result ?? null, null, 2) });
|
|
557
|
+
this.onTurnFinished();
|
|
558
|
+
return;
|
|
559
|
+
}
|
|
560
|
+
await this.runTaskTurn(token, turnMessages, approvalContext);
|
|
561
|
+
});
|
|
504
562
|
}
|
|
505
563
|
catch (error) {
|
|
506
564
|
failed = true;
|
|
@@ -534,6 +592,8 @@ export class WebSession {
|
|
|
534
592
|
await this.autoCompress(token);
|
|
535
593
|
if (token !== this.turnToken || this.paused)
|
|
536
594
|
return;
|
|
595
|
+
if (this.activeAgentMode === FAST_MODE)
|
|
596
|
+
return this.runFastTurn(token, turnMessages, approvalContext);
|
|
537
597
|
if (this.activeAgentMode !== "standard")
|
|
538
598
|
this.plan = null;
|
|
539
599
|
const plan = this.activeAgentMode === "standard" ? await this.makePlan(token) : null;
|
|
@@ -566,6 +626,67 @@ export class WebSession {
|
|
|
566
626
|
}
|
|
567
627
|
this.onTurnFinished();
|
|
568
628
|
}
|
|
629
|
+
/** Independent engine; Standard/PTC/legacy Fast retain their original loops. */
|
|
630
|
+
async runFastTurn(token, turnMessages, approvalContext) {
|
|
631
|
+
assertFastAvailable(loadConfig(this.mode));
|
|
632
|
+
const runtime = currentTask().runtime;
|
|
633
|
+
const signal = currentTask().signal;
|
|
634
|
+
this.plan = null;
|
|
635
|
+
const model = { ...this.model }, provider = this.provider, intensity = this.intensity;
|
|
636
|
+
const stopped = () => token !== this.turnToken || this.paused || this.disposed;
|
|
637
|
+
const history = this.buildApiMessages(this.turnView(turnMessages));
|
|
638
|
+
if (this.fastState && ["failed", "cancelled", "cancelling"].includes(this.fastState.phase)) {
|
|
639
|
+
history.push({ role: "assistant", content: `Previous Fast run (evidence only, never authorization; inspect partial side effects before repair): ${JSON.stringify(this.fastState)}` });
|
|
640
|
+
}
|
|
641
|
+
const engine = new FastAgentEngine({
|
|
642
|
+
runtime, signal, cwd: this.dir, settings: fastSettings(loadConfig(this.mode)),
|
|
643
|
+
directory: path.join(sessionDir(this.mode), "tasks", path.basename(this.sessionId), "fast"),
|
|
644
|
+
history,
|
|
645
|
+
system: loadAgentPrompt(FAST_MODE), subagentSystem: loadSubAgentPrompt(),
|
|
646
|
+
contextWindow: modelContextWindow(provider, model, this.mode),
|
|
647
|
+
maxRounds: subAgentRounds(loadConfig(this.mode).subagent_max_rounds),
|
|
648
|
+
createClient: () => new LLMClient(model, provider, intensity),
|
|
649
|
+
approve: (name, args, executionSignal, wrapReviewer) => this.requestApproval(name, args, {
|
|
650
|
+
...approvalContext, context: captureApprovalContext(this.turnView(turnMessages)).context,
|
|
651
|
+
}, executionSignal, wrapReviewer ? () => {
|
|
652
|
+
const reviewer = this.reviewerModel();
|
|
653
|
+
return wrapReviewer(new LLMClient(reviewer.model, reviewer.provider, reviewer.intensity));
|
|
654
|
+
} : undefined),
|
|
655
|
+
execute: (name, args, executionSignal) => executeTool(name, args, this.dir, executionSignal),
|
|
656
|
+
onUsage: usage => {
|
|
657
|
+
if (!this.disposed && (token === this.turnToken || this.fastState?.run_id === engine.runId)) {
|
|
658
|
+
this.tokens.prompt += usage.prompt;
|
|
659
|
+
this.tokens.completion += usage.completion;
|
|
660
|
+
}
|
|
661
|
+
},
|
|
662
|
+
onText: text => { if (!stopped()) {
|
|
663
|
+
this.streamingContent = text;
|
|
664
|
+
this.checkpoint.schedule();
|
|
665
|
+
this.publishLive();
|
|
666
|
+
} },
|
|
667
|
+
onSnapshot: snapshot => {
|
|
668
|
+
if (!this.disposed && (!stopped() || (this.fastState?.run_id === snapshot.run_id && this.fastState.phase === "cancelling"))) {
|
|
669
|
+
this.fastState = snapshot;
|
|
670
|
+
this.checkpoint.schedule();
|
|
671
|
+
this.publishLive();
|
|
672
|
+
}
|
|
673
|
+
},
|
|
674
|
+
onTool: (name, args, result) => {
|
|
675
|
+
if (!stopped()) {
|
|
676
|
+
this.messages.push({ role: "tool", name, arguments: args, result });
|
|
677
|
+
this.save();
|
|
678
|
+
this.publishLive();
|
|
679
|
+
}
|
|
680
|
+
},
|
|
681
|
+
});
|
|
682
|
+
const result = await engine.run();
|
|
683
|
+
if (stopped())
|
|
684
|
+
return;
|
|
685
|
+
this.fastState = result.snapshot;
|
|
686
|
+
this.taskOutcome = result.status;
|
|
687
|
+
this.messages.push({ role: "assistant", content: result.report });
|
|
688
|
+
this.onTurnFinished();
|
|
689
|
+
}
|
|
569
690
|
/** Freeze one resolved team per task, plan first, then give the whole brief to main. */
|
|
570
691
|
async runProTurn(token, turnMessages, approvalContext) {
|
|
571
692
|
const taskSignal = currentTask()?.signal;
|
|
@@ -723,6 +844,7 @@ export class WebSession {
|
|
|
723
844
|
try {
|
|
724
845
|
const client = new LLMClient(this.model, this.provider, this.intensity);
|
|
725
846
|
const answer = await client.complete(loadPlannerPrompt(), buildPlannerInput({ request, cwd: this.dir, transcript: planTranscript(this.messages) }), {
|
|
847
|
+
...(currentTask()?.runtime.diagnostics ? { diagnostic: { role: "planning" } } : {}),
|
|
726
848
|
onRetry: (notice) => {
|
|
727
849
|
this.retrying = notice;
|
|
728
850
|
this.publishLive();
|
|
@@ -845,7 +967,7 @@ export class WebSession {
|
|
|
845
967
|
return false;
|
|
846
968
|
}
|
|
847
969
|
if (client.outcome?.error?.code.endsWith("_budget") && currentTask()) {
|
|
848
|
-
const report = await finalTaskReport(currentTask().runtime, () => team?.client("main_model") ?? new LLMClient(this.model, this.provider, this.intensity), usage => { this.tokens.prompt += usage.prompt; this.tokens.completion += usage.completion; });
|
|
970
|
+
const report = await finalTaskReport(currentTask().runtime, () => team?.client("main_model") ?? new LLMClient(this.model, this.provider, this.intensity), usage => { this.tokens.prompt += usage.prompt; this.tokens.completion += usage.completion; }, apiMessages);
|
|
849
971
|
if (token !== this.turnToken || this.paused)
|
|
850
972
|
return false;
|
|
851
973
|
this.taskOutcome = "partial";
|
|
@@ -941,6 +1063,11 @@ export class WebSession {
|
|
|
941
1063
|
result = await this.runTool(call.name, args, toolMsg, token, apiMessages, approvalContext);
|
|
942
1064
|
}
|
|
943
1065
|
catch (err) {
|
|
1066
|
+
if (err instanceof TaskControlError && err.code.endsWith("_budget")) {
|
|
1067
|
+
toolMsg.result = `[Not executed: ${describeError(err)}]`;
|
|
1068
|
+
this.save();
|
|
1069
|
+
throw err;
|
|
1070
|
+
}
|
|
944
1071
|
if (token === this.turnToken)
|
|
945
1072
|
this.taskOutcome = "failed";
|
|
946
1073
|
result = `Error: ${err instanceof Error ? err.message : String(err)}`;
|
|
@@ -961,6 +1088,15 @@ export class WebSession {
|
|
|
961
1088
|
catch (err) {
|
|
962
1089
|
if (token !== this.turnToken)
|
|
963
1090
|
return false;
|
|
1091
|
+
if (err instanceof TaskControlError && err.code.endsWith("_budget") && currentTask()) {
|
|
1092
|
+
const report = await finalTaskReport(currentTask().runtime, () => team?.client("main_model") ?? new LLMClient(this.model, this.provider, this.intensity), usage => { this.tokens.prompt += usage.prompt; this.tokens.completion += usage.completion; }, apiMessages);
|
|
1093
|
+
if (token !== this.turnToken || this.paused)
|
|
1094
|
+
return false;
|
|
1095
|
+
this.taskOutcome = "partial";
|
|
1096
|
+
this.streamingContent = null;
|
|
1097
|
+
this.messages.push({ role: "assistant", content: report });
|
|
1098
|
+
return false;
|
|
1099
|
+
}
|
|
964
1100
|
if (this.streamingContent) {
|
|
965
1101
|
this.messages.push({ role: "assistant", content: this.streamingContent });
|
|
966
1102
|
}
|
|
@@ -976,6 +1112,7 @@ export class WebSession {
|
|
|
976
1112
|
this.retrying = null;
|
|
977
1113
|
this.busy = false;
|
|
978
1114
|
this.paused = false;
|
|
1115
|
+
this.autoApproveToken = null;
|
|
979
1116
|
this.streamingContent = null;
|
|
980
1117
|
this.save();
|
|
981
1118
|
this.publishLive();
|
|
@@ -986,11 +1123,15 @@ export class WebSession {
|
|
|
986
1123
|
}
|
|
987
1124
|
// ---- approval gate ---------------------------------------------------
|
|
988
1125
|
/** Classify a tool call and, when it is risky, wait for a decision. */
|
|
989
|
-
async requestApproval(tool, args, context = captureApprovalContext(this.messages), signal = currentTask()?.runtime.signal ?? this.turnAbort?.signal) {
|
|
1126
|
+
async requestApproval(tool, args, context = captureApprovalContext(this.messages), signal = currentTask()?.runtime.signal ?? this.turnAbort?.signal, createReviewerClient) {
|
|
1127
|
+
context = pluginApprovalContext(context);
|
|
990
1128
|
const linked = linkSignals(signal, currentTask()?.signal);
|
|
991
1129
|
signal = linked.signal;
|
|
992
1130
|
try {
|
|
993
1131
|
currentTask()?.runtime.assertTool(tool, args);
|
|
1132
|
+
const plugins = currentTask()?.runtime.plugins;
|
|
1133
|
+
if (plugins)
|
|
1134
|
+
await plugins.guardTool(tool, args);
|
|
994
1135
|
}
|
|
995
1136
|
catch (error) {
|
|
996
1137
|
linked.dispose();
|
|
@@ -1004,21 +1145,29 @@ export class WebSession {
|
|
|
1004
1145
|
const approvalToken = this.turnToken;
|
|
1005
1146
|
const team = this.proTeam;
|
|
1006
1147
|
const gate = new ApprovalGate({
|
|
1007
|
-
askHuman: () =>
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1148
|
+
askHuman: () => {
|
|
1149
|
+
const requestedMode = this.approvalMode;
|
|
1150
|
+
return this.approvalQueue.run(() => new Promise((resolve) => {
|
|
1151
|
+
if (approvalToken !== this.turnToken || this.paused || this.disposed || signal?.aborted === true) {
|
|
1152
|
+
resolve(false);
|
|
1153
|
+
return;
|
|
1154
|
+
}
|
|
1155
|
+
if (requestedMode !== this.approvalMode) {
|
|
1156
|
+
resolve(null);
|
|
1157
|
+
return;
|
|
1158
|
+
}
|
|
1159
|
+
this.pendingApproval = { id: randomUUID(), tool, args, risk, resolve };
|
|
1160
|
+
this.publishLive();
|
|
1161
|
+
}), signal);
|
|
1162
|
+
},
|
|
1015
1163
|
reviewer: () => this.reviewerModel(),
|
|
1164
|
+
...(createReviewerClient ? { createReviewerClient } : {}),
|
|
1016
1165
|
...(this.mode === "pro" && team ? {
|
|
1017
1166
|
createReviewerClient: () => team.client("confirm_model"),
|
|
1018
1167
|
reviewerSystemPrompt: () => loadProPrompt("confirm_model"),
|
|
1019
1168
|
} : {}),
|
|
1020
1169
|
cancelled: () => approvalToken !== this.turnToken || this.paused || this.disposed || signal?.aborted === true,
|
|
1021
|
-
autoApprovalEnabled: () => this.approvalMode === "auto",
|
|
1170
|
+
autoApprovalEnabled: () => this.approvalMode === "auto" || approvalToken === this.autoApproveToken,
|
|
1022
1171
|
extremeAutoApprovalEnabled: () => loadConfig(this.mode).auto_approve_extreme_risk === true,
|
|
1023
1172
|
onReview: (info) => {
|
|
1024
1173
|
if (approvalToken !== this.turnToken || this.paused || this.disposed || signal?.aborted === true)
|
|
@@ -1194,7 +1343,7 @@ export class WebSession {
|
|
|
1194
1343
|
createClient: () => new LLMClient(this.model, this.provider, this.intensity),
|
|
1195
1344
|
system: loadSubAgentPrompt(),
|
|
1196
1345
|
task: buildSubAgentTask({ task, context, cwd: this.dir, rounds: maxRounds }),
|
|
1197
|
-
tools:
|
|
1346
|
+
tools: getSubAgentToolDefinitions(),
|
|
1198
1347
|
maxRounds,
|
|
1199
1348
|
approve: (name, callArgs) => this.requestApproval(name, callArgs, approval, signal),
|
|
1200
1349
|
execute: (name, callArgs) => name === DELEGATE_TOOL
|
|
@@ -1332,7 +1481,7 @@ export class WebSession {
|
|
|
1332
1481
|
if (chat.length === 0)
|
|
1333
1482
|
return true;
|
|
1334
1483
|
try {
|
|
1335
|
-
this.sessionId = saveSession(chat, this.model.name, this.provider, this.intensity, this.dir, this.sessionId, this.tokens, this.context, this.approvalMode, this.plan, this.mode, this.proPreset ?? undefined, this.agentMode, this.taskRuntime?.snapshot() ?? this.lastTask);
|
|
1484
|
+
this.sessionId = saveSession(chat, this.model.name, this.provider, this.intensity, this.dir, this.sessionId, this.tokens, this.context, this.approvalMode, this.plan, this.mode, this.proPreset ?? undefined, this.agentMode, this.taskRuntime?.snapshot() ?? this.lastTask, this.fastState);
|
|
1336
1485
|
this.saveError = null;
|
|
1337
1486
|
return true;
|
|
1338
1487
|
}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import { type Mode } from "../config.js";
|
|
2
|
+
/**
|
|
3
|
+
* Public settings use a whitelist: API keys and cached credentials stay on disk.
|
|
4
|
+
*
|
|
5
|
+
* `mode` selects the workspace. The Pro workspace has its own defaults,
|
|
6
|
+
* language, plan and reviewer settings; the login password and the approval
|
|
7
|
+
* policy are layered in from the shared config by loadConfig(), so the two
|
|
8
|
+
* pages cannot drift apart on those.
|
|
9
|
+
*/
|
|
10
|
+
export declare function getWebSettings(mode?: Mode): {
|
|
11
|
+
providers: {
|
|
12
|
+
name: string;
|
|
13
|
+
models: {
|
|
14
|
+
name: string;
|
|
15
|
+
configured: boolean;
|
|
16
|
+
context_window: number;
|
|
17
|
+
intensities: string[];
|
|
18
|
+
}[];
|
|
19
|
+
}[];
|
|
20
|
+
intensities: string[];
|
|
21
|
+
languages: {
|
|
22
|
+
value: "en" | "zh";
|
|
23
|
+
label: string;
|
|
24
|
+
}[];
|
|
25
|
+
selection: {
|
|
26
|
+
provider: string;
|
|
27
|
+
model: string;
|
|
28
|
+
intensity: string;
|
|
29
|
+
};
|
|
30
|
+
settings: {
|
|
31
|
+
default_intensity: string;
|
|
32
|
+
model_context_window: number;
|
|
33
|
+
approval_model: string;
|
|
34
|
+
auto_approve_extreme_risk: boolean;
|
|
35
|
+
sound_alerts: boolean;
|
|
36
|
+
language: "en" | "zh";
|
|
37
|
+
plan_first: boolean;
|
|
38
|
+
approval_mode: import("../config.js").ApprovalMode;
|
|
39
|
+
web_console: boolean;
|
|
40
|
+
web_lan: boolean;
|
|
41
|
+
background: boolean;
|
|
42
|
+
};
|
|
43
|
+
pro?: {
|
|
44
|
+
presets: {
|
|
45
|
+
name: string;
|
|
46
|
+
chinese_name: string;
|
|
47
|
+
"model-setting": {
|
|
48
|
+
type: "confirm_model" | "main_model" | "thinking_model" | "front_output_model" | "backend_output_model" | "genimage_model" | "plan_model" | "modeling_model";
|
|
49
|
+
provider: string;
|
|
50
|
+
model: string | null;
|
|
51
|
+
intensity: string;
|
|
52
|
+
context_window: number;
|
|
53
|
+
}[];
|
|
54
|
+
}[];
|
|
55
|
+
preset: string;
|
|
56
|
+
max_recursion_depth: number;
|
|
57
|
+
} | undefined;
|
|
58
|
+
update: {
|
|
59
|
+
current_version: string;
|
|
60
|
+
latest_version: string;
|
|
61
|
+
has_update: boolean;
|
|
62
|
+
};
|
|
63
|
+
};
|
|
64
|
+
/** Validate the complete patch before writing anything, preserving unrelated settings. */
|
|
65
|
+
export declare function updateWebSettings(value: unknown, mode?: Mode): void;
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { TerminalBridgeInfo } from "../background.js";
|
|
2
|
+
/** A private loopback bridge exposes only the terminal's live hub. */
|
|
3
|
+
export declare function startTerminalBridge(): Promise<TerminalBridgeInfo & {
|
|
4
|
+
close(): void;
|
|
5
|
+
}>;
|
|
6
|
+
/** Mirror each attached terminal's live sessions into the Web service. */
|
|
7
|
+
export declare class TerminalBridges {
|
|
8
|
+
private entries;
|
|
9
|
+
private timer;
|
|
10
|
+
constructor();
|
|
11
|
+
add(owner: number, bridge: TerminalBridgeInfo): void;
|
|
12
|
+
remove(owner: number): void;
|
|
13
|
+
close(): void;
|
|
14
|
+
private poll;
|
|
15
|
+
}
|
|
@@ -66,10 +66,16 @@ export async function startTerminalBridge() {
|
|
|
66
66
|
else if (action === "message") {
|
|
67
67
|
if (typeof body.text !== "string" || !body.text.trim())
|
|
68
68
|
throw new Error("empty message");
|
|
69
|
-
|
|
69
|
+
if (body.agent_mode !== undefined && !isAgentMode(body.agent_mode))
|
|
70
|
+
throw new Error("invalid agent mode");
|
|
71
|
+
const sent = hub.sendConfigured(key, body.text, body.agent_mode);
|
|
72
|
+
if (!sent.ok) {
|
|
73
|
+
sendJson(res, 409, { error: sent.error });
|
|
74
|
+
return;
|
|
75
|
+
}
|
|
70
76
|
}
|
|
71
77
|
else if (action === "approve") {
|
|
72
|
-
if (!hub.approve(key, body.approved === true)) {
|
|
78
|
+
if (!hub.approve(key, body.approved === true, typeof body.approval_id === "string" ? body.approval_id : undefined)) {
|
|
73
79
|
sendJson(res, 409, { error: "no approval is pending" });
|
|
74
80
|
return;
|
|
75
81
|
}
|
package/dist/width.d.ts
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
export declare function wcwidth(cp: number): number;
|
|
2
|
+
export declare function displayWidth(text: string): number;
|
|
3
|
+
export declare function truncateByWidth(text: string, width: number): string;
|
|
4
|
+
export declare function ellipsize(text: string, width: number): string;
|
|
5
|
+
/** Trim the beginning of `text`, keeping the end (useful for long paths). */
|
|
6
|
+
export declare function ellipsizeStart(text: string, width: number): string;
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
# Neutrino 插件指南(API v1)
|
|
2
|
+
|
|
3
|
+
插件可以同时提供 Agent 工具、用户命令、菜单、选项卡、输入框、消息与工具卡片、主题、skill 和提示词。安装插件不需要修改 Neutrino 源码。Standard 和 Pro 共用安装包,分别保存启用状态、配置和组件选择。
|
|
4
|
+
|
|
5
|
+
## 快速开始
|
|
6
|
+
|
|
7
|
+
从仓库根目录运行:
|
|
8
|
+
|
|
9
|
+
```sh
|
|
10
|
+
npm run build
|
|
11
|
+
node dist/bin.js plugin install ./examples/plugins/function-tools
|
|
12
|
+
node dist/bin.js plugin enable community.text-tools
|
|
13
|
+
node dist/bin.js plugin doctor
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
在终端或浏览器的对话中输入以下命令,可直接得到结果,不请求模型:
|
|
17
|
+
|
|
18
|
+
```text
|
|
19
|
+
/plugin community.text-tools/count {"text":"hello plugin"}
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
正常对话中,模型也可以调用此插件的文本统计和文件统计工具。工具使用 `p_<插件 ID 的散列>_<工具名>` 作为模型协议名;描述中保留完整的 `插件ID/工具名`。
|
|
23
|
+
|
|
24
|
+
安装主题:
|
|
25
|
+
|
|
26
|
+
```sh
|
|
27
|
+
neutrino plugin install ./examples/plugins/theme
|
|
28
|
+
neutrino plugin enable community.indigo-theme
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
安装自定义输入框后,在管理界面的 **组件提供者 → chat.composer** 中选择它。命令行也支持选择:
|
|
32
|
+
|
|
33
|
+
```sh
|
|
34
|
+
neutrino plugin install ./examples/plugins/composer
|
|
35
|
+
neutrino plugin enable community.writing-composer
|
|
36
|
+
neutrino plugin provider chat.composer community.writing-composer/editor
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
恢复默认输入框:
|
|
40
|
+
|
|
41
|
+
```sh
|
|
42
|
+
neutrino plugin provider chat.composer default
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
## 管理界面
|
|
46
|
+
|
|
47
|
+
浏览器侧边栏的 **插件 / Plugins** 按钮打开管理界面。可安装本机目录或 `.neutrino-plugin` 包、启用/停用插件、编辑由 Schema 生成的配置表单、更新、回滚、卸载,以及选择输入框、消息、计划、工具卡片和人格提示词提供者。
|
|
48
|
+
|
|
49
|
+
“当前模式”修改当前 Standard/Pro 的默认配置;“当前项目”将启用状态和配置保存到该项目的覆盖层。安装、更新、回滚、卸载作用于共享包。组件提供者选择按 Standard/Pro 保存。
|
|
50
|
+
|
|
51
|
+
终端起始页有 **Plugins** 入口,在其他页面按 `Ctrl+X` 也可打开。终端插件页支持:
|
|
52
|
+
|
|
53
|
+
| 按键 | 操作 |
|
|
54
|
+
| --- | --- |
|
|
55
|
+
| ↑ / ↓、Enter | 选择插件、查看详情 |
|
|
56
|
+
| E | 启用 / 停用 |
|
|
57
|
+
| I | 安装本机目录或插件包 |
|
|
58
|
+
| C | 编辑完整配置 JSON |
|
|
59
|
+
| U / R / D | 更新 / 回滚 / 卸载 |
|
|
60
|
+
| V | 选择该插件提供的替换组件 |
|
|
61
|
+
| M | 查看扩展页面的文本内容 |
|
|
62
|
+
| Tab | 切换 Standard / Pro |
|
|
63
|
+
| Esc | 返回 |
|
|
64
|
+
|
|
65
|
+
启用原生插件时,终端要求按 Y 明确信任该版本。Web 使用独立的信任复选框。
|
|
66
|
+
|
|
67
|
+
## 命令行
|
|
68
|
+
|
|
69
|
+
```text
|
|
70
|
+
neutrino plugin help
|
|
71
|
+
neutrino plugin list [--profile standard|pro] [--workspace /absolute/project]
|
|
72
|
+
neutrino plugin inspect <id>
|
|
73
|
+
neutrino plugin create <new-directory>
|
|
74
|
+
neutrino plugin install <directory-or-bundle>
|
|
75
|
+
neutrino plugin enable <id> [--trust-native]
|
|
76
|
+
neutrino plugin disable <id>
|
|
77
|
+
neutrino plugin config <id> '<完整配置 JSON>'
|
|
78
|
+
neutrino plugin update <id>
|
|
79
|
+
neutrino plugin rollback <id>
|
|
80
|
+
neutrino plugin remove <id>
|
|
81
|
+
neutrino plugin provider <slot> <plugin-id/contribution-id|default>
|
|
82
|
+
neutrino plugin pack <directory> <output.neutrino-plugin>
|
|
83
|
+
neutrino plugin doctor
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
配置命令提交完整插件配置,未提交的字段回到 Schema 默认值。参数包含空格或 JSON 时,请使用所在 shell 的引号规则。更新从上次安装的本机来源重新读取,原文件夹更新或替换原插件包后执行即可。
|
|
87
|
+
|
|
88
|
+
## 示例插件
|
|
89
|
+
|
|
90
|
+
| 目录 | ID | 验证的扩展方式 |
|
|
91
|
+
| --- | --- | --- |
|
|
92
|
+
| `examples/plugins/function-tools` | `community.text-tools` | 受限脚本工具、命令、只读文件访问、事件与存储 |
|
|
93
|
+
| `examples/plugins/menus-tabs` | `community.workbench` | 菜单、工作区选项卡、侧栏和终端回退 |
|
|
94
|
+
| `examples/plugins/composer` | `community.writing-composer` | 输入框替换、草稿版本检查、发送、工具栏 |
|
|
95
|
+
| `examples/plugins/cards` | `community.inspector-cards` | 工具、计划和消息渲染器 |
|
|
96
|
+
| `examples/plugins/theme` | `community.indigo-theme` | 无脚本 Web / TUI 主题 |
|
|
97
|
+
| `examples/plugins/skills-prompts` | `community.review-skills` | 按需 skill、规则片段和可选人格 |
|
|
98
|
+
|
|
99
|
+
插件默认全部停用。卸载不删除旧会话,也不自动重放过去的工具调用。旧包文件会保留,以供运行中的回合继续使用;用户数据保存在独立的 `data` 目录。
|
|
100
|
+
|
|
101
|
+
## 故障恢复
|
|
102
|
+
|
|
103
|
+
```sh
|
|
104
|
+
neutrino plugin disable <id>
|
|
105
|
+
neutrino plugin doctor
|
|
106
|
+
neutrino --safe-mode --no-web
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
安全模式跳过所有外部插件。`--no-web` 可确保不连接到之前已启动、未处于安全模式的后台控制台。如需浏览器安全模式,先停止旧后台服务,再运行 `neutrino --safe-mode web`。
|
|
110
|
+
|
|
111
|
+
运行中的回合固定包版本和配置,更新通常从下一回合生效。增加权限或更新原生代码时,插件会自动停用,需重新启用。Web 外观与组件配置会自动刷新。插件脚本激活失败时,该插件的注册项撤销,其他插件和内置功能继续运行;脚本执行失败会记录为该次调用的错误。
|
|
112
|
+
|
|
113
|
+
## 文档目录与支持范围
|
|
114
|
+
|
|
115
|
+
- [架构、作用域与生命周期](architecture.md)
|
|
116
|
+
- [清单与配置格式](manifest.md),[编辑器 JSON Schema](manifest.schema.json)
|
|
117
|
+
- [Agent SDK 与事件契约](agent-sdk.md)
|
|
118
|
+
- [Web / TUI 界面 SDK](ui-sdk.md)
|
|
119
|
+
- [Skill 与提示词](skills-prompts.md)
|
|
120
|
+
- [权限、受限执行与原生信任](security.md)
|
|
121
|
+
- [开发、打包、测试与发布](development.md)
|
|
122
|
+
- [兼容性与迁移](compatibility.md)
|
|
123
|
+
|
|
124
|
+
API v1 支持本机目录和可校验的离线插件包。插件市场、直接拉取 npm/Git 依赖、模型供应商注册、任意替换 Agent 主循环,以及 DSH 插件二进制兼容不属于这一版的公开 API。可通过工具、命令、钩子、提示词和界面接口组合工作流。Web 可用 HTML/CSS/JavaScript 完整替换相应视图;TUI 提供菜单、文本页面、主题和文本渲染契约,浏览器组件可声明仅在 Web 生效。
|
|
125
|
+
|
|
126
|
+
架构参考:[DeepSeek Harness](https://github.com/deepseek-ai/deepseek-harness/blob/master/docs/architecture.md)、[可逆注册与 Context](https://github.com/deepseek-ai/deepseek-harness/blob/master/docs/cordis-primer.md)。Neutrino 插件使用独立清单和 SDK。
|