@juspay/neurolink 12.0.5 → 12.2.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/CHANGELOG.md +3 -3
- package/dist/agent/agentToolRegistrar.d.ts +30 -0
- package/dist/agent/agentToolRegistrar.js +72 -18
- package/dist/agent/backgroundCommands.d.ts +110 -0
- package/dist/agent/backgroundCommands.js +914 -0
- package/dist/agent/backgroundDelegation.d.ts +87 -0
- package/dist/agent/backgroundDelegation.js +753 -0
- package/dist/agent/gitTools.d.ts +43 -0
- package/dist/agent/gitTools.js +618 -0
- package/dist/agent/taskChecklist.d.ts +58 -0
- package/dist/agent/taskChecklist.js +322 -0
- package/dist/artifacts/artifactBanking.d.ts +57 -0
- package/dist/artifacts/artifactBanking.js +123 -0
- package/dist/artifacts/artifactStore.d.ts +36 -8
- package/dist/artifacts/artifactStore.js +164 -13
- package/dist/browser/neurolink.min.js +442 -414
- package/dist/cli/commands/setup.js +2 -1
- package/dist/constants/enums.d.ts +19 -0
- package/dist/constants/enums.js +20 -0
- package/dist/factories/providerDescriptors.js +16 -1
- package/dist/models/manifestRegistry.js +2 -0
- package/dist/models/manifests/cerebras.d.ts +9 -0
- package/dist/models/manifests/cerebras.js +19 -0
- package/dist/neurolink.d.ts +294 -3
- package/dist/neurolink.js +447 -4
- package/dist/providers/openaiCompatCatalog.d.ts +1 -1
- package/dist/providers/openaiCompatCatalog.js +34 -3
- package/dist/types/artifact.d.ts +54 -0
- package/dist/types/backgroundCommand.d.ts +174 -0
- package/dist/types/backgroundCommand.js +22 -0
- package/dist/types/delegation.d.ts +178 -0
- package/dist/types/delegation.js +18 -0
- package/dist/types/gitTools.d.ts +69 -0
- package/dist/types/gitTools.js +22 -0
- package/dist/types/index.d.ts +5 -0
- package/dist/types/index.js +8 -0
- package/dist/types/pathSandbox.d.ts +23 -0
- package/dist/types/pathSandbox.js +12 -0
- package/dist/types/providers.d.ts +4 -0
- package/dist/types/tasks.d.ts +85 -0
- package/dist/types/tasks.js +14 -0
- package/dist/types/tools.d.ts +11 -0
- package/dist/utils/modelChoices.js +17 -1
- package/dist/utils/pathSandbox.d.ts +49 -0
- package/dist/utils/pathSandbox.js +127 -0
- package/dist/utils/providerConfig.d.ts +4 -0
- package/dist/utils/providerConfig.js +17 -0
- package/package.json +5 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
## [12.0
|
|
1
|
+
## [12.2.0](https://github.com/juspay/neurolink/compare/v12.1.0...v12.2.0) (2026-08-27)
|
|
2
2
|
|
|
3
|
-
###
|
|
3
|
+
### Features
|
|
4
4
|
|
|
5
|
-
- **(
|
|
5
|
+
- **(agent):** task checklist, async delegation, artifact banking, background commands ([444b2ab](https://github.com/juspay/neurolink/commit/444b2ab6a76cc55b2e3d36223502151b3ad1fee2))
|
|
6
6
|
|
|
7
7
|
## [11.2.3](https://github.com/juspay/neurolink/compare/v11.2.2...v11.2.3) (2026-08-19)
|
|
8
8
|
|
|
@@ -14,6 +14,8 @@
|
|
|
14
14
|
*/
|
|
15
15
|
import type { NeuroLink } from "../neurolink.js";
|
|
16
16
|
import type { AgentToolRegistrationOptions, GenerateOptions, IsolatedAgentDefinition } from "../types/index.js";
|
|
17
|
+
/** Bound on the content text returned into the host loop per delegation. */
|
|
18
|
+
export declare const DELEGATION_RESULT_CONTENT_CHARS = 4000;
|
|
17
19
|
/**
|
|
18
20
|
* Enter a per-turn delegation scope for a top-level generate(). Returns null
|
|
19
21
|
* when a scope is already active (nested/internal generates share the
|
|
@@ -31,6 +33,34 @@ export declare function beginDelegationTurn(host: NeuroLink, options: GenerateOp
|
|
|
31
33
|
* checks) — request-scoped via AsyncLocalStorage, never instance state.
|
|
32
34
|
*/
|
|
33
35
|
export declare function runWithNestedDelegationDepth<T>(fn: () => Promise<T>): Promise<T>;
|
|
36
|
+
/**
|
|
37
|
+
* The delegation depth a tool call is being made AT — execution context
|
|
38
|
+
* first (a worker running the host's registered closure arrives with its own
|
|
39
|
+
* `agentDepth`), then the per-turn ALS scope, then the host's own context.
|
|
40
|
+
*
|
|
41
|
+
* Exported so every delegation surface answers "how deep am I?" the same
|
|
42
|
+
* way; `executeDelegation` and the background form both call it.
|
|
43
|
+
*/
|
|
44
|
+
export declare function resolveDelegationDepth(host: NeuroLink, executionContext?: Record<string, unknown>): number;
|
|
45
|
+
/**
|
|
46
|
+
* Raise the process-wide delegation pool to at least `capacity` slots.
|
|
47
|
+
*
|
|
48
|
+
* Raises only — never lowers. The pool is shared by every delegation surface,
|
|
49
|
+
* so a per-agent "max concurrent" that could shrink it would be a throttle on
|
|
50
|
+
* everyone else's work. Queued waiters the raise just unblocked are granted
|
|
51
|
+
* immediately, so a raise never leaves capacity idle behind a full queue.
|
|
52
|
+
*/
|
|
53
|
+
export declare function raiseDelegationPoolCapacity(capacity: number): void;
|
|
54
|
+
/**
|
|
55
|
+
* Take a pool slot only if one is free right now. Returns the release
|
|
56
|
+
* function, or undefined when the pool is full — never queues.
|
|
57
|
+
*
|
|
58
|
+
* Background delegation needs the answer synchronously: `spawnDelegate`
|
|
59
|
+
* returns a handle BEFORE the worker starts, and the handle reports whether
|
|
60
|
+
* the worker is running or waiting for a slot. Asking after the fact would be
|
|
61
|
+
* a guess.
|
|
62
|
+
*/
|
|
63
|
+
export declare function tryAcquireDelegationSlot(): (() => void) | undefined;
|
|
34
64
|
/**
|
|
35
65
|
* Acquire a slot in the process-wide delegation pool, waiting up to
|
|
36
66
|
* `timeoutMs` in the queue. Resolves to a release function.
|
|
@@ -19,7 +19,7 @@ import { hasOpenIsolatedAgentHandle, runIsolatedAgent, } from "./isolatedAgentRu
|
|
|
19
19
|
const DEFAULT_POOL_CAPACITY = 4;
|
|
20
20
|
const DEFAULT_POOL_QUEUE_TIMEOUT_MS = 30_000;
|
|
21
21
|
/** Bound on the content text returned into the host loop per delegation. */
|
|
22
|
-
const DELEGATION_RESULT_CONTENT_CHARS = 4000;
|
|
22
|
+
export const DELEGATION_RESULT_CONTENT_CHARS = 4000;
|
|
23
23
|
// ── Registrations ──────────────────────────────────────────────────────────
|
|
24
24
|
const hostRegistrations = new WeakMap();
|
|
25
25
|
function registrationsFor(host) {
|
|
@@ -80,13 +80,28 @@ export function runWithNestedDelegationDepth(fn) {
|
|
|
80
80
|
: { counts: new Map(), depth: 1 };
|
|
81
81
|
return turnStorage.run(state, fn);
|
|
82
82
|
}
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
83
|
+
/**
|
|
84
|
+
* The delegation depth a tool call is being made AT — execution context
|
|
85
|
+
* first (a worker running the host's registered closure arrives with its own
|
|
86
|
+
* `agentDepth`), then the per-turn ALS scope, then the host's own context.
|
|
87
|
+
*
|
|
88
|
+
* Exported so every delegation surface answers "how deep am I?" the same
|
|
89
|
+
* way; `executeDelegation` and the background form both call it.
|
|
90
|
+
*/
|
|
91
|
+
export function resolveDelegationDepth(host, executionContext) {
|
|
86
92
|
const fromExecution = executionContext?.agentDepth;
|
|
87
93
|
if (typeof fromExecution === "number") {
|
|
88
94
|
return fromExecution;
|
|
89
95
|
}
|
|
96
|
+
const turnScope = turnStorage.getStore();
|
|
97
|
+
return turnScope?.depth ?? resolveDepth(host);
|
|
98
|
+
}
|
|
99
|
+
/**
|
|
100
|
+
* Depth declared by the CALL or the instance. The execution context — which
|
|
101
|
+
* carries the agentDepth stamped on the worker actually making the call — is
|
|
102
|
+
* checked one level up, in {@link resolveDelegationDepth}.
|
|
103
|
+
*/
|
|
104
|
+
function resolveDepth(host, options) {
|
|
90
105
|
const fromCall = options?.context?.agentDepth;
|
|
91
106
|
if (typeof fromCall === "number") {
|
|
92
107
|
return fromCall;
|
|
@@ -106,6 +121,57 @@ function releasePoolSlot() {
|
|
|
106
121
|
next.grant();
|
|
107
122
|
}
|
|
108
123
|
}
|
|
124
|
+
/**
|
|
125
|
+
* Raise the process-wide delegation pool to at least `capacity` slots.
|
|
126
|
+
*
|
|
127
|
+
* Raises only — never lowers. The pool is shared by every delegation surface,
|
|
128
|
+
* so a per-agent "max concurrent" that could shrink it would be a throttle on
|
|
129
|
+
* everyone else's work. Queued waiters the raise just unblocked are granted
|
|
130
|
+
* immediately, so a raise never leaves capacity idle behind a full queue.
|
|
131
|
+
*/
|
|
132
|
+
export function raiseDelegationPoolCapacity(capacity) {
|
|
133
|
+
// Infinity historically meant "unbounded" (Math.max accepted it); map it to
|
|
134
|
+
// a finite stand-in rather than silently dropping to the default. NaN stays
|
|
135
|
+
// rejected — it used to deadlock the pool permanently (Math.max(4, NaN) is
|
|
136
|
+
// NaN, and `poolInUse < NaN` is always false).
|
|
137
|
+
const UNBOUNDED_STAND_IN = 1024;
|
|
138
|
+
if (capacity === Number.POSITIVE_INFINITY) {
|
|
139
|
+
capacity = UNBOUNDED_STAND_IN;
|
|
140
|
+
}
|
|
141
|
+
if (!Number.isFinite(capacity) || capacity <= poolCapacity) {
|
|
142
|
+
return;
|
|
143
|
+
}
|
|
144
|
+
poolCapacity = capacity;
|
|
145
|
+
while (poolInUse < poolCapacity && poolWaiters.length > 0) {
|
|
146
|
+
const next = poolWaiters.shift();
|
|
147
|
+
if (next) {
|
|
148
|
+
poolInUse++;
|
|
149
|
+
next.grant();
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
/**
|
|
154
|
+
* Take a pool slot only if one is free right now. Returns the release
|
|
155
|
+
* function, or undefined when the pool is full — never queues.
|
|
156
|
+
*
|
|
157
|
+
* Background delegation needs the answer synchronously: `spawnDelegate`
|
|
158
|
+
* returns a handle BEFORE the worker starts, and the handle reports whether
|
|
159
|
+
* the worker is running or waiting for a slot. Asking after the fact would be
|
|
160
|
+
* a guess.
|
|
161
|
+
*/
|
|
162
|
+
export function tryAcquireDelegationSlot() {
|
|
163
|
+
if (poolInUse >= poolCapacity) {
|
|
164
|
+
return undefined;
|
|
165
|
+
}
|
|
166
|
+
poolInUse++;
|
|
167
|
+
let released = false;
|
|
168
|
+
return () => {
|
|
169
|
+
if (!released) {
|
|
170
|
+
released = true;
|
|
171
|
+
releasePoolSlot();
|
|
172
|
+
}
|
|
173
|
+
};
|
|
174
|
+
}
|
|
109
175
|
/**
|
|
110
176
|
* Acquire a slot in the process-wide delegation pool, waiting up to
|
|
111
177
|
* `timeoutMs` in the queue. Resolves to a release function.
|
|
@@ -169,17 +235,7 @@ export function registerAgentTool(host, definition, options = {}) {
|
|
|
169
235
|
throw new Error(`registerAgentTool: an agent tool named "${name}" is already registered on this instance`);
|
|
170
236
|
}
|
|
171
237
|
if (options.maxConcurrent !== undefined) {
|
|
172
|
-
|
|
173
|
-
// never lowers; this is not a per-agent throttle). Grant any queued
|
|
174
|
-
// waiters the raise just unblocked.
|
|
175
|
-
poolCapacity = Math.max(poolCapacity, options.maxConcurrent);
|
|
176
|
-
while (poolInUse < poolCapacity && poolWaiters.length > 0) {
|
|
177
|
-
const next = poolWaiters.shift();
|
|
178
|
-
if (next) {
|
|
179
|
-
poolInUse++;
|
|
180
|
-
next.grant();
|
|
181
|
-
}
|
|
182
|
-
}
|
|
238
|
+
raiseDelegationPoolCapacity(options.maxConcurrent);
|
|
183
239
|
}
|
|
184
240
|
const registration = {
|
|
185
241
|
name,
|
|
@@ -228,9 +284,7 @@ async function executeDelegation(host, registration, params, context) {
|
|
|
228
284
|
// execution context carries no depth, the ALS turn scope's depth covers
|
|
229
285
|
// the composed path (AgentNetwork wraps its agents one level deeper).
|
|
230
286
|
const turnScope = turnStorage.getStore();
|
|
231
|
-
const depth =
|
|
232
|
-
? contextRecord.agentDepth
|
|
233
|
-
: (turnScope?.depth ?? resolveDepth(host));
|
|
287
|
+
const depth = resolveDelegationDepth(host, contextRecord);
|
|
234
288
|
if (options.maxDepth !== undefined && depth >= options.maxDepth) {
|
|
235
289
|
return refusal(`Delegation depth limit reached (${depth}/${options.maxDepth}). Complete this investigation yourself with your own tools instead of delegating further.`);
|
|
236
290
|
}
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Background commands (N4) — run a command detached, bank every byte it
|
|
3
|
+
* writes, monitor it while it runs.
|
|
4
|
+
*
|
|
5
|
+
* A reviewing agent has to run real commands — a build, a test suite, a linter
|
|
6
|
+
* whose output IS the evidence for a finding — and the naive shapes both fail.
|
|
7
|
+
* `bashTool` blocks the loop, hands the model a shell, and truncates its own
|
|
8
|
+
* output at 100 KB. A `child_process` call with a string command is a shell
|
|
9
|
+
* injection with extra steps.
|
|
10
|
+
*
|
|
11
|
+
* This module keeps three promises instead:
|
|
12
|
+
*
|
|
13
|
+
* - **Detached.** `startBackgroundCommand` returns a `taskId` immediately; the
|
|
14
|
+
* agent keeps working and asks about the command when it wants to.
|
|
15
|
+
* - **Nothing discarded.** Both streams are written straight to files as they
|
|
16
|
+
* arrive and the COMPLETE files are banked as artifacts (N3) when the
|
|
17
|
+
* command settles. The conversation gets a bounded tail plus a read-back
|
|
18
|
+
* call. The single bound is `maxOutputBytes`, and reaching it is a state
|
|
19
|
+
* (`output-limit`) the caller can see, not a silent cut.
|
|
20
|
+
* - **Hardened by contract.** argv arrays with `shell: false`, an exact-match
|
|
21
|
+
* executable allowlist, a cwd that must resolve through symlinks inside a
|
|
22
|
+
* declared root, and a timeout that escalates SIGTERM → SIGKILL.
|
|
23
|
+
*
|
|
24
|
+
* Completion reaches the model the same way a delegate's does (N2.3): the
|
|
25
|
+
* `running` / `finished` counters ride on every command tool result and — via
|
|
26
|
+
* the checklist — on every `tasks_list`. The core generate loop is untouched.
|
|
27
|
+
*
|
|
28
|
+
* @module agent/backgroundCommands
|
|
29
|
+
*/
|
|
30
|
+
import type { NeuroLink } from "../neurolink.js";
|
|
31
|
+
import type { BackgroundCommandCounts, BackgroundCommandHandle, BackgroundCommandOptions, BackgroundCommandOutputPage, BackgroundCommandPageRequest, BackgroundCommandPolicy, BackgroundCommandStatus, MCPExecutableTool } from "../types/index.js";
|
|
32
|
+
/**
|
|
33
|
+
* Declare what this host may execute. There is no default: until this is
|
|
34
|
+
* called, every start is refused, because "run whatever the model asks" is not
|
|
35
|
+
* a defensible default for a primitive that spawns processes.
|
|
36
|
+
*/
|
|
37
|
+
export declare function setBackgroundCommandPolicy(host: NeuroLink, policy: BackgroundCommandPolicy): void;
|
|
38
|
+
/** The policy in force for a host, or undefined when none was declared. */
|
|
39
|
+
export declare function getBackgroundCommandPolicy(host: NeuroLink): BackgroundCommandPolicy | undefined;
|
|
40
|
+
/**
|
|
41
|
+
* Commands for a host's session: `running` have not settled, `finished` have.
|
|
42
|
+
* Carried on every command tool result and on every `ChecklistToolResult`, so
|
|
43
|
+
* the model learns a build finished without polling for it.
|
|
44
|
+
*/
|
|
45
|
+
export declare function backgroundCommandCounts(host: NeuroLink, sessionId?: string): BackgroundCommandCounts;
|
|
46
|
+
/**
|
|
47
|
+
* Start a command in the background and get its task id immediately.
|
|
48
|
+
*
|
|
49
|
+
* @throws when no policy is set, argv is malformed, the executable is not
|
|
50
|
+
* allowlisted, the policy vetoes the command, or the cwd escapes the
|
|
51
|
+
* sandbox root. Every message names its own recovery step.
|
|
52
|
+
*/
|
|
53
|
+
export declare function startBackgroundCommand(host: NeuroLink, argv: string[], options: BackgroundCommandOptions): Promise<BackgroundCommandHandle>;
|
|
54
|
+
/**
|
|
55
|
+
* Everything known about one command right now — synchronous, because the
|
|
56
|
+
* job state is live and a monitor that has to be awaited is a monitor nobody
|
|
57
|
+
* calls mid-loop.
|
|
58
|
+
*
|
|
59
|
+
* @throws when the task id is unknown to this host
|
|
60
|
+
*/
|
|
61
|
+
export declare function getBackgroundCommandStatus(host: NeuroLink, taskId: string): BackgroundCommandStatus;
|
|
62
|
+
/**
|
|
63
|
+
* Wait for a command to settle.
|
|
64
|
+
*
|
|
65
|
+
* `timeoutMs` bounds the WAIT, not the command: when it elapses the current
|
|
66
|
+
* (still running) status is returned rather than throwing, so a caller can
|
|
67
|
+
* poll in bounded steps without ever losing the job.
|
|
68
|
+
*
|
|
69
|
+
* @throws when the task id is unknown to this host
|
|
70
|
+
*/
|
|
71
|
+
export declare function awaitBackgroundCommand(host: NeuroLink, taskId: string, opts?: {
|
|
72
|
+
timeoutMs?: number;
|
|
73
|
+
}): Promise<BackgroundCommandStatus>;
|
|
74
|
+
/**
|
|
75
|
+
* Kill a running command: SIGTERM (or the signal you name), SIGKILL five
|
|
76
|
+
* seconds later if it is still there. Resolves with the settled status, so a
|
|
77
|
+
* caller never has to guess whether the output was banked yet.
|
|
78
|
+
*
|
|
79
|
+
* Killing an already-settled command is a no-op that returns its status —
|
|
80
|
+
* the outcome is not discarded.
|
|
81
|
+
*
|
|
82
|
+
* @throws when the task id is unknown to this host
|
|
83
|
+
*/
|
|
84
|
+
export declare function killBackgroundCommand(host: NeuroLink, taskId: string, signal?: NodeJS.Signals): Promise<BackgroundCommandStatus>;
|
|
85
|
+
/**
|
|
86
|
+
* Kill every unsettled command this host started. Host lifecycle only
|
|
87
|
+
* (`shutdown()`/`dispose()`): a disposed instance must not leave child
|
|
88
|
+
* processes running with nobody left to collect them.
|
|
89
|
+
*
|
|
90
|
+
* @returns how many commands were signalled
|
|
91
|
+
*/
|
|
92
|
+
export declare function killAllBackgroundCommands(host: NeuroLink): Promise<number>;
|
|
93
|
+
/**
|
|
94
|
+
* Read one character window of a command's output, straight from its log file.
|
|
95
|
+
*
|
|
96
|
+
* Works while the command is still running — that is the monitor case — and
|
|
97
|
+
* after it settled. Character offsets, `totalSize` and `hasMore` match
|
|
98
|
+
* `retrieve_context` exactly, so paging code written for one works on the
|
|
99
|
+
* other.
|
|
100
|
+
*
|
|
101
|
+
* @throws when the task id is unknown to this host
|
|
102
|
+
*/
|
|
103
|
+
export declare function readBackgroundCommandOutput(host: NeuroLink, taskId: string, page: BackgroundCommandPageRequest): Promise<BackgroundCommandOutputPage>;
|
|
104
|
+
/**
|
|
105
|
+
* The four model-facing command tools, bound to `host`. Register them with
|
|
106
|
+
* `host.registerTool()` (see `NeuroLink.registerBackgroundCommandTools()`),
|
|
107
|
+
* never on the tool registry directly: only the "user-defined" category
|
|
108
|
+
* reaches the LLM's tool schema.
|
|
109
|
+
*/
|
|
110
|
+
export declare function createBackgroundCommandTools(host: NeuroLink): Record<string, MCPExecutableTool>;
|