@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.
Files changed (48) hide show
  1. package/CHANGELOG.md +3 -3
  2. package/dist/agent/agentToolRegistrar.d.ts +30 -0
  3. package/dist/agent/agentToolRegistrar.js +72 -18
  4. package/dist/agent/backgroundCommands.d.ts +110 -0
  5. package/dist/agent/backgroundCommands.js +914 -0
  6. package/dist/agent/backgroundDelegation.d.ts +87 -0
  7. package/dist/agent/backgroundDelegation.js +753 -0
  8. package/dist/agent/gitTools.d.ts +43 -0
  9. package/dist/agent/gitTools.js +618 -0
  10. package/dist/agent/taskChecklist.d.ts +58 -0
  11. package/dist/agent/taskChecklist.js +322 -0
  12. package/dist/artifacts/artifactBanking.d.ts +57 -0
  13. package/dist/artifacts/artifactBanking.js +123 -0
  14. package/dist/artifacts/artifactStore.d.ts +36 -8
  15. package/dist/artifacts/artifactStore.js +164 -13
  16. package/dist/browser/neurolink.min.js +442 -414
  17. package/dist/cli/commands/setup.js +2 -1
  18. package/dist/constants/enums.d.ts +19 -0
  19. package/dist/constants/enums.js +20 -0
  20. package/dist/factories/providerDescriptors.js +16 -1
  21. package/dist/models/manifestRegistry.js +2 -0
  22. package/dist/models/manifests/cerebras.d.ts +9 -0
  23. package/dist/models/manifests/cerebras.js +19 -0
  24. package/dist/neurolink.d.ts +294 -3
  25. package/dist/neurolink.js +447 -4
  26. package/dist/providers/openaiCompatCatalog.d.ts +1 -1
  27. package/dist/providers/openaiCompatCatalog.js +34 -3
  28. package/dist/types/artifact.d.ts +54 -0
  29. package/dist/types/backgroundCommand.d.ts +174 -0
  30. package/dist/types/backgroundCommand.js +22 -0
  31. package/dist/types/delegation.d.ts +178 -0
  32. package/dist/types/delegation.js +18 -0
  33. package/dist/types/gitTools.d.ts +69 -0
  34. package/dist/types/gitTools.js +22 -0
  35. package/dist/types/index.d.ts +5 -0
  36. package/dist/types/index.js +8 -0
  37. package/dist/types/pathSandbox.d.ts +23 -0
  38. package/dist/types/pathSandbox.js +12 -0
  39. package/dist/types/providers.d.ts +4 -0
  40. package/dist/types/tasks.d.ts +85 -0
  41. package/dist/types/tasks.js +14 -0
  42. package/dist/types/tools.d.ts +11 -0
  43. package/dist/utils/modelChoices.js +17 -1
  44. package/dist/utils/pathSandbox.d.ts +49 -0
  45. package/dist/utils/pathSandbox.js +127 -0
  46. package/dist/utils/providerConfig.d.ts +4 -0
  47. package/dist/utils/providerConfig.js +17 -0
  48. package/package.json +5 -1
package/CHANGELOG.md CHANGED
@@ -1,8 +1,8 @@
1
- ## [12.0.5](https://github.com/juspay/neurolink/compare/v12.0.4...v12.0.5) (2026-08-26)
1
+ ## [12.2.0](https://github.com/juspay/neurolink/compare/v12.1.0...v12.2.0) (2026-08-27)
2
2
 
3
- ### Bug Fixes
3
+ ### Features
4
4
 
5
- - **(core):** window an over-budget prompt instead of dead-ending prompt+tools-only calls ([6d0b436](https://github.com/juspay/neurolink/commit/6d0b4363cb8b38cdbe37e905d5f267668d2ec3bf))
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
- function resolveDepth(host, options, executionContext) {
84
- // Execution context wins: it carries the agentDepth stamped on the worker
85
- // that is actually making this call (see executeDelegation).
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
- // Process-wide pool: the largest registered capacity wins (raises only —
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 = typeof contextRecord.agentDepth === "number"
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>;