@xingjisoft/neutrino 0.6.0 → 0.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +6 -1
- package/assets/fast-planner.txt +57 -0
- package/assets/fast-replanner.txt +28 -0
- package/dist/agents.d.ts +25 -0
- package/dist/app.d.ts +206 -0
- package/dist/app.js +15 -0
- package/dist/background.d.ts +96 -0
- package/dist/bin.d.ts +2 -0
- package/dist/bin.js +40 -3
- package/dist/browser.d.ts +18 -0
- package/dist/clipboard.d.ts +16 -0
- package/dist/colors.d.ts +29 -0
- package/dist/compact.d.ts +52 -0
- package/dist/config.d.ts +221 -0
- package/dist/config.js +22 -7
- package/dist/cwd.d.ts +19 -0
- package/dist/fast/artifacts.d.ts +26 -0
- package/dist/fast/artifacts.js +109 -0
- package/dist/fast/engine.d.ts +78 -0
- package/dist/fast/engine.js +657 -0
- package/dist/fast/evidence.d.ts +21 -0
- package/dist/fast/evidence.js +68 -0
- package/dist/fast/planner.d.ts +10 -0
- package/dist/fast/planner.js +51 -0
- package/dist/fast/pool.d.ts +17 -0
- package/dist/fast/pool.js +78 -0
- package/dist/fast/scheduler.d.ts +54 -0
- package/dist/fast/scheduler.js +383 -0
- package/dist/fast/types.d.ts +100 -0
- package/dist/fast/types.js +21 -0
- package/dist/fast/worker.d.ts +23 -0
- package/dist/fast/worker.js +185 -0
- package/dist/header.d.ts +3 -0
- package/dist/index.d.ts +5 -0
- package/dist/llm/approval.d.ts +204 -0
- package/dist/llm/approval.js +14 -3
- package/dist/llm/client.d.ts +118 -0
- package/dist/llm/client.js +57 -8
- package/dist/llm/codeExecutor.d.ts +14 -0
- package/dist/llm/codemode.d.ts +15 -0
- package/dist/llm/context.d.ts +29 -0
- package/dist/llm/context.js +17 -4
- package/dist/llm/diagnostics.d.ts +91 -0
- package/dist/llm/diagnostics.js +242 -0
- package/dist/llm/plan.d.ts +148 -0
- package/dist/llm/retry.d.ts +80 -0
- package/dist/llm/risk.d.ts +21 -0
- package/dist/llm/risk.js +7 -0
- package/dist/llm/runtime.d.ts +189 -0
- package/dist/llm/runtime.js +178 -24
- package/dist/llm/search.d.ts +2 -0
- package/dist/llm/searchNetwork.d.ts +8 -0
- package/dist/llm/subagent.d.ts +112 -0
- package/dist/llm/subagent.js +25 -12
- package/dist/llm/tools.d.ts +20 -0
- package/dist/llm/tools.js +4 -1
- package/dist/llm/workspace.d.ts +6 -0
- package/dist/llm/workspace.js +271 -0
- package/dist/markdown.d.ts +58 -0
- package/dist/migration.d.ts +8 -0
- package/dist/persistence.d.ts +14 -0
- package/dist/plugins/catalog.d.ts +20 -0
- package/dist/plugins/catalog.js +45 -0
- package/dist/plugins/cli.d.ts +1 -0
- package/dist/plugins/cli.js +106 -0
- package/dist/plugins/host.d.ts +121 -0
- package/dist/plugins/host.js +450 -0
- package/dist/plugins/manager.d.ts +24 -0
- package/dist/plugins/manager.js +288 -0
- package/dist/plugins/package.d.ts +5 -0
- package/dist/plugins/package.js +53 -0
- package/dist/plugins/runner.d.ts +1 -0
- package/dist/plugins/runner.js +227 -0
- package/dist/plugins/session.d.ts +12 -0
- package/dist/plugins/session.js +97 -0
- package/dist/plugins/terminal.d.ts +22 -0
- package/dist/plugins/terminal.js +33 -0
- package/dist/plugins/types.d.ts +178 -0
- package/dist/plugins/types.js +6 -0
- package/dist/plugins/validation.d.ts +14 -0
- package/dist/plugins/validation.js +174 -0
- package/dist/plugins/web-sdk.d.ts +58 -0
- package/dist/plugins/web-sdk.js +1 -0
- package/dist/pro/agents.d.ts +34 -0
- package/dist/pro/agents.js +1 -1
- package/dist/pro/config.d.ts +75 -0
- package/dist/pro/config.js +3 -2
- package/dist/pro/preflight.d.ts +21 -0
- package/dist/sanitize.d.ts +34 -0
- package/dist/screens/agentConversation.d.ts +28 -0
- package/dist/screens/agentHistory.d.ts +25 -0
- package/dist/screens/agentSelect.d.ts +16 -0
- package/dist/screens/chat.d.ts +292 -0
- package/dist/screens/chat.js +179 -65
- package/dist/screens/history.d.ts +23 -0
- package/dist/screens/modelSelect.d.ts +29 -0
- package/dist/screens/plugins.d.ts +29 -0
- package/dist/screens/plugins.js +196 -0
- package/dist/screens/settings.d.ts +42 -0
- package/dist/screens/start.d.ts +43 -0
- package/dist/screens/start.js +25 -17
- package/dist/screens/updatePrompt.d.ts +41 -0
- package/dist/selection.d.ts +75 -0
- package/dist/state.d.ts +26 -0
- package/dist/terminal/buffer.d.ts +47 -0
- package/dist/terminal/input.d.ts +70 -0
- package/dist/terminal/renderer.d.ts +43 -0
- package/dist/terminal/restore.d.ts +41 -0
- package/dist/terminal/screen.d.ts +57 -0
- package/dist/tray.d.ts +14 -0
- package/dist/ui.d.ts +123 -0
- package/dist/update.d.ts +71 -0
- package/dist/updateWatch.d.ts +43 -0
- package/dist/web/approvalQueue.d.ts +8 -0
- package/dist/web/approvalQueue.js +59 -0
- package/dist/web/auth.d.ts +14 -0
- package/dist/web/clientState.d.ts +16 -0
- package/dist/web/folderPicker.d.ts +34 -0
- package/dist/web/folderPicker.js +544 -0
- package/dist/web/hub.d.ts +213 -0
- package/dist/web/hub.js +14 -2
- package/dist/web/localApi.d.ts +11 -0
- package/dist/web/localApi.js +260 -0
- package/dist/web/plugins.d.ts +8 -0
- package/dist/web/plugins.js +64 -0
- package/dist/web/server.d.ts +81 -0
- package/dist/web/server.js +127 -19
- package/dist/web/service.d.ts +7 -0
- package/dist/web/service.js +1 -0
- package/dist/web/session.d.ts +176 -0
- package/dist/web/session.js +170 -21
- package/dist/web/settings.d.ts +65 -0
- package/dist/web/terminalBridge.d.ts +15 -0
- package/dist/web/terminalBridge.js +8 -2
- package/dist/width.d.ts +6 -0
- package/docs/plugins/README.md +126 -0
- package/docs/plugins/agent-sdk.md +92 -0
- package/docs/plugins/architecture.md +64 -0
- package/docs/plugins/compatibility.md +34 -0
- package/docs/plugins/development.md +55 -0
- package/docs/plugins/manifest.md +59 -0
- package/docs/plugins/manifest.schema.json +286 -0
- package/docs/plugins/security.md +53 -0
- package/docs/plugins/skills-prompts.md +69 -0
- package/docs/plugins/ui-sdk.md +109 -0
- package/examples/local-api.py +105 -0
- package/examples/plugins/cards/card.html +6 -0
- package/examples/plugins/cards/neutrino.plugin.json +42 -0
- package/examples/plugins/composer/editor.html +10 -0
- package/examples/plugins/composer/neutrino.plugin.json +10 -0
- package/examples/plugins/function-tools/agent.js +32 -0
- package/examples/plugins/function-tools/neutrino.plugin.json +15 -0
- package/examples/plugins/menus-tabs/neutrino.plugin.json +15 -0
- package/examples/plugins/menus-tabs/panel.html +6 -0
- package/examples/plugins/skills-prompts/neutrino.plugin.json +13 -0
- package/examples/plugins/skills-prompts/prompts/evidence.md +1 -0
- package/examples/plugins/skills-prompts/prompts/reviewer.md +1 -0
- package/examples/plugins/skills-prompts/skills/review/SKILL.md +8 -0
- package/examples/plugins/theme/neutrino.plugin.json +12 -0
- package/package.json +66 -42
- package/web/LOCAL-API.md +65 -0
- package/web/README.md +30 -8
- package/web/app.js +256 -67
- package/web/i18n.js +18 -3
- package/web/index.html +3 -1
- package/web/plugins.css +10 -0
- package/web/plugins.js +230 -0
- package/web/style.css +12 -3
- package/web/turns.js +7 -8
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
import { type TaskRuntime } from "./runtime.js";
|
|
2
|
+
import type { ToolDefinition } from "./tools.js";
|
|
3
|
+
/** `active` is the step the worker is on; the rest are its past and future. */
|
|
4
|
+
export type PlanStepStatus = "pending" | "active" | "done" | "failed" | "blocked" | "cancelled" | "skipped";
|
|
5
|
+
export interface PlanStep {
|
|
6
|
+
id?: string;
|
|
7
|
+
dependsOn?: string[];
|
|
8
|
+
acceptanceCriteria?: string[];
|
|
9
|
+
evidenceIds?: string[];
|
|
10
|
+
artifacts?: string[];
|
|
11
|
+
summary?: string;
|
|
12
|
+
deliverable?: string;
|
|
13
|
+
/** One imperative action, in the language of the request. */
|
|
14
|
+
title: string;
|
|
15
|
+
/** What the step needs: files, parameters, acceptance criteria. */
|
|
16
|
+
detail?: string;
|
|
17
|
+
status: PlanStepStatus;
|
|
18
|
+
}
|
|
19
|
+
/** The plan of one user turn, persisted with the session. */
|
|
20
|
+
export interface PlanState {
|
|
21
|
+
/** The request restated in one line. */
|
|
22
|
+
goal: string;
|
|
23
|
+
steps: PlanStep[];
|
|
24
|
+
/** The turn stopped before the last step: paused, cancelled, or interrupted. */
|
|
25
|
+
stopped?: boolean;
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* One step is not a plan, it is the answer: such a turn runs as a plain turn.
|
|
29
|
+
* The ceiling keeps the card readable and the turn's cost proportionate.
|
|
30
|
+
*/
|
|
31
|
+
export declare const PLAN_MIN_STEPS = 2;
|
|
32
|
+
export declare const PLAN_MAX_STEPS = 10;
|
|
33
|
+
export declare const PLAN_MAX_TITLE = 160;
|
|
34
|
+
export declare const PLAN_MAX_DETAIL = 400;
|
|
35
|
+
/**
|
|
36
|
+
* A request longer than this many columns is treated as a brief and planned.
|
|
37
|
+
* In practice that is roughly eighty Chinese characters or one hundred and
|
|
38
|
+
* eighty ASCII ones — past that, "one action" is already unlikely.
|
|
39
|
+
*/
|
|
40
|
+
export declare const PLAN_LENGTH_LIMIT = 180;
|
|
41
|
+
/**
|
|
42
|
+
* Does this request need a plan at all?
|
|
43
|
+
*
|
|
44
|
+
* Planning costs a model round-trip before any work starts, and for "rename
|
|
45
|
+
* this variable" that round-trip is pure friction. A request is planned only
|
|
46
|
+
* when it shows a reason to be split into steps: it asks for more than one
|
|
47
|
+
* thing, brings its own structure (a list), reads as a brief (long), or asks
|
|
48
|
+
* for a plan. Everything else runs in one pass, where the system prompt still
|
|
49
|
+
* has the agent state the steps it is taking in its own answer.
|
|
50
|
+
*/
|
|
51
|
+
export declare function needsPlan(request: string): boolean;
|
|
52
|
+
/** Turn an already-parsed planner answer into a plan, or null when unusable. */
|
|
53
|
+
export declare function normalizePlan(value: unknown, minSteps?: number): PlanState | null;
|
|
54
|
+
/**
|
|
55
|
+
* Read a planner answer. JSON first, because that is what the prompt asks for,
|
|
56
|
+
* then a numbered list so a model that ignores the format still produces a
|
|
57
|
+
* usable plan. Anything else is "no plan": the turn then runs as before.
|
|
58
|
+
*/
|
|
59
|
+
export declare function parsePlan(text: string, minSteps?: number): PlanState | null;
|
|
60
|
+
export interface PlannerInput {
|
|
61
|
+
/** The request being planned. */
|
|
62
|
+
request: string;
|
|
63
|
+
/** The conversation before that request, oldest first. */
|
|
64
|
+
transcript?: string;
|
|
65
|
+
/** Directory the work happens in. */
|
|
66
|
+
cwd?: string;
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* The user turn handed to the planner agent. The planner gets no tools, so
|
|
70
|
+
* everything it needs to make the steps concrete is in this text: where the
|
|
71
|
+
* work happens, what was said before, and what is being asked now.
|
|
72
|
+
*/
|
|
73
|
+
export declare function buildPlannerInput(input: PlannerInput): string;
|
|
74
|
+
/** The plan as numbered text: what the worker and the reviewers read. */
|
|
75
|
+
export declare function planSpec(plan: PlanState): string;
|
|
76
|
+
/**
|
|
77
|
+
* The plan as the user reads it in the transcript. It is an ordinary assistant
|
|
78
|
+
* message on purpose: it is saved, replayed, folded and sent back to the model
|
|
79
|
+
* exactly like any other answer.
|
|
80
|
+
*
|
|
81
|
+
* A detail stays on its step's line: markdown renderers fold an indented line
|
|
82
|
+
* into the list item before it, which would run two steps together in the log.
|
|
83
|
+
*/
|
|
84
|
+
export declare function planMessage(plan: PlanState): string;
|
|
85
|
+
/**
|
|
86
|
+
* The system text for one step. It repeats the whole plan so the worker knows
|
|
87
|
+
* where the step sits, and states the two rules the split exists for: do only
|
|
88
|
+
* this step, and stop when it is verified.
|
|
89
|
+
*/
|
|
90
|
+
export declare function renderStepDirective(template: string, plan: PlanState, index: number): string;
|
|
91
|
+
/** How far a plan got. Used by the card header and by tests. */
|
|
92
|
+
export declare function planProgress(plan: PlanState): {
|
|
93
|
+
done: number;
|
|
94
|
+
total: number;
|
|
95
|
+
active: number;
|
|
96
|
+
};
|
|
97
|
+
/** A plan loaded from disk was, by definition, no longer running. */
|
|
98
|
+
export declare function resumePlan(loaded: PlanState | null | undefined): PlanState | null;
|
|
99
|
+
/** Point the plan at one step. Returns false when there was nothing to change. */
|
|
100
|
+
export declare function advancePlan(plan: PlanState | null, index: number, status: PlanStepStatus): boolean;
|
|
101
|
+
/** A turn that cannot continue must not leave a step looking alive. */
|
|
102
|
+
export declare function stopPlan(plan: PlanState | null): boolean;
|
|
103
|
+
/** The message shape both loops share: enough to tell the planner what happened. */
|
|
104
|
+
export interface TranscriptMessage {
|
|
105
|
+
role: string;
|
|
106
|
+
content?: string;
|
|
107
|
+
name?: string;
|
|
108
|
+
result?: string;
|
|
109
|
+
}
|
|
110
|
+
/** The user message a turn answers. */
|
|
111
|
+
export declare function lastRequestText(messages: readonly TranscriptMessage[]): string;
|
|
112
|
+
/**
|
|
113
|
+
* The exchanges before the current request, for the planner.
|
|
114
|
+
*
|
|
115
|
+
* Tool output is clipped hard: the planner needs to know what was done, not to
|
|
116
|
+
* re-read it, and a long log must not crowd the request out of its own plan.
|
|
117
|
+
*/
|
|
118
|
+
export declare function planTranscript(messages: readonly TranscriptMessage[]): string;
|
|
119
|
+
/**
|
|
120
|
+
* One row of the info card. The kind carries the meaning (and therefore the
|
|
121
|
+
* colour); the text is what goes on screen.
|
|
122
|
+
*/
|
|
123
|
+
export type PlanRowKind = "header" | "step-active" | "step-done" | "step-pending" | "step-failed" | "step-blocked" | "step-cancelled" | "step-skipped" | "connector" | "more" | "line";
|
|
124
|
+
export interface PlanRow {
|
|
125
|
+
kind: PlanRowKind;
|
|
126
|
+
text: string;
|
|
127
|
+
}
|
|
128
|
+
/**
|
|
129
|
+
* Lay the plan out as a vertical timeline, top to bottom in execution order.
|
|
130
|
+
*
|
|
131
|
+
* A finished step is drawn dark, a step still to come is drawn lit, and the step
|
|
132
|
+
* being worked on is the one highlighted row — so the card reads as "these are
|
|
133
|
+
* done, this is running, these are next" without a legend. When the plan is
|
|
134
|
+
* longer than the room, a window around the running step is shown with the
|
|
135
|
+
* number of steps above and below it, rather than dropping the middle.
|
|
136
|
+
*/
|
|
137
|
+
export declare function planCardRows(plan: PlanState, options: {
|
|
138
|
+
width: number;
|
|
139
|
+
maxRows: number;
|
|
140
|
+
}): PlanRow[];
|
|
141
|
+
/** The width a card row actually needs, in columns. */
|
|
142
|
+
export declare function planRowsWidth(rows: PlanRow[]): number;
|
|
143
|
+
/** Pro plans use explicit step acceptance instead of inferring success from prose. */
|
|
144
|
+
export declare const FINISH_STEP_TOOL = "finish_step";
|
|
145
|
+
export declare const FINISH_STEP_DEFINITION: ToolDefinition;
|
|
146
|
+
export declare function preparePlan(plan: PlanState): PlanState;
|
|
147
|
+
export declare function finishPlanStep(plan: PlanState | null, args: Record<string, unknown>, runtime: TaskRuntime): string;
|
|
148
|
+
export declare function settlePlan(plan: PlanState | null, cancelled?: boolean): void;
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Retry policy for the model connection.
|
|
3
|
+
*
|
|
4
|
+
* A single turn is minutes of work, and the connection carries it the whole way.
|
|
5
|
+
* Providers, and the proxies and load balancers in front of them, drop sockets
|
|
6
|
+
* for reasons a client can never observe: an idle timeout, a rolling restart, a
|
|
7
|
+
* middlebox resetting the flow. Those blips clear on their own within seconds,
|
|
8
|
+
* so a failed request that is simply re-sent saves the turn — while surfacing
|
|
9
|
+
* the error throws away everything the user typed and everything the model had
|
|
10
|
+
* already produced.
|
|
11
|
+
*
|
|
12
|
+
* The policy is exponential: seven attempts in total, waiting 5s, 10s, 20s,
|
|
13
|
+
* 40s, 80s and finally 160s between them (about five minutes end to end). The
|
|
14
|
+
* waits grow because a link that is merely flaky recovers long before the
|
|
15
|
+
* budget runs out, and a link that is genuinely down must not be hammered.
|
|
16
|
+
*/
|
|
17
|
+
/** Total tries, the first one included. */
|
|
18
|
+
export declare const DEFAULT_NETWORK_ATTEMPTS = 7;
|
|
19
|
+
/** Wait before the first retry; every further retry doubles it. */
|
|
20
|
+
export declare const DEFAULT_RETRY_DELAY_MS = 5000;
|
|
21
|
+
export interface RetryPolicy {
|
|
22
|
+
/** Total tries, including the first one. */
|
|
23
|
+
attempts: number;
|
|
24
|
+
/** Wait before the first retry; doubled for each retry after that. */
|
|
25
|
+
baseDelayMs: number;
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* The policy in force.
|
|
29
|
+
*
|
|
30
|
+
* Read on every use rather than cached at import time, so the environment can
|
|
31
|
+
* tune it (`NEUTRINO_NETWORK_ATTEMPTS`, `NEUTRINO_RETRY_DELAY_MS`) without a
|
|
32
|
+
* rebuild and the tests can collapse the waits to a millisecond.
|
|
33
|
+
*/
|
|
34
|
+
export declare function retryPolicy(): RetryPolicy;
|
|
35
|
+
/** Wait before retry number `retry` (0-based): base, 2×base, 4×base, … */
|
|
36
|
+
export declare function retryDelayMs(retry: number, policy?: RetryPolicy): number;
|
|
37
|
+
/** What the UI is told before a retried request goes out again. */
|
|
38
|
+
export interface RetryNotice {
|
|
39
|
+
/** The attempt about to go out (2 = the first retry), 1-based. */
|
|
40
|
+
attempt: number;
|
|
41
|
+
/** Total attempts allowed. */
|
|
42
|
+
attempts: number;
|
|
43
|
+
/** How long this attempt waits before it is sent. */
|
|
44
|
+
delayMs: number;
|
|
45
|
+
/** What went wrong, in the same wording the transcript uses. */
|
|
46
|
+
reason: string;
|
|
47
|
+
}
|
|
48
|
+
/** A wait in human terms: "5s", "40s", "2m 40s". */
|
|
49
|
+
export declare function formatDelay(ms: number): string;
|
|
50
|
+
export declare const DROP_HINT_RE: RegExp;
|
|
51
|
+
/**
|
|
52
|
+
* Flatten an error and its `cause` chain into one readable line.
|
|
53
|
+
*
|
|
54
|
+
* `fetch` reports almost every transport problem as a bare
|
|
55
|
+
* `TypeError: fetch failed`; the actual reason (`ECONNRESET`, `EMFILE`,
|
|
56
|
+
* `ENOTFOUND`, `UND_ERR_…`) is tucked away in `error.cause`. Without walking
|
|
57
|
+
* that chain a failure is impossible to diagnose, so this always reports it.
|
|
58
|
+
*/
|
|
59
|
+
export declare function describeError(e: unknown): string;
|
|
60
|
+
/**
|
|
61
|
+
* Whether a failed request is worth retrying: a transient connection problem
|
|
62
|
+
* (reset / refused / DNS / timeout) rather than a real response from the API.
|
|
63
|
+
*/
|
|
64
|
+
export declare function isRetriableNetworkError(e: unknown): boolean;
|
|
65
|
+
export declare function isRetriableStatus(status: number): boolean;
|
|
66
|
+
/** A response that did arrive, but carried a failure status. */
|
|
67
|
+
export declare class HttpStatusError extends Error {
|
|
68
|
+
readonly status: number;
|
|
69
|
+
constructor(status: number, body: string);
|
|
70
|
+
}
|
|
71
|
+
/** Whether a failure is transient, including HTTP statuses that invite a retry. */
|
|
72
|
+
export declare function isRetriableFailure(e: unknown): boolean;
|
|
73
|
+
/**
|
|
74
|
+
* Sleep before retry number `retry`, in short slices.
|
|
75
|
+
*
|
|
76
|
+
* The wait can reach 160s, so it must not be a single `setTimeout`: a paused
|
|
77
|
+
* turn or a newer message has to be able to cut it short. Returns false when
|
|
78
|
+
* `cancelled` fired, which tells the caller to stop without an error.
|
|
79
|
+
*/
|
|
80
|
+
export declare function waitBeforeRetry(retry: number, policy: RetryPolicy, cancelled?: () => boolean): Promise<boolean>;
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
export type RiskLevel = "high" | "extreme";
|
|
2
|
+
export interface RiskAssessment {
|
|
3
|
+
level: RiskLevel;
|
|
4
|
+
/** Tool that was called. */
|
|
5
|
+
tool: string;
|
|
6
|
+
/** One-line description of what is about to happen. */
|
|
7
|
+
summary: string;
|
|
8
|
+
/** Why this was flagged. Shown to the user (and to the AI reviewer). */
|
|
9
|
+
reasons: string[];
|
|
10
|
+
}
|
|
11
|
+
/** Score a shell command. */
|
|
12
|
+
export declare function assessCommand(command: string, cwd: string): {
|
|
13
|
+
level: RiskLevel;
|
|
14
|
+
reasons: string[];
|
|
15
|
+
};
|
|
16
|
+
/**
|
|
17
|
+
* Classify a single tool call.
|
|
18
|
+
*
|
|
19
|
+
* Returns `null` when the call is routine and may run without approval.
|
|
20
|
+
*/
|
|
21
|
+
export declare function assessToolCall(name: string, args: Record<string, unknown>, cwd: string): RiskAssessment | null;
|
package/dist/llm/risk.js
CHANGED
|
@@ -14,6 +14,7 @@
|
|
|
14
14
|
*/
|
|
15
15
|
import os from "node:os";
|
|
16
16
|
import path from "node:path";
|
|
17
|
+
import { currentTask } from "./runtime.js";
|
|
17
18
|
/** Wrappers that merely delegate to the command that follows them. */
|
|
18
19
|
const WRAPPERS = new Set([
|
|
19
20
|
"sudo",
|
|
@@ -355,6 +356,12 @@ function summarizeArgs(name, args) {
|
|
|
355
356
|
export function assessToolCall(name, args, cwd) {
|
|
356
357
|
const workingDir = path.resolve(cwd || process.cwd());
|
|
357
358
|
const summary = summarizeArgs(name, args);
|
|
359
|
+
const plugin = currentTask()?.runtime.plugins?.toolPolicy(name);
|
|
360
|
+
if (plugin && (plugin.native || plugin.effects.some(e => e !== "filesystem.read"))) {
|
|
361
|
+
return { level: "high", tool: name, summary, reasons: [plugin.native ? "runs a fully trusted native plugin" : `plugin declares: ${plugin.effects.join(", ")}`] };
|
|
362
|
+
}
|
|
363
|
+
if (name === "plugin_fetch")
|
|
364
|
+
return { level: "high", tool: name, summary: String(args.url ?? name), reasons: ["sends an HTTP request from a plugin"] };
|
|
358
365
|
if (name === "web_search") {
|
|
359
366
|
return {
|
|
360
367
|
level: "high", tool: name, summary,
|
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
import { DiagnosticRecorder } from "./diagnostics.js";
|
|
2
|
+
import type { PlanState } from "./plan.js";
|
|
3
|
+
import type { AgentMode } from "../config.js";
|
|
4
|
+
import type { PluginHost } from "../plugins/host.js";
|
|
5
|
+
import type { PluginPin } from "../plugins/types.js";
|
|
6
|
+
import type { OwnedUI } from "../plugins/host.js";
|
|
7
|
+
export type AgentStatus = "completed" | "partial" | "blocked" | "failed" | "cancelled";
|
|
8
|
+
export interface AgentError {
|
|
9
|
+
code: string;
|
|
10
|
+
message: string;
|
|
11
|
+
}
|
|
12
|
+
export interface AgentResult {
|
|
13
|
+
runId: string;
|
|
14
|
+
status: AgentStatus;
|
|
15
|
+
report: string;
|
|
16
|
+
evidenceIds: string[];
|
|
17
|
+
artifacts: string[];
|
|
18
|
+
blockers: string[];
|
|
19
|
+
error?: AgentError;
|
|
20
|
+
}
|
|
21
|
+
export interface TaskLimits {
|
|
22
|
+
agentRuns: number;
|
|
23
|
+
modelRequests: number;
|
|
24
|
+
toolCalls: number;
|
|
25
|
+
tokens: number;
|
|
26
|
+
durationMs: number;
|
|
27
|
+
}
|
|
28
|
+
export type TaskBudgetScope = "task" | "delegation";
|
|
29
|
+
export declare function taskBudgetScope(config: Record<string, unknown>): TaskBudgetScope;
|
|
30
|
+
export interface TaskBudgetStop {
|
|
31
|
+
code: string;
|
|
32
|
+
scope: TaskBudgetScope;
|
|
33
|
+
limit: number;
|
|
34
|
+
used: number;
|
|
35
|
+
requested: number;
|
|
36
|
+
reserved?: number;
|
|
37
|
+
runId: string;
|
|
38
|
+
}
|
|
39
|
+
export declare const DEFAULT_TASK_LIMITS: Readonly<TaskLimits>;
|
|
40
|
+
export declare function taskLimits(config: Record<string, unknown>): TaskLimits;
|
|
41
|
+
export interface TaskPolicy {
|
|
42
|
+
readonly readOnly: boolean;
|
|
43
|
+
/** Each group is an additional restriction; a write must match every group. */
|
|
44
|
+
readonly writePathGroups: readonly (readonly string[])[];
|
|
45
|
+
}
|
|
46
|
+
export interface TaskScope {
|
|
47
|
+
readonly signal: AbortSignal;
|
|
48
|
+
readonly runtime: TaskRuntime;
|
|
49
|
+
readonly runId: string;
|
|
50
|
+
readonly parentRunId?: string;
|
|
51
|
+
readonly role: string;
|
|
52
|
+
readonly depth: number;
|
|
53
|
+
readonly policy: TaskPolicy;
|
|
54
|
+
readonly ancestors: readonly string[];
|
|
55
|
+
}
|
|
56
|
+
export interface ToolEvidence {
|
|
57
|
+
id: string;
|
|
58
|
+
runId: string;
|
|
59
|
+
name: string;
|
|
60
|
+
arguments: Record<string, unknown>;
|
|
61
|
+
result: string;
|
|
62
|
+
status: "completed" | "failed" | "denied" | "cancelled";
|
|
63
|
+
}
|
|
64
|
+
export interface TaskSnapshot {
|
|
65
|
+
execution_mode?: AgentMode | "pro";
|
|
66
|
+
plugins?: PluginPin[];
|
|
67
|
+
pluginErrors?: string[];
|
|
68
|
+
pluginUI?: OwnedUI[];
|
|
69
|
+
pluginPrompt?: {
|
|
70
|
+
role: string;
|
|
71
|
+
sections: unknown;
|
|
72
|
+
};
|
|
73
|
+
rootRunId: string;
|
|
74
|
+
state: "running" | "cancelling" | AgentStatus;
|
|
75
|
+
counts: {
|
|
76
|
+
agentRuns: number;
|
|
77
|
+
modelRequests: number;
|
|
78
|
+
toolCalls: number;
|
|
79
|
+
tokens: number;
|
|
80
|
+
};
|
|
81
|
+
limits: TaskLimits;
|
|
82
|
+
budgetScope?: TaskBudgetScope;
|
|
83
|
+
delegatedCounts?: {
|
|
84
|
+
modelRequests: number;
|
|
85
|
+
toolCalls: number;
|
|
86
|
+
tokens: number;
|
|
87
|
+
};
|
|
88
|
+
budgetStop?: TaskBudgetStop;
|
|
89
|
+
journal?: string;
|
|
90
|
+
persistenceError?: string;
|
|
91
|
+
}
|
|
92
|
+
/** A saved task has no live worker after restart. Never replay its tool ledger. */
|
|
93
|
+
export declare function resumeTaskSnapshot(snapshot: TaskSnapshot | undefined): TaskSnapshot | undefined;
|
|
94
|
+
export declare class TaskControlError extends Error {
|
|
95
|
+
readonly code: string;
|
|
96
|
+
constructor(code: string, message: string);
|
|
97
|
+
}
|
|
98
|
+
export declare function currentTask(): TaskScope | undefined;
|
|
99
|
+
/** Node 18-compatible signal composition with deterministic listener cleanup. */
|
|
100
|
+
export declare function linkSignals(...values: Array<AbortSignal | undefined>): {
|
|
101
|
+
signal: AbortSignal;
|
|
102
|
+
dispose(): void;
|
|
103
|
+
};
|
|
104
|
+
/** One user turn owns one ledger and cancellation tree, with optional root limits. */
|
|
105
|
+
export declare class TaskRuntime {
|
|
106
|
+
private readonly executionMode?;
|
|
107
|
+
plugins?: PluginHost;
|
|
108
|
+
private pluginPrompt?;
|
|
109
|
+
readonly rootRunId: `${string}-${string}-${string}-${string}-${string}`;
|
|
110
|
+
readonly diagnostics?: DiagnosticRecorder;
|
|
111
|
+
readonly signal: AbortSignal;
|
|
112
|
+
readonly originalRequest: string;
|
|
113
|
+
readonly priorUserRequests: readonly string[];
|
|
114
|
+
readonly cwd: string;
|
|
115
|
+
readonly workspaceOnly: boolean;
|
|
116
|
+
readonly limits: Readonly<TaskLimits>;
|
|
117
|
+
readonly budgetScope: TaskBudgetScope;
|
|
118
|
+
readonly root: TaskScope;
|
|
119
|
+
readonly evidence: ToolEvidence[];
|
|
120
|
+
readonly results: AgentResult[];
|
|
121
|
+
plan?: PlanState;
|
|
122
|
+
private readonly controller;
|
|
123
|
+
private readonly deadline?;
|
|
124
|
+
private readonly delegationController;
|
|
125
|
+
private delegationDeadline?;
|
|
126
|
+
private delegationStarted?;
|
|
127
|
+
private readonly started;
|
|
128
|
+
private readonly pending;
|
|
129
|
+
private readonly usage;
|
|
130
|
+
private readonly delegatedUsage;
|
|
131
|
+
private reservedTokens;
|
|
132
|
+
private delegatedReservedTokens;
|
|
133
|
+
private budgetStop?;
|
|
134
|
+
private state;
|
|
135
|
+
private persistenceError?;
|
|
136
|
+
private readonly journal?;
|
|
137
|
+
private journalStarted;
|
|
138
|
+
private journalDiscarded;
|
|
139
|
+
private readonly detachSignal?;
|
|
140
|
+
constructor(options: {
|
|
141
|
+
request: string;
|
|
142
|
+
cwd?: string;
|
|
143
|
+
priorUserRequests?: readonly string[];
|
|
144
|
+
limits?: Partial<TaskLimits>;
|
|
145
|
+
budgetScope?: TaskBudgetScope;
|
|
146
|
+
executionMode?: AgentMode | "pro";
|
|
147
|
+
signal?: AbortSignal;
|
|
148
|
+
readOnly?: boolean;
|
|
149
|
+
writePaths?: readonly string[];
|
|
150
|
+
workspaceOnly?: boolean;
|
|
151
|
+
journalDir?: string;
|
|
152
|
+
diagnostics?: DiagnosticRecorder;
|
|
153
|
+
});
|
|
154
|
+
run<T>(callback: () => T): T;
|
|
155
|
+
withSignal<T>(signal: AbortSignal, callback: () => Promise<T>): Promise<T>;
|
|
156
|
+
child<T>(role: string, task: string, callback: (scope: TaskScope) => Promise<T>, options?: {
|
|
157
|
+
readOnly?: boolean;
|
|
158
|
+
writePaths?: readonly string[];
|
|
159
|
+
}): Promise<T>;
|
|
160
|
+
check(): void;
|
|
161
|
+
cancel(reason?: string): void;
|
|
162
|
+
track<T>(operation: Promise<T>): Promise<T>;
|
|
163
|
+
activity(): () => void;
|
|
164
|
+
get settling(): boolean;
|
|
165
|
+
drain(): Promise<void>;
|
|
166
|
+
finish(status: AgentStatus): void;
|
|
167
|
+
snapshot(): TaskSnapshot;
|
|
168
|
+
instructions(scope?: TaskScope): string;
|
|
169
|
+
assertTool(name: string, args: Record<string, unknown>, scope?: TaskScope): void;
|
|
170
|
+
availableTools<T extends {
|
|
171
|
+
function: {
|
|
172
|
+
name: string;
|
|
173
|
+
};
|
|
174
|
+
}>(tools: T[], scope?: TaskScope): T[];
|
|
175
|
+
tool(name: string, args: Record<string, unknown>, execute: () => Promise<string>): Promise<string>;
|
|
176
|
+
recordTool(name: string, args: Record<string, unknown>, result: string, status: ToolEvidence["status"], id?: `${string}-${string}-${string}-${string}-${string}`, runId?: string): ToolEvidence;
|
|
177
|
+
recordResult(result: AgentResult): void;
|
|
178
|
+
evidenceFor(runId: string): ToolEvidence[];
|
|
179
|
+
partialReport(): string;
|
|
180
|
+
/** Reserve before every actual HTTP attempt, including retries, reviews and summaries. */
|
|
181
|
+
request(inputTokens: number, outputTokens: number, final?: boolean): {
|
|
182
|
+
settle(used: number): void;
|
|
183
|
+
};
|
|
184
|
+
private isDelegated;
|
|
185
|
+
private expireTask;
|
|
186
|
+
private stopBudget;
|
|
187
|
+
note(type: string, data: unknown): void;
|
|
188
|
+
private event;
|
|
189
|
+
}
|