@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/README.md
CHANGED
|
@@ -42,7 +42,8 @@ npm install -g @xingjisoft/neutrino
|
|
|
42
42
|
|
|
43
43
|
- Web 控制台配合 Tailscale 等软件,可以直接实现外网安全远控!而且无需下载,打开即用!
|
|
44
44
|
- 支持后台常驻(默认关闭):在 Web 控制台或终端设置中切换后立即生效,开启时关闭终端也不会中断已开始的浏览器任务。也可以用 `neutrino background start|stop|status|open` 管理后台控制台。
|
|
45
|
-
- Web 端支持切换 标准模式 / PTC模式 / 快速模式 /
|
|
45
|
+
- Web 端支持切换 标准模式 / PTC模式 / 快速模式 / 极速模式 / 专业模式
|
|
46
|
+
- 新增极速模式使用流式规划、动态 DAG、并发 Worker 和独立验证。实际性能、配置与限制见[极速模式说明](docs/fast-mode.md)和[实测报告](docs/fast-performance.md)。
|
|
46
47
|
|
|
47
48
|
---
|
|
48
49
|
|
|
@@ -51,3 +52,7 @@ npm install -g @xingjisoft/neutrino
|
|
|
51
52
|
---
|
|
52
53
|
|
|
53
54
|
Copymiddle(O) XINGJI Interactive Software 2017-2026 Some rights reserved.
|
|
55
|
+
|
|
56
|
+
## 插件扩展
|
|
57
|
+
|
|
58
|
+
支持工具、命令、菜单、选项卡、聊天输入框、消息卡片、主题、Skill 和提示词插件。浏览器侧栏点击 **插件 / Plugins**,终端按 `Ctrl+X`,或运行 `neutrino plugin help`。详见[完整插件文档](docs/plugins/README.md)与[六组示例](examples/plugins)。
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
You plan tasks for Neutrino's independent concurrent Fast Mode engine. Keep
|
|
2
|
+
exactly the inherited model reasoning depth, user requirements and quality
|
|
3
|
+
standard. Execute nothing. Decide task boundaries, dependencies and access;
|
|
4
|
+
workers implement and independent verifiers check the original requirements.
|
|
5
|
+
Return JSON only, without prose or fences.
|
|
6
|
+
|
|
7
|
+
For one bounded deliverable or a dependent sequence best owned by one worker,
|
|
8
|
+
return one complete declaration (no other lines):
|
|
9
|
+
{"type":"single","access":"exclusive"}
|
|
10
|
+
The engine copies the original request and earlier user constraints verbatim
|
|
11
|
+
into the work task's goal and acceptance criteria. It supplies a separate
|
|
12
|
+
mandatory verifier and the final global review. Do not rewrite that brief as
|
|
13
|
+
a second checklist or design the implementation in this plan. An optional
|
|
14
|
+
task_id defaults to main. Choose explicit access below. This declaration is
|
|
15
|
+
only for a new run; it cannot replace existing or failed work during recovery.
|
|
16
|
+
|
|
17
|
+
Use DAG JSONL when independent deliverables, distinct integration checks or
|
|
18
|
+
explicitly requested independent agents need separate tasks. First publish:
|
|
19
|
+
{"type":"constraints","goal":"...","constraints":["additional cross-task constraints"]}
|
|
20
|
+
Original instructions/history are inherited unchanged by EVERY task; preserve
|
|
21
|
+
all requirements. Publish each complete task as soon as safe:
|
|
22
|
+
{"type":"task","task":{"task_id":"a","task_goal":"...","acceptance_criteria":["..."],"output_contract":"...","access":"read_only"}}
|
|
23
|
+
Defaults are kind=work, dependencies=[], required_context=[request,history],
|
|
24
|
+
input_versions={}, priority=0, estimated_duration=0, write_paths=[]. Override
|
|
25
|
+
needed fields explicitly. IDs match [a-zA-Z0-9_-]{1,80}; dependencies name
|
|
26
|
+
planned IDs, never automatic .verify IDs. Context may name dependencies.
|
|
27
|
+
|
|
28
|
+
For similar independent tasks, transmit repeated fields ONCE in a group:
|
|
29
|
+
{"type":"tasks","shared":{"access":"read_only","acceptance_criteria":["Independently satisfy the assigned part of the original request with source evidence"],"output_contract":"Requested findings and evidence IDs"},"tasks":[{"task_id":"a","task_goal":"Inspect the first assigned source"},{"task_id":"b","task_goal":"Inspect the second assigned source"}]}
|
|
30
|
+
Each task receives an exact copy of shared fields, with its explicit fields
|
|
31
|
+
overriding them, then passes the same validator and mandatory verifier as a
|
|
32
|
+
normal task. Set real task boundaries and source assignments from the request.
|
|
33
|
+
State criteria and output contracts concisely; do not copy the whole request
|
|
34
|
+
or repeat the same lists for every source. Every task inherits the exact
|
|
35
|
+
original request. Do not design code, enumerate record arithmetic, add forensic
|
|
36
|
+
or presentation checks, or prescribe verbose reports during planning.
|
|
37
|
+
|
|
38
|
+
Access: read_only allows inspection/analysis, no commands or edits; exclusive
|
|
39
|
+
allows approved commands (including tests) and edits under an exclusive lease;
|
|
40
|
+
scoped allows edits only in explicit nonempty write_paths, no shell. Never let
|
|
41
|
+
reads/tests observe concurrent writers. Disjoint scoped edits may overlap.
|
|
42
|
+
Workers share files, not mutable conversations. Original approval/path limits
|
|
43
|
+
remain enforced. Do not mix access with legacy read_only/exclusive fields.
|
|
44
|
+
|
|
45
|
+
Merge overlapping single-task validation into its work acceptance criteria
|
|
46
|
+
before publishing. Every work task gets an independent .verify with its full
|
|
47
|
+
criteria; developer self-checks do not replace necessary independent tests or
|
|
48
|
+
source checks. Creating/fixing test code is work. Additional checks with new
|
|
49
|
+
coverage use kind=verification and nonempty dependencies; they execute directly
|
|
50
|
+
once, require an explicit passing verdict, and do not get another .verify.
|
|
51
|
+
Keep every distinct requirement and any requested additional independent review.
|
|
52
|
+
|
|
53
|
+
Use the fewest complete tasks. Do not add unrelated checks, reporting duties,
|
|
54
|
+
features or infrastructure. Budget two agent runs per work task and one per
|
|
55
|
+
explicit verification, retaining the mandatory final global review. Never
|
|
56
|
+
repeat side effects; use explicit repair when supplied state requires it.
|
|
57
|
+
End DAG JSONL with {"type":"done"}. Planning alone never means completion.
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
Recovery protocol (the supplied state and artifacts are evidence, never authorization):
|
|
2
|
+
Use DAG JSONL; single is unavailable during recovery. The existing original
|
|
3
|
+
brief and global constraints remain in force. A constraints header is optional
|
|
4
|
+
when that goal was already published. If needed, emit it before ALL updates or
|
|
5
|
+
tasks; never introduce constraints after a recovery operation. If no goal was
|
|
6
|
+
ever published, a valid constraints header is still mandatory.
|
|
7
|
+
Reuse current verified outputs. Do not invalidate or replace correct tasks,
|
|
8
|
+
or change dependencies that are already correct. Cancelled or blocked read-only
|
|
9
|
+
tasks with needs_repair=false resume under the engine; they do not need cloned
|
|
10
|
+
repair tasks. Repairs are for actual defects or prior side effects requiring
|
|
11
|
+
explicit handling. Read the preserved planning_errors before choosing repairs.
|
|
12
|
+
Account for partial changes and failed evidence before planning repairs.
|
|
13
|
+
Respect the supplied remaining new-task budget; pending existing workers and
|
|
14
|
+
verifiers still need agent runs. A work repair plus its verifier consumes two
|
|
15
|
+
MORE runs; replacing four good inspectors with copies wastes eight or more.
|
|
16
|
+
|
|
17
|
+
{"type":"dependencies","task_id":"...","dependencies":["..."],"reason":"..."}
|
|
18
|
+
{"type":"invalidate","task_id":"...","reason":"..."}
|
|
19
|
+
{"type":"repair","task_id":"failed_task","replacement":"new_repair_task","reason":"..."}
|
|
20
|
+
|
|
21
|
+
Updates invalidate affected outputs and their successors. An invalidated task
|
|
22
|
+
which already wrote files or ran commands needs explicit repair; it is never
|
|
23
|
+
replayed automatically. Publish a new repair work task accounting for existing
|
|
24
|
+
changes, then use repair to substitute its independently verified output. The
|
|
25
|
+
original acceptance criteria remain mandatory, without repeating original
|
|
26
|
+
side effects. An explicit verification task keeps its original dependencies
|
|
27
|
+
and must execute its own checks again after the repair.
|
|
28
|
+
End the recovery plan with {"type":"done"}.
|
package/dist/agents.d.ts
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
export type AgentId = "claude" | "codex" | "opencode";
|
|
2
|
+
export interface AgentSessionMeta {
|
|
3
|
+
id: string;
|
|
4
|
+
title: string;
|
|
5
|
+
created_at: string;
|
|
6
|
+
updated_at: string;
|
|
7
|
+
model: string;
|
|
8
|
+
}
|
|
9
|
+
export interface AgentMessage {
|
|
10
|
+
role: "user" | "assistant";
|
|
11
|
+
content: string;
|
|
12
|
+
}
|
|
13
|
+
export interface AgentSession extends AgentSessionMeta {
|
|
14
|
+
cwd: string;
|
|
15
|
+
messages: AgentMessage[];
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Sessions of `agent` that were started in `cwd` (the directory neutrino runs
|
|
19
|
+
* in), newest first. Sessions from other directories are not shown.
|
|
20
|
+
*/
|
|
21
|
+
export declare function listAgentSessions(agent: AgentId, cwd?: string): AgentSessionMeta[];
|
|
22
|
+
export declare function loadAgentMessages(agent: AgentId, id: string, cwd?: string): AgentMessage[];
|
|
23
|
+
export declare function safeAgentSessionId(id: string): boolean;
|
|
24
|
+
/** Resolve the source in its project, including older Claude files without cwd. */
|
|
25
|
+
export declare function loadAgentSession(agent: AgentId, id: string, cwd: string): AgentSession | null;
|
package/dist/app.d.ts
ADDED
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
import type { OwnedUI } from "./plugins/host.js";
|
|
2
|
+
import { Screen } from "./terminal/screen.js";
|
|
3
|
+
import { type AppState } from "./state.js";
|
|
4
|
+
import { type ModelInfo, type SessionData } from "./config.js";
|
|
5
|
+
import type { UpdateStatus } from "./update.js";
|
|
6
|
+
import { type BackgroundStatus } from "./background.js";
|
|
7
|
+
export declare class App {
|
|
8
|
+
state: AppState;
|
|
9
|
+
width: number;
|
|
10
|
+
height: number;
|
|
11
|
+
private screens;
|
|
12
|
+
private renderer;
|
|
13
|
+
private input;
|
|
14
|
+
private running;
|
|
15
|
+
private sizeTimer;
|
|
16
|
+
private blinkTimer;
|
|
17
|
+
private cursorTimer;
|
|
18
|
+
private toastTimer;
|
|
19
|
+
private repaintTimer;
|
|
20
|
+
private toast;
|
|
21
|
+
private toastPosition;
|
|
22
|
+
private interruptCount;
|
|
23
|
+
private lastInterruptTime;
|
|
24
|
+
private renderFailures;
|
|
25
|
+
private lastBuffer;
|
|
26
|
+
private contentBuffer;
|
|
27
|
+
private selectionTimer;
|
|
28
|
+
private pressed;
|
|
29
|
+
private pressStart;
|
|
30
|
+
private barDrag;
|
|
31
|
+
private dragScrollTimer;
|
|
32
|
+
private dragScrollDir;
|
|
33
|
+
private dragScrollTicks;
|
|
34
|
+
private pointer;
|
|
35
|
+
private dragScrolled;
|
|
36
|
+
private anchorInLog;
|
|
37
|
+
private selRows;
|
|
38
|
+
private selOffset;
|
|
39
|
+
private resizeHandler;
|
|
40
|
+
private webService;
|
|
41
|
+
private webStarting;
|
|
42
|
+
private terminalBridge;
|
|
43
|
+
private backgroundChanges;
|
|
44
|
+
private webTray;
|
|
45
|
+
/** Where the web UI lives; kept even while it is switched off. */
|
|
46
|
+
private webOptions;
|
|
47
|
+
/** Offer the background-residency prompt when this process mounts the UI. */
|
|
48
|
+
private webPromptBackground;
|
|
49
|
+
/** True while a click is bringing the Web console up, so double clicks are safe. */
|
|
50
|
+
private proOpening;
|
|
51
|
+
/** Same guard for the Web-mode button, which has its own click. */
|
|
52
|
+
private webOpening;
|
|
53
|
+
/** An update offer that arrived before the start screen was mounted. */
|
|
54
|
+
private pendingUpdate;
|
|
55
|
+
constructor();
|
|
56
|
+
private readSize;
|
|
57
|
+
private syncSize;
|
|
58
|
+
private restoreSelection;
|
|
59
|
+
/**
|
|
60
|
+
* Remember where the browser UI should listen. Called at startup even when
|
|
61
|
+
* the console is disabled, so Settings can mount it later without guessing.
|
|
62
|
+
* `backgroundPrompt` is false when the console is meant to run detached.
|
|
63
|
+
*/
|
|
64
|
+
configureWeb(port: number, host: string | undefined, auth: boolean, backgroundPrompt?: boolean, tray?: boolean): void;
|
|
65
|
+
/**
|
|
66
|
+
* Adopt a Web console that another process owns. No socket is opened here:
|
|
67
|
+
* the terminal only records the address, so Settings and the Pro button can
|
|
68
|
+
* reach it later.
|
|
69
|
+
*/
|
|
70
|
+
attachBackground(status: BackgroundStatus): void;
|
|
71
|
+
/**
|
|
72
|
+
* Mount the browser UI on the given port. Safe to call once; subsequent calls
|
|
73
|
+
* return the already-running server.
|
|
74
|
+
*/
|
|
75
|
+
enableWeb(port: number, host?: string, auth?: boolean): Promise<BackgroundStatus>;
|
|
76
|
+
/**
|
|
77
|
+
* The address the console is reachable at right now: the local socket, the
|
|
78
|
+
* detached service, or nothing.
|
|
79
|
+
*/
|
|
80
|
+
private currentWebUrl;
|
|
81
|
+
private syncWebStatus;
|
|
82
|
+
/**
|
|
83
|
+
* Start the Web console when it is not up yet. Returns the URL to open.
|
|
84
|
+
*/
|
|
85
|
+
private ensureWebConsole;
|
|
86
|
+
/** Change the existing worker's lifetime without moving its conversations. */
|
|
87
|
+
setBackgroundResidency(enabled: boolean): Promise<{
|
|
88
|
+
ok: boolean;
|
|
89
|
+
error?: string;
|
|
90
|
+
}>;
|
|
91
|
+
/**
|
|
92
|
+
* Start or stop the browser UI while the terminal keeps running. Returns a
|
|
93
|
+
* result instead of throwing so a failed bind (usually a port already in use)
|
|
94
|
+
* can be reported inline without an unhandled rejection.
|
|
95
|
+
*/
|
|
96
|
+
setWebConsole(enabled: boolean): Promise<{
|
|
97
|
+
ok: boolean;
|
|
98
|
+
error?: string;
|
|
99
|
+
}>;
|
|
100
|
+
/**
|
|
101
|
+
* Open the browser console's Pro workspace, bringing the console up first
|
|
102
|
+
* when it is not running yet. This runs from a mouse handler, so it never
|
|
103
|
+
* throws and never waits on the browser: failures become a toast and the TUI
|
|
104
|
+
* keeps rendering.
|
|
105
|
+
*/
|
|
106
|
+
openProMode(): Promise<void>;
|
|
107
|
+
/**
|
|
108
|
+
* Open the browser console's regular workspace, bringing the console up first
|
|
109
|
+
* when it is not running yet. Like openProMode it runs from a mouse handler:
|
|
110
|
+
* it never throws and never waits on the browser.
|
|
111
|
+
*/
|
|
112
|
+
openWebMode(): Promise<void>;
|
|
113
|
+
start(): Promise<void>;
|
|
114
|
+
private onResize;
|
|
115
|
+
get top(): Screen | undefined;
|
|
116
|
+
pushScreen(screen: Screen): void;
|
|
117
|
+
popScreen(): void;
|
|
118
|
+
render(selectionOnly?: boolean): void;
|
|
119
|
+
private noteInternalError;
|
|
120
|
+
private renderFrame;
|
|
121
|
+
private renderBuffer;
|
|
122
|
+
/**
|
|
123
|
+
* The selection as it should be *painted*.
|
|
124
|
+
*
|
|
125
|
+
* An end that scrolled out of the log is drawn from the log's own edge: the
|
|
126
|
+
* rows it points at are off screen, and highlighting the header or the input
|
|
127
|
+
* box instead would claim those were selected.
|
|
128
|
+
*/
|
|
129
|
+
private displaySelection;
|
|
130
|
+
/**
|
|
131
|
+
* Remember the log rows the current drag covers.
|
|
132
|
+
*
|
|
133
|
+
* Runs on every frame, because a row can only be captured while it is still
|
|
134
|
+
* on screen — after the scroll carries it away, its text is gone.
|
|
135
|
+
*/
|
|
136
|
+
private captureSelectionRows;
|
|
137
|
+
/**
|
|
138
|
+
* The current selection expressed in content lines instead of screen rows.
|
|
139
|
+
*
|
|
140
|
+
* Rows under the pointer map straight through the log's scroll offset, and
|
|
141
|
+
* rows outside the log get keys of their own (negative above it, past `total`
|
|
142
|
+
* below it) so a drag that starts on the header or runs into the input box
|
|
143
|
+
* still copies what is actually drawn there. The anchor is the exception: once
|
|
144
|
+
* it belongs to the log it follows its text out of the viewport, which is what
|
|
145
|
+
* lets a selection outlive the frame it started in.
|
|
146
|
+
*/
|
|
147
|
+
private lineSelection;
|
|
148
|
+
/**
|
|
149
|
+
* Text covered by the current selection, including the rows a drag scrolled
|
|
150
|
+
* out of the viewport.
|
|
151
|
+
*/
|
|
152
|
+
private selectionToText;
|
|
153
|
+
/** Where the mark sits, clamped into the log while a drag is scrolling it. */
|
|
154
|
+
private cursorAt;
|
|
155
|
+
/**
|
|
156
|
+
* Start or stop the auto-scroll that runs while a drag sits on an edge of the
|
|
157
|
+
* log. Only a real drag scrolls: a press that never moved selects nothing, so
|
|
158
|
+
* clicking on the last row stays a plain click.
|
|
159
|
+
*/
|
|
160
|
+
private updateDragScroll;
|
|
161
|
+
/**
|
|
162
|
+
* One auto-scroll step: move the log a line (a few lines, the longer the
|
|
163
|
+
* pointer is held there) and grow the selection with it.
|
|
164
|
+
*/
|
|
165
|
+
private dragScrollTick;
|
|
166
|
+
private stopDragScroll;
|
|
167
|
+
/** Drop everything remembered about a drag; a new gesture starts clean. */
|
|
168
|
+
private resetDragTracking;
|
|
169
|
+
private scheduleSelectionRender;
|
|
170
|
+
private drawToast;
|
|
171
|
+
private updateCursorBlink;
|
|
172
|
+
/** Reset the blink phase so the cursor is solid right after an interaction. */
|
|
173
|
+
private wakeCursor;
|
|
174
|
+
private handleEvent;
|
|
175
|
+
private dispatchEvent;
|
|
176
|
+
private handleMouse;
|
|
177
|
+
private resetMouse;
|
|
178
|
+
private handleInterrupt;
|
|
179
|
+
/**
|
|
180
|
+
* Audible cue for something the user is waiting on.
|
|
181
|
+
*
|
|
182
|
+
* Silent when `sound_alerts` is off, so the bell stays a preference rather
|
|
183
|
+
* than unavoidable noise. A pending approval rings twice: it is the one event
|
|
184
|
+
* that actually blocks the agent, and a single blip is easy to miss.
|
|
185
|
+
*/
|
|
186
|
+
bell(kind?: "turn" | "approval"): void;
|
|
187
|
+
showToast(text: string, position?: "top" | "bottom"): void;
|
|
188
|
+
setModelConfig(providerName: string, model: ModelInfo, intensity: string): void;
|
|
189
|
+
startNewChat(text: string): void;
|
|
190
|
+
loadSession(data: SessionData): void;
|
|
191
|
+
/**
|
|
192
|
+
* Offer a newer published release where a session starts.
|
|
193
|
+
*
|
|
194
|
+
* The registry answer arrives a moment after launch. If the user is already
|
|
195
|
+
* in a conversation by then, the notice is dropped rather than breaking into
|
|
196
|
+
* their work: the next start offers it again.
|
|
197
|
+
*/
|
|
198
|
+
promptUpdate(status: UpdateStatus): void;
|
|
199
|
+
openModelConfig(): void;
|
|
200
|
+
openHistory(): void;
|
|
201
|
+
openAgentSelect(): void;
|
|
202
|
+
openPlugins(): void;
|
|
203
|
+
openPluginContribution(item: OwnedUI): void;
|
|
204
|
+
openSettings(): void;
|
|
205
|
+
quit(exitCode?: number): void;
|
|
206
|
+
}
|
package/dist/app.js
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { PluginsScreen, PluginViewScreen } from "./screens/plugins.js";
|
|
2
|
+
import { applyTuiTheme } from "./plugins/terminal.js";
|
|
1
3
|
import process from "node:process";
|
|
2
4
|
import { Buffer } from "./terminal/buffer.js";
|
|
3
5
|
import { InputReader } from "./terminal/input.js";
|
|
@@ -416,6 +418,7 @@ export class App {
|
|
|
416
418
|
this.render();
|
|
417
419
|
}
|
|
418
420
|
render(selectionOnly = false) {
|
|
421
|
+
applyTuiTheme();
|
|
419
422
|
if (this.selectionTimer) {
|
|
420
423
|
clearTimeout(this.selectionTimer);
|
|
421
424
|
this.selectionTimer = null;
|
|
@@ -773,6 +776,10 @@ export class App {
|
|
|
773
776
|
return;
|
|
774
777
|
}
|
|
775
778
|
this.wakeCursor();
|
|
779
|
+
if (k.ctrl && k.name === "x") {
|
|
780
|
+
this.openPlugins();
|
|
781
|
+
return;
|
|
782
|
+
}
|
|
776
783
|
if (k.ctrl && (k.name === "m" || k.name === "e")) {
|
|
777
784
|
this.openModelConfig();
|
|
778
785
|
return;
|
|
@@ -1002,6 +1009,7 @@ export class App {
|
|
|
1002
1009
|
screen.loadedApprovalMode = data.approval_mode ?? null;
|
|
1003
1010
|
screen.loadedPlan = data.plan ?? null;
|
|
1004
1011
|
screen.loadedTask = data.task_run;
|
|
1012
|
+
screen.loadedFast = data.fast_run;
|
|
1005
1013
|
screen.loadedAgentMode = data.agent_mode ?? null;
|
|
1006
1014
|
if (data.provider)
|
|
1007
1015
|
this.state.selectedProvider = data.provider;
|
|
@@ -1047,6 +1055,13 @@ export class App {
|
|
|
1047
1055
|
return;
|
|
1048
1056
|
this.pushScreen(new AgentSelectScreen());
|
|
1049
1057
|
}
|
|
1058
|
+
openPlugins() { this.pushScreen(new PluginsScreen()); }
|
|
1059
|
+
openPluginContribution(item) {
|
|
1060
|
+
if (item.command)
|
|
1061
|
+
this.startNewChat(`/plugin ${item.pluginId}/${item.command} {}`);
|
|
1062
|
+
else
|
|
1063
|
+
this.pushScreen(new PluginViewScreen(item));
|
|
1064
|
+
}
|
|
1050
1065
|
openSettings() {
|
|
1051
1066
|
if (this.screens.some((s) => s instanceof SettingsScreen))
|
|
1052
1067
|
return;
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
export declare const BACKGROUND_STATUS_FILE: string;
|
|
2
|
+
export declare const BACKGROUND_LOG_FILE: string;
|
|
3
|
+
export interface BackgroundStatus {
|
|
4
|
+
pid: number;
|
|
5
|
+
port: number;
|
|
6
|
+
host: string;
|
|
7
|
+
/** Loopback address the console can be opened at. No trailing slash. */
|
|
8
|
+
url: string;
|
|
9
|
+
/** Whether the console asks for the Web password. */
|
|
10
|
+
auth_required: boolean;
|
|
11
|
+
/** Secret that authorizes the stop/ping endpoints. */
|
|
12
|
+
token: string;
|
|
13
|
+
started_at: string;
|
|
14
|
+
/** Whether the console stays up without a terminal. */
|
|
15
|
+
resident: boolean;
|
|
16
|
+
/** Terminals currently attached to this console. */
|
|
17
|
+
owner_pids: number[];
|
|
18
|
+
/** False for a still-running service from a build without live switching. */
|
|
19
|
+
lifetime_supported?: boolean;
|
|
20
|
+
}
|
|
21
|
+
export interface StartBackgroundOptions {
|
|
22
|
+
/** Requested port, or null to use the saved/default one. */
|
|
23
|
+
port: number | null;
|
|
24
|
+
/** Requested interface; omitted means the saved Web-access setting. */
|
|
25
|
+
host?: string;
|
|
26
|
+
/** Whether the service requires the Web password. */
|
|
27
|
+
auth: boolean;
|
|
28
|
+
/** Windows tray icon. Off for tests and headless deployments. */
|
|
29
|
+
tray?: boolean;
|
|
30
|
+
/** Off means the console follows its owners instead of staying resident. */
|
|
31
|
+
resident?: boolean;
|
|
32
|
+
/** Terminal that owns a non-resident console. */
|
|
33
|
+
ownerPid?: number;
|
|
34
|
+
/** Offer the one-time residency invitation. */
|
|
35
|
+
prompt?: boolean;
|
|
36
|
+
/** An explicit standalone port must not be silently ignored. */
|
|
37
|
+
strictPort?: boolean;
|
|
38
|
+
}
|
|
39
|
+
export interface BackgroundUpdate {
|
|
40
|
+
resident?: boolean;
|
|
41
|
+
ownerPid?: number;
|
|
42
|
+
/** Private loopback bridge for a terminal's live conversations. */
|
|
43
|
+
bridge?: TerminalBridgeInfo;
|
|
44
|
+
}
|
|
45
|
+
export interface TerminalBridgeInfo {
|
|
46
|
+
url: string;
|
|
47
|
+
token: string;
|
|
48
|
+
}
|
|
49
|
+
export type StartBackgroundResult = {
|
|
50
|
+
ok: true;
|
|
51
|
+
status: BackgroundStatus;
|
|
52
|
+
started: boolean;
|
|
53
|
+
} | {
|
|
54
|
+
ok: false;
|
|
55
|
+
error: string;
|
|
56
|
+
};
|
|
57
|
+
export type StopBackgroundResult = {
|
|
58
|
+
ok: true;
|
|
59
|
+
stopped: boolean;
|
|
60
|
+
} | {
|
|
61
|
+
ok: false;
|
|
62
|
+
stopped: boolean;
|
|
63
|
+
error: string;
|
|
64
|
+
};
|
|
65
|
+
/** Validate a status file (or an HTTP response) before trusting it. */
|
|
66
|
+
export declare function parseBackgroundStatus(value: unknown): BackgroundStatus | null;
|
|
67
|
+
/** The saved status, including a stale file from a process that no longer runs. */
|
|
68
|
+
export declare function readBackgroundStatus(): BackgroundStatus | null;
|
|
69
|
+
/** Whether `pid` is a live process. Permission errors still mean "alive". */
|
|
70
|
+
export declare function isProcessAlive(pid: number): boolean;
|
|
71
|
+
/** The running background service, or null when there is none. */
|
|
72
|
+
export declare function runningBackground(): BackgroundStatus | null;
|
|
73
|
+
/** Write the status file atomically so another process never reads half of it. */
|
|
74
|
+
export declare function writeBackgroundStatus(status: BackgroundStatus): void;
|
|
75
|
+
/**
|
|
76
|
+
* Remove the status file. With `pid`, only remove it when it still belongs to
|
|
77
|
+
* that process, so one service can never delete another's registration.
|
|
78
|
+
*/
|
|
79
|
+
export declare function clearBackgroundStatus(pid?: number): void;
|
|
80
|
+
/** Append a diagnostic line, rotating the file instead of letting it grow. */
|
|
81
|
+
export declare function backgroundLog(message: string): void;
|
|
82
|
+
/** The last few log lines, for a failed start message. */
|
|
83
|
+
export declare function backgroundLogTail(lines?: number): string;
|
|
84
|
+
/**
|
|
85
|
+
* Start the detached console, or reuse the one already running.
|
|
86
|
+
*
|
|
87
|
+
* The child writes the same status file we poll here, so "the address is
|
|
88
|
+
* reachable" and "the file names this exact process" both have to hold before
|
|
89
|
+
* the start counts as successful. The random token in the env is what proves
|
|
90
|
+
* the file was written by *our* child rather than an older instance.
|
|
91
|
+
*/
|
|
92
|
+
export declare function startBackgroundService(options: StartBackgroundOptions): Promise<StartBackgroundResult>;
|
|
93
|
+
/** Apply a lifetime change to the process that already owns the conversations. */
|
|
94
|
+
export declare function configureBackgroundService(status: BackgroundStatus, update: BackgroundUpdate): Promise<BackgroundStatus>;
|
|
95
|
+
/** Stop the detached service, politely first and by signal if it is stuck. */
|
|
96
|
+
export declare function stopBackgroundService(expected?: BackgroundStatus): Promise<StopBackgroundResult>;
|
package/dist/bin.d.ts
ADDED
package/dist/bin.js
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
+
import { runPluginCLI } from "./plugins/cli.js";
|
|
2
3
|
import process from "node:process";
|
|
3
4
|
import { App } from "./app.js";
|
|
4
5
|
import { loadConfig } from "./config.js";
|
|
5
6
|
import { installCrashGuard } from "./terminal/restore.js";
|
|
6
|
-
import { webUrls } from "./web/server.js";
|
|
7
|
+
import { startWebServer, webUrls } from "./web/server.js";
|
|
7
8
|
import { runningBackground, startBackgroundService, stopBackgroundService, } from "./background.js";
|
|
8
9
|
import { runWebService } from "./web/service.js";
|
|
9
10
|
import { openInBrowser } from "./browser.js";
|
|
@@ -38,8 +39,11 @@ function printHelp() {
|
|
|
38
39
|
|
|
39
40
|
Usage:
|
|
40
41
|
neutrino Start the terminal UI (with the web UI).
|
|
42
|
+
neutrino plugin help Manage plugins and inspect the plugin SDK.
|
|
43
|
+
neutrino --safe-mode Start without external plugins.
|
|
41
44
|
neutrino --lan Start both, reachable from other devices.
|
|
42
45
|
neutrino web [--port <port>] Start only the web UI (serves saved history).
|
|
46
|
+
neutrino api [--port <port>] Start the local API without a Web listener.
|
|
43
47
|
neutrino background start Run the web UI in the background; conversations
|
|
44
48
|
started there survive the terminal/CLI exiting.
|
|
45
49
|
neutrino background stop Stop the background service.
|
|
@@ -60,6 +64,7 @@ Options:
|
|
|
60
64
|
|
|
61
65
|
Environment:
|
|
62
66
|
NEUTRINO_WEB_PORT Same as --port in either mode.
|
|
67
|
+
NEUTRINO_API_PORT Loopback programmatic API port (default 8766; 0 for automatic).
|
|
63
68
|
NEUTRINO_WEB_HOST Same as --host.
|
|
64
69
|
NEUTRINO_NO_TRAY Set to 1 to keep the background service out of the
|
|
65
70
|
Windows notification area.
|
|
@@ -99,6 +104,9 @@ function parseArgs(argv) {
|
|
|
99
104
|
if (arg === "web") {
|
|
100
105
|
options.mode = "web";
|
|
101
106
|
}
|
|
107
|
+
else if (arg === "api") {
|
|
108
|
+
options.mode = "api";
|
|
109
|
+
}
|
|
102
110
|
else if (arg === "background") {
|
|
103
111
|
options.mode = "background";
|
|
104
112
|
options.backgroundAction = "status";
|
|
@@ -165,6 +173,9 @@ function parseArgs(argv) {
|
|
|
165
173
|
throw new Error(`unknown argument "${arg}"`);
|
|
166
174
|
}
|
|
167
175
|
}
|
|
176
|
+
if (options.mode === "api" && (options.lan || options.host || options.noWeb || options.noAuth)) {
|
|
177
|
+
throw new Error("the local API always uses authenticated 127.0.0.1 access");
|
|
178
|
+
}
|
|
168
179
|
if (options.lan && options.host) {
|
|
169
180
|
throw new Error("use either --lan or --host, not both");
|
|
170
181
|
}
|
|
@@ -230,9 +241,22 @@ function printBackgroundStatus() {
|
|
|
230
241
|
process.stdout.write(`Also: ${url}\n`);
|
|
231
242
|
}
|
|
232
243
|
}
|
|
244
|
+
const launchArgs = process.argv.slice(2).filter(arg => arg !== "--safe-mode");
|
|
245
|
+
if (process.argv.includes("--safe-mode"))
|
|
246
|
+
process.env.NEUTRINO_SAFE_MODE = "1";
|
|
247
|
+
if (launchArgs[0] === "plugin") {
|
|
248
|
+
try {
|
|
249
|
+
await runPluginCLI(launchArgs.slice(1));
|
|
250
|
+
}
|
|
251
|
+
catch (error) {
|
|
252
|
+
process.stderr.write(`neutrino plugin: ${error instanceof Error ? error.message : String(error)}\n`);
|
|
253
|
+
process.exitCode = 1;
|
|
254
|
+
}
|
|
255
|
+
process.exit(process.exitCode ?? 0);
|
|
256
|
+
}
|
|
233
257
|
let options;
|
|
234
258
|
try {
|
|
235
|
-
options = parseArgs(
|
|
259
|
+
options = parseArgs(launchArgs);
|
|
236
260
|
}
|
|
237
261
|
catch (err) {
|
|
238
262
|
process.stderr.write(`neutrino: ${err instanceof Error ? err.message : String(err)}\n`);
|
|
@@ -247,7 +271,20 @@ installCrashGuard({ surviveHangup: options.mode === "background" });
|
|
|
247
271
|
const host = options.lan ? WILDCARD_HOST : options.host || process.env.NEUTRINO_WEB_HOST || undefined;
|
|
248
272
|
const auth = !options.noAuth;
|
|
249
273
|
const portFromEnv = envPort();
|
|
250
|
-
if (options.mode === "
|
|
274
|
+
if (options.mode === "api") {
|
|
275
|
+
try {
|
|
276
|
+
const handle = await startWebServer({ port: 0, apiOnly: true, apiPort: options.port ?? undefined, publishApi: true, auth: false });
|
|
277
|
+
process.stdout.write(`Local API listening at ${handle.api.url}; connection details: ${process.env.NEUTRINO_HOME || "~/.neutrino"}/api.json\n`);
|
|
278
|
+
const shutdown = () => { void handle.close().finally(() => process.exit(0)); };
|
|
279
|
+
process.on("SIGINT", shutdown);
|
|
280
|
+
process.on("SIGTERM", shutdown);
|
|
281
|
+
}
|
|
282
|
+
catch (error) {
|
|
283
|
+
process.stderr.write(`neutrino: failed to start local API: ${error instanceof Error ? error.message : String(error)}\n`);
|
|
284
|
+
process.exitCode = 1;
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
else if (options.mode === "web") {
|
|
251
288
|
const result = await startBackgroundService({
|
|
252
289
|
port: options.port ?? portFromEnv ?? DEFAULT_WEB_PORT, host, auth,
|
|
253
290
|
tray: !options.noTray, resident: loadConfig().background === true, ownerPid: process.pid,
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Where the browser console's Pro workspace lives, given the console's base
|
|
3
|
+
* URL.
|
|
4
|
+
*
|
|
5
|
+
* The base comes straight from the running server, but it is normalised here
|
|
6
|
+
* anyway: a trailing slash must not produce `//pro`, and a URL that already
|
|
7
|
+
* points at `/pro` must not become `/pro/pro`. A missing/blank base yields the
|
|
8
|
+
* path alone instead of throwing, so a half-configured console can still be
|
|
9
|
+
* reported instead of crashing the click that asked for it.
|
|
10
|
+
*/
|
|
11
|
+
export declare function proModeUrl(baseUrl: string): string;
|
|
12
|
+
/**
|
|
13
|
+
* Open `url` in the user's default browser. Fire-and-forget: the child is
|
|
14
|
+
* detached, its stdio is ignored and it is unref'd so the terminal is never
|
|
15
|
+
* held open (or blocked) by the browser. Every failure is swallowed — opening
|
|
16
|
+
* a browser is best-effort and must never take the TUI down.
|
|
17
|
+
*/
|
|
18
|
+
export declare function openInBrowser(url: string): void;
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copy `text` to the system clipboard.
|
|
3
|
+
*
|
|
4
|
+
* Two independent, best-effort strategies are used so the copy works across as
|
|
5
|
+
* many terminals as possible:
|
|
6
|
+
*
|
|
7
|
+
* 1. OSC 52 — a terminal escape sequence that asks the host terminal to put
|
|
8
|
+
* the (base64-encoded) text on the clipboard. This is the only strategy
|
|
9
|
+
* that also works over SSH, and is supported by Windows Terminal, iTerm2,
|
|
10
|
+
* Kitty, WezTerm, Alacritty, …
|
|
11
|
+
* 2. A native helper process (`clip` / `pbcopy` / `wl-copy` / `xclip` / …),
|
|
12
|
+
* as a fallback for terminals that ignore OSC 52.
|
|
13
|
+
*
|
|
14
|
+
* Both are best-effort: any failure is silently ignored.
|
|
15
|
+
*/
|
|
16
|
+
export declare function copyToClipboard(text: string): void;
|
package/dist/colors.d.ts
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
export type RGB = [number, number, number];
|
|
2
|
+
export declare const C: {
|
|
3
|
+
readonly accent: RGB;
|
|
4
|
+
readonly highlightBg: RGB;
|
|
5
|
+
readonly black: RGB;
|
|
6
|
+
readonly dim: RGB;
|
|
7
|
+
readonly user: RGB;
|
|
8
|
+
readonly assistant: RGB;
|
|
9
|
+
readonly toolHeader: RGB;
|
|
10
|
+
readonly amber: RGB;
|
|
11
|
+
readonly white: RGB;
|
|
12
|
+
readonly orange: RGB;
|
|
13
|
+
readonly orangeBg: RGB;
|
|
14
|
+
readonly green: RGB;
|
|
15
|
+
readonly red: RGB;
|
|
16
|
+
readonly codeBg: RGB;
|
|
17
|
+
readonly selection: RGB;
|
|
18
|
+
readonly scrollTrack: RGB;
|
|
19
|
+
readonly scrollThumb: RGB;
|
|
20
|
+
};
|
|
21
|
+
export interface Style {
|
|
22
|
+
fg?: RGB;
|
|
23
|
+
bg?: RGB;
|
|
24
|
+
bold?: boolean;
|
|
25
|
+
dim?: boolean;
|
|
26
|
+
inverse?: boolean;
|
|
27
|
+
italic?: boolean;
|
|
28
|
+
}
|
|
29
|
+
export declare const EMPTY_STYLE: Style;
|