orkestrator 2.25.0-alpha → 2.26.0-alpha

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/dist/client.js CHANGED
@@ -7444,7 +7444,7 @@ import path4 from "path";
7444
7444
  // ../../apps/backend/package.json
7445
7445
  var package_default = {
7446
7446
  name: "@orkestrator/backend",
7447
- version: "2.25.0-alpha",
7447
+ version: "2.26.0-alpha",
7448
7448
  private: true,
7449
7449
  type: "module",
7450
7450
  exports: {
package/dist/main.js CHANGED
@@ -815,7 +815,7 @@ var package_default;
815
815
  var init_package = __esm(() => {
816
816
  package_default = {
817
817
  name: "@orkestrator/backend",
818
- version: "2.25.0-alpha",
818
+ version: "2.26.0-alpha",
819
819
  private: true,
820
820
  type: "module",
821
821
  exports: {
@@ -126665,12 +126665,12 @@ function toNodeProjectSummary(nodeId, raw, index) {
126665
126665
  ref,
126666
126666
  key: projectKey(ref),
126667
126667
  name: boundedText(raw.name) || "Untitled project",
126668
- order: typeof raw.order === "number" ? raw.order : index,
126668
+ order: typeof raw.order === "number" && Number.isFinite(raw.order) ? raw.order : index,
126669
126669
  ...typeof raw.folder === "string" && raw.folder ? { folder: boundedText(raw.folder, 64) } : {},
126670
126670
  ...hint ? { repositoryHint: hint } : {}
126671
126671
  };
126672
126672
  }
126673
- function toNodeEnvironmentSummary(nodeId, raw) {
126673
+ function toNodeEnvironmentSummary(nodeId, raw, index = 0) {
126674
126674
  if (typeof raw.id !== "string" || !raw.id || typeof raw.projectId !== "string")
126675
126675
  return null;
126676
126676
  const ref = environmentRef(nodeId, raw.id);
@@ -126679,6 +126679,7 @@ function toNodeEnvironmentSummary(nodeId, raw) {
126679
126679
  key: environmentKey(ref),
126680
126680
  projectId: raw.projectId,
126681
126681
  name: boundedText(raw.name) || "Environment",
126682
+ order: typeof raw.order === "number" && Number.isFinite(raw.order) ? raw.order : index,
126682
126683
  ...typeof raw.branch === "string" ? { branch: boundedText(raw.branch) } : {},
126683
126684
  status: boundedText(raw.status, 32) || "stopped",
126684
126685
  activity: normalizeActivity(raw.agentActivityState),
@@ -128468,6 +128469,7 @@ class NodeCoordination {
128468
128469
  }
128469
128470
 
128470
128471
  // ../../apps/backend/src/core/nodes/node-portal.ts
128472
+ init_backend_profile();
128471
128473
  init_federation();
128472
128474
  import { randomUUID as randomUUID6 } from "crypto";
128473
128475
 
@@ -128821,6 +128823,7 @@ class NodeReadModel {
128821
128823
  changeTimer = null;
128822
128824
  started = false;
128823
128825
  listeners = new Set;
128826
+ self = { id: null, summary: null, stale: true, reading: null };
128824
128827
  constructor(options2) {
128825
128828
  this.options = options2;
128826
128829
  }
@@ -128855,7 +128858,7 @@ class NodeReadModel {
128855
128858
  this.listeners.add(listener);
128856
128859
  return () => this.listeners.delete(listener);
128857
128860
  }
128858
- async summaries(nodeIds) {
128861
+ async summaries(nodeIds, options2 = {}) {
128859
128862
  const list = await this.options.nodes.listNodes();
128860
128863
  for (const view of list.nodes)
128861
128864
  this.views.set(view.nodeId, view);
@@ -128878,7 +128881,73 @@ class NodeReadModel {
128878
128881
  environments: state ? [...state.environmentsByProject.values()].flat() : []
128879
128882
  });
128880
128883
  }
128881
- return { selfId: await this.options.nodes.selfIdentity(), nodes };
128884
+ const self = options2.includeSelf ? await this.readSelf() : null;
128885
+ return { selfId: await this.options.nodes.selfIdentity(), nodes, ...self ? { self } : {} };
128886
+ }
128887
+ noteLocalEvent(event, payload) {
128888
+ if (event === RESOURCE_CHANGED_EVENT) {
128889
+ const resource = payload?.resource;
128890
+ if (resource !== "project" && resource !== "environment")
128891
+ return;
128892
+ } else if (event !== BACKEND_PROFILE_CHANGED_EVENT) {
128893
+ return;
128894
+ }
128895
+ this.self.stale = true;
128896
+ if (this.self.id)
128897
+ this.noteChanged(this.self.id);
128898
+ }
128899
+ now() {
128900
+ return (this.options.now ?? Date.now)();
128901
+ }
128902
+ readSelf() {
128903
+ const local = this.options.local;
128904
+ if (!local)
128905
+ return Promise.resolve(null);
128906
+ if (this.self.summary && !this.self.stale)
128907
+ return Promise.resolve(this.self.summary);
128908
+ if (this.self.reading)
128909
+ return this.self.reading;
128910
+ this.self.stale = false;
128911
+ const run = (async () => {
128912
+ try {
128913
+ const [nodeId, profile] = await Promise.all([
128914
+ this.options.nodes.selfIdentity(),
128915
+ this.options.nodes.selfProfile()
128916
+ ]);
128917
+ this.self.id = nodeId;
128918
+ const { projects, truncated: projectsTruncated } = await readProjects(nodeId, local.invoke);
128919
+ const environments = await this.readEnvironments(nodeId, local.invoke, projects.map((project) => project.ref.projectId));
128920
+ let truncated = projectsTruncated;
128921
+ const rows = [];
128922
+ for (const project of projects) {
128923
+ const result = environments.get(project.ref.projectId);
128924
+ if (!result) {
128925
+ this.self.stale = true;
128926
+ truncated = true;
128927
+ rows.push(...this.self.summary?.environments.filter((row) => row.projectId === project.ref.projectId) ?? []);
128928
+ continue;
128929
+ }
128930
+ truncated ||= result.truncated;
128931
+ rows.push(...result.environments);
128932
+ }
128933
+ this.self.summary = {
128934
+ nodeId,
128935
+ displayName: profile.displayName,
128936
+ ...profile.colorToken ? { colorToken: profile.colorToken } : {},
128937
+ loadedAt: this.now(),
128938
+ truncated,
128939
+ projects,
128940
+ environments: rows
128941
+ };
128942
+ } catch {
128943
+ this.self.stale = true;
128944
+ }
128945
+ return this.self.summary;
128946
+ })().finally(() => {
128947
+ this.self.reading = null;
128948
+ });
128949
+ this.self.reading = run;
128950
+ return run;
128882
128951
  }
128883
128952
  async sync() {
128884
128953
  if (!this.started)
@@ -128918,7 +128987,7 @@ class NodeReadModel {
128918
128987
  prefixes: NODE_SUMMARY_EVENT_PREFIXES,
128919
128988
  ...this.options.fetchImpl ? { fetchImpl: this.options.fetchImpl } : {},
128920
128989
  onOpen: () => void this.requestHydrate(view.nodeId),
128921
- onEvent: (event, payload) => this.handleEvent(view.nodeId, epoch, event, payload)
128990
+ onEvent: (event, payload) => void this.handleEvent(view.nodeId, epoch, event, payload)
128922
128991
  });
128923
128992
  state.stream.start();
128924
128993
  this.requestHydrate(view.nodeId);
@@ -128958,7 +129027,7 @@ class NodeReadModel {
128958
129027
  }
128959
129028
  return state;
128960
129029
  }
128961
- handleEvent(nodeId, epoch, event, payload) {
129030
+ async handleEvent(nodeId, epoch, event, payload) {
128962
129031
  const state = this.states.get(nodeId);
128963
129032
  if (!state || state.epoch !== epoch)
128964
129033
  return;
@@ -128973,7 +129042,12 @@ class NodeReadModel {
128973
129042
  return;
128974
129043
  }
128975
129044
  if (event === BACKEND_PROFILE_CHANGED_EVENT) {
128976
- this.noteChanged(nodeId);
129045
+ try {
129046
+ await this.options.nodes.recordProfile(nodeId, parseBackendProfile(payload));
129047
+ if (this.started && this.states.get(nodeId) === state && state.epoch === epoch) {
129048
+ this.noteChanged(nodeId);
129049
+ }
129050
+ } catch {}
128977
129051
  return;
128978
129052
  }
128979
129053
  if (event !== RESOURCE_CHANGED_EVENT || !payload || typeof payload !== "object")
@@ -129025,7 +129099,7 @@ class NodeReadModel {
129025
129099
  if (!state || !link?.connected)
129026
129100
  return;
129027
129101
  const epoch = state.epoch;
129028
- const updates = await this.readEnvironments(nodeId, link, projectIds);
129102
+ const updates = await this.readEnvironments(nodeId, (command, args) => link.invoke(command, args), projectIds);
129029
129103
  if (this.states.get(nodeId) !== state || state.epoch !== epoch)
129030
129104
  return;
129031
129105
  for (const [projectId, result] of updates) {
@@ -129060,17 +129134,13 @@ class NodeReadModel {
129060
129134
  return;
129061
129135
  const epoch = state.epoch;
129062
129136
  try {
129063
- const rawProjects = await link.invoke("get_projects");
129064
- const list = Array.isArray(rawProjects) ? rawProjects : [];
129065
- const projects = list.slice(0, MAX_SUMMARY_PROJECTS).flatMap((raw, index) => {
129066
- const project = raw && typeof raw === "object" ? toNodeProjectSummary(state.nodeId, raw, index) : null;
129067
- return project ? [project] : [];
129068
- }).sort((a, b) => a.order - b.order);
129069
- const environments = await this.readEnvironments(state.nodeId, link, projects.map((project) => project.ref.projectId));
129137
+ const invoke = (command, args) => link.invoke(command, args);
129138
+ const { projects, truncated: projectsTruncated } = await readProjects(state.nodeId, invoke);
129139
+ const environments = await this.readEnvironments(state.nodeId, invoke, projects.map((project) => project.ref.projectId));
129070
129140
  if (this.states.get(state.nodeId) !== state || state.epoch !== epoch)
129071
129141
  return;
129072
129142
  const environmentsByProject = new Map;
129073
- let truncated = list.length > MAX_SUMMARY_PROJECTS;
129143
+ let truncated = projectsTruncated;
129074
129144
  let failed = false;
129075
129145
  for (const project of projects) {
129076
129146
  const result = environments.get(project.ref.projectId);
@@ -129098,19 +129168,19 @@ class NodeReadModel {
129098
129168
  this.noteChanged(state.nodeId);
129099
129169
  }
129100
129170
  }
129101
- async readEnvironments(nodeId, link, projectIds) {
129171
+ async readEnvironments(nodeId, invoke, projectIds) {
129102
129172
  const results = new Map;
129103
129173
  let next = 0;
129104
129174
  const worker = async () => {
129105
129175
  while (next < projectIds.length) {
129106
129176
  const projectId = projectIds[next++];
129107
129177
  try {
129108
- const raw = await link.invoke("get_environment_snapshots", { projectId });
129178
+ const raw = await invoke("get_environment_snapshots", { projectId });
129109
129179
  const list = Array.isArray(raw) ? raw : [];
129110
- const environments = list.slice(0, MAX_SUMMARY_ENVIRONMENTS_PER_PROJECT).flatMap((entry) => {
129111
- const environment = entry && typeof entry === "object" ? toNodeEnvironmentSummary(nodeId, entry) : null;
129180
+ const environments = list.flatMap((entry, index) => {
129181
+ const environment = entry && typeof entry === "object" ? toNodeEnvironmentSummary(nodeId, entry, index) : null;
129112
129182
  return environment ? [environment] : [];
129113
- });
129183
+ }).sort((a, b) => a.order - b.order).slice(0, MAX_SUMMARY_ENVIRONMENTS_PER_PROJECT);
129114
129184
  results.set(projectId, {
129115
129185
  environments,
129116
129186
  truncated: list.length > MAX_SUMMARY_ENVIRONMENTS_PER_PROJECT
@@ -129195,6 +129265,15 @@ class NodeReadModel {
129195
129265
  }
129196
129266
  }
129197
129267
  }
129268
+ async function readProjects(nodeId, invoke) {
129269
+ const raw = await invoke("get_projects");
129270
+ const list = Array.isArray(raw) ? raw : [];
129271
+ const projects = list.slice(0, MAX_SUMMARY_PROJECTS).flatMap((entry, index) => {
129272
+ const project = entry && typeof entry === "object" ? toNodeProjectSummary(nodeId, entry, index) : null;
129273
+ return project ? [project] : [];
129274
+ }).sort((a, b) => a.order - b.order);
129275
+ return { projects, truncated: list.length > MAX_SUMMARY_PROJECTS };
129276
+ }
129198
129277
  function safeMessage(error) {
129199
129278
  const message = error instanceof Error ? error.message : String(error);
129200
129279
  return message.replace(/[\u0000-\u001f\u007f]+/g, " ").replace(/Bearer\s+\S+/gi, "Bearer [redacted]").slice(0, 200);
@@ -129555,6 +129634,7 @@ class NodePortal {
129555
129634
  nodes: options2.nodes,
129556
129635
  emit: options2.emit,
129557
129636
  ...options2.summaries ? { persistence: options2.summaries } : {},
129637
+ ...options2.local ? { local: options2.local } : {},
129558
129638
  ...options2.fetchImpl ? { fetchImpl: options2.fetchImpl } : {},
129559
129639
  ...options2.now ? { now: options2.now } : {}
129560
129640
  });
@@ -129573,11 +129653,16 @@ class NodePortal {
129573
129653
  this.grants.revokeAll();
129574
129654
  this.workspaces.clear();
129575
129655
  }
129576
- summaries(nodeIds) {
129656
+ summaries(nodeIds, includeSelf) {
129577
129657
  if (nodeIds !== undefined && (!Array.isArray(nodeIds) || !nodeIds.every(isBackendId))) {
129578
129658
  throw new Error("Expected nodeIds to be node identities.");
129579
129659
  }
129580
- return this.readModel.summaries(nodeIds);
129660
+ if (includeSelf !== undefined && typeof includeSelf !== "boolean") {
129661
+ throw new Error("Expected includeSelf to be a boolean.");
129662
+ }
129663
+ return this.readModel.summaries(nodeIds, {
129664
+ includeSelf: includeSelf === true
129665
+ });
129581
129666
  }
129582
129667
  async invokeOnNode(args) {
129583
129668
  const nodeId = args.nodeId;
@@ -129598,6 +129683,11 @@ class NodePortal {
129598
129683
  await this.afterRouted?.(nodeId, args.command, commandArgs, result.result).catch((error) => {
129599
129684
  console.warn("[nodes] Post-command step on a node failed:", error instanceof Error ? error.name : "unknown");
129600
129685
  });
129686
+ if (args.command === "update_backend_profile") {
129687
+ try {
129688
+ await this.options.nodes.recordProfile(nodeId, parseBackendProfile(result.result));
129689
+ } catch {}
129690
+ }
129601
129691
  this.readModel.requestHydrate(nodeId);
129602
129692
  }
129603
129693
  return result;
@@ -129850,6 +129940,9 @@ class NodeService {
129850
129940
  selfIdentity() {
129851
129941
  return this.self();
129852
129942
  }
129943
+ selfProfile() {
129944
+ return this.storage.getBackendProfile();
129945
+ }
129853
129946
  async status() {
129854
129947
  const registry = await this.currentRegistry();
129855
129948
  const mode = this.mode();
@@ -130490,6 +130583,37 @@ class NodeService {
130490
130583
  console.warn("[nodes] Could not record a node verification:", errorCode(error));
130491
130584
  }
130492
130585
  }
130586
+ async recordProfile(nodeId, profile) {
130587
+ try {
130588
+ await this.mutateRegistry((registry) => {
130589
+ const node = registry.nodes.find((entry) => entry.nodeId === nodeId);
130590
+ const cached = node?.profileCache;
130591
+ if (!node || cached && cached.revision > profile.revision) {
130592
+ return { next: null, result: undefined };
130593
+ }
130594
+ if (cached && cached.revision === profile.revision && cached.displayName === profile.displayName && cached.colorToken === profile.colorToken) {
130595
+ return { next: null, result: undefined };
130596
+ }
130597
+ const updated = {
130598
+ ...node,
130599
+ profileCache: {
130600
+ displayName: profile.displayName,
130601
+ colorToken: profile.colorToken,
130602
+ revision: profile.revision
130603
+ }
130604
+ };
130605
+ return {
130606
+ next: {
130607
+ ...registry,
130608
+ nodes: registry.nodes.map((entry) => entry.nodeId === nodeId ? updated : entry)
130609
+ },
130610
+ result: undefined
130611
+ };
130612
+ });
130613
+ } catch (error) {
130614
+ console.warn("[nodes] Could not record a node profile:", errorCode(error));
130615
+ }
130616
+ }
130493
130617
  async adoptIdentity(provisionalId, installationId) {
130494
130618
  const selfId = await this.self();
130495
130619
  if (installationId === selfId)
@@ -156958,6 +157082,7 @@ function registerBackendProfileCommands(register) {
156958
157082
  register("update_backend_profile", async (args, context) => {
156959
157083
  const profile = await context.storage.updateBackendProfile(parseBackendProfileUpdate(args));
156960
157084
  context.emit(BACKEND_PROFILE_CHANGED_EVENT, profile);
157085
+ context.nodePortal?.readModel.noteLocalEvent(BACKEND_PROFILE_CHANGED_EVENT, profile);
156961
157086
  return profile;
156962
157087
  });
156963
157088
  }
@@ -157062,8 +157187,8 @@ function registerNodeCommands(register) {
157062
157187
  return nodes(context).trustHostKey(args.probeId);
157063
157188
  });
157064
157189
  register("get_node_summaries", async (args, context) => {
157065
- assertOnlyKeys(args, ["nodeIds"], "arguments");
157066
- return portal(context).summaries(args.nodeIds);
157190
+ assertOnlyKeys(args, ["nodeIds", "includeSelf"], "arguments");
157191
+ return portal(context).summaries(args.nodeIds, args.includeSelf);
157067
157192
  });
157068
157193
  register("invoke_on_node", async (args, context) => {
157069
157194
  assertOnlyKeys(args, ["nodeId", "command", "args", "requestKey"], "arguments");
@@ -227349,6 +227474,7 @@ class OrkestratorBackend {
227349
227474
  }
227350
227475
  storage.setResourceChangeListener((change) => {
227351
227476
  options2.emit(RESOURCE_CHANGED_EVENT, change);
227477
+ this.nodePortal?.readModel.noteLocalEvent(RESOURCE_CHANGED_EVENT, change);
227352
227478
  });
227353
227479
  const context = {
227354
227480
  storage,
@@ -227416,6 +227542,7 @@ class OrkestratorBackend {
227416
227542
  this.nodePortal = new NodePortal({
227417
227543
  nodes: this.nodes,
227418
227544
  emit: (event, payload) => options2.emit(event, payload),
227545
+ local: { invoke: (command, args = {}) => this.invoke(command, args) },
227419
227546
  summaries: {
227420
227547
  load: () => storage.loadNodeSummariesCache(),
227421
227548
  save: (value) => storage.saveNodeSummariesCache(value)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "orkestrator",
3
- "version": "2.25.0-alpha",
3
+ "version": "2.26.0-alpha",
4
4
  "description": "Run the Orkestrator backend service without the desktop application",
5
5
  "homepage": "https://orkestrator.dev",
6
6
  "repository": {
@@ -39,8 +39,8 @@
39
39
  "sharp": "0.35.5"
40
40
  },
41
41
  "devDependencies": {
42
- "@orkestrator/protocol": "2.25.0-alpha",
43
- "@orkestrator/toolchain": "2.25.0-alpha"
42
+ "@orkestrator/protocol": "2.26.0-alpha",
43
+ "@orkestrator/toolchain": "2.26.0-alpha"
44
44
  },
45
45
  "engines": {
46
46
  "bun": ">=1.4.3"