@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
@@ -18,7 +18,7 @@
18
18
  * @module artifacts/artifactStore
19
19
  */
20
20
  import { randomUUID } from "node:crypto";
21
- import { mkdir, readFile, rm, writeFile } from "node:fs/promises";
21
+ import { mkdir, readFile, rm, stat, writeFile } from "node:fs/promises";
22
22
  import { tmpdir } from "node:os";
23
23
  import { join } from "node:path";
24
24
  import { logger } from "../utils/logger.js";
@@ -28,17 +28,74 @@ import { logger } from "../utils/logger.js";
28
28
  // ---------------------------------------------------------------------------
29
29
  /** Characters used for the quick preview embedded in surrogate results. */
30
30
  const DEFAULT_PREVIEW_CHARS = 500;
31
+ /**
32
+ * Sidecar written beside every payload so a process that never called
33
+ * `store()` can still resolve the id (see the index-miss path in `retrieve`).
34
+ */
35
+ const META_SUFFIX = ".meta.json";
36
+ /**
37
+ * Ids that may be turned into a path.
38
+ *
39
+ * Before the index-miss fallback existed, an unknown id simply missed the
40
+ * in-memory map and no filesystem lookup happened. Now that a miss probes
41
+ * `join(dir, id + ext)`, the id reaches the path layer — and ids arrive from
42
+ * the model through `retrieve_context`. No dots and no separators means
43
+ * `../../etc/passwd` can never become a probe. Real ids are UUIDs.
44
+ */
45
+ const SAFE_ARTIFACT_ID = /^[A-Za-z0-9][A-Za-z0-9_-]{0,127}$/;
46
+ /** Extensions `store()` can produce, in the order the fallback probes them. */
47
+ const PAYLOAD_EXTENSIONS = [".json", ".txt"];
48
+ /**
49
+ * Runtime shape check for a sidecar read off disk — it is untrusted input
50
+ * (another process, an older version, a truncated write), so it is validated
51
+ * rather than asserted into `IndexEntry`.
52
+ */
53
+ function parseSidecar(value) {
54
+ if (!value || typeof value !== "object") {
55
+ return undefined;
56
+ }
57
+ const row = { ...value };
58
+ const { toolName, serverId, sizeBytes, contentType, createdAt, path } = row;
59
+ if (typeof toolName !== "string" ||
60
+ typeof serverId !== "string" ||
61
+ typeof sizeBytes !== "number" ||
62
+ (contentType !== "json" && contentType !== "text") ||
63
+ typeof createdAt !== "number" ||
64
+ typeof path !== "string") {
65
+ return undefined;
66
+ }
67
+ const entry = {
68
+ toolName,
69
+ serverId,
70
+ sizeBytes,
71
+ contentType,
72
+ createdAt,
73
+ path,
74
+ };
75
+ if (typeof row.sessionId === "string") {
76
+ entry.sessionId = row.sessionId;
77
+ }
78
+ if (typeof row.label === "string") {
79
+ entry.label = row.label;
80
+ }
81
+ const kind = row.kind;
82
+ if (kind === "worker-report" ||
83
+ kind === "command-output" ||
84
+ kind === "stage-output" ||
85
+ kind === "other") {
86
+ entry.kind = kind;
87
+ }
88
+ return entry;
89
+ }
31
90
  /**
32
91
  * Filesystem-backed artifact store using the OS temp directory.
33
92
  *
34
93
  * Files are written with mode 0o600 (owner read/write only).
35
- * An in-memory index tracks metadata without a separate index file.
36
- *
37
- * Suitable for:
38
- * - CLI usage
39
- * - Single-process SDK deployments
40
- * - Multi-process deployments where each process manages its own artifacts
41
- * (artifacts created in one process are not visible to others)
94
+ * An in-memory index tracks metadata for the fast path; every payload also
95
+ * gets a `<id>.meta.json` sidecar, so an id this process never stored — from
96
+ * another process, or from before a restart — still resolves (see
97
+ * `rehydrate`). `cleanup()` remains index-scoped: it expires what this process
98
+ * knows about, and never walks the directory deleting another process's work.
42
99
  *
43
100
  * @example
44
101
  * ```typescript
@@ -56,8 +113,21 @@ const DEFAULT_PREVIEW_CHARS = 500;
56
113
  export class LocalTempArtifactStore {
57
114
  dir;
58
115
  index = new Map();
59
- constructor(dir) {
116
+ rehydrateFromDisk;
117
+ /**
118
+ * @param dir - Storage directory; defaults to `tmpdir()/neurolink-artifacts`
119
+ * @param options - `rehydrateFromDisk` (default true) lets `retrieve()` and
120
+ * `delete()` fall back to the on-disk sidecar index on an in-memory miss,
121
+ * which makes artifacts READABLE AND DELETABLE ACROSS PROCESSES sharing
122
+ * the same directory and unix user. Pass `false` — or set
123
+ * `NEUROLINK_ARTIFACT_REHYDRATE=false` — to restore strict per-process
124
+ * isolation: ids not stored by this process resolve to nothing.
125
+ */
126
+ constructor(dir, options) {
60
127
  this.dir = dir ?? join(tmpdir(), "neurolink-artifacts");
128
+ this.rehydrateFromDisk =
129
+ options?.rehydrateFromDisk ??
130
+ process.env.NEUROLINK_ARTIFACT_REHYDRATE !== "false";
61
131
  }
62
132
  generatePreview(payload) {
63
133
  if (payload.length <= DEFAULT_PREVIEW_CHARS) {
@@ -77,6 +147,7 @@ export class LocalTempArtifactStore {
77
147
  path: filePath,
78
148
  };
79
149
  this.index.set(id, fullMeta);
150
+ await this.writeSidecar(id, fullMeta);
80
151
  logger.debug(`[ArtifactStore] Stored artifact ${id} for tool "${meta.toolName}" ` +
81
152
  `(${formatBytes(meta.sizeBytes)})`);
82
153
  return {
@@ -87,9 +158,10 @@ export class LocalTempArtifactStore {
87
158
  };
88
159
  }
89
160
  async retrieve(id) {
90
- const entry = this.index.get(id);
161
+ const entry = this.index.get(id) ??
162
+ (this.rehydrateFromDisk ? await this.rehydrate(id) : undefined);
91
163
  if (!entry) {
92
- logger.debug(`[ArtifactStore] Artifact ${id} not in index`);
164
+ logger.debug(`[ArtifactStore] Artifact ${id} not found on disk`);
93
165
  return null;
94
166
  }
95
167
  try {
@@ -103,15 +175,17 @@ export class LocalTempArtifactStore {
103
175
  }
104
176
  }
105
177
  async delete(id) {
106
- const entry = this.index.get(id);
178
+ const entry = this.index.get(id) ??
179
+ (this.rehydrateFromDisk ? await this.rehydrate(id) : undefined);
107
180
  if (!entry) {
108
181
  return;
109
182
  }
110
183
  try {
111
184
  await rm(entry.path, { force: true });
185
+ await rm(join(this.dir, `${id}${META_SUFFIX}`), { force: true });
112
186
  }
113
187
  catch {
114
- // Suppress — file may already be gone
188
+ // Suppress — files may already be gone
115
189
  }
116
190
  this.index.delete(id);
117
191
  }
@@ -119,6 +193,11 @@ export class LocalTempArtifactStore {
119
193
  const cutoff = Date.now() - olderThanMs;
120
194
  let count = 0;
121
195
  for (const [id, entry] of this.index.entries()) {
196
+ // Rehydrated entries are another process's artifacts — readable from
197
+ // here, but never this process's to expire.
198
+ if (entry.rehydrated === true) {
199
+ continue;
200
+ }
122
201
  if (entry.createdAt < cutoff) {
123
202
  await this.delete(id);
124
203
  count++;
@@ -129,6 +208,78 @@ export class LocalTempArtifactStore {
129
208
  }
130
209
  return count;
131
210
  }
211
+ /**
212
+ * Record the index row next to the payload.
213
+ *
214
+ * The index is per-process, so without this an artifact written by one
215
+ * process is invisible to every other one — and to the same process after a
216
+ * restart. A failed sidecar write is logged, never fatal: the payload is
217
+ * already safely on disk and this process can still read it from its index.
218
+ */
219
+ async writeSidecar(id, entry) {
220
+ try {
221
+ await writeFile(join(this.dir, `${id}${META_SUFFIX}`), JSON.stringify(entry), { encoding: "utf-8", mode: 0o600 });
222
+ }
223
+ catch (err) {
224
+ logger.warn(`[ArtifactStore] Failed to write index sidecar for ${id} — the ` +
225
+ `artifact stays readable in this process only: ${err instanceof Error ? err.message : String(err)}`);
226
+ }
227
+ }
228
+ /**
229
+ * Resolve an id the in-memory index does not know: another process stored
230
+ * it, or this process restarted. Reads the sidecar first (full metadata);
231
+ * falls back to probing the payload file itself, so an artifact whose
232
+ * sidecar was lost is still readable with metadata recovered from `stat`.
233
+ *
234
+ * Returns undefined for an unsafe id without touching the filesystem.
235
+ */
236
+ async rehydrate(id) {
237
+ if (!SAFE_ARTIFACT_ID.test(id)) {
238
+ logger.debug(`[ArtifactStore] Rejected unsafe artifact id "${id}"`);
239
+ return undefined;
240
+ }
241
+ try {
242
+ const raw = await readFile(join(this.dir, `${id}${META_SUFFIX}`), "utf-8");
243
+ const entry = parseSidecar(JSON.parse(raw));
244
+ if (entry) {
245
+ // The payload's location is fully determined by id + contentType, and
246
+ // the store directory is shared OS temp — so the path is DERIVED, never
247
+ // honoured from a sidecar another local account could have planted
248
+ // before this store first ran.
249
+ const derived = {
250
+ ...entry,
251
+ path: join(this.dir, `${id}${entry.contentType === "json" ? ".json" : ".txt"}`),
252
+ rehydrated: true,
253
+ };
254
+ this.index.set(id, derived);
255
+ return derived;
256
+ }
257
+ }
258
+ catch {
259
+ // No sidecar, or an unreadable one — fall through to the probe.
260
+ }
261
+ for (const ext of PAYLOAD_EXTENSIONS) {
262
+ const path = join(this.dir, `${id}${ext}`);
263
+ try {
264
+ const stats = await stat(path);
265
+ const entry = {
266
+ toolName: "unknown",
267
+ serverId: "unknown",
268
+ sizeBytes: stats.size,
269
+ contentType: ext === ".json" ? "json" : "text",
270
+ createdAt: stats.mtimeMs,
271
+ path,
272
+ rehydrated: true,
273
+ };
274
+ this.index.set(id, entry);
275
+ return entry;
276
+ }
277
+ catch {
278
+ // Not this extension — try the next.
279
+ }
280
+ }
281
+ return undefined;
282
+ }
132
283
  }
133
284
  // ---------------------------------------------------------------------------
134
285
  // Helpers