@xingjisoft/neutrino 0.5.1 → 0.6.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 +1 -0
- package/assets/fast.txt +18 -0
- package/assets/pro/common.txt +4 -0
- package/assets/pro/main_model.txt +2 -0
- package/assets/pro/plan_model.txt +3 -1
- package/assets/prompt.txt +9 -0
- package/assets/ptc.txt +27 -0
- package/assets/subagent.txt +22 -1
- package/dist/app.js +2 -0
- package/dist/config.js +30 -1
- package/dist/llm/client.js +61 -8
- package/dist/llm/codeExecutor.js +239 -0
- package/dist/llm/codemode.js +164 -0
- package/dist/llm/context.js +97 -0
- package/dist/llm/plan.js +97 -9
- package/dist/llm/risk.js +10 -0
- package/dist/llm/runtime.js +301 -0
- package/dist/llm/search.js +426 -0
- package/dist/llm/searchNetwork.js +129 -0
- package/dist/llm/subagent.js +236 -112
- package/dist/llm/tools.js +101 -4
- package/dist/pro/agents.js +49 -19
- package/dist/pro/preflight.js +24 -2
- package/dist/screens/chat.js +299 -27
- package/dist/update.js +8 -2
- package/dist/updateWatch.js +76 -0
- package/dist/web/hub.js +9 -0
- package/dist/web/server.js +183 -13
- package/dist/web/service.js +22 -0
- package/dist/web/session.js +327 -62
- package/dist/web/terminalBridge.js +10 -1
- package/package.json +42 -37
- package/web/app.js +347 -29
- package/web/i18n.js +27 -0
- package/web/index.html +465 -442
- package/web/settings.js +31 -1
- package/web/style.css +3581 -3473
package/dist/pro/agents.js
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
import { LLMClient } from "../llm/client.js";
|
|
2
|
-
import { buildSubAgentTask, formatSubAgentResult, runSubAgent } from "../llm/subagent.js";
|
|
2
|
+
import { buildSubAgentTask, formatSubAgentResult, runSubAgent, subAgentRounds } from "../llm/subagent.js";
|
|
3
3
|
import { DELEGATE_TOOL, getSubAgentToolDefinitions } from "../llm/tools.js";
|
|
4
|
+
import { loadConfig } from "../config.js";
|
|
4
5
|
import { ACTIVE_PRO_ROLES, PRO_ROLES, loadProPrompt } from "./config.js";
|
|
6
|
+
import { currentTask, TaskRuntime, TaskControlError, taskLimits } from "../llm/runtime.js";
|
|
5
7
|
export const PRO_AGENT_TOOL = "call_pro_agent";
|
|
6
8
|
const CALL_AGENT = {
|
|
7
9
|
type: "function",
|
|
@@ -16,6 +18,8 @@ const CALL_AGENT = {
|
|
|
16
18
|
role: { type: "string", enum: [...ACTIVE_PRO_ROLES] },
|
|
17
19
|
task: { type: "string", description: "Complete task, expected deliverables and acceptance criteria." },
|
|
18
20
|
context: { type: "string", description: "Relevant evidence, constraints and decisions; the agent cannot see the parent's conversation." },
|
|
21
|
+
read_only: { type: "boolean", description: "Restrict this task and all descendants to read-only tools." },
|
|
22
|
+
write_paths: { type: "array", items: { type: "string" }, description: "Optional allowed write paths. Descendants can only narrow this scope; arbitrary shell commands are unavailable under a path restriction." },
|
|
19
23
|
reason: { type: "string", description: "Why this specialist is needed for the user's request." },
|
|
20
24
|
},
|
|
21
25
|
required: ["role", "task", "reason"],
|
|
@@ -28,10 +32,12 @@ export function getProToolDefinitions() {
|
|
|
28
32
|
}
|
|
29
33
|
/** A resolved model team belongs to one task; substitutions never touch disk. */
|
|
30
34
|
export class ProAgentTeam {
|
|
35
|
+
runtime;
|
|
31
36
|
options;
|
|
32
37
|
maxDepth;
|
|
33
38
|
constructor(options) {
|
|
34
39
|
this.options = options;
|
|
40
|
+
this.runtime = options.runtime ?? currentTask()?.runtime ?? new TaskRuntime({ request: options.request ?? "", cwd: options.cwd, limits: taskLimits(loadConfig("pro")) });
|
|
35
41
|
this.maxDepth = Number.isSafeInteger(options.maxDepth) && options.maxDepth > 0 ? options.maxDepth : 10;
|
|
36
42
|
}
|
|
37
43
|
client(role) {
|
|
@@ -40,36 +46,56 @@ export class ProAgentTeam {
|
|
|
40
46
|
throw new Error(`Pro agent ${role} is unavailable`);
|
|
41
47
|
return new LLMClient(selection.model, selection.provider, selection.intensity, { professional: true });
|
|
42
48
|
}
|
|
43
|
-
async call(role, task, context = "", depth = 1, readOnly = false) {
|
|
49
|
+
async call(role, task, context = "", depth = 1, readOnly = false, writePaths) {
|
|
50
|
+
return formatSubAgentResult(await this.callResult(role, task, context, depth, readOnly, writePaths));
|
|
51
|
+
}
|
|
52
|
+
contextWindow(role) {
|
|
53
|
+
const selection = this.options.models[role];
|
|
54
|
+
const overrides = loadConfig("pro").model_context_windows;
|
|
55
|
+
const value = selection && overrides?.[`${selection.provider}/${selection.model.name}`];
|
|
56
|
+
return typeof value === "number" && Number.isSafeInteger(value) && value > 0 ? value : selection?.context_window ?? 1_000_000;
|
|
57
|
+
}
|
|
58
|
+
async callResult(role, task, context = "", depth = 1, readOnly = false, writePaths) {
|
|
59
|
+
const failure = (code, message) => {
|
|
60
|
+
const result = { runId: currentTask()?.runId ?? this.runtime.rootRunId,
|
|
61
|
+
status: code === "cancelled" ? "cancelled" : "failed", report: "", evidenceIds: [], artifacts: [], blockers: [],
|
|
62
|
+
error: { code, message }, aborted: code === "cancelled", exhausted: false, rounds: 0, calls: 0 };
|
|
63
|
+
this.runtime.recordResult(result);
|
|
64
|
+
return result;
|
|
65
|
+
};
|
|
44
66
|
if (!PRO_ROLES.includes(role))
|
|
45
|
-
return "
|
|
67
|
+
return failure("unknown_role", "unknown Pro agent role");
|
|
46
68
|
if (role === "genimage_model")
|
|
47
|
-
return "
|
|
48
|
-
if (this.options.cancelled())
|
|
49
|
-
return "
|
|
69
|
+
return failure("reserved", "image generation is reserved and is not enabled");
|
|
70
|
+
if (this.options.cancelled() || this.runtime.signal.aborted)
|
|
71
|
+
return failure("cancelled", "the turn was paused or replaced");
|
|
50
72
|
if (!Number.isSafeInteger(depth) || depth < 0 || depth > this.maxDepth) {
|
|
51
|
-
return `
|
|
73
|
+
return failure("recursion_limit", `Pro agent recursion limit (${this.maxDepth}) reached. Complete the work without further delegation.`);
|
|
52
74
|
}
|
|
53
75
|
if (typeof task !== "string" || !task.trim())
|
|
54
|
-
return "
|
|
76
|
+
return failure("invalid_task", "a Pro agent needs a nonempty task");
|
|
55
77
|
const selection = this.options.models[role];
|
|
56
78
|
if (!selection)
|
|
57
|
-
return `
|
|
79
|
+
return failure("unavailable", `Pro agent ${role} is unavailable`);
|
|
58
80
|
// Reviewers are always read-only, including when consulted as specialists.
|
|
59
|
-
const
|
|
81
|
+
const parent = currentTask()?.runtime === this.runtime ? currentTask() : this.runtime.root;
|
|
82
|
+
const onlyRead = readOnly || role === "confirm_model" || parent.policy.readOnly;
|
|
60
83
|
const tools = getProToolDefinitions().filter((tool) => !onlyRead ||
|
|
61
|
-
["read_file", "list_files", PRO_AGENT_TOOL].includes(tool.function.name));
|
|
84
|
+
["read_file", "list_files", "web_search", PRO_AGENT_TOOL].includes(tool.function.name));
|
|
62
85
|
const names = new Set(tools.map((tool) => tool.function.name));
|
|
63
86
|
const label = `${role} · ${selection.model.name} · ${depth}/${this.maxDepth}`;
|
|
64
87
|
try {
|
|
65
|
-
const
|
|
88
|
+
const maxRounds = subAgentRounds(loadConfig("pro").subagent_max_rounds);
|
|
89
|
+
return await this.runtime.child(role, task, async (scope) => runSubAgent({
|
|
90
|
+
runtime: this.runtime, scope,
|
|
66
91
|
createClient: () => this.client(role),
|
|
67
92
|
system: `${loadProPrompt(role)}\n\nDelegation depth: ${depth}/${this.maxDepth}. ` +
|
|
68
93
|
(onlyRead ? "This consultation is read-only. Do not modify files or execute commands, including through another agent." :
|
|
69
94
|
"Use the shared approval gate for every tool call. Return a self-contained report with changes, evidence and unresolved work."),
|
|
70
|
-
task: buildSubAgentTask({ task, context, cwd: this.options.cwd }),
|
|
95
|
+
task: buildSubAgentTask({ task, context, cwd: this.options.cwd, rounds: maxRounds }),
|
|
71
96
|
tools,
|
|
72
|
-
contextWindow:
|
|
97
|
+
contextWindow: this.contextWindow(role),
|
|
98
|
+
maxRounds,
|
|
73
99
|
approve: (name, args) => {
|
|
74
100
|
if (!names.has(name) && name !== DELEGATE_TOOL)
|
|
75
101
|
return Promise.resolve({
|
|
@@ -82,8 +108,9 @@ export class ProAgentTeam {
|
|
|
82
108
|
const nextRole = typeof args.role === "string" ? args.role : "backend_output_model";
|
|
83
109
|
const nextTask = typeof args.task === "string" ? args.task : "";
|
|
84
110
|
const nextContext = typeof args.context === "string" ? args.context : "";
|
|
85
|
-
const report = await this.call(nextRole, nextTask, nextContext, depth + 1, onlyRead);
|
|
86
|
-
this.options.
|
|
111
|
+
const report = await this.call(nextRole, nextTask, nextContext, depth + 1, onlyRead || args.read_only === true, Array.isArray(args.write_paths) ? args.write_paths.filter((p) => typeof p === "string") : undefined);
|
|
112
|
+
if (!this.options.cancelled() && !this.runtime.signal.aborted)
|
|
113
|
+
this.options.onProgress?.(label);
|
|
87
114
|
return report;
|
|
88
115
|
}
|
|
89
116
|
if (!names.has(name))
|
|
@@ -94,11 +121,14 @@ export class ProAgentTeam {
|
|
|
94
121
|
onUsage: this.options.onUsage,
|
|
95
122
|
onRetry: this.options.onRetry,
|
|
96
123
|
onProgress: (activity) => this.options.onProgress?.(`${label} · ${activity}`),
|
|
97
|
-
});
|
|
98
|
-
|
|
124
|
+
}), { readOnly: onlyRead, writePaths });
|
|
125
|
+
}
|
|
126
|
+
catch (error) {
|
|
127
|
+
return failure(error instanceof TaskControlError ? error.code : "agent_error", error instanceof Error ? error.message : String(error));
|
|
99
128
|
}
|
|
100
129
|
finally {
|
|
101
|
-
this.options.
|
|
130
|
+
if (!this.options.cancelled() && !this.runtime.signal.aborted)
|
|
131
|
+
this.options.onProgress?.(null);
|
|
102
132
|
}
|
|
103
133
|
}
|
|
104
134
|
}
|
package/dist/pro/preflight.js
CHANGED
|
@@ -1,4 +1,7 @@
|
|
|
1
1
|
import { loadModels } from "../config.js";
|
|
2
|
+
import { completionLimit } from "../llm/client.js";
|
|
3
|
+
import { currentTask, TaskControlError } from "../llm/runtime.js";
|
|
4
|
+
import { inputTokenBound, DEFAULT_OUTPUT_TOKENS } from "../llm/context.js";
|
|
2
5
|
import { ACTIVE_PRO_ROLES, loadProModels, } from "./config.js";
|
|
3
6
|
export class ProPreflightError extends Error {
|
|
4
7
|
fallbacks;
|
|
@@ -50,6 +53,13 @@ function resolveSetting(role, preset, catalog) {
|
|
|
50
53
|
/** An authenticated, tiny inference validates the same intensity/tool contract. */
|
|
51
54
|
async function probe(model, options) {
|
|
52
55
|
const controller = new AbortController();
|
|
56
|
+
const runtime = currentTask()?.runtime;
|
|
57
|
+
const outputTokens = runtime ? DEFAULT_OUTPUT_TOKENS : 4096;
|
|
58
|
+
const release = runtime?.activity();
|
|
59
|
+
const rootAbort = () => controller.abort(runtime?.signal.reason);
|
|
60
|
+
runtime?.signal.addEventListener("abort", rootAbort, { once: true });
|
|
61
|
+
if (runtime?.signal.aborted)
|
|
62
|
+
rootAbort();
|
|
53
63
|
const relay = () => controller.abort(options.signal?.reason);
|
|
54
64
|
if (options.signal?.aborted)
|
|
55
65
|
relay();
|
|
@@ -62,16 +72,20 @@ async function probe(model, options) {
|
|
|
62
72
|
name: "pro_availability_check", description: "A harmless availability check; this tool is never executed.",
|
|
63
73
|
};
|
|
64
74
|
const payload = anthropic ? {
|
|
65
|
-
model: model.model.name, stream: false, max_tokens:
|
|
75
|
+
model: model.model.name, stream: false, max_tokens: outputTokens,
|
|
66
76
|
output_config: { effort: model.intensity },
|
|
67
77
|
messages: [{ role: "user", content: "Availability check. Reply only OK. Do not call tools." }],
|
|
68
78
|
tools: [{ ...tool, input_schema: parameters }],
|
|
69
79
|
} : {
|
|
70
80
|
model: model.model.name, stream: false, reasoning_effort: model.intensity,
|
|
81
|
+
...(runtime ? completionLimit(model.model, model.provider, outputTokens) : {}),
|
|
71
82
|
messages: [{ role: "user", content: "Availability check. Reply only OK. Do not call tools." }],
|
|
72
83
|
tools: [{ type: "function", function: { ...tool, parameters } }],
|
|
73
84
|
};
|
|
85
|
+
let reservation;
|
|
86
|
+
let used = inputTokenBound(payload) + outputTokens;
|
|
74
87
|
try {
|
|
88
|
+
reservation = runtime?.request(inputTokenBound(payload), outputTokens);
|
|
75
89
|
const response = await fetch(model.model.url.replace(/\/+$/, "") + (anthropic ? "/messages" : "/chat/completions"), {
|
|
76
90
|
method: "POST", signal: controller.signal,
|
|
77
91
|
headers: {
|
|
@@ -94,16 +108,24 @@ async function probe(model, options) {
|
|
|
94
108
|
if (body.usage) {
|
|
95
109
|
const prompt = body.usage.prompt_tokens ?? body.usage.input_tokens ?? 0;
|
|
96
110
|
const completion = body.usage.completion_tokens ?? body.usage.output_tokens ?? 0;
|
|
111
|
+
used = prompt + completion;
|
|
97
112
|
options.onUsage?.({ prompt, completion, total: prompt + completion });
|
|
98
113
|
}
|
|
99
114
|
return { ok: true, reason: "" };
|
|
100
115
|
}
|
|
101
|
-
catch {
|
|
116
|
+
catch (error) {
|
|
117
|
+
if (error instanceof TaskControlError)
|
|
118
|
+
throw error;
|
|
119
|
+
if (runtime?.signal.aborted)
|
|
120
|
+
throw new Error("Pro availability check was cancelled");
|
|
102
121
|
if (options.signal?.aborted)
|
|
103
122
|
throw new Error("Pro availability check was cancelled");
|
|
104
123
|
return { ok: false, reason: controller.signal.aborted ? "availability request timed out" : "availability request failed" };
|
|
105
124
|
}
|
|
106
125
|
finally {
|
|
126
|
+
reservation?.settle(used);
|
|
127
|
+
release?.();
|
|
128
|
+
runtime?.signal.removeEventListener("abort", rootAbort);
|
|
107
129
|
clearTimeout(timer);
|
|
108
130
|
options.signal?.removeEventListener("abort", relay);
|
|
109
131
|
}
|