orionfold-relay 0.23.0 → 0.24.1

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 (130) hide show
  1. package/dist/cli.js +102 -26
  2. package/next.config.mjs +52 -1
  3. package/package.json +2 -1
  4. package/src/app/api/snapshots/[id]/restore/route.ts +6 -0
  5. package/src/app/api/snapshots/route.ts +6 -0
  6. package/src/app/apps/page.tsx +1 -1
  7. package/src/app/inbox/page.tsx +1 -1
  8. package/src/app/layout.tsx +8 -3
  9. package/src/app/packs/page.tsx +1 -1
  10. package/src/app/projects/[id]/page.tsx +0 -11
  11. package/src/components/apps/last-run-card.tsx +1 -1
  12. package/src/components/apps/ledger-hero-panel.tsx +1 -1
  13. package/src/components/charts/time-series-chart.tsx +1 -1
  14. package/src/components/chat/app-composer-hero.tsx +1 -1
  15. package/src/components/chat/app-materialized-card.tsx +1 -1
  16. package/src/components/chat/app-view-editor-card.tsx +1 -1
  17. package/src/components/chat/chat-command-popover.tsx +2 -2
  18. package/src/components/chat/chat-empty-state.tsx +3 -3
  19. package/src/components/chat/chat-message.tsx +2 -1
  20. package/src/components/chat/chat-session-provider.tsx +2 -2
  21. package/src/components/chat/conversation-template-picker.tsx +1 -1
  22. package/src/components/chat/skill-row.tsx +1 -15
  23. package/src/components/customers/customer-form-sheet.tsx +1 -1
  24. package/src/components/dashboard/greeting.tsx +1 -1
  25. package/src/components/dashboard/welcome-landing.tsx +4 -4
  26. package/src/components/documents/document-upload-dialog.tsx +1 -1
  27. package/src/components/documents/smart-extracted-text.tsx +1 -1
  28. package/src/components/instance/instance-section.tsx +1 -1
  29. package/src/components/instance/upgrade-badge.tsx +1 -1
  30. package/src/components/notifications/batch-proposal-review.tsx +1 -1
  31. package/src/components/onboarding/runtime-preference-modal.tsx +39 -10
  32. package/src/components/packs/pack-update-button.tsx +1 -1
  33. package/src/components/profiles/profile-card.tsx +18 -8
  34. package/src/components/profiles/profile-detail-view.tsx +1 -1
  35. package/src/components/projects/project-create-dialog.tsx +1 -1
  36. package/src/components/projects/project-form-sheet.tsx +2 -2
  37. package/src/components/schedules/schedule-create-dialog.tsx +1 -1
  38. package/src/components/schedules/schedule-create-sheet.tsx +1 -1
  39. package/src/components/schedules/schedule-edit-sheet.tsx +1 -1
  40. package/src/components/schedules/schedule-form.tsx +1 -1
  41. package/src/components/settings/data-management-section.tsx +2 -2
  42. package/src/components/settings/database-snapshots-section.tsx +6 -6
  43. package/src/components/settings/ollama-section.tsx +2 -2
  44. package/src/components/settings/presets-section.tsx +2 -2
  45. package/src/components/settings/providers-runtimes-section.tsx +6 -6
  46. package/src/components/settings/web-search-section.tsx +1 -1
  47. package/src/components/shared/command-palette.tsx +2 -2
  48. package/src/components/shared/trust-tier-badge.tsx +3 -3
  49. package/src/components/shell/app-bar.tsx +156 -164
  50. package/src/components/shell/app-shell.tsx +16 -8
  51. package/src/components/shell/nav-items.ts +72 -30
  52. package/src/components/tasks/ai-assist-panel.tsx +2 -2
  53. package/src/components/tasks/task-create-panel.tsx +1 -1
  54. package/src/components/tasks/task-edit-dialog.tsx +1 -1
  55. package/src/components/workflows/blueprint-gallery.tsx +1 -1
  56. package/src/components/workflows/delay-step-body.tsx +1 -1
  57. package/src/components/workflows/workflow-confirmation-view.tsx +1 -1
  58. package/src/components/workflows/workflow-form-view.tsx +1 -1
  59. package/src/components/workspace/discover-workspace-dialog.tsx +1 -1
  60. package/src/lib/agents/runtime/ollama-adapter.ts +11 -6
  61. package/src/lib/agents/runtime/ollama-model-resolver.ts +76 -0
  62. package/src/lib/agents/runtime/openai-codex.ts +1 -1
  63. package/src/lib/apps/apps-events.ts +15 -0
  64. package/src/lib/apps/use-apps.ts +4 -3
  65. package/src/lib/channels/slack-adapter.ts +1 -1
  66. package/src/lib/channels/telegram-adapter.ts +1 -1
  67. package/src/lib/channels/webhook-adapter.ts +2 -2
  68. package/src/lib/chat/ollama-engine.ts +17 -4
  69. package/src/lib/chat/system-prompt.ts +4 -4
  70. package/src/lib/chat/tool-catalog.ts +3 -3
  71. package/src/lib/chat/tools/app-view-tools.ts +3 -2
  72. package/src/lib/data/seed-data/environment.ts +2 -2
  73. package/src/lib/db/bootstrap.ts +5 -1
  74. package/src/lib/packs/templates/relay-agency/pack.yaml +1 -1
  75. package/src/lib/packs/templates/relay-agency-pro/pack.yaml +32 -16
  76. package/src/lib/plugins/examples/echo-server/plugin.yaml +1 -1
  77. package/src/lib/plugins/examples/finance-pack/plugin.yaml +1 -1
  78. package/src/lib/plugins/examples/reading-radar/plugin.yaml +1 -1
  79. package/src/lib/plugins/registry.ts +1 -1
  80. package/src/lib/plugins/sdk/types.ts +1 -1
  81. package/src/lib/snapshots/snapshot-manager.ts +40 -6
  82. package/src/lib/theme.ts +23 -11
  83. package/src/lib/workflows/blueprints/registry.ts +59 -10
  84. package/src/app/analytics/page.tsx +0 -40
  85. package/src/app/api/environment/artifacts/[id]/route.ts +0 -17
  86. package/src/app/api/environment/artifacts/route.ts +0 -33
  87. package/src/app/api/environment/checkpoints/[id]/route.ts +0 -86
  88. package/src/app/api/environment/checkpoints/route.ts +0 -80
  89. package/src/app/api/environment/profiles/create/route.ts +0 -28
  90. package/src/app/api/environment/profiles/suggest/route.ts +0 -41
  91. package/src/app/api/environment/scan/route.ts +0 -70
  92. package/src/app/api/environment/sync/history/route.ts +0 -20
  93. package/src/app/api/environment/sync/preview/route.ts +0 -27
  94. package/src/app/api/environment/sync/route.ts +0 -43
  95. package/src/app/api/environment/templates/[id]/route.ts +0 -34
  96. package/src/app/api/environment/templates/route.ts +0 -27
  97. package/src/app/environment/compare/page.tsx +0 -20
  98. package/src/app/environment/loading.tsx +0 -56
  99. package/src/app/environment/page.tsx +0 -61
  100. package/src/app/environment/skills/page.tsx +0 -20
  101. package/src/components/analytics/analytics-dashboard.tsx +0 -200
  102. package/src/components/environment/adoption-prompt.tsx +0 -76
  103. package/src/components/environment/artifact-card.tsx +0 -93
  104. package/src/components/environment/artifact-detail-sheet.tsx +0 -136
  105. package/src/components/environment/artifact-presence-cell.tsx +0 -44
  106. package/src/components/environment/category-filter-bar.tsx +0 -132
  107. package/src/components/environment/checkpoint-list.tsx +0 -169
  108. package/src/components/environment/comparison-matrix.tsx +0 -132
  109. package/src/components/environment/environment-dashboard.tsx +0 -248
  110. package/src/components/environment/environment-summary-card.tsx +0 -153
  111. package/src/components/environment/health-score-card.tsx +0 -86
  112. package/src/components/environment/persona-indicator.tsx +0 -43
  113. package/src/components/environment/profile-create-dialog.tsx +0 -178
  114. package/src/components/environment/project-diff-view.tsx +0 -59
  115. package/src/components/environment/project-scan-badge.tsx +0 -36
  116. package/src/components/environment/rollback-confirm-dialog.tsx +0 -105
  117. package/src/components/environment/scan-status-bar.tsx +0 -48
  118. package/src/components/environment/skill-catalog.tsx +0 -117
  119. package/src/components/environment/skill-detail-sheet.tsx +0 -106
  120. package/src/components/environment/skill-drift-indicator.tsx +0 -42
  121. package/src/components/environment/suggested-profiles.tsx +0 -162
  122. package/src/components/environment/summary-cards-row.tsx +0 -91
  123. package/src/components/environment/sync-action-buttons.tsx +0 -63
  124. package/src/components/environment/sync-preview-dialog.tsx +0 -205
  125. package/src/components/environment/template-list.tsx +0 -149
  126. package/src/components/environment/tool-comparison-view.tsx +0 -75
  127. package/src/lib/analytics/queries.ts +0 -207
  128. package/src/lib/environment/comparison.ts +0 -170
  129. package/src/lib/environment/health-scoring.ts +0 -162
  130. package/src/lib/environment/skill-portfolio.ts +0 -97
package/dist/cli.js CHANGED
@@ -464,7 +464,7 @@ function bootstrapAinativeDatabase(sqlite3) {
464
464
  sqlite3.exec(ddl);
465
465
  } catch (err2) {
466
466
  const msg = err2 instanceof Error ? err2.message : String(err2);
467
- if (!msg.includes("duplicate column")) {
467
+ if (!msg.includes("duplicate column") && !msg.includes("no such table")) {
468
468
  console.error("[bootstrap] ALTER TABLE failed:", msg);
469
469
  }
470
470
  }
@@ -1186,7 +1186,7 @@ var CURRENT_PLUGIN_API_VERSION, CAPABILITY_VALUES, ORIGIN_VALUES, PrimitivesBund
1186
1186
  var init_types = __esm({
1187
1187
  "src/lib/plugins/sdk/types.ts"() {
1188
1188
  "use strict";
1189
- CURRENT_PLUGIN_API_VERSION = "0.23";
1189
+ CURRENT_PLUGIN_API_VERSION = "0.24";
1190
1190
  CAPABILITY_VALUES = ["fs", "net", "child_process", "env"];
1191
1191
  ORIGIN_VALUES = ["ainative-internal", "third-party"];
1192
1192
  PrimitivesBundleManifestSchema = z.object({
@@ -5795,6 +5795,13 @@ __export(registry_exports3, {
5795
5795
  import fs11 from "fs";
5796
5796
  import path11 from "path";
5797
5797
  import yaml7 from "js-yaml";
5798
+ function userDirMtimeMs() {
5799
+ try {
5800
+ return fs11.statSync(USER_BLUEPRINTS_DIR).mtimeMs;
5801
+ } catch {
5802
+ return null;
5803
+ }
5804
+ }
5798
5805
  function scanDirectory(dir, isBuiltin2) {
5799
5806
  const blueprints = /* @__PURE__ */ new Map();
5800
5807
  if (!fs11.existsSync(dir)) return blueprints;
@@ -5829,8 +5836,13 @@ function loadAll() {
5829
5836
  return all;
5830
5837
  }
5831
5838
  function ensureLoaded2() {
5839
+ if (blueprintCache && userDirMtimeMs() !== cachedDirMtimeMs) {
5840
+ blueprintCache = null;
5841
+ }
5832
5842
  if (!blueprintCache) {
5843
+ cachedDirMtimeMs = userDirMtimeMs();
5833
5844
  blueprintCache = loadAll();
5845
+ applyPluginBlueprints(blueprintCache);
5834
5846
  }
5835
5847
  return blueprintCache;
5836
5848
  }
@@ -5878,30 +5890,35 @@ function deleteBlueprint(id) {
5878
5890
  function getUserBlueprintsDir() {
5879
5891
  return USER_BLUEPRINTS_DIR;
5880
5892
  }
5893
+ function applyPluginBlueprints(cache) {
5894
+ for (const byId of pluginBlueprints.values()) {
5895
+ for (const [id, bp] of byId) cache.set(id, bp);
5896
+ }
5897
+ }
5881
5898
  function mergePluginBlueprints(entries) {
5882
5899
  const cache = ensureLoaded2();
5883
5900
  for (const entry of entries) {
5884
5901
  cache.set(entry.blueprint.id, entry.blueprint);
5885
- if (!pluginBlueprintIndex.has(entry.pluginId)) {
5886
- pluginBlueprintIndex.set(entry.pluginId, /* @__PURE__ */ new Set());
5902
+ if (!pluginBlueprints.has(entry.pluginId)) {
5903
+ pluginBlueprints.set(entry.pluginId, /* @__PURE__ */ new Map());
5887
5904
  }
5888
- pluginBlueprintIndex.get(entry.pluginId).add(entry.blueprint.id);
5905
+ pluginBlueprints.get(entry.pluginId).set(entry.blueprint.id, entry.blueprint);
5889
5906
  }
5890
5907
  }
5891
5908
  function clearPluginBlueprints(pluginId) {
5892
5909
  const cache = blueprintCache;
5893
- const ids = pluginBlueprintIndex.get(pluginId);
5894
- if (!ids) return;
5895
- if (cache) for (const id of ids) cache.delete(id);
5896
- pluginBlueprintIndex.delete(pluginId);
5910
+ const byId = pluginBlueprints.get(pluginId);
5911
+ if (!byId) return;
5912
+ if (cache) for (const id of byId.keys()) cache.delete(id);
5913
+ pluginBlueprints.delete(pluginId);
5897
5914
  }
5898
5915
  function clearAllPluginBlueprints() {
5899
- for (const pluginId of Array.from(pluginBlueprintIndex.keys())) {
5916
+ for (const pluginId of Array.from(pluginBlueprints.keys())) {
5900
5917
  clearPluginBlueprints(pluginId);
5901
5918
  }
5902
5919
  }
5903
5920
  function listPluginBlueprintIds(pluginId) {
5904
- return Array.from(pluginBlueprintIndex.get(pluginId) ?? []);
5921
+ return Array.from(pluginBlueprints.get(pluginId)?.keys() ?? []);
5905
5922
  }
5906
5923
  function validateBlueprintRefs(bp, opts2) {
5907
5924
  for (const step of bp.steps ?? []) {
@@ -5923,7 +5940,7 @@ function validateBlueprintRefs(bp, opts2) {
5923
5940
  }
5924
5941
  return { ok: true };
5925
5942
  }
5926
- var BUILTINS_DIR2, USER_BLUEPRINTS_DIR, blueprintCache, pluginBlueprintIndex;
5943
+ var BUILTINS_DIR2, USER_BLUEPRINTS_DIR, blueprintCache, cachedDirMtimeMs, pluginBlueprints;
5927
5944
  var init_registry3 = __esm({
5928
5945
  "src/lib/workflows/blueprints/registry.ts"() {
5929
5946
  "use strict";
@@ -5941,7 +5958,8 @@ var init_registry3 = __esm({
5941
5958
  );
5942
5959
  USER_BLUEPRINTS_DIR = getAinativeBlueprintsDir();
5943
5960
  blueprintCache = null;
5944
- pluginBlueprintIndex = /* @__PURE__ */ new Map();
5961
+ cachedDirMtimeMs = null;
5962
+ pluginBlueprints = /* @__PURE__ */ new Map();
5945
5963
  }
5946
5964
  });
5947
5965
 
@@ -12945,7 +12963,7 @@ var init_registry6 = __esm({
12945
12963
  init_registry5();
12946
12964
  init_installer();
12947
12965
  init_schedule_spec();
12948
- SUPPORTED_API_VERSIONS = /* @__PURE__ */ new Set([CURRENT_PLUGIN_API_VERSION, "0.22"]);
12966
+ SUPPORTED_API_VERSIONS = /* @__PURE__ */ new Set([CURRENT_PLUGIN_API_VERSION, "0.23"]);
12949
12967
  pluginCache = null;
12950
12968
  lastLoadedPluginIds = /* @__PURE__ */ new Set();
12951
12969
  PluginTableSchema = z16.object({
@@ -17617,11 +17635,20 @@ var init_plugin_spec_tools = __esm({
17617
17635
  }
17618
17636
  });
17619
17637
 
17638
+ // src/lib/apps/apps-events.ts
17639
+ var APPS_CHANGED_EVENT;
17640
+ var init_apps_events = __esm({
17641
+ "src/lib/apps/apps-events.ts"() {
17642
+ "use strict";
17643
+ APPS_CHANGED_EVENT = "relay-apps-changed";
17644
+ }
17645
+ });
17646
+
17620
17647
  // src/lib/chat/tools/app-view-tools.ts
17621
17648
  import { z as z28 } from "zod";
17622
17649
  function dispatchAppsChangedFromTool() {
17623
17650
  if (typeof window !== "undefined") {
17624
- window.dispatchEvent(new CustomEvent("ainative-apps-changed"));
17651
+ window.dispatchEvent(new CustomEvent(APPS_CHANGED_EVENT));
17625
17652
  }
17626
17653
  }
17627
17654
  function loadManifestOrError(appId) {
@@ -17758,6 +17785,7 @@ var init_app_view_tools = __esm({
17758
17785
  "src/lib/chat/tools/app-view-tools.ts"() {
17759
17786
  "use strict";
17760
17787
  init_tool_registry();
17788
+ init_apps_events();
17761
17789
  init_helpers2();
17762
17790
  init_registry();
17763
17791
  BindingsSchema = ViewSchema.shape.bindings;
@@ -20155,7 +20183,7 @@ async function handleServerRequest(client, taskId, request) {
20155
20183
  contentItems: [
20156
20184
  {
20157
20185
  type: "inputText",
20158
- text: "Dynamic tool calls are not supported by ainative's Codex runtime yet."
20186
+ text: "Dynamic tool calls are not supported by Relay's Codex runtime yet."
20159
20187
  }
20160
20188
  ]
20161
20189
  });
@@ -21698,6 +21726,41 @@ var init_openai_direct = __esm({
21698
21726
  }
21699
21727
  });
21700
21728
 
21729
+ // src/lib/agents/runtime/ollama-model-resolver.ts
21730
+ async function listPulledOllamaModels(baseUrl) {
21731
+ try {
21732
+ const response = await fetch(`${baseUrl}/api/tags`, {
21733
+ signal: AbortSignal.timeout(5e3)
21734
+ });
21735
+ if (!response.ok) return [];
21736
+ const data = await response.json();
21737
+ return (data.models ?? []).map((m) => m.name).filter((n) => typeof n === "string" && n.length > 0);
21738
+ } catch {
21739
+ return [];
21740
+ }
21741
+ }
21742
+ async function resolveOllamaModel(baseUrl, requestedModel, defaultModel) {
21743
+ const explicit = requestedModel?.trim() || defaultModel?.trim();
21744
+ if (explicit) return explicit;
21745
+ const pulled = await listPulledOllamaModels(baseUrl);
21746
+ if (pulled.length > 0) return pulled[0];
21747
+ throw new OllamaModelNotConfiguredError();
21748
+ }
21749
+ var OllamaModelNotConfiguredError;
21750
+ var init_ollama_model_resolver = __esm({
21751
+ "src/lib/agents/runtime/ollama-model-resolver.ts"() {
21752
+ "use strict";
21753
+ OllamaModelNotConfiguredError = class extends Error {
21754
+ constructor(message) {
21755
+ super(
21756
+ message ?? "No Ollama model is configured. Pull a model (e.g. `ollama pull llama3.2`) or set a default in Settings \u2192 Ollama."
21757
+ );
21758
+ this.name = "OllamaModelNotConfiguredError";
21759
+ }
21760
+ };
21761
+ }
21762
+ });
21763
+
21701
21764
  // src/lib/agents/runtime/ollama-adapter.ts
21702
21765
  import { eq as eq38 } from "drizzle-orm";
21703
21766
  async function getOllamaBaseUrl() {
@@ -21706,11 +21769,11 @@ async function getOllamaBaseUrl() {
21706
21769
  const url = await getSetting2(SETTINGS_KEYS2.OLLAMA_BASE_URL);
21707
21770
  return url || DEFAULT_OLLAMA_BASE_URL;
21708
21771
  }
21709
- async function getOllamaModel() {
21772
+ async function getOllamaModel(baseUrl) {
21710
21773
  const { getSetting: getSetting2 } = await Promise.resolve().then(() => (init_helpers(), helpers_exports));
21711
21774
  const { SETTINGS_KEYS: SETTINGS_KEYS2 } = await Promise.resolve().then(() => (init_settings(), settings_exports));
21712
- const model = await getSetting2(SETTINGS_KEYS2.OLLAMA_DEFAULT_MODEL);
21713
- return model || DEFAULT_OLLAMA_MODEL;
21775
+ const defaultModel = await getSetting2(SETTINGS_KEYS2.OLLAMA_DEFAULT_MODEL);
21776
+ return resolveOllamaModel(baseUrl, null, defaultModel);
21714
21777
  }
21715
21778
  async function streamOllamaChat(baseUrl, model, messages, signal) {
21716
21779
  const response = await fetch(`${baseUrl}/api/chat`, {
@@ -21805,7 +21868,7 @@ async function executeOllamaTask(taskId) {
21805
21868
  await db.update(tasks).set({ status: "running", updatedAt: /* @__PURE__ */ new Date() }).where(eq38(tasks.id, taskId));
21806
21869
  const ctx = await buildTaskQueryContext(task, agentProfileId);
21807
21870
  const baseUrl = await getOllamaBaseUrl();
21808
- const modelId = await getOllamaModel();
21871
+ const modelId = await getOllamaModel(baseUrl);
21809
21872
  const messages = [];
21810
21873
  if (ctx.systemInstructions) {
21811
21874
  messages.push({ role: "system", content: ctx.systemInstructions });
@@ -21896,7 +21959,7 @@ async function executeOllamaTask(taskId) {
21896
21959
  }
21897
21960
  async function runOllamaTaskAssist(input) {
21898
21961
  const baseUrl = await getOllamaBaseUrl();
21899
- const modelId = await getOllamaModel();
21962
+ const modelId = await getOllamaModel(baseUrl);
21900
21963
  const profileIds = listProfiles().map((p) => p.id);
21901
21964
  const profileList = profileIds.length > 0 ? `Available agent profiles: ${profileIds.join(", ")}` : "No explicit profiles available.";
21902
21965
  const systemPrompt = `You are an AI task definition assistant. Analyze the given task and return ONLY a JSON object (no markdown) with:
@@ -21952,7 +22015,7 @@ async function testOllamaConnection() {
21952
22015
  };
21953
22016
  }
21954
22017
  }
21955
- var DEFAULT_OLLAMA_BASE_URL, DEFAULT_OLLAMA_MODEL, ollamaRuntimeAdapter;
22018
+ var DEFAULT_OLLAMA_BASE_URL, ollamaRuntimeAdapter;
21956
22019
  var init_ollama_adapter = __esm({
21957
22020
  "src/lib/agents/runtime/ollama-adapter.ts"() {
21958
22021
  "use strict";
@@ -21961,10 +22024,10 @@ var init_ollama_adapter = __esm({
21961
22024
  init_execution_manager();
21962
22025
  init_claude_agent();
21963
22026
  init_catalog2();
22027
+ init_ollama_model_resolver();
21964
22028
  init_registry2();
21965
22029
  init_ledger();
21966
22030
  DEFAULT_OLLAMA_BASE_URL = "http://localhost:11434";
21967
- DEFAULT_OLLAMA_MODEL = "llama3.2";
21968
22031
  ollamaRuntimeAdapter = {
21969
22032
  metadata: getRuntimeCatalogEntry("ollama"),
21970
22033
  async executeTask(taskId) {
@@ -25775,8 +25838,8 @@ import { execFileSync as execFileSync3 } from "child_process";
25775
25838
  import yaml12 from "js-yaml";
25776
25839
  import semver from "semver";
25777
25840
  function relayCoreVersion() {
25778
- if (semver.valid("0.23.0")) {
25779
- return "0.23.0";
25841
+ if (semver.valid("0.24.1")) {
25842
+ return "0.24.1";
25780
25843
  }
25781
25844
  try {
25782
25845
  const root = getAppRoot(import.meta.dirname, 3);
@@ -27147,7 +27210,8 @@ var __dirname = dirname5(fileURLToPath4(import.meta.url));
27147
27210
  var appDir = join21(__dirname, "..");
27148
27211
  var launchCwd2 = process.cwd();
27149
27212
  var _envLocalPath = join21(launchCwd2, ".env.local");
27150
- var _firstRunNeedsEnv = !existsSync13(_envLocalPath) && !process.env.RELAY_DATA_DIR && !isDevMode(launchCwd2);
27213
+ var _hasDataDirFlag = process.argv.slice(2).some((a) => a === "--data-dir" || a.startsWith("--data-dir="));
27214
+ var _firstRunNeedsEnv = !existsSync13(_envLocalPath) && !process.env.RELAY_DATA_DIR && !_hasDataDirFlag && !isDevMode(launchCwd2);
27151
27215
  if (_firstRunNeedsEnv) {
27152
27216
  const folderName = basename6(launchCwd2);
27153
27217
  const autoDataDir = join21(homedir8(), `.${folderName}`);
@@ -27187,6 +27251,18 @@ if (existsSync13(_envLocalPath)) {
27187
27251
  }
27188
27252
  }
27189
27253
  }
27254
+ function scanDataDirFlag(argv) {
27255
+ for (let i = 0; i < argv.length; i++) {
27256
+ const tok = argv[i];
27257
+ if (tok === "--data-dir") return argv[i + 1];
27258
+ if (tok.startsWith("--data-dir=")) return tok.slice("--data-dir=".length);
27259
+ }
27260
+ return void 0;
27261
+ }
27262
+ var _dataDirFlag = scanDataDirFlag(process.argv.slice(2));
27263
+ if (_dataDirFlag) {
27264
+ process.env.RELAY_DATA_DIR = _dataDirFlag;
27265
+ }
27190
27266
  var pkg = JSON.parse(readFileSync9(join21(appDir, "package.json"), "utf-8"));
27191
27267
  function getHelpText() {
27192
27268
  const dir = getAinativeDataDir();
package/next.config.mjs CHANGED
@@ -1,3 +1,9 @@
1
+ import { readFileSync } from "node:fs";
2
+ import {
3
+ PHASE_PRODUCTION_BUILD,
4
+ PHASE_DEVELOPMENT_SERVER,
5
+ } from "next/constants.js";
6
+
1
7
  // When the operator opts into LAN binding (`--hostname` to a non-loopback host,
2
8
  // see bin/cli.ts), the CLI sets RELAY_ALLOW_LAN_ORIGINS=true. In dev mode Next
3
9
  // otherwise blocks cross-origin requests to /_next/* dev assets from the LAN
@@ -16,6 +22,26 @@ const RFC1918_DEV_ORIGINS = [
16
22
  ];
17
23
  const allowLanDevOrigins = process.env.RELAY_ALLOW_LAN_ORIGINS === "true";
18
24
 
25
+ // Build-time core version, mirroring tsup's `define` (tsup.config.ts). tsup
26
+ // only builds the CLI bundle (dist/cli.js), so WITHOUT this the Next.js server
27
+ // leaves `__RELAY_CORE_VERSION__` undefined — relayCoreVersion() then falls to
28
+ // its "0.0.0" default and every /packs UI install is rejected with
29
+ // `requires relay-core >=X, but this install is 0.0.0` (fix-packs-ui-install-
30
+ // core-version). We inject it via `compiler.defineServer` below, which Next
31
+ // applies to BOTH bundlers (webpack for `next build`, Turbopack for `next dev`)
32
+ // so the server resolves the real version exactly as the shipped CLI does.
33
+ // `defineServer` (not `define`) because relayCoreVersion() is server-only —
34
+ // keeps the value out of client bundles. Single source of truth: pkg.version.
35
+ //
36
+ // NOTE: pass the RAW version string, NOT `JSON.stringify(...)`. Next's
37
+ // `compiler.define` takes literal values and quotes them itself, unlike tsup's
38
+ // `define` which takes JS-source text. Stringifying here would inject the
39
+ // double-quoted `"0.23.0"`, which fails `semver.valid()` and silently falls
40
+ // back to the "0.0.0" default — reintroducing the exact bug this fixes.
41
+ const CORE_VERSION_DEFINE = JSON.parse(
42
+ readFileSync(new URL("./package.json", import.meta.url), "utf-8")
43
+ ).version;
44
+
19
45
  /** @type {import('next').NextConfig} */
20
46
  const nextConfig = {
21
47
  serverExternalPackages: ["better-sqlite3", "pdf-parse", "pdfjs-dist"],
@@ -41,4 +67,29 @@ const nextConfig = {
41
67
  },
42
68
  };
43
69
 
44
- export default nextConfig;
70
+ // Phase-aware export. `compiler.defineServer` is a COMPILE-TIME directive — it
71
+ // only does anything while a bundler runs (build + dev). At runtime `next start`
72
+ // re-reads this config but never recompiles, so the define is inert there AND
73
+ // Next's config validator emits a spurious "defineServer.__RELAY_CORE_VERSION__
74
+ // is missing, expected boolean" warning (a false positive from its union-error
75
+ // flattener — the string value is valid; see next/dist/shared/lib/zod.js). That
76
+ // warning would land in every customer's prod server log. So we attach
77
+ // defineServer ONLY in the build/dev phases, keeping the shipped `next start`
78
+ // path warning-free while the version is already baked into `.next`.
79
+ export default function config(phase) {
80
+ if (
81
+ phase === PHASE_PRODUCTION_BUILD ||
82
+ phase === PHASE_DEVELOPMENT_SERVER
83
+ ) {
84
+ return {
85
+ ...nextConfig,
86
+ compiler: {
87
+ ...nextConfig.compiler,
88
+ defineServer: {
89
+ __RELAY_CORE_VERSION__: CORE_VERSION_DEFINE,
90
+ },
91
+ },
92
+ };
93
+ }
94
+ return nextConfig;
95
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "orionfold-relay",
3
- "version": "0.23.0",
3
+ "version": "0.24.1",
4
4
  "description": "Orionfold Relay — a local-first, multi-agent orchestration runtime and builder scaffold for AI-native work.",
5
5
  "keywords": [
6
6
  "ai",
@@ -68,6 +68,7 @@
68
68
  "test:e2e": "vitest run --config vitest.config.e2e.ts",
69
69
  "test:ui": "vitest --ui",
70
70
  "validate:tokens": "npx tsx design-system/validate-tokens.ts",
71
+ "check:price-drift": "node scripts/check-price-drift.mjs",
71
72
  "sync-worktree": "bash bin/sync-worktree.sh",
72
73
  "prepublishOnly": "npm run build:cli"
73
74
  },
@@ -2,6 +2,7 @@ import { NextRequest, NextResponse } from "next/server";
2
2
  import {
3
3
  restoreFromSnapshot,
4
4
  isSnapshotLocked,
5
+ SnapshotBusyError,
5
6
  } from "@/lib/snapshots/snapshot-manager";
6
7
  import { db } from "@/lib/db";
7
8
  import { tasks } from "@/lib/db/schema";
@@ -51,6 +52,11 @@ export async function POST(
51
52
  "Restore complete. Please restart the server to load the restored database.",
52
53
  });
53
54
  } catch (error) {
55
+ // Lock contention is a 409, not a server error — never conflate it with a
56
+ // genuine failure (issue #24).
57
+ if (error instanceof SnapshotBusyError) {
58
+ return NextResponse.json({ error: error.message }, { status: 409 });
59
+ }
54
60
  return NextResponse.json(
55
61
  {
56
62
  error:
@@ -4,6 +4,7 @@ import {
4
4
  listSnapshots,
5
5
  getSnapshotsSize,
6
6
  isSnapshotLocked,
7
+ SnapshotBusyError,
7
8
  } from "@/lib/snapshots/snapshot-manager";
8
9
 
9
10
  /** GET /api/snapshots — list all snapshots with disk usage */
@@ -46,6 +47,11 @@ export async function POST(req: NextRequest) {
46
47
 
47
48
  return NextResponse.json(snapshot, { status: 201 });
48
49
  } catch (error) {
50
+ // A lock grabbed between the isSnapshotLocked() check and createSnapshot is
51
+ // still contention, not a server error (issue #24).
52
+ if (error instanceof SnapshotBusyError) {
53
+ return NextResponse.json({ error: error.message }, { status: 409 });
54
+ }
49
55
  return NextResponse.json(
50
56
  { error: error instanceof Error ? error.message : "Failed to create snapshot" },
51
57
  { status: 500 }
@@ -100,7 +100,7 @@ function EmptyHero({ starters }: { starters: ReturnType<typeof listStarters> })
100
100
  <Sparkles className="h-10 w-10 text-primary mb-4" aria-hidden="true" />
101
101
  <h2 className="text-lg font-semibold">Teach this instance a new job.</h2>
102
102
  <p className="mt-2 max-w-md text-sm text-muted-foreground">
103
- Describe the thing you do every week. Orionfold Relay composes a profile, blueprint, schedule, and tables into a running app no code, no deploys.
103
+ Describe the thing you do every week. Orionfold Relay composes a profile, blueprint, schedule, and tables into a running app. No code, no deploys.
104
104
  </p>
105
105
  <div className="mt-4 flex items-center gap-4">
106
106
  <Link
@@ -69,7 +69,7 @@ export default async function InboxPage() {
69
69
  return (
70
70
  <PageShell
71
71
  title="Inbox"
72
- description="Governance command center review approvals, questions, and agent activity."
72
+ description="Governance command center. Review approvals, questions, and agent activity."
73
73
  >
74
74
  <GovernanceStats
75
75
  pending={pendingCount}
@@ -12,6 +12,7 @@ import { ChatSessionProvider } from "@/components/chat/chat-session-provider";
12
12
  import { RuntimePreferenceBootstrapper } from "@/components/onboarding/runtime-preference-bootstrapper";
13
13
  import {
14
14
  DEFAULT_THEME,
15
+ LEGACY_THEME_COOKIE,
15
16
  THEME_COOKIE,
16
17
  isResolvedTheme,
17
18
  type ResolvedTheme,
@@ -82,12 +83,16 @@ export default async function RootLayout({
82
83
  }: {
83
84
  children: React.ReactNode;
84
85
  }) {
85
- // Resolve theme server-side from the ainative-theme cookie. Every client-side
86
+ // Resolve theme server-side from the relay-theme cookie. Every client-side
86
87
  // theme toggle writes this cookie (see src/lib/theme.ts), so SSR stays in
87
88
  // sync with the user's preference and there is no FOUC — and no pre-hydration
88
- // <script> tag, which is what React 19 warns about.
89
+ // <script> tag, which is what React 19 warns about. Fall back to the legacy
90
+ // ainative-theme cookie so a returning pre-rebrand user does not flash the
91
+ // default theme before the client re-writes the new cookie.
89
92
  const cookieStore = await cookies();
90
- const cookieValue = cookieStore.get(THEME_COOKIE)?.value;
93
+ const cookieValue =
94
+ cookieStore.get(THEME_COOKIE)?.value ??
95
+ cookieStore.get(LEGACY_THEME_COOKIE)?.value;
91
96
  const theme: ResolvedTheme = isResolvedTheme(cookieValue)
92
97
  ? cookieValue
93
98
  : DEFAULT_THEME;
@@ -81,7 +81,7 @@ export default async function PacksPage({
81
81
  return (
82
82
  <PageShell
83
83
  title="Packs"
84
- description="Vertical content bundles an app, profiles, blueprints, tables, and seed data installed in one step."
84
+ description="Vertical content bundles. An app, profiles, blueprints, tables, and seed data installed in one step."
85
85
  filters={
86
86
  templates.length > 1 ? (
87
87
  <FilterChips active={filter} counts={counts} />
@@ -11,7 +11,6 @@ import Link from "next/link";
11
11
  import { FileText } from "lucide-react";
12
12
  import { Sparkline } from "@/components/charts/sparkline";
13
13
  import { getProjectCompletionTrend } from "@/lib/queries/chart-data";
14
- import { EnvironmentSummaryCard } from "@/components/environment/environment-summary-card";
15
14
 
16
15
  export const dynamic = "force-dynamic";
17
16
 
@@ -160,16 +159,6 @@ export default async function ProjectDetailPage({
160
159
  </div>
161
160
  )}
162
161
 
163
- {/* Environment summary */}
164
- {project.workingDirectory && (
165
- <div className="mb-6">
166
- <EnvironmentSummaryCard
167
- projectId={id}
168
- workingDirectory={project.workingDirectory}
169
- />
170
- </div>
171
- )}
172
-
173
162
  {/* Recent documents */}
174
163
  {recentDocs.length > 0 && (
175
164
  <div className="mb-6">
@@ -100,7 +100,7 @@ function HeroVariant({ task, previousRuns }: HeroProps) {
100
100
  if (!task) {
101
101
  return (
102
102
  <div className="surface-card rounded-xl p-6 text-center text-muted-foreground border">
103
- No digest yet click <strong>Run now</strong> to generate the first one.
103
+ No digest yet. Click <strong>Run now</strong> to generate the first one.
104
104
  </div>
105
105
  );
106
106
  }
@@ -15,7 +15,7 @@ export function LedgerHeroPanel({ series, categories, period }: LedgerHeroPanelP
15
15
  if (series.length === 0 && categories.length === 0) {
16
16
  return (
17
17
  <div className="surface-card rounded-xl p-12 text-center text-muted-foreground border">
18
- No data yet add transactions or click <strong>Run now</strong> to ingest a CSV.
18
+ No data yet. Add transactions or click <strong>Run now</strong> to ingest a CSV.
19
19
  </div>
20
20
  );
21
21
  }
@@ -39,7 +39,7 @@ export function TimeSeriesChart({
39
39
  style={{ height }}
40
40
  data-chart-height={String(height)}
41
41
  >
42
- No data yet runs will populate this chart
42
+ No data yet. Runs will populate this chart.
43
43
  </div>
44
44
  );
45
45
  }
@@ -16,7 +16,7 @@ const EXAMPLE_PROMPTS: { label: string; prompt: string }[] = [
16
16
  {
17
17
  label: "Build me a reading log…",
18
18
  prompt:
19
- "Build me a reading log. Track each book with title, author, date finished, and a 15 rating. Every Friday at 5pm, summarize what I read this week.",
19
+ "Build me a reading log. Track each book with title, author, date finished, and a rating from 1 to 5. Every Friday at 5pm, summarize what I read this week.",
20
20
  },
21
21
  {
22
22
  label: "Build me an expense tracker for my contractors…",
@@ -137,7 +137,7 @@ export function AppMaterializedCard({
137
137
  ))}
138
138
  </ul>
139
139
  <p className="text-[11px] text-muted-foreground/60 pt-1">
140
- Informational these files are written under your control. No approval required.
140
+ Informational. These files are written under your control. No approval required.
141
141
  </p>
142
142
  </div>
143
143
  )}
@@ -193,7 +193,7 @@ export function AppViewEditorCard({
193
193
  </p>
194
194
  )}
195
195
  {status === "cancelled" && (
196
- <p className="text-xs text-muted-foreground">Cancelled no changes written.</p>
196
+ <p className="text-xs text-muted-foreground">Cancelled. No changes written.</p>
197
197
  )}
198
198
  {status === "failed" && errorMessage && (
199
199
  <p className="text-xs text-destructive">Failed: {errorMessage}</p>
@@ -176,7 +176,7 @@ export function ChatCommandPopover({
176
176
  const activeCount = Array.isArray(body.activeSkillIds)
177
177
  ? (body.activeSkillIds as string[]).length
178
178
  : 1;
179
- toast.success(`Added ${skillName} ${activeCount} skill${activeCount !== 1 ? "s" : ""} active`);
179
+ toast.success(`Added ${skillName}. ${activeCount} skill${activeCount !== 1 ? "s" : ""} active`);
180
180
  },
181
181
  [conversationId, refetchActive]
182
182
  );
@@ -494,7 +494,7 @@ function ToolCatalogItems({
494
494
  const showDisabled = !isActive && !canAdd && !!onAddSkill;
495
495
  const disabledReason = atCapacity
496
496
  ? `Max ${resolvedMax} skills active`
497
- : "Single skill only on this runtime switch runtime to compose";
497
+ : "Single skill only on this runtime. Switch runtime to compose";
498
498
 
499
499
  return (
500
500
  <SkillRow
@@ -97,10 +97,10 @@ export function ChatEmptyState({
97
97
  <div className="flex h-12 w-12 items-center justify-center rounded-xl bg-primary/10">
98
98
  <Bot className="h-6 w-6 text-primary" />
99
99
  </div>
100
- <h2 className="text-lg font-semibold">Describe an app. Orionfold Relay builds it.</h2>
100
+ <h2 className="text-lg font-semibold">Describe an app. Relay builds it.</h2>
101
101
  <p className="text-sm text-muted-foreground text-center max-w-md">
102
- Profiles, blueprints, tables, and schedules — composed from a single prompt.
103
- Or ask anything about your workspace.
102
+ One prompt builds the whole app: profiles, blueprints, tables, and
103
+ schedules. Or ask anything about your workspace.
104
104
  </p>
105
105
  </div>
106
106
 
@@ -18,6 +18,7 @@ import {
18
18
  import { AlertCircle } from "lucide-react";
19
19
  import { resolveModelLabel, type ChatQuestion, type QuickAccessItem, type ScreenshotAttachment } from "@/lib/chat/types";
20
20
  import type { ComposedAppSummary } from "@/lib/apps/composition-detector";
21
+ import { APPS_CHANGED_EVENT } from "@/lib/apps/apps-events";
21
22
 
22
23
  interface ExtensionFallbackSummary {
23
24
  plugin: CreatePluginSpecInputForCard;
@@ -56,7 +57,7 @@ function ComposedAppCard({ app }: { app: ComposedAppSummary }) {
56
57
  });
57
58
  if (res.ok) {
58
59
  setAppStatus("undone");
59
- window.dispatchEvent(new CustomEvent("ainative-apps-changed"));
60
+ window.dispatchEvent(new CustomEvent(APPS_CHANGED_EVENT));
60
61
  }
61
62
  }, [app.appId]);
62
63
 
@@ -331,7 +331,7 @@ export function ChatSessionProvider({ children }: { children: ReactNode }) {
331
331
  void createConversation();
332
332
  };
333
333
  const handleCompact = () => {
334
- toast.info("Compact is not wired yet coming soon.");
334
+ toast.info("Compact is not wired yet. Coming soon.");
335
335
  };
336
336
  const handleExport = async () => {
337
337
  const activeConversationId = activeIdRef.current;
@@ -339,7 +339,7 @@ export function ChatSessionProvider({ children }: { children: ReactNode }) {
339
339
  ? messagesByConversationRef.current[activeConversationId]
340
340
  : undefined;
341
341
  if (!msgs || msgs.length === 0) {
342
- toast.error("Nothing to export this conversation is empty.");
342
+ toast.error("Nothing to export. This conversation is empty.");
343
343
  return;
344
344
  }
345
345
  const title = `Chat — ${new Date().toISOString().slice(0, 10)}`;
@@ -325,7 +325,7 @@ function ParameterForm({
325
325
  if (blueprint.variables.length === 0) {
326
326
  return (
327
327
  <p className="text-sm text-muted-foreground">
328
- This blueprint has no parameters starting a conversation now.
328
+ This blueprint has no parameters. Starting a conversation now.
329
329
  </p>
330
330
  );
331
331
  }