@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
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Helpers for compressing a long conversation so it fits back inside the
|
|
3
|
+
* model's context window.
|
|
4
|
+
*
|
|
5
|
+
* Everything here is pure: deciding *what* to summarise, and flattening that
|
|
6
|
+
* part into a plain transcript. The actual "ask the model to summarise" step
|
|
7
|
+
* lives in `LLMClient.summarize`, so this module stays trivially unit-testable.
|
|
8
|
+
*/
|
|
9
|
+
export interface CompactMessage {
|
|
10
|
+
role: string;
|
|
11
|
+
content?: string;
|
|
12
|
+
/** Tool name, when `role` is "tool". */
|
|
13
|
+
name?: string;
|
|
14
|
+
}
|
|
15
|
+
/** A model-only summary of a prefix of the complete, non-system transcript. */
|
|
16
|
+
export interface ConversationContext {
|
|
17
|
+
/** Number of original messages covered by the summary (exclusive boundary). */
|
|
18
|
+
end: number;
|
|
19
|
+
summary: string;
|
|
20
|
+
}
|
|
21
|
+
/** Ignore stale/corrupt metadata rather than hiding any original messages. */
|
|
22
|
+
export declare function normalizeContext(value: unknown, messageCount: number): ConversationContext | null;
|
|
23
|
+
/** Derive a bounded model history while leaving the complete transcript intact. */
|
|
24
|
+
export declare function modelHistory<T extends CompactMessage>(messages: T[], context: ConversationContext | null): Array<T | {
|
|
25
|
+
role: "summary";
|
|
26
|
+
content: string;
|
|
27
|
+
}>;
|
|
28
|
+
export interface CompactionPlan<T> {
|
|
29
|
+
/** Older messages that should be summarised away. */
|
|
30
|
+
head: T[];
|
|
31
|
+
/** Recent messages kept verbatim. */
|
|
32
|
+
tail: T[];
|
|
33
|
+
}
|
|
34
|
+
/** How many trailing messages are always kept verbatim when compressing. */
|
|
35
|
+
export declare const COMPACT_KEEP = 4;
|
|
36
|
+
/** Per-message character budget fed to the summariser, to bound its own input. */
|
|
37
|
+
export declare const COMPACT_MAX_CHARS_PER_MESSAGE = 4000;
|
|
38
|
+
/**
|
|
39
|
+
* Split `messages` into an older prefix to summarise and a recent suffix to
|
|
40
|
+
* keep. The kept suffix never begins with a bare tool result — that would be an
|
|
41
|
+
* orphan without the assistant tool-call that produced it — so such messages
|
|
42
|
+
* are folded into the prefix instead.
|
|
43
|
+
*
|
|
44
|
+
* Returns null when there is nothing worth compressing (the conversation is
|
|
45
|
+
* already short enough).
|
|
46
|
+
*/
|
|
47
|
+
export declare function planCompaction<T extends CompactMessage>(messages: T[], keep?: number): CompactionPlan<T> | null;
|
|
48
|
+
/**
|
|
49
|
+
* Flatten messages into a plain transcript for the summariser, clipping each
|
|
50
|
+
* message so a single huge tool result cannot dominate the summary request.
|
|
51
|
+
*/
|
|
52
|
+
export declare function transcriptFor(messages: CompactMessage[], maxCharsPerMessage?: number): string;
|
package/dist/config.d.ts
ADDED
|
@@ -0,0 +1,221 @@
|
|
|
1
|
+
import { type ConversationContext } from "./compact.js";
|
|
2
|
+
import type { PlanState } from "./llm/plan.js";
|
|
3
|
+
import { type TaskSnapshot } from "./llm/runtime.js";
|
|
4
|
+
import type { FastSnapshot } from "./fast/types.js";
|
|
5
|
+
export declare const NEUTRINO_DIR: string;
|
|
6
|
+
export declare const MODEL_FILE: string;
|
|
7
|
+
export declare const SESSION_DIR: string;
|
|
8
|
+
export declare const CONFIG_FILE: string;
|
|
9
|
+
/**
|
|
10
|
+
* The browser console has two workspaces: the regular one and the isolated
|
|
11
|
+
* "Pro" workspace served at `/pro`. Everything a workspace owns — transcripts,
|
|
12
|
+
* model defaults, plan/compaction settings, reviewer model and interface
|
|
13
|
+
* language — is stored per mode; only the credentials and the approval policy
|
|
14
|
+
* below are shared, because they protect or gate the whole process.
|
|
15
|
+
*/
|
|
16
|
+
export declare const MODES: readonly ["standard", "pro"];
|
|
17
|
+
export type Mode = (typeof MODES)[number];
|
|
18
|
+
/** Conversation strategy inside the regular workspace; Pro owns its own workspace. */
|
|
19
|
+
export declare const AGENT_MODES: readonly ["standard", "ptc", "fast", "fast_dag"];
|
|
20
|
+
export type AgentMode = (typeof AGENT_MODES)[number];
|
|
21
|
+
export declare function isAgentMode(value: unknown): value is AgentMode;
|
|
22
|
+
export declare function normalizeAgentMode(value: unknown): AgentMode;
|
|
23
|
+
export declare const PRO_DIR: string;
|
|
24
|
+
export declare const PRO_CONFIG_FILE: string;
|
|
25
|
+
export declare const PRO_SESSION_DIR: string;
|
|
26
|
+
/**
|
|
27
|
+
* Settings that intentionally apply to both workspaces. The Web password, the
|
|
28
|
+
* approval policy, the console's interface/LAN access and background residency
|
|
29
|
+
* are properties of this process, not of a conversation workspace.
|
|
30
|
+
*/
|
|
31
|
+
export declare const SHARED_CONFIG_KEYS: readonly ["web_password_hash", "approval_mode", "auto_approve_extreme_risk", "web_lan", "background"];
|
|
32
|
+
export declare function normalizeMode(value: unknown): Mode;
|
|
33
|
+
export declare function configFile(mode?: Mode): string;
|
|
34
|
+
export declare function sessionDir(mode?: Mode): string;
|
|
35
|
+
/** Key used to remember a model's context capacity per workspace. */
|
|
36
|
+
export declare function modelContextWindowKey(provider: string, modelName: string): string;
|
|
37
|
+
export interface ModelInfo {
|
|
38
|
+
name: string;
|
|
39
|
+
url: string;
|
|
40
|
+
api_key: string;
|
|
41
|
+
supports_streaming: boolean;
|
|
42
|
+
/** Context capacity in tokens; omitted models use 1M. */
|
|
43
|
+
context_window?: number;
|
|
44
|
+
/**
|
|
45
|
+
* DeepSeek-style thinking mode: the endpoint rejects a request that offers
|
|
46
|
+
* tools while an assistant message in the history carries no
|
|
47
|
+
* `reasoning_content`. Defaults to true for DeepSeek endpoints; set it
|
|
48
|
+
* explicitly for a differently named endpoint (or to opt out).
|
|
49
|
+
*/
|
|
50
|
+
reasoning?: boolean;
|
|
51
|
+
/**
|
|
52
|
+
* Thinking intensities this model accepts, e.g. `["low", "high", "max"]`.
|
|
53
|
+
* Names are matched case-insensitively and normalised to the canonical
|
|
54
|
+
* `INTENSITY_LEVELS` spelling. Omitted or empty means every level is allowed,
|
|
55
|
+
* which keeps hand-written catalogues backwards compatible.
|
|
56
|
+
*/
|
|
57
|
+
intensity?: string[];
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Whether assistant messages must be sent back with a `reasoning_content`
|
|
61
|
+
* field. DeepSeek's thinking mode requires one on *every* assistant message of
|
|
62
|
+
* a tools-bearing request (an empty string is accepted), and the plan message
|
|
63
|
+
* written by the planner is otherwise an assistant message without it.
|
|
64
|
+
*/
|
|
65
|
+
export declare function modelUsesReasoning(provider: string, model: ModelInfo): boolean;
|
|
66
|
+
export declare const DEFAULT_CONTEXT_WINDOW = 1000000;
|
|
67
|
+
export declare const CONTEXT_COMPRESSION_RATIO = 0.8;
|
|
68
|
+
export declare const CONTEXT_WINDOW_OPTIONS: number[];
|
|
69
|
+
export declare function isContextWindow(value: unknown): value is number;
|
|
70
|
+
export declare function contextWindow(value: unknown): number;
|
|
71
|
+
/** Reload the exact provider/model so settings also apply to active chats. */
|
|
72
|
+
export declare function modelContextWindow(provider: string, model: ModelInfo, mode?: Mode): number;
|
|
73
|
+
export declare function contextCompressionThreshold(provider: string, model: ModelInfo, mode?: Mode): number;
|
|
74
|
+
export interface ModelSelection {
|
|
75
|
+
provider: string;
|
|
76
|
+
model: string;
|
|
77
|
+
intensity: string;
|
|
78
|
+
}
|
|
79
|
+
/** Resolved only on the server; credentials must never be serialized to clients. */
|
|
80
|
+
export interface ResolvedModelSelection {
|
|
81
|
+
provider: string;
|
|
82
|
+
model: ModelInfo;
|
|
83
|
+
intensity: string;
|
|
84
|
+
}
|
|
85
|
+
export interface Provider {
|
|
86
|
+
name: string;
|
|
87
|
+
models: ModelInfo[];
|
|
88
|
+
}
|
|
89
|
+
export interface ModelsData {
|
|
90
|
+
providers: Provider[];
|
|
91
|
+
}
|
|
92
|
+
export declare const DEFAULT_MODELS: ModelsData;
|
|
93
|
+
/** Languages the browser console can render its own interface in. */
|
|
94
|
+
export declare const WEB_LANGUAGES: readonly ["en", "zh"];
|
|
95
|
+
export type WebLanguage = (typeof WEB_LANGUAGES)[number];
|
|
96
|
+
/** Unknown or missing values fall back to English rather than breaking the UI. */
|
|
97
|
+
export declare function normalizeWebLanguage(value: unknown): WebLanguage;
|
|
98
|
+
export declare const DEFAULT_CONFIG: Record<string, unknown>;
|
|
99
|
+
export declare const INTENSITY_LEVELS: string[];
|
|
100
|
+
export declare const INTENSITY_PARAMS: Record<string, {
|
|
101
|
+
temperature: number;
|
|
102
|
+
top_p: number;
|
|
103
|
+
}>;
|
|
104
|
+
/** Canonical spelling of a configured intensity name, or null when unknown. */
|
|
105
|
+
export declare function normalizeIntensity(value: unknown): string | null;
|
|
106
|
+
/**
|
|
107
|
+
* The intensities a model accepts, in the canonical Low → Ultra order.
|
|
108
|
+
*
|
|
109
|
+
* An absent or empty list keeps the historical behaviour: every level is
|
|
110
|
+
* allowed. Unknown names are ignored rather than making the model unusable, so
|
|
111
|
+
* a typo in `model.json` falls back to the remaining valid entries (or to all
|
|
112
|
+
* levels when none is valid).
|
|
113
|
+
*/
|
|
114
|
+
export declare function modelIntensities(model: ModelInfo | null | undefined): string[];
|
|
115
|
+
/**
|
|
116
|
+
* Pick a usable intensity for a model. The preferred value wins when the model
|
|
117
|
+
* accepts it; otherwise the configured fallback, then High, then the first
|
|
118
|
+
* supported level. This is the one place stale UI state, a saved session or a
|
|
119
|
+
* hand-edited `config.json` is reconciled with the model's limits.
|
|
120
|
+
*/
|
|
121
|
+
export declare function intensityForModel(model: ModelInfo | null | undefined, preferred?: unknown, fallback?: unknown): string;
|
|
122
|
+
/** Exact model lookup within a provider (names may repeat across providers). */
|
|
123
|
+
export declare function findConfiguredModel(providerName: string, modelName: string): ModelInfo | null;
|
|
124
|
+
export interface SessionMeta {
|
|
125
|
+
id: string;
|
|
126
|
+
created_at: string;
|
|
127
|
+
updated_at: string;
|
|
128
|
+
model: string;
|
|
129
|
+
provider: string;
|
|
130
|
+
intensity: string;
|
|
131
|
+
cwd: string;
|
|
132
|
+
title: string;
|
|
133
|
+
message_count: number;
|
|
134
|
+
agent_mode?: AgentMode;
|
|
135
|
+
}
|
|
136
|
+
export interface ToolMessage {
|
|
137
|
+
role: "tool";
|
|
138
|
+
name: string;
|
|
139
|
+
arguments: Record<string, unknown>;
|
|
140
|
+
result: string;
|
|
141
|
+
}
|
|
142
|
+
export interface ChatMessage {
|
|
143
|
+
role: "user" | "assistant" | "system";
|
|
144
|
+
content: string;
|
|
145
|
+
}
|
|
146
|
+
export type SessionMessage = ChatMessage | ToolMessage;
|
|
147
|
+
export interface TokenUsage {
|
|
148
|
+
prompt: number;
|
|
149
|
+
completion: number;
|
|
150
|
+
}
|
|
151
|
+
export interface SessionData {
|
|
152
|
+
fast_run?: FastSnapshot;
|
|
153
|
+
task_run?: TaskSnapshot;
|
|
154
|
+
id: string;
|
|
155
|
+
created_at: string;
|
|
156
|
+
updated_at: string;
|
|
157
|
+
model: string;
|
|
158
|
+
provider: string;
|
|
159
|
+
intensity: string;
|
|
160
|
+
cwd: string;
|
|
161
|
+
title: string;
|
|
162
|
+
messages: SessionMessage[];
|
|
163
|
+
tokens?: TokenUsage;
|
|
164
|
+
/** Model context is separate from messages, which always retain the originals. */
|
|
165
|
+
context?: ConversationContext;
|
|
166
|
+
approval_mode?: ApprovalMode;
|
|
167
|
+
agent_mode?: AgentMode;
|
|
168
|
+
/** The plan of the turn being worked on, so reopening shows the same steps. */
|
|
169
|
+
plan?: PlanState | null;
|
|
170
|
+
/** Pro preset selection; resolved fallback models are always task-local. */
|
|
171
|
+
pro_preset?: string;
|
|
172
|
+
}
|
|
173
|
+
export type ApprovalMode = "request" | "auto";
|
|
174
|
+
export declare function normalizeApprovalMode(value: unknown): ApprovalMode;
|
|
175
|
+
export declare function ensureDirs(mode?: Mode): void;
|
|
176
|
+
export declare function loadModels(): ModelsData;
|
|
177
|
+
export declare function saveModels(data: ModelsData): void;
|
|
178
|
+
export declare function loadConfig(mode?: Mode): Record<string, unknown>;
|
|
179
|
+
export declare function saveConfig(data: Record<string, unknown>, mode?: Mode): void;
|
|
180
|
+
/** Look a model up by name across every configured provider. */
|
|
181
|
+
export declare function findModel(name: string): {
|
|
182
|
+
provider: string;
|
|
183
|
+
model: ModelInfo;
|
|
184
|
+
} | null;
|
|
185
|
+
export declare function getModelsByProvider(): Record<string, ModelInfo[]>;
|
|
186
|
+
/** Resolve a provider/model pair exactly, including when names repeat across providers. */
|
|
187
|
+
export declare function resolveModelSelection(value: unknown, requireKey?: boolean): ResolvedModelSelection;
|
|
188
|
+
export declare function saveSession(messages: SessionMessage[], modelName: string, provider: string, intensity: string, cwd?: string, sessionId?: string, tokens?: TokenUsage, context?: ConversationContext | null, approvalMode?: ApprovalMode, plan?: PlanState | null, mode?: Mode, proPreset?: string, agentMode?: AgentMode, taskRun?: TaskSnapshot, fastRun?: FastSnapshot): string;
|
|
189
|
+
export declare function loadSession(sessionId: string, mode?: Mode): SessionData | null;
|
|
190
|
+
export declare function deleteSession(sessionId: string, mode?: Mode): boolean;
|
|
191
|
+
export declare function listSessions(mode?: Mode): SessionMeta[];
|
|
192
|
+
/**
|
|
193
|
+
* Sessions that were started in `cwd` (the directory neutrino runs in).
|
|
194
|
+
* The history menu is scoped to the current project so sessions started in
|
|
195
|
+
* other directories do not clutter it.
|
|
196
|
+
*/
|
|
197
|
+
export declare function listSessionsForCwd(cwd?: string, mode?: Mode): SessionMeta[];
|
|
198
|
+
export declare function loadLogo(): string;
|
|
199
|
+
export declare function loadPrompt(): string;
|
|
200
|
+
/** Mode prompts are standalone so standard planning/delegation directives cannot conflict. */
|
|
201
|
+
export declare function loadAgentPrompt(mode: AgentMode): string;
|
|
202
|
+
/** The planning agent's system prompt. */
|
|
203
|
+
export declare function loadPlannerPrompt(): string;
|
|
204
|
+
/** Template for the per-step system text; `{{index}}`, `{{plan}}`, … are filled in. */
|
|
205
|
+
export declare function loadStepPrompt(): string;
|
|
206
|
+
/** The system prompt a delegated sub-agent works from. */
|
|
207
|
+
export declare function loadSubAgentPrompt(): string;
|
|
208
|
+
/**
|
|
209
|
+
* Whether a turn is planned before it is executed. Unset means on: the plan is
|
|
210
|
+
* the default workflow, and only an explicit `false` turns it off.
|
|
211
|
+
*
|
|
212
|
+
* `NEUTRINO_PLAN=off` overrides both ways for a single run, the way the other
|
|
213
|
+
* environment variables override the saved configuration.
|
|
214
|
+
*/
|
|
215
|
+
export declare function planFirstEnabled(mode?: Mode): boolean;
|
|
216
|
+
/**
|
|
217
|
+
* Read the CLI version straight from the package's `package.json` so the
|
|
218
|
+
* displayed version can never drift out of sync with the published package.
|
|
219
|
+
* The result is cached after the first successful read.
|
|
220
|
+
*/
|
|
221
|
+
export declare function loadVersion(): string;
|
package/dist/config.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { pluginPrompt } from "./plugins/session.js";
|
|
1
2
|
import fs from "node:fs";
|
|
2
3
|
import os from "node:os";
|
|
3
4
|
import path from "node:path";
|
|
@@ -9,7 +10,7 @@ import { SUBAGENT_MAX_ROUNDS } from "./llm/subagent.js";
|
|
|
9
10
|
import { DEFAULT_TASK_LIMITS } from "./llm/runtime.js";
|
|
10
11
|
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
11
12
|
const PKG_ROOT = path.resolve(__dirname, "..");
|
|
12
|
-
export const NEUTRINO_DIR = path.join(os.homedir(), ".neutrino");
|
|
13
|
+
export const NEUTRINO_DIR = process.env.NEUTRINO_HOME || path.join(os.homedir(), ".neutrino");
|
|
13
14
|
export const MODEL_FILE = path.join(NEUTRINO_DIR, "model.json");
|
|
14
15
|
export const SESSION_DIR = path.join(NEUTRINO_DIR, "session");
|
|
15
16
|
export const CONFIG_FILE = path.join(NEUTRINO_DIR, "config.json");
|
|
@@ -22,7 +23,7 @@ export const CONFIG_FILE = path.join(NEUTRINO_DIR, "config.json");
|
|
|
22
23
|
*/
|
|
23
24
|
export const MODES = ["standard", "pro"];
|
|
24
25
|
/** Conversation strategy inside the regular workspace; Pro owns its own workspace. */
|
|
25
|
-
export const AGENT_MODES = ["standard", "ptc", "fast"];
|
|
26
|
+
export const AGENT_MODES = ["standard", "ptc", "fast", "fast_dag"];
|
|
26
27
|
export function isAgentMode(value) {
|
|
27
28
|
return typeof value === "string" && AGENT_MODES.includes(value);
|
|
28
29
|
}
|
|
@@ -151,6 +152,15 @@ export const DEFAULT_CONFIG = {
|
|
|
151
152
|
task_max_tools: DEFAULT_TASK_LIMITS.toolCalls,
|
|
152
153
|
task_max_tokens: DEFAULT_TASK_LIMITS.tokens,
|
|
153
154
|
task_timeout_ms: DEFAULT_TASK_LIMITS.durationMs,
|
|
155
|
+
// Independent concurrent engine. Legacy "fast" remains a separate strategy.
|
|
156
|
+
fast_mode_enabled: true,
|
|
157
|
+
fast_model_slots: 4,
|
|
158
|
+
fast_tool_slots: 8,
|
|
159
|
+
fast_background_slots: 2,
|
|
160
|
+
fast_task_timeout_ms: DEFAULT_TASK_LIMITS.durationMs,
|
|
161
|
+
fast_max_tasks: 24,
|
|
162
|
+
fast_retries: 1,
|
|
163
|
+
fast_replans: 2,
|
|
154
164
|
// Mount the browser UI alongside the terminal UI unless it is turned off
|
|
155
165
|
// here or on the command line.
|
|
156
166
|
web_console: true,
|
|
@@ -357,7 +367,7 @@ export function resolveModelSelection(value, requireKey = true) {
|
|
|
357
367
|
throw new Error("this model has no API key configured");
|
|
358
368
|
return { provider: provider.name, model: { ...model }, intensity };
|
|
359
369
|
}
|
|
360
|
-
export function saveSession(messages, modelName, provider, intensity, cwd, sessionId, tokens, context, approvalMode, plan, mode = "standard", proPreset, agentMode, taskRun) {
|
|
370
|
+
export function saveSession(messages, modelName, provider, intensity, cwd, sessionId, tokens, context, approvalMode, plan, mode = "standard", proPreset, agentMode, taskRun, fastRun) {
|
|
361
371
|
ensureDirs(mode);
|
|
362
372
|
const now = new Date();
|
|
363
373
|
const id = sessionId ?? `${now.toISOString().replace(/[:.]/g, "-")}-${randomUUID().slice(0, 8)}`;
|
|
@@ -381,6 +391,7 @@ export function saveSession(messages, modelName, provider, intensity, cwd, sessi
|
|
|
381
391
|
title,
|
|
382
392
|
messages,
|
|
383
393
|
tokens,
|
|
394
|
+
...((fastRun ?? existing?.fast_run) ? { fast_run: fastRun ?? existing?.fast_run } : {}),
|
|
384
395
|
...((taskRun ?? existing?.task_run) ? { task_run: taskRun ?? existing?.task_run } : {}),
|
|
385
396
|
approval_mode: normalizeApprovalMode(approvalMode ?? existing?.approval_mode),
|
|
386
397
|
agent_mode: mode === "pro" ? "standard" : normalizeAgentMode(agentMode ?? existing?.agent_mode),
|
|
@@ -509,16 +520,17 @@ export function loadPrompt() {
|
|
|
509
520
|
}
|
|
510
521
|
/** Mode prompts are standalone so standard planning/delegation directives cannot conflict. */
|
|
511
522
|
export function loadAgentPrompt(mode) {
|
|
512
|
-
if (mode === "standard")
|
|
513
|
-
return loadPrompt();
|
|
523
|
+
if (mode === "standard" || mode === "fast_dag")
|
|
524
|
+
return pluginPrompt(loadPrompt(), "main");
|
|
514
525
|
const file = mode === "fast" ? "fast.txt" : "ptc.txt";
|
|
515
|
-
return readAsset(path.join(PKG_ROOT, "assets", file)) ?? loadPrompt();
|
|
526
|
+
return pluginPrompt(readAsset(path.join(PKG_ROOT, "assets", file)) ?? loadPrompt(), "main");
|
|
516
527
|
}
|
|
517
528
|
/** The planning agent's system prompt. */
|
|
518
529
|
export function loadPlannerPrompt() {
|
|
519
|
-
|
|
530
|
+
const prompt = (readAsset(path.join(PKG_ROOT, "assets", "planner.txt"), path.join(PKG_ROOT, "planner.txt")) ??
|
|
520
531
|
"Split the user's request into ordered, independent steps a coding agent can execute one at a " +
|
|
521
532
|
"time. Answer with JSON only: {\"goal\": \"...\", \"steps\": [{\"title\": \"...\"}]}.");
|
|
533
|
+
return pluginPrompt(prompt, "planner");
|
|
522
534
|
}
|
|
523
535
|
/** Template for the per-step system text; `{{index}}`, `{{plan}}`, … are filled in. */
|
|
524
536
|
export function loadStepPrompt() {
|
package/dist/cwd.d.ts
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Helpers for deciding whether a stored session belongs to the directory the
|
|
3
|
+
* agent is currently running in.
|
|
4
|
+
*
|
|
5
|
+
* Every agent records the working directory in its own flavour — `C:\a\b`,
|
|
6
|
+
* `C:/a/b`, a trailing slash, a differently cased drive letter, or a WSL style
|
|
7
|
+
* `/mnt/c/a/b` — so the comparison has to be forgiving about the spelling and
|
|
8
|
+
* strict about the actual location.
|
|
9
|
+
*/
|
|
10
|
+
/** Reduce a directory path to a canonical form that can be compared for equality. */
|
|
11
|
+
export declare function normalizeCwd(dir: string): string;
|
|
12
|
+
/** True when both paths point at the same directory. Empty paths never match. */
|
|
13
|
+
export declare function sameCwd(a: string, b: string): boolean;
|
|
14
|
+
/**
|
|
15
|
+
* Claude Code names each project directory after the working directory with
|
|
16
|
+
* every non-alphanumeric character replaced by a dash. That lets us attribute
|
|
17
|
+
* transcripts that do not carry an explicit `cwd` field.
|
|
18
|
+
*/
|
|
19
|
+
export declare function claudeProjectKey(dir: string): string;
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import type { FastArtifact, FastOutput, FastSnapshot } from "./types.js";
|
|
2
|
+
import type { DiagnosticRecorder } from "../llm/diagnostics.js";
|
|
3
|
+
/** Every turn has its own store; immutable versions remain available after invalidation. */
|
|
4
|
+
export declare class ArtifactStore {
|
|
5
|
+
private directory?;
|
|
6
|
+
private diagnostics?;
|
|
7
|
+
private history;
|
|
8
|
+
private stable;
|
|
9
|
+
private persistenceStarted;
|
|
10
|
+
private discarded;
|
|
11
|
+
cacheHits: number;
|
|
12
|
+
constructor(directory?: string | undefined, diagnostics?: DiagnosticRecorder | undefined);
|
|
13
|
+
context(key: string, content: string): string;
|
|
14
|
+
readContext(key: string): {
|
|
15
|
+
version: string;
|
|
16
|
+
content: string;
|
|
17
|
+
} | undefined;
|
|
18
|
+
latest(id: string): FastArtifact | undefined;
|
|
19
|
+
put(id: string, output: FastOutput, versions: Record<string, number | string>): FastArtifact;
|
|
20
|
+
verify(id: string): void;
|
|
21
|
+
invalidate(id: string): void;
|
|
22
|
+
all(): FastArtifact[];
|
|
23
|
+
checkpoint(snapshot: FastSnapshot): void;
|
|
24
|
+
private persist;
|
|
25
|
+
private ensureDirectory;
|
|
26
|
+
}
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
import { createHash, randomUUID } from "node:crypto";
|
|
2
|
+
import fs from "node:fs";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import { currentTask } from "../llm/runtime.js";
|
|
5
|
+
/** Every turn has its own store; immutable versions remain available after invalidation. */
|
|
6
|
+
export class ArtifactStore {
|
|
7
|
+
directory;
|
|
8
|
+
diagnostics;
|
|
9
|
+
history = new Map();
|
|
10
|
+
stable = new Map();
|
|
11
|
+
persistenceStarted = false;
|
|
12
|
+
discarded = false;
|
|
13
|
+
cacheHits = 0;
|
|
14
|
+
constructor(directory, diagnostics) {
|
|
15
|
+
this.directory = directory;
|
|
16
|
+
this.diagnostics = diagnostics;
|
|
17
|
+
}
|
|
18
|
+
context(key, content) {
|
|
19
|
+
const version = createHash("sha256").update(content).digest("hex");
|
|
20
|
+
const cached = this.stable.get(key);
|
|
21
|
+
if (cached?.version === version)
|
|
22
|
+
this.cacheHits++;
|
|
23
|
+
else
|
|
24
|
+
this.stable.set(key, { version, content });
|
|
25
|
+
return version;
|
|
26
|
+
}
|
|
27
|
+
readContext(key) {
|
|
28
|
+
const value = this.stable.get(key);
|
|
29
|
+
if (value)
|
|
30
|
+
this.cacheHits++;
|
|
31
|
+
return value ? { ...value } : undefined;
|
|
32
|
+
}
|
|
33
|
+
latest(id) {
|
|
34
|
+
const item = this.history.get(id)?.at(-1);
|
|
35
|
+
return item ? structuredClone(item) : undefined;
|
|
36
|
+
}
|
|
37
|
+
put(id, output, versions) {
|
|
38
|
+
const versionsSoFar = this.history.get(id) ?? [];
|
|
39
|
+
const artifact = { ...structuredClone(output), task_id: id,
|
|
40
|
+
version: versionsSoFar.length + 1, input_versions: { ...versions }, verification: "pending",
|
|
41
|
+
created_at: new Date().toISOString() };
|
|
42
|
+
versionsSoFar.push(artifact);
|
|
43
|
+
this.history.set(id, versionsSoFar);
|
|
44
|
+
this.persist(id);
|
|
45
|
+
return structuredClone(artifact);
|
|
46
|
+
}
|
|
47
|
+
verify(id) {
|
|
48
|
+
const item = this.history.get(id)?.at(-1);
|
|
49
|
+
if (!item || item.verification === "invalidated")
|
|
50
|
+
throw new Error(`Cannot verify invalid artifact: ${id}`);
|
|
51
|
+
item.verification = "verified";
|
|
52
|
+
this.persist(id);
|
|
53
|
+
}
|
|
54
|
+
invalidate(id) {
|
|
55
|
+
const item = this.history.get(id)?.at(-1);
|
|
56
|
+
if (item)
|
|
57
|
+
item.verification = "invalidated";
|
|
58
|
+
this.persist(id);
|
|
59
|
+
}
|
|
60
|
+
all() { return structuredClone([...this.history.values()].flat()); }
|
|
61
|
+
checkpoint(snapshot) {
|
|
62
|
+
if (!this.directory || !this.ensureDirectory())
|
|
63
|
+
return;
|
|
64
|
+
const target = path.join(this.directory, "run.json"), temp = `${target}.${randomUUID()}.tmp`;
|
|
65
|
+
const span = this.diagnostics?.span("persistence", { artifact_kind: "run" }, currentTask());
|
|
66
|
+
try {
|
|
67
|
+
const serialized = JSON.stringify(snapshot);
|
|
68
|
+
if (span)
|
|
69
|
+
this.diagnostics?.emit("persistence_encoded", { span_id: span.id, artifact_kind: "run", payload_bytes: Buffer.byteLength(serialized) });
|
|
70
|
+
fs.writeFileSync(temp, serialized, { mode: 0o600 });
|
|
71
|
+
fs.renameSync(temp, target);
|
|
72
|
+
}
|
|
73
|
+
finally {
|
|
74
|
+
if (fs.existsSync(temp))
|
|
75
|
+
fs.unlinkSync(temp);
|
|
76
|
+
span?.end();
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
persist(taskId) {
|
|
80
|
+
if (!this.directory || !this.ensureDirectory())
|
|
81
|
+
return;
|
|
82
|
+
const target = path.join(this.directory, "artifacts.json");
|
|
83
|
+
const temp = `${target}.${randomUUID()}.tmp`;
|
|
84
|
+
const span = this.diagnostics?.span("persistence", { artifact_kind: "artifacts", task_id: taskId }, currentTask());
|
|
85
|
+
try {
|
|
86
|
+
const serialized = JSON.stringify({ contexts: Object.fromEntries(this.stable), artifacts: this.all() });
|
|
87
|
+
if (span)
|
|
88
|
+
this.diagnostics?.emit("persistence_encoded", { span_id: span.id, artifact_kind: "artifacts", payload_bytes: Buffer.byteLength(serialized) });
|
|
89
|
+
fs.writeFileSync(temp, serialized, { mode: 0o600 });
|
|
90
|
+
fs.renameSync(temp, target);
|
|
91
|
+
}
|
|
92
|
+
finally {
|
|
93
|
+
if (fs.existsSync(temp))
|
|
94
|
+
fs.unlinkSync(temp);
|
|
95
|
+
span?.end();
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
ensureDirectory() {
|
|
99
|
+
if (!this.directory || this.discarded)
|
|
100
|
+
return false;
|
|
101
|
+
if (this.persistenceStarted && !fs.existsSync(this.directory)) {
|
|
102
|
+
this.discarded = true;
|
|
103
|
+
return false;
|
|
104
|
+
}
|
|
105
|
+
fs.mkdirSync(this.directory, { recursive: true, mode: 0o700 });
|
|
106
|
+
this.persistenceStarted = true;
|
|
107
|
+
return true;
|
|
108
|
+
}
|
|
109
|
+
}
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import { LLMClient, type UsageInfo } from "../llm/client.js";
|
|
2
|
+
import { type TaskRuntime } from "../llm/runtime.js";
|
|
3
|
+
import { type ApprovalDecision } from "../llm/approval.js";
|
|
4
|
+
import { ArtifactStore } from "./artifacts.js";
|
|
5
|
+
import { ResourcePool } from "./pool.js";
|
|
6
|
+
import { DynamicScheduler } from "./scheduler.js";
|
|
7
|
+
import { type FastSettings, type FastSnapshot } from "./types.js";
|
|
8
|
+
type Messages = Parameters<LLMClient["streamTurn"]>[0];
|
|
9
|
+
export declare const FAST_FINISH_REVIEW = "finish_fast_review";
|
|
10
|
+
export interface FastEngineOptions {
|
|
11
|
+
runtime: TaskRuntime;
|
|
12
|
+
settings: FastSettings;
|
|
13
|
+
signal: AbortSignal;
|
|
14
|
+
cwd: string;
|
|
15
|
+
history: Messages;
|
|
16
|
+
system: string;
|
|
17
|
+
subagentSystem: string;
|
|
18
|
+
contextWindow: number;
|
|
19
|
+
maxRounds: number;
|
|
20
|
+
directory?: string;
|
|
21
|
+
/** The session's unchanged model/provider/reasoning configuration, fresh for every request. */
|
|
22
|
+
createClient(): LLMClient;
|
|
23
|
+
approve(name: string, args: Record<string, unknown>, signal: AbortSignal, wrapReviewer?: (client: LLMClient) => LLMClient): Promise<ApprovalDecision>;
|
|
24
|
+
execute(name: string, args: Record<string, unknown>, signal: AbortSignal): Promise<string>;
|
|
25
|
+
onUsage?(usage: UsageInfo): void;
|
|
26
|
+
onSnapshot?(snapshot: FastSnapshot): void;
|
|
27
|
+
onText?(text: string): void;
|
|
28
|
+
onTool?(name: string, args: Record<string, unknown>, result: string): void;
|
|
29
|
+
}
|
|
30
|
+
/** Separate engine; no changes to Standard's planner or agent loop. */
|
|
31
|
+
export declare class FastAgentEngine {
|
|
32
|
+
private options;
|
|
33
|
+
readonly runId: `${string}-${string}-${string}-${string}-${string}`;
|
|
34
|
+
readonly models: ResourcePool;
|
|
35
|
+
readonly tools: ResourcePool;
|
|
36
|
+
readonly background: ResourcePool;
|
|
37
|
+
readonly store: ArtifactStore;
|
|
38
|
+
readonly scheduler: DynamicScheduler;
|
|
39
|
+
private phase;
|
|
40
|
+
private error?;
|
|
41
|
+
private started;
|
|
42
|
+
private integrated;
|
|
43
|
+
private integrations;
|
|
44
|
+
private integrationError?;
|
|
45
|
+
private modelBlocker?;
|
|
46
|
+
private constraints;
|
|
47
|
+
private goal;
|
|
48
|
+
private approvalPool;
|
|
49
|
+
private diagnosticPhase?;
|
|
50
|
+
private toolWaitMs;
|
|
51
|
+
private replans;
|
|
52
|
+
private planningErrors;
|
|
53
|
+
private reviewLog;
|
|
54
|
+
private metrics;
|
|
55
|
+
constructor(options: FastEngineOptions);
|
|
56
|
+
snapshot(): FastSnapshot;
|
|
57
|
+
run(): Promise<{
|
|
58
|
+
report: string;
|
|
59
|
+
status: "completed" | "partial" | "failed" | "cancelled";
|
|
60
|
+
snapshot: FastSnapshot;
|
|
61
|
+
}>;
|
|
62
|
+
/** JSONL lines are admitted during streaming, before the planner request completes. */
|
|
63
|
+
private plan;
|
|
64
|
+
private worker;
|
|
65
|
+
private review;
|
|
66
|
+
private approve;
|
|
67
|
+
private taskTools;
|
|
68
|
+
private historyForPrompt;
|
|
69
|
+
private evidenceView;
|
|
70
|
+
private dependencyEvidence;
|
|
71
|
+
private evidenceContext;
|
|
72
|
+
private execute;
|
|
73
|
+
private delegate;
|
|
74
|
+
private client;
|
|
75
|
+
private bindClient;
|
|
76
|
+
private publish;
|
|
77
|
+
}
|
|
78
|
+
export {};
|