@nosana/node 1.1.71 → 1.1.73-rc

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 (74) hide show
  1. package/README.md +2 -2
  2. package/dist/.env +2 -0
  3. package/dist/package.json +9 -6
  4. package/dist/{src/NodeManager/provider/containerOrchestration → packages/container-orchestrator/src}/docker/createDockerRunOptions.d.ts +1 -1
  5. package/dist/{src/NodeManager/provider/containerOrchestration → packages/container-orchestrator/src}/docker/createDockerRunOptions.js +5 -1
  6. package/dist/{src/NodeManager/provider/containerOrchestration → packages/container-orchestrator/src}/docker/dockerExtended/index.d.ts +3 -0
  7. package/dist/{src/NodeManager/provider/containerOrchestration → packages/container-orchestrator/src}/docker/dockerExtended/index.js +12 -5
  8. package/dist/{src/NodeManager/provider/containerOrchestration → packages/container-orchestrator/src}/docker/index.d.ts +11 -10
  9. package/dist/{src/NodeManager/provider/containerOrchestration → packages/container-orchestrator/src}/docker/index.js +18 -37
  10. package/dist/packages/container-orchestrator/src/hooks.d.ts +32 -0
  11. package/dist/packages/container-orchestrator/src/index.d.ts +17 -0
  12. package/dist/packages/container-orchestrator/src/index.js +16 -0
  13. package/dist/{src/NodeManager/provider/containerOrchestration → packages/container-orchestrator/src}/interface.d.ts +14 -5
  14. package/dist/packages/container-orchestrator/src/interface.js +1 -0
  15. package/dist/{src/NodeManager/provider/containerOrchestration → packages/container-orchestrator/src}/labels.d.ts +2 -0
  16. package/dist/{src/NodeManager/provider/containerOrchestration → packages/container-orchestrator/src}/labels.js +2 -0
  17. package/dist/{src/NodeManager/provider/containerOrchestration → packages/container-orchestrator/src}/podman/index.d.ts +3 -3
  18. package/dist/{src/NodeManager/provider/containerOrchestration → packages/container-orchestrator/src}/podman/index.js +7 -17
  19. package/dist/{src/NodeManager/provider/containerOrchestration → packages/container-orchestrator/src}/podman/utils/createPodmanRunOptions.d.ts +12 -0
  20. package/dist/{src/NodeManager/provider/containerOrchestration → packages/container-orchestrator/src}/podman/utils/createPodmanRunOptions.js +4 -2
  21. package/dist/packages/container-orchestrator/src/selectContainerOrchestration.d.ts +3 -0
  22. package/dist/{src/NodeManager/provider/containerOrchestration → packages/container-orchestrator/src}/selectContainerOrchestration.js +3 -3
  23. package/dist/packages/container-orchestrator/src/types.d.ts +15 -0
  24. package/dist/packages/container-orchestrator/src/types.js +1 -0
  25. package/dist/packages/container-orchestrator/src/utils/ifStringCastToArray.d.ts +2 -0
  26. package/dist/packages/container-orchestrator/src/utils/ifStringCastToArray.js +2 -0
  27. package/dist/src/NodeManager/configs/configs.d.ts +6 -0
  28. package/dist/src/NodeManager/configs/configs.js +6 -0
  29. package/dist/src/NodeManager/index.js +7 -0
  30. package/dist/src/NodeManager/node/Node.d.ts +5 -0
  31. package/dist/src/NodeManager/node/Node.js +23 -2
  32. package/dist/src/NodeManager/node/health/healthHandler.d.ts +1 -1
  33. package/dist/src/NodeManager/node/resource/definition/index.d.ts +0 -1
  34. package/dist/src/NodeManager/node/resource/definition/index.js +0 -1
  35. package/dist/src/NodeManager/node/resource/helpers/createHFArgs.d.ts +1 -1
  36. package/dist/src/NodeManager/node/resource/helpers/createHFArgs.js +2 -3
  37. package/dist/src/NodeManager/node/resource/helpers/createOllamaArgs.d.ts +1 -1
  38. package/dist/src/NodeManager/node/resource/helpers/createOllamaArgs.js +2 -3
  39. package/dist/src/NodeManager/node/resource/helpers/createS3Args.d.ts +1 -1
  40. package/dist/src/NodeManager/node/resource/helpers/createS3Args.js +3 -3
  41. package/dist/src/NodeManager/node/resource/image/imageManager.d.ts +1 -1
  42. package/dist/src/NodeManager/node/resource/resourceManager.d.ts +1 -1
  43. package/dist/src/NodeManager/node/resource/volume/volumeManager.d.ts +1 -1
  44. package/dist/src/NodeManager/node/resource/volume/volumeManager.js +11 -8
  45. package/dist/src/NodeManager/node/task/TaskManager.js +25 -0
  46. package/dist/src/NodeManager/provider/ContainerStateManager.js +1 -1
  47. package/dist/src/NodeManager/provider/Provider.d.ts +34 -10
  48. package/dist/src/NodeManager/provider/Provider.js +217 -93
  49. package/dist/src/NodeManager/provider/orchestrationHooks.d.ts +8 -0
  50. package/dist/src/NodeManager/provider/orchestrationHooks.js +13 -0
  51. package/dist/src/NodeManager/provider/sshSidecar.d.ts +2 -2
  52. package/dist/src/NodeManager/provider/sshSidecar.js +2 -2
  53. package/dist/src/NodeManager/provider/types.d.ts +0 -9
  54. package/dist/src/NodeManager/provider/webTerminal.d.ts +1 -1
  55. package/dist/src/NodeManager/provider/webTerminal.js +1 -1
  56. package/dist/src/NodeManager/utils/utils.d.ts +1 -7
  57. package/dist/src/NodeManager/utils/utils.js +1 -7
  58. package/dist/src/cli/commands/prune/action.js +3 -2
  59. package/dist/src/cli/commands/prune/command.js +1 -1
  60. package/dist/src/cli/commands/run/action.js +3 -2
  61. package/dist/src/cli/commands/run/command.js +1 -1
  62. package/dist/src/cli/commands/start/command.js +1 -1
  63. package/npm-shrinkwrap.json +21 -2
  64. package/package.json +9 -6
  65. package/dist/src/NodeManager/provider/containerOrchestration/docker/utils/createServerObject.d.ts +0 -5
  66. package/dist/src/NodeManager/provider/containerOrchestration/docker/utils/createServerObject.js +0 -15
  67. package/dist/src/NodeManager/provider/containerOrchestration/docker/utils/deadline.d.ts +0 -1
  68. package/dist/src/NodeManager/provider/containerOrchestration/docker/utils/deadline.js +0 -15
  69. package/dist/src/NodeManager/provider/containerOrchestration/selectContainerOrchestration.d.ts +0 -3
  70. /package/dist/{src/NodeManager/provider/containerOrchestration → packages/container-orchestrator/src}/docker/utils/repoTagsContainsImage.d.ts +0 -0
  71. /package/dist/{src/NodeManager/provider/containerOrchestration → packages/container-orchestrator/src}/docker/utils/repoTagsContainsImage.js +0 -0
  72. /package/dist/{src/NodeManager/provider/containerOrchestration/interface.js → packages/container-orchestrator/src/hooks.js} +0 -0
  73. /package/dist/{src/NodeManager → packages/container-orchestrator/src}/utils/liveAbortSignal.d.ts +0 -0
  74. /package/dist/{src/NodeManager → packages/container-orchestrator/src}/utils/liveAbortSignal.js +0 -0
@@ -16,6 +16,12 @@ export const configs = (options) => {
16
16
  api: {
17
17
  port: parseInt(loadConfigurationValue('API_PORT')),
18
18
  },
19
+ jobRuntime: {
20
+ containerImage: loadConfigurationValue('JOB_RUNTIME_IMAGE'),
21
+ },
22
+ resourceManager: {
23
+ containerImage: loadConfigurationValue('RESOURCE_MANAGER_IMAGE'),
24
+ },
19
25
  minDiskSpace: parseInt(loadConfigurationValue('MIN_DISK_SPACE')),
20
26
  network: options?.network ?? nodeConfigs.getNetwork(),
21
27
  };
@@ -138,6 +138,13 @@ export default class NodeManager {
138
138
  if (market) {
139
139
  await this.node.setup(market);
140
140
  }
141
+ /**
142
+ * job runtime image
143
+ *
144
+ * pulled here, with or without a requested market, so a job's sidecar
145
+ * never waits on a registry
146
+ */
147
+ await this.node.pullJobRuntimeImage();
141
148
  /**
142
149
  * this variable was added to know when the node has gone past the setup/checks stages
143
150
  * and is now starting job work and queueing,
@@ -34,6 +34,11 @@ export declare class BasicNode {
34
34
  clean(): Promise<void>;
35
35
  start(): Promise<void>;
36
36
  setup(market: Market): Promise<void>;
37
+ /**
38
+ * Best effort: on failure jobs run without the sidecar, so it is shown to the
39
+ * operator and reported off the node rather than stopping the node.
40
+ */
41
+ pullJobRuntimeImage(): Promise<void>;
37
42
  run(): Promise<void>;
38
43
  pending(): Promise<boolean>;
39
44
  queue(market?: string): Promise<void>;
@@ -13,13 +13,15 @@ import { KeyHandler } from './key/keyHandler.js';
13
13
  import { ExpiryHandler } from './expiry/expiryHandler.js';
14
14
  import { GridHandler } from './grid/gridHandler.js';
15
15
  import { ResourceManager } from './resource/resourceManager.js';
16
- import { selectContainerOrchestrationProvider } from '../provider/containerOrchestration/selectContainerOrchestration.js';
16
+ import { selectContainerOrchestrationProvider } from '../../../packages/container-orchestrator/src/index.js';
17
+ import { nodeOrchestrationHooks } from '../provider/orchestrationHooks.js';
17
18
  import { RegisterHandler } from './register/index.js';
18
19
  import { BalanceHandler } from './balance/balanceHandler.js';
19
20
  import { abortControllerSelector, nodeAbortControllerSelector, } from './abort/abortControllerSelector.js';
20
21
  import { pollForRun } from './utils/poll.js';
21
22
  import { configs } from '../configs/configs.js';
22
23
  import { TaskManagerRegistry } from './task/TaskManagerRegistry.js';
24
+ import { reportError } from '../monitoring/reportError.js';
23
25
  import { StopReasons } from './task/TaskManager.js';
24
26
  export class BasicNode {
25
27
  constructor(options) {
@@ -30,7 +32,7 @@ export class BasicNode {
30
32
  this.sdk = getSDK();
31
33
  const db = new DB(options.config).db;
32
34
  this.repository = new NodeRepository(db);
33
- this.containerOrchestration = selectContainerOrchestrationProvider(options.provider, options.podman, options.gpu, options.trustedExecutionRuntime, this.repository);
35
+ this.containerOrchestration = selectContainerOrchestrationProvider(options.provider, options.podman, options.gpu, options.trustedExecutionRuntime, nodeOrchestrationHooks(this.repository));
34
36
  this.resourceManager = new ResourceManager(this.containerOrchestration, this.repository);
35
37
  this.provider = new Provider(this.containerOrchestration, this.repository, this.resourceManager);
36
38
  this.apiHandler = new ApiHandler(this.sdk, this.repository, this.provider, options.port);
@@ -117,6 +119,25 @@ export class BasicNode {
117
119
  await this.resourceManager.resyncResourcesDB();
118
120
  await this.resourceManager.fetchMarketRequiredResources(market);
119
121
  }
122
+ /**
123
+ * Best effort: on failure jobs run without the sidecar, so it is shown to the
124
+ * operator and reported off the node rather than stopping the node.
125
+ */
126
+ async pullJobRuntimeImage() {
127
+ try {
128
+ await this.provider.pullJobRuntimeImage();
129
+ }
130
+ catch (error) {
131
+ const failure = error instanceof Error ? error : new Error(String(error));
132
+ console.error(`Failed to pull the job runtime image, jobs will run without it: ${failure.message}`);
133
+ void reportError({
134
+ error_type: 'jobRuntimeImagePull',
135
+ error_name: failure.name,
136
+ error_message: failure.message,
137
+ error_stack: failure.stack ?? '',
138
+ });
139
+ }
140
+ }
120
141
  run() {
121
142
  return new Promise(async (resolve, reject) => {
122
143
  try {
@@ -1,7 +1,7 @@
1
1
  import { Market, Client as SDK } from '@nosana/sdk';
2
2
  import { MarketHandler } from '../market/marketHandler.js';
3
3
  import { KeyHandler } from '../key/keyHandler.js';
4
- import { ContainerOrchestrationInterface } from '../../provider/containerOrchestration/interface.js';
4
+ import { ContainerOrchestrationInterface } from '../../../../packages/container-orchestrator/src/index.js';
5
5
  export declare class HealthHandler {
6
6
  private sdk;
7
7
  private containerOrchestration;
@@ -1,2 +1 @@
1
- export declare const s3HelperImage = "registry.hub.docker.com/nosana/resource-manager:v1.1.19";
2
1
  export declare const nosanaBucket = "https://models.nosana.io";
@@ -1,2 +1 @@
1
- export const s3HelperImage = 'registry.hub.docker.com/nosana/resource-manager:v1.1.19';
2
1
  export const nosanaBucket = 'https://models.nosana.io';
@@ -3,4 +3,4 @@ export declare const createHFArgs: (volumeName: string, hf: {
3
3
  repo: string;
4
4
  revision?: string;
5
5
  files?: string[];
6
- }, accessToken?: string | undefined) => ContainerCreateOptions;
6
+ }, accessToken: string | undefined, image: string) => ContainerCreateOptions;
@@ -1,5 +1,4 @@
1
- import { s3HelperImage } from '../definition/index.js';
2
- export const createHFArgs = (volumeName, hf, accessToken) => ({
1
+ export const createHFArgs = (volumeName, hf, accessToken, image) => ({
3
2
  AttachStdin: false,
4
3
  AttachStdout: true,
5
4
  AttachStderr: true,
@@ -12,7 +11,7 @@ export const createHFArgs = (volumeName, hf, accessToken) => ({
12
11
  hf.revision ?? (hf.files ? 'main' : ''),
13
12
  hf.files ? hf.files.join(',') : '',
14
13
  ],
15
- Image: s3HelperImage,
14
+ Image: image,
16
15
  Env: accessToken ? [`HUGGING_FACE_TOKEN=${accessToken}`] : undefined,
17
16
  HostConfig: {
18
17
  Mounts: [
@@ -1,2 +1,2 @@
1
1
  import { ContainerCreateOptions } from 'dockerode';
2
- export declare const createOllamaArgs: (volumeName: string, model: string) => ContainerCreateOptions;
2
+ export declare const createOllamaArgs: (volumeName: string, model: string, image: string) => ContainerCreateOptions;
@@ -1,5 +1,4 @@
1
- import { s3HelperImage } from '../definition/index.js';
2
- export const createOllamaArgs = (volumeName, model) => ({
1
+ export const createOllamaArgs = (volumeName, model, image) => ({
3
2
  AttachStdin: false,
4
3
  AttachStdout: true,
5
4
  AttachStderr: true,
@@ -7,7 +6,7 @@ export const createOllamaArgs = (volumeName, model) => ({
7
6
  OpenStdin: false,
8
7
  StdinOnce: false,
9
8
  Cmd: ['Ollama', model],
10
- Image: s3HelperImage,
9
+ Image: image,
11
10
  HostConfig: {
12
11
  Mounts: [
13
12
  {
@@ -4,4 +4,4 @@ export declare const createS3Args: (volumeName: string, s3: {
4
4
  url: string;
5
5
  files?: string[];
6
6
  bucket?: string;
7
- }, s3Auth: S3Auth | undefined) => ContainerCreateOptions;
7
+ }, s3Auth: S3Auth | undefined, image: string) => ContainerCreateOptions;
@@ -1,5 +1,5 @@
1
- import { nosanaBucket, s3HelperImage } from '../definition/index.js';
2
- export const createS3Args = (volumeName, s3, s3Auth) => ({
1
+ import { nosanaBucket } from '../definition/index.js';
2
+ export const createS3Args = (volumeName, s3, s3Auth, image) => ({
3
3
  AttachStdin: false,
4
4
  AttachStdout: true,
5
5
  AttachStderr: true,
@@ -11,7 +11,7 @@ export const createS3Args = (volumeName, s3, s3Auth) => ({
11
11
  s3.url.replace('s3://nos-ai-models-qllsn32u', nosanaBucket),
12
12
  s3.files ? s3.files.join(',') : '',
13
13
  ],
14
- Image: s3HelperImage,
14
+ Image: image,
15
15
  Env: s3Auth
16
16
  ? [
17
17
  s3.bucket ? `BUCKET=${s3.bucket}` : '',
@@ -1,5 +1,5 @@
1
1
  import { DockerAuth } from '@nosana/sdk';
2
- import { ContainerOrchestrationInterface } from '../../../provider/containerOrchestration/interface.js';
2
+ import { ContainerOrchestrationInterface } from '../../../../../packages/container-orchestrator/src/index.js';
3
3
  import { NodeRepository } from '../../../repository/NodeRepository.js';
4
4
  /**
5
5
  * An image the market requires, and the registry credentials it takes to pull
@@ -1,6 +1,6 @@
1
1
  import { Market, RequiredResource } from '@nosana/sdk';
2
2
  import { Resource } from '@nosana/sdk/dist/types/resources.js';
3
- import { ContainerOrchestrationInterface } from '../../provider/containerOrchestration/interface.js';
3
+ import { ContainerOrchestrationInterface } from '../../../../packages/container-orchestrator/src/index.js';
4
4
  import { NodeRepository } from '../../repository/NodeRepository.js';
5
5
  import { ImageManager, RequiredImage } from './image/imageManager.js';
6
6
  import { JobContext, VolumeManager } from './volume/volumeManager.js';
@@ -1,7 +1,7 @@
1
1
  import { ContainerInspectInfo } from 'dockerode';
2
2
  import { Resource } from '@nosana/sdk/dist/types/resources.js';
3
3
  import { RequiredResource } from '@nosana/sdk';
4
- import { ContainerOrchestrationInterface } from '../../../provider/containerOrchestration/interface.js';
4
+ import { ContainerOrchestrationInterface } from '../../../../../packages/container-orchestrator/src/index.js';
5
5
  import { NodeRepository } from '../../../repository/NodeRepository.js';
6
6
  /**
7
7
  * Renders the runtime state that says why the download stopped, for inclusion in
@@ -5,12 +5,13 @@ import { ProgressBarReporter } from '../../utils/progressBarReporter.js';
5
5
  import { createResourceName } from '../helpers/createResourceName.js';
6
6
  import { hasDockerVolume } from '../helpers/hasDockerVolume.js';
7
7
  import { hoursSinceDate } from '../helpers/hoursSunceDate.js';
8
- import { nosanaBucket, s3HelperImage } from '../definition/index.js';
8
+ import { configs } from '../../../configs/configs.js';
9
+ import { nosanaBucket } from '../definition/index.js';
9
10
  import { convertFromBytes } from '../../utils/convertFromBytes.js';
10
11
  import { createHFArgs } from '../helpers/createHFArgs.js';
11
12
  import { createS3Args } from '../helpers/createS3Args.js';
12
13
  import { createOllamaArgs } from '../helpers/createOllamaArgs.js';
13
- import { liveAbortSignal } from '../../../utils/liveAbortSignal.js';
14
+ import { liveAbortSignal } from '../../../../../packages/container-orchestrator/src/index.js';
14
15
  import { formatTime } from '../../../utils/formatTime.js';
15
16
  /**
16
17
  * Renders the runtime state that says why the download stopped, for inclusion in
@@ -100,9 +101,10 @@ export class VolumeManager {
100
101
  async pullMarketRequiredVolumes(remoteResources) {
101
102
  this.fetched = true;
102
103
  this.market_required_volumes = remoteResources;
103
- const hasResourceManagerImage = await this.containerOrchestration.hasImage(s3HelperImage);
104
+ const resourceManagerImage = configs().resourceManager.containerImage;
105
+ const hasResourceManagerImage = await this.containerOrchestration.hasImage(resourceManagerImage);
104
106
  if (!hasResourceManagerImage) {
105
- await this.containerOrchestration.pullImage(s3HelperImage);
107
+ await this.containerOrchestration.pullImage(resourceManagerImage);
106
108
  }
107
109
  const controller = new AbortController();
108
110
  const savedVolumes = this.repository.getVolumesResources();
@@ -199,6 +201,7 @@ export class VolumeManager {
199
201
  project,
200
202
  });
201
203
  }
204
+ const resourceManagerImage = configs().resourceManager.containerImage;
202
205
  switch (resource.type) {
203
206
  case 'S3':
204
207
  const s3Resource = resource;
@@ -208,12 +211,12 @@ export class VolumeManager {
208
211
  url: s3Resource.url,
209
212
  files: s3Resource.files,
210
213
  bucket: s3Resource.bucket,
211
- }, s3Resource.IAM);
214
+ }, s3Resource.IAM, resourceManagerImage);
212
215
  await this.runResourceManagerContainer(volumeName, resourceName, args, controller, sync, job);
213
216
  }
214
217
  else {
215
218
  for (const bucket of s3Resource.buckets) {
216
- const args = createS3Args(volumeName, { url: bucket.url, files: bucket.files }, s3Resource.IAM);
219
+ const args = createS3Args(volumeName, { url: bucket.url, files: bucket.files }, s3Resource.IAM, resourceManagerImage);
217
220
  await this.runResourceManagerContainer(volumeName, resourceName, args, controller, sync, job);
218
221
  }
219
222
  }
@@ -226,7 +229,7 @@ export class VolumeManager {
226
229
  case 'HF':
227
230
  try {
228
231
  const { repo, revision, files: hfFiles, accessToken, } = resource;
229
- const args = createHFArgs(volumeName, { repo, revision, files: hfFiles }, accessToken);
232
+ const args = createHFArgs(volumeName, { repo, revision, files: hfFiles }, accessToken, resourceManagerImage);
230
233
  await this.runResourceManagerContainer(volumeName, resourceName, args, controller, sync, job);
231
234
  this.setVolume(resourceName, volumeName, project);
232
235
  }
@@ -237,7 +240,7 @@ export class VolumeManager {
237
240
  case 'Ollama':
238
241
  try {
239
242
  const { model } = resource;
240
- const args = createOllamaArgs(volumeName, model);
243
+ const args = createOllamaArgs(volumeName, model, resourceManagerImage);
241
244
  await this.runResourceManagerContainer(volumeName, resourceName, args, controller, sync, job);
242
245
  this.setVolume(resourceName, volumeName, project);
243
246
  }
@@ -43,6 +43,19 @@ export const OperationProgressStatuses = {
43
43
  PENDING: 'pending',
44
44
  INIT: 'init',
45
45
  };
46
+ /**
47
+ * The job runtime sidecar is best effort: its failure is reported off the
48
+ * node (the provider's logging proxy has the entry too) and the job runs on.
49
+ */
50
+ function reportJobRuntimeSidecarFailure(stage, error) {
51
+ const failure = error instanceof Error ? error : new Error(String(error));
52
+ void reportError({
53
+ error_type: stage === 'start' ? 'jobRuntimeSidecarStart' : 'jobRuntimeSidecarStop',
54
+ error_name: failure.name,
55
+ error_message: failure.message,
56
+ error_stack: failure.stack ?? '',
57
+ });
58
+ }
46
59
  export default class TaskManager {
47
60
  constructor(provider, repository, job, project, definition) {
48
61
  this.provider = provider;
@@ -298,6 +311,11 @@ export default class TaskManager {
298
311
  });
299
312
  // update all operations status to pending since we have started
300
313
  [...this.opMap.keys()].forEach((op) => this.operationStatus.set(op, OperationProgressStatuses.PENDING));
314
+ // Best effort. After the ops are pending, so a stop that lands during
315
+ // it marks them stopped and the loop skips them.
316
+ await this.provider
317
+ .startJobRuntimeSidecar(flow)
318
+ .catch((error) => reportJobRuntimeSidecarFailure('start', error));
301
319
  // Loop through execution plan, group by group
302
320
  for (const p of this.executionPlan) {
303
321
  this.currentGroup = p.group;
@@ -403,6 +421,9 @@ export default class TaskManager {
403
421
  for (const op of this.opMap.values()) {
404
422
  closingOperationPromises.push(this.provider.stopTaskManagerOperation(flow, op));
405
423
  }
424
+ closingOperationPromises.push(this.provider
425
+ .stopJobRuntimeSidecar(this.job)
426
+ .catch((error) => reportJobRuntimeSidecarFailure('stop', error)));
406
427
  await Promise.all(closingOperationPromises);
407
428
  }
408
429
  })();
@@ -439,6 +460,10 @@ export default class TaskManager {
439
460
  // We ignore start errors during shutdown — cleanup should proceed regardless
440
461
  }
441
462
  }
463
+ // start() normally removed it already.
464
+ await this.provider
465
+ .stopJobRuntimeSidecar(this.job)
466
+ .catch((error) => reportJobRuntimeSidecarFailure('stop', error));
442
467
  }
443
468
  finally {
444
469
  // Cleanup the lock regardless of outcome
@@ -2,7 +2,7 @@ import { Readable } from 'stream';
2
2
  import { createInterface } from 'readline';
3
3
  import { parseLogFrames } from './utils/parseBuffer.js';
4
4
  import { parseDockerStat } from './utils/parseDockerStat.js';
5
- import { liveAbortSignal } from '../utils/liveAbortSignal.js';
5
+ import { liveAbortSignal } from '../../../packages/container-orchestrator/src/index.js';
6
6
  function destroyStream(stream) {
7
7
  if (!stream)
8
8
  return;
@@ -3,13 +3,14 @@ import { Flow, Operation, OperationArgsMap, OperationType } from '@nosana/sdk';
3
3
  import EventEmitter from 'events';
4
4
  import { NodeRepository } from '../repository/NodeRepository.js';
5
5
  import { ResourceManager } from '../node/resource/resourceManager.js';
6
- import { ContainerOrchestrationInterface } from './containerOrchestration/interface.js';
6
+ import { ContainerOrchestrationInterface } from '../../../packages/container-orchestrator/src/index.js';
7
7
  import { type TerminalSession } from './webTerminal.js';
8
8
  export declare class Provider {
9
9
  containerOrchestration: ContainerOrchestrationInterface;
10
10
  private repository;
11
11
  private resourceManager;
12
12
  private readonly frpcImage;
13
+ private jobRuntimeVolumesSwept;
13
14
  private readonly sshAccess;
14
15
  private proxyStartupAbortController;
15
16
  constructor(containerOrchestration: ContainerOrchestrationInterface, repository: NodeRepository, resourceManager: ResourceManager);
@@ -17,11 +18,41 @@ export declare class Provider {
17
18
  stopReverseProxyApi(address: string): Promise<boolean>;
18
19
  setUpReverseProxyApi(address: string, port: string): Promise<boolean>;
19
20
  generateApiFrpcProxies(address: string, port: string): any[];
21
+ /**
22
+ * Starts the job's API sidecar (packages/job-runtime) and publishes it at
23
+ * https://<jobId>.<frp server>. The sidecar serves the job API's runtime
24
+ * routes from the container runtime alone; the node's own API is unchanged.
25
+ *
26
+ * Throws on failure; the task manager treats it as best effort and the job
27
+ * runs on without it. Only onchain jobs get one. The job handler registers
28
+ * those in the task manager registry, and a market benchmark or
29
+ * `nosana node run` never does.
30
+ */
31
+ startJobRuntimeSidecar(flow: Flow): Promise<void>;
32
+ /**
33
+ * Pulls the job runtime image, so the next job's sidecar finds it local.
34
+ * Runs before each job loop; a cached image costs no registry round trip.
35
+ */
36
+ pullJobRuntimeImage(): Promise<void>;
37
+ /**
38
+ * Removes the job's API sidecar, its proxy and its volume. Throws on
39
+ * failure; a container that will not go stays tracked for the next resync.
40
+ */
41
+ stopJobRuntimeSidecar(flowId: string): Promise<void>;
42
+ /** Removes the sidecar and its proxy by name; one already gone is fine. */
43
+ private removeJobRuntimeContainers;
44
+ /**
45
+ * Removes job-runtime-<jobId> volumes left by a job whose stop never ran (the
46
+ * node died mid-job). The volume resync does not know them, and a reaped
47
+ * sidecar container leaves its named volume behind. Runs once, on the first
48
+ * sidecar start after boot. A job still registered keeps its volume, and a
49
+ * volume still in use fails to delete and is kept for the next boot.
50
+ */
51
+ private removeOrphanedJobRuntimeVolumes;
20
52
  /**
21
53
  * Opens an interactive PTY exec session into a running job container for the
22
54
  * wallet-authorized web terminal. The authorization check is performed by the
23
- * caller (the terminal websocket route); this only enforces that the feature
24
- * is enabled and that we can reach the runtime over a socket.
55
+ * caller (the terminal websocket route).
25
56
  */
26
57
  createJobTerminalSession(jobId: string, options?: {
27
58
  op?: string;
@@ -31,13 +62,6 @@ export declare class Provider {
31
62
  authorizeSshKeyForJob(jobId: string, publicKey: string, expiresAt?: string): Promise<void>;
32
63
  listSshKeysForJob(jobId: string): Promise<import("../db/index.js").SshKeyGrant[]>;
33
64
  revokeSshKeyForJob(jobId: string, publicKey: string): Promise<void>;
34
- /**
35
- * SSH requires the runtime to be reached over a socket the sidecar can mount
36
- * to exec into the workload. Every SSH entry point, including the web
37
- * terminal, checks this and nothing else.
38
- */
39
- private get sshEnabled();
40
- private assertSshEnabled;
41
65
  /** The SSH sidecars of a job, one per operation, created ones included. */
42
66
  private getSshSidecars;
43
67
  private hostSocketPath?;