ultravisor 1.0.17 → 1.0.18

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ultravisor",
3
- "version": "1.0.17",
3
+ "version": "1.0.18",
4
4
  "description": "Cyclic process execution with ai integration.",
5
5
  "main": "source/Ultravisor.cjs",
6
6
  "bin": {
@@ -34,7 +34,7 @@
34
34
  "pict": "^1.0.361",
35
35
  "pict-service-commandlineutility": "^1.0.19",
36
36
  "pict-serviceproviderbase": "^1.0.4",
37
- "ultravisor-beacon": "^0.0.8",
37
+ "ultravisor-beacon": "^0.0.9",
38
38
  "ws": "^8.20.0"
39
39
  },
40
40
  "devDependencies": {
@@ -231,6 +231,16 @@ class UltravisorBeaconCoordinator extends libPictService
231
231
  {
232
232
  tmpExistingBeacon.BindAddresses = pBeaconInfo.BindAddresses;
233
233
  }
234
+ // Refresh shared-fs identity on reconnect — host id can change between
235
+ // container restarts and a reconnecting beacon may have new mounts.
236
+ if (typeof pBeaconInfo.HostID === 'string' && pBeaconInfo.HostID.length > 0)
237
+ {
238
+ tmpExistingBeacon.HostID = pBeaconInfo.HostID;
239
+ }
240
+ if (Array.isArray(pBeaconInfo.SharedMounts))
241
+ {
242
+ tmpExistingBeacon.SharedMounts = pBeaconInfo.SharedMounts;
243
+ }
234
244
 
235
245
  this.log.info(`BeaconCoordinator: reconnected beacon [${tmpExistingBeacon.BeaconID}] "${tmpName}" with session [${tmpExistingBeacon.SessionID}].`);
236
246
 
@@ -272,6 +282,11 @@ class UltravisorBeaconCoordinator extends libPictService
272
282
  Tags: pBeaconInfo.Tags || {},
273
283
  Contexts: pBeaconInfo.Contexts || {},
274
284
  BindAddresses: Array.isArray(pBeaconInfo.BindAddresses) ? pBeaconInfo.BindAddresses : [],
285
+ // Shared-fs identity. Both fields are optional for backwards compatibility:
286
+ // older beacons that don't send them just get null/[] and the shared-fs
287
+ // strategy is silently skipped for them.
288
+ HostID: (typeof pBeaconInfo.HostID === 'string' && pBeaconInfo.HostID.length > 0) ? pBeaconInfo.HostID : null,
289
+ SharedMounts: Array.isArray(pBeaconInfo.SharedMounts) ? pBeaconInfo.SharedMounts : [],
275
290
  RegisteredAt: new Date().toISOString()
276
291
  };
277
292
 
@@ -362,9 +362,17 @@ class UltravisorBeaconReachability extends libPictService
362
362
  * Determine the transfer strategy between a source beacon (file
363
363
  * owner) and a requesting beacon (the one that needs the file).
364
364
  *
365
+ * Strategy ordering (most efficient first):
366
+ * local — same beacon, no transport at all
367
+ * shared-fs — different beacons but on the same host with overlapping
368
+ * filesystem mounts, so the requesting beacon can read the
369
+ * source's file path directly with no copy
370
+ * direct — HTTP fetch from the source beacon's bind address
371
+ * proxy — HTTP fetch via the coordinator
372
+ *
365
373
  * @param {string} pSourceBeaconID - Beacon that owns the resource
366
374
  * @param {string} pRequestingBeaconID - Beacon that wants the resource
367
- * @returns {object} { Strategy, DirectURL }
375
+ * @returns {object} { Strategy, DirectURL, SharedMountRoot? }
368
376
  */
369
377
  resolveStrategy(pSourceBeaconID, pRequestingBeaconID)
370
378
  {
@@ -386,6 +394,28 @@ class UltravisorBeaconReachability extends libPictService
386
394
  return { Strategy: 'proxy', DirectURL: '' };
387
395
  }
388
396
 
397
+ // Shared-fs detection — check whether both beacons live on the same host
398
+ // AND advertise at least one shared filesystem mount in common. When they
399
+ // do, the requesting beacon can read the source file directly from the
400
+ // shared mount without an HTTP transfer.
401
+ let tmpRequestingBeacon = tmpCoordinator.getBeacon(pRequestingBeaconID);
402
+ if (tmpRequestingBeacon
403
+ && tmpSourceBeacon.HostID
404
+ && tmpRequestingBeacon.HostID
405
+ && tmpSourceBeacon.HostID === tmpRequestingBeacon.HostID)
406
+ {
407
+ let tmpSharedMount = this._findSharedMount(
408
+ tmpSourceBeacon.SharedMounts, tmpRequestingBeacon.SharedMounts);
409
+ if (tmpSharedMount)
410
+ {
411
+ return {
412
+ Strategy: 'shared-fs',
413
+ DirectURL: '',
414
+ SharedMountRoot: tmpSharedMount.Root
415
+ };
416
+ }
417
+ }
418
+
389
419
  // Check matrix
390
420
  let tmpEntry = this.getReachability(pRequestingBeaconID, pSourceBeaconID);
391
421
 
@@ -398,6 +428,52 @@ class UltravisorBeaconReachability extends libPictService
398
428
  // unreachable or untested — fall back to proxy
399
429
  return { Strategy: 'proxy', DirectURL: '' };
400
430
  }
431
+
432
+ /**
433
+ * Find the first MountID that appears in both beacons' SharedMounts arrays.
434
+ *
435
+ * The MountID is derived from `stat.dev` + the resolved root path on the
436
+ * beacon side, so two beacons that bind-mount the same host directory get
437
+ * the same ID, while two unrelated /media directories on different machines
438
+ * (or in different containers without shared mounts) get different IDs.
439
+ *
440
+ * @param {Array} pSourceMounts - SharedMounts reported by the source beacon
441
+ * @param {Array} pRequestingMounts - SharedMounts reported by the requester
442
+ * @returns {object|null} The matching mount entry from the source beacon,
443
+ * or null if no overlap exists
444
+ */
445
+ _findSharedMount(pSourceMounts, pRequestingMounts)
446
+ {
447
+ if (!Array.isArray(pSourceMounts) || pSourceMounts.length === 0)
448
+ {
449
+ return null;
450
+ }
451
+ if (!Array.isArray(pRequestingMounts) || pRequestingMounts.length === 0)
452
+ {
453
+ return null;
454
+ }
455
+ for (let i = 0; i < pSourceMounts.length; i++)
456
+ {
457
+ let tmpSrc = pSourceMounts[i];
458
+ if (!tmpSrc || !tmpSrc.MountID)
459
+ {
460
+ continue;
461
+ }
462
+ for (let j = 0; j < pRequestingMounts.length; j++)
463
+ {
464
+ let tmpReq = pRequestingMounts[j];
465
+ if (!tmpReq || !tmpReq.MountID)
466
+ {
467
+ continue;
468
+ }
469
+ if (tmpSrc.MountID === tmpReq.MountID)
470
+ {
471
+ return tmpSrc;
472
+ }
473
+ }
474
+ }
475
+ return null;
476
+ }
401
477
  }
402
478
 
403
479
  module.exports = UltravisorBeaconReachability;
@@ -60,7 +60,7 @@ module.exports =
60
60
  {
61
61
  return fCallback(null, {
62
62
  EventToFire: 'Error',
63
- Outputs: { URL: '', BeaconID: '', BeaconName: '', Context: '', Path: '', Filename: '' },
63
+ Outputs: { URL: '', BeaconID: '', BeaconName: '', Context: '', Path: '', Filename: '', LocalPath: '' },
64
64
  Log: [`Resolve Address: could not resolve "${tmpAddress}". Beacon may be offline or context not registered.`]
65
65
  });
66
66
  }
@@ -74,7 +74,8 @@ module.exports =
74
74
  Filename: tmpResolved.Filename,
75
75
  Strategy: 'direct',
76
76
  DirectURL: '',
77
- ProxyURL: ''
77
+ ProxyURL: '',
78
+ LocalPath: ''
78
79
  };
79
80
 
80
81
  // Helper: build a full URL from a beacon's bind address + context path + resource path
@@ -115,7 +116,33 @@ module.exports =
115
116
  let tmpStrategyResult = tmpReachability.resolveStrategy(tmpResolved.BeaconID, tmpRequestingBeaconID);
116
117
  tmpOutputs.Strategy = tmpStrategyResult.Strategy;
117
118
 
118
- if (tmpStrategyResult.Strategy === 'direct' && tmpStrategyResult.DirectURL)
119
+ if (tmpStrategyResult.Strategy === 'shared-fs')
120
+ {
121
+ // Both beacons see the same filesystem mount. Look up the
122
+ // source beacon's context BasePath and join it with the inner
123
+ // resource path to get an absolute path that's also valid on
124
+ // the requesting beacon (because they share the mount).
125
+ let tmpSourceBeacon = tmpCoordinator.getBeacon(tmpResolved.BeaconID);
126
+ let tmpCtx = tmpSourceBeacon && tmpSourceBeacon.Contexts
127
+ ? tmpSourceBeacon.Contexts[tmpResolved.Context]
128
+ : null;
129
+ if (tmpCtx && tmpCtx.BasePath)
130
+ {
131
+ let tmpAbsPath = libPath.join(tmpCtx.BasePath, tmpResolved.Path);
132
+ tmpOutputs.LocalPath = tmpAbsPath;
133
+ // URL is intentionally left as the original (relative) URL
134
+ // — file-transfer will see LocalPath and short-circuit, so
135
+ // the URL is never actually fetched.
136
+ }
137
+ else
138
+ {
139
+ // No BasePath available — fall back to direct so the
140
+ // transfer still works via HTTP.
141
+ pTask.log.warn(`Resolve Address: shared-fs strategy chosen but no BasePath available for context [${tmpResolved.Context}] on beacon [${tmpResolved.BeaconID}], falling back to direct.`);
142
+ tmpOutputs.Strategy = 'direct';
143
+ }
144
+ }
145
+ else if (tmpStrategyResult.Strategy === 'direct' && tmpStrategyResult.DirectURL)
119
146
  {
120
147
  tmpOutputs.DirectURL = _buildDirectURL(tmpStrategyResult.DirectURL);
121
148
  tmpOutputs.URL = tmpOutputs.DirectURL;
@@ -158,19 +185,26 @@ module.exports =
158
185
  tmpStateWrites[pResolvedSettings.Destination] = tmpOutputs;
159
186
  }
160
187
 
161
- pTask.log.info(`Resolve Address: ${tmpAddress} → ${tmpOutputs.URL} [${tmpOutputs.Strategy}] (beacon: ${tmpResolved.BeaconName})`);
188
+ let tmpResolvedDest = tmpOutputs.LocalPath || tmpOutputs.URL;
189
+ pTask.log.info(`Resolve Address: ${tmpAddress} → ${tmpResolvedDest} [${tmpOutputs.Strategy}] (beacon: ${tmpResolved.BeaconName})`);
190
+
191
+ let tmpLogLines = [
192
+ `Resolved: ${tmpAddress}`,
193
+ `URL: ${tmpOutputs.URL}`,
194
+ `Strategy: ${tmpOutputs.Strategy}`,
195
+ `Beacon: ${tmpResolved.BeaconName} (${tmpResolved.BeaconID})`,
196
+ `Context: ${tmpResolved.Context}, Path: ${tmpResolved.Path}`
197
+ ];
198
+ if (tmpOutputs.LocalPath)
199
+ {
200
+ tmpLogLines.push(`LocalPath: ${tmpOutputs.LocalPath} (shared filesystem — no transfer needed)`);
201
+ }
162
202
 
163
203
  return fCallback(null, {
164
204
  EventToFire: 'Complete',
165
205
  Outputs: tmpOutputs,
166
206
  StateWrites: tmpStateWrites,
167
- Log: [
168
- `Resolved: ${tmpAddress}`,
169
- `URL: ${tmpOutputs.URL}`,
170
- `Strategy: ${tmpOutputs.Strategy}`,
171
- `Beacon: ${tmpResolved.BeaconName} (${tmpResolved.BeaconID})`,
172
- `Context: ${tmpResolved.Context}, Path: ${tmpResolved.Path}`
173
- ]
207
+ Log: tmpLogLines
174
208
  });
175
209
  }
176
210
  },
@@ -182,13 +216,56 @@ module.exports =
182
216
  Execute: function (pTask, pResolvedSettings, pExecutionContext, fCallback)
183
217
  {
184
218
  let tmpSourceURL = pResolvedSettings.SourceURL;
219
+ let tmpSourceLocalPath = pResolvedSettings.SourceLocalPath;
185
220
  let tmpFilename = pResolvedSettings.Filename;
186
221
 
222
+ // Shared-filesystem fast path: if the source beacon and the requesting
223
+ // beacon both report the same MountID, resolve-address will populate
224
+ // SourceLocalPath with the absolute on-disk path. Both beacons see the
225
+ // same file at the same path because they share the mount, so we hand
226
+ // it back as the LocalPath without copying anything. This is the
227
+ // difference between "instant" and "374 MB download to staging" on a
228
+ // stack-mode deployment where retold-remote and orator-conversion live
229
+ // in the same container.
230
+ if (tmpSourceLocalPath)
231
+ {
232
+ if (libFS.existsSync(tmpSourceLocalPath))
233
+ {
234
+ let tmpStat;
235
+ try
236
+ {
237
+ tmpStat = libFS.statSync(tmpSourceLocalPath);
238
+ }
239
+ catch (pStatError)
240
+ {
241
+ tmpStat = null;
242
+ }
243
+ pTask.log.info(`File Transfer: shared-fs hit, using ${tmpSourceLocalPath} directly (${tmpStat ? tmpStat.size : '?'} bytes, no copy).`);
244
+ return fCallback(null, {
245
+ EventToFire: 'Complete',
246
+ Outputs: {
247
+ LocalPath: tmpSourceLocalPath,
248
+ BytesTransferred: 0,
249
+ DurationMs: 0,
250
+ Strategy: 'shared-fs'
251
+ },
252
+ Log: [
253
+ `Shared filesystem detected — no transfer needed.`,
254
+ `Source path: ${tmpSourceLocalPath}`,
255
+ `Bytes transferred: 0 (zero-copy)`
256
+ ]
257
+ });
258
+ }
259
+ // Path was provided but doesn't exist on this beacon — log and fall
260
+ // through to the HTTP path so we still satisfy the request.
261
+ pTask.log.warn(`File Transfer: SourceLocalPath [${tmpSourceLocalPath}] does not exist on this beacon, falling back to HTTP transfer.`);
262
+ }
263
+
187
264
  if (!tmpSourceURL)
188
265
  {
189
266
  return fCallback(null, {
190
267
  EventToFire: 'Error',
191
- Outputs: { LocalPath: '', BytesTransferred: 0, DurationMs: 0 },
268
+ Outputs: { LocalPath: '', BytesTransferred: 0, DurationMs: 0, Strategy: '' },
192
269
  Log: ['File Transfer: no SourceURL provided.']
193
270
  });
194
271
  }
@@ -197,7 +274,7 @@ module.exports =
197
274
  {
198
275
  return fCallback(null, {
199
276
  EventToFire: 'Error',
200
- Outputs: { LocalPath: '', BytesTransferred: 0, DurationMs: 0 },
277
+ Outputs: { LocalPath: '', BytesTransferred: 0, DurationMs: 0, Strategy: '' },
201
278
  Log: ['File Transfer: no Filename provided.']
202
279
  });
203
280
  }
@@ -467,7 +544,7 @@ function _pipeToFile(pResponse, pOutputPath, pStartTime, pFilename, pTask, fCall
467
544
 
468
545
  return fCallback(null, {
469
546
  EventToFire: 'Complete',
470
- Outputs: { LocalPath: pOutputPath, BytesTransferred: tmpBytes, DurationMs: tmpDuration },
547
+ Outputs: { LocalPath: pOutputPath, BytesTransferred: tmpBytes, DurationMs: tmpDuration, Strategy: 'http' },
471
548
  Log: [
472
549
  `Downloaded: ${pFilename}`,
473
550
  `Size: ${tmpBytes} bytes`,
@@ -2,7 +2,7 @@
2
2
  "Hash": "file-transfer",
3
3
  "Type": "file-transfer",
4
4
  "Name": "File Transfer",
5
- "Description": "Download a file from a URL to the operation's staging directory. Streams the download — no base64 encoding. Logs bytes transferred and duration for visibility in the manifest.",
5
+ "Description": "Download a file from a URL to the operation's staging directory. Streams the download — no base64 encoding. When SourceLocalPath is provided (set by resolve-address with a shared-fs strategy), the task short-circuits and returns that path directly without copying anything. Logs bytes transferred and duration for visibility in the manifest.",
6
6
  "Category": "platform",
7
7
  "Capability": "Data Transfer",
8
8
  "Action": "Download",
@@ -13,14 +13,17 @@
13
13
  { "Name": "Error", "IsError": true }
14
14
  ],
15
15
  "SettingsInputs": [
16
- { "Name": "SourceURL", "DataType": "String", "Required": true, "Description": "URL to download from" },
17
- { "Name": "Filename", "DataType": "String", "Required": true, "Description": "Filename to save as in the staging directory" },
18
- { "Name": "AffinityKey", "DataType": "String", "Required": false, "Description": "Affinity key for caching (same key skips re-download)" }
16
+ { "Name": "SourceURL", "DataType": "String", "Required": false, "Description": "URL to download from. Required unless SourceLocalPath is provided." },
17
+ { "Name": "SourceLocalPath", "DataType": "String", "Required": false, "Description": "Absolute filesystem path to read directly. When set and the file exists, the HTTP download is skipped entirely and BytesTransferred reports 0. Used by the shared-fs reachability strategy." },
18
+ { "Name": "Filename", "DataType": "String", "Required": false, "Description": "Filename to save as in the staging directory. Required unless SourceLocalPath is provided." },
19
+ { "Name": "AffinityKey", "DataType": "String", "Required": false, "Description": "Affinity key for caching (same key skips re-download)" },
20
+ { "Name": "TimeoutMs", "DataType": "Number", "Required": false, "Description": "HTTP download timeout in milliseconds (default 300000)" }
19
21
  ],
20
22
  "StateOutputs": [
21
- { "Name": "LocalPath", "DataType": "String", "Description": "Absolute path to the downloaded file in staging" },
22
- { "Name": "BytesTransferred", "DataType": "Number", "Description": "Size of the downloaded file in bytes" },
23
- { "Name": "DurationMs", "DataType": "Number", "Description": "Download duration in milliseconds" }
23
+ { "Name": "LocalPath", "DataType": "String", "Description": "Absolute path to the file (downloaded to staging, or pointing at the shared-fs source when no copy was needed)" },
24
+ { "Name": "BytesTransferred", "DataType": "Number", "Description": "Size of the downloaded file in bytes (0 when shared-fs zero-copy was used)" },
25
+ { "Name": "DurationMs", "DataType": "Number", "Description": "Download duration in milliseconds" },
26
+ { "Name": "Strategy", "DataType": "String", "Description": "Which path was taken: 'shared-fs' (zero-copy) or 'http' (downloaded)" }
24
27
  ],
25
- "DefaultSettings": { "SourceURL": "", "Filename": "", "AffinityKey": "" }
28
+ "DefaultSettings": { "SourceURL": "", "SourceLocalPath": "", "Filename": "", "AffinityKey": "", "TimeoutMs": 300000 }
26
29
  }
@@ -2,7 +2,7 @@
2
2
  "Hash": "resolve-address",
3
3
  "Type": "resolve-address",
4
4
  "Name": "Resolve Address",
5
- "Description": "Resolve a universal address (>beacon/context/path) to a concrete URL and metadata, with optional transfer strategy resolution (local/direct/proxy) when a requesting beacon is specified.",
5
+ "Description": "Resolve a universal address (>beacon/context/path) to a concrete URL and metadata, with optional transfer strategy resolution (local/shared-fs/direct/proxy) when a requesting beacon is specified.",
6
6
  "Category": "platform",
7
7
  "Capability": "Address Resolution",
8
8
  "Action": "Resolve",
@@ -24,9 +24,10 @@
24
24
  { "Name": "Context", "DataType": "String", "Description": "Context name (e.g. File, Cache)" },
25
25
  { "Name": "Path", "DataType": "String", "Description": "Path within the context" },
26
26
  { "Name": "Filename", "DataType": "String", "Description": "Filename portion of the path" },
27
- { "Name": "Strategy", "DataType": "String", "Description": "Transfer strategy: local, direct, or proxy" },
27
+ { "Name": "Strategy", "DataType": "String", "Description": "Transfer strategy: local, shared-fs, direct, or proxy" },
28
28
  { "Name": "DirectURL", "DataType": "String", "Description": "Direct beacon-to-beacon URL (when strategy is direct)" },
29
- { "Name": "ProxyURL", "DataType": "String", "Description": "Proxy URL through Ultravisor (when strategy is proxy)" }
29
+ { "Name": "ProxyURL", "DataType": "String", "Description": "Proxy URL through Ultravisor (when strategy is proxy)" },
30
+ { "Name": "LocalPath", "DataType": "String", "Description": "Absolute path to the resource on the shared filesystem (when strategy is shared-fs); empty otherwise" }
30
31
  ],
31
32
  "DefaultSettings": { "Address": "", "Destination": "", "RequestingBeaconID": "" }
32
33
  }