orionfold-relay 0.22.0 → 0.23.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -104,7 +104,7 @@ The governance is *in* the workflow, not bolted on. A blueprint is a fixed step
104
104
  ## Why it stays trustworthy
105
105
 
106
106
  - **Local-first** — SQLite database, no cloud dependency, `npx orionfold-relay` and go
107
- - **Never phones home** — no telemetry, no update checks, no license server; the complete outbound-network inventory is documented and code-linked in [docs/trust/data-flow.md](docs/trust/data-flow.md)
107
+ - **Relay never sends your data to Orionfold** — no telemetry, no update checks, no license server; the complete outbound-network inventory is documented and code-linked in [docs/trust/data-flow.md](docs/trust/data-flow.md)
108
108
  - **Your rules, enforced** — tool permissions, inbox approvals, and audit trails for every agent action
109
109
  - **Your AI team** — 21 specialist profiles ready to deploy, each with instructions, tool policies, and runtime tuning
110
110
  - **Know what you spend** — usage metering, budgets, and per-provider/per-model spend visibility on governed runs
@@ -134,8 +134,8 @@ relay license remove <license-id> # forget a license
134
134
 
135
135
  - **Verification is 100% offline** — an Ed25519 signature check against keys embedded in
136
136
  this repo ([`src/lib/licensing/verify.ts`](src/lib/licensing/verify.ts)). Relay never
137
- phones home: no activation server, no telemetry, no network call of any kind. Works
138
- air-gapped.
137
+ sends your data to Orionfold: no activation server, no telemetry, no network call of
138
+ any kind. Works air-gapped.
139
139
  - **Your packs are yours forever. Renewal gets you the year's new and updated packs +
140
140
  priority support.** An expired or removed license never re-locks content you already
141
141
  installed — it only gates new premium installs and updates.
package/dist/cli.js CHANGED
@@ -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.22";
1189
+ CURRENT_PLUGIN_API_VERSION = "0.23";
1190
1190
  CAPABILITY_VALUES = ["fs", "net", "child_process", "env"];
1191
1191
  ORIGIN_VALUES = ["ainative-internal", "third-party"];
1192
1192
  PrimitivesBundleManifestSchema = z.object({
@@ -3739,9 +3739,25 @@ var init_format = __esm({
3739
3739
  /**
3740
3740
  * Premium display copy (D6). Offline strings rendered on the locked
3741
3741
  * gallery card — the Website still owns actual pricing. Meaningful only
3742
- * alongside `entitlement`; harmless on a free pack.
3742
+ * alongside `entitlement`; harmless on a free pack. Either a flat string
3743
+ * ("$499/year") or a two-phase offer ({ list, intro?, note? }) so a
3744
+ * founding/introductory price can render alongside the list price.
3745
+ * Render sites consume `packPrice()` — never branch on the raw shape.
3743
3746
  */
3744
- price: z3.string().min(1).optional(),
3747
+ price: z3.union([
3748
+ z3.string().min(1),
3749
+ z3.object({
3750
+ list: z3.string().min(1),
3751
+ intro: z3.string().min(1).optional(),
3752
+ note: z3.string().min(1).optional()
3753
+ }).strict()
3754
+ ]).optional(),
3755
+ /**
3756
+ * Card identity token — a lucide icon name rendered on the gallery card
3757
+ * (e.g. "briefcase"). Unknown tokens fall back to the default glyph;
3758
+ * never a remote asset.
3759
+ */
3760
+ icon: z3.string().min(1).optional(),
3745
3761
  /** Get-license CTA target on the locked card. */
3746
3762
  purchaseUrl: z3.url().optional(),
3747
3763
  /**
@@ -6910,6 +6926,34 @@ var init_output_scanner = __esm({
6910
6926
  }
6911
6927
  });
6912
6928
 
6929
+ // src/lib/agents/runtime/model-preference.ts
6930
+ var model_preference_exports = {};
6931
+ __export(model_preference_exports, {
6932
+ resolvePreferredModel: () => resolvePreferredModel
6933
+ });
6934
+ async function resolvePreferredModel(runtimeId, options) {
6935
+ if (options?.pinnedModelId) {
6936
+ return { modelId: options.pinnedModelId, source: "pin" };
6937
+ }
6938
+ const models = getRuntimeCatalogEntry(runtimeId).models;
6939
+ const preference = await getModelPreference();
6940
+ const tierModel = preference === "balanced" ? models.tiers?.balanced : preference === "cost" ? models.tiers?.fast : preference === "quality" ? models.tiers?.quality : void 0;
6941
+ if (tierModel) {
6942
+ return { modelId: tierModel, source: "preference" };
6943
+ }
6944
+ return {
6945
+ modelId: models.tiers?.quality ?? models.default,
6946
+ source: "default"
6947
+ };
6948
+ }
6949
+ var init_model_preference = __esm({
6950
+ "src/lib/agents/runtime/model-preference.ts"() {
6951
+ "use strict";
6952
+ init_catalog2();
6953
+ init_helpers();
6954
+ }
6955
+ });
6956
+
6913
6957
  // src/lib/agents/runtime/claude-sdk.ts
6914
6958
  function buildClaudeSdkEnv(authEnv) {
6915
6959
  const { CLAUDECODE, ANTHROPIC_API_KEY, ...cleanEnv } = process.env;
@@ -7679,6 +7723,9 @@ async function deriveUsageCostMicros(input) {
7679
7723
  if (!input.modelId) {
7680
7724
  return { costMicros: null, pricingVersion: null };
7681
7725
  }
7726
+ if (input.providerId === "ollama") {
7727
+ return { costMicros: 0, pricingVersion: "local-free" };
7728
+ }
7682
7729
  if (input.providerId !== "anthropic" && input.providerId !== "openai") {
7683
7730
  return { costMicros: null, pricingVersion: null };
7684
7731
  }
@@ -12898,7 +12945,7 @@ var init_registry6 = __esm({
12898
12945
  init_registry5();
12899
12946
  init_installer();
12900
12947
  init_schedule_spec();
12901
- SUPPORTED_API_VERSIONS = /* @__PURE__ */ new Set([CURRENT_PLUGIN_API_VERSION, "0.21"]);
12948
+ SUPPORTED_API_VERSIONS = /* @__PURE__ */ new Set([CURRENT_PLUGIN_API_VERSION, "0.22"]);
12902
12949
  pluginCache = null;
12903
12950
  lastLoadedPluginIds = /* @__PURE__ */ new Set();
12904
12951
  PluginTableSchema = z16.object({
@@ -18074,13 +18121,17 @@ ${learnedCtx}
18074
18121
  You are operating inside a git worktree (branch: ${ws.gitBranch ?? "unknown"}). All file operations MUST use paths relative to the working directory: ${cwd}. Do NOT navigate to or create files in the main repository directory.` : "";
18075
18122
  const systemInstructions = [worktreeNote, profileInstructions, learnedCtxBlock, docContext, tableContext, outputInstructions].filter(Boolean).join("\n\n");
18076
18123
  const maxTurns = profile?.maxTurns ?? DEFAULT_MAX_TURNS;
18124
+ const { modelId } = await resolvePreferredModel("claude-code", {
18125
+ pinnedModelId: profile?.capabilityOverrides?.["claude-code"]?.modelId
18126
+ });
18077
18127
  return {
18078
18128
  userPrompt: basePrompt,
18079
18129
  systemInstructions,
18080
18130
  cwd,
18081
18131
  payload,
18082
18132
  maxTurns,
18083
- canUseToolPolicy: payload?.canUseToolPolicy
18133
+ canUseToolPolicy: payload?.canUseToolPolicy,
18134
+ modelId
18084
18135
  };
18085
18136
  }
18086
18137
  async function executeClaudeTask(taskId) {
@@ -18126,6 +18177,10 @@ async function executeClaudeTask(taskId) {
18126
18177
  prompt: ctx.userPrompt,
18127
18178
  options: {
18128
18179
  abortController,
18180
+ // Explicit model: profile pin > onboarding preference > quality
18181
+ // default. Omitting this let the SDK pick ITS default (Opus) and
18182
+ // silently bill the wrong tier.
18183
+ model: ctx.modelId,
18129
18184
  includePartialMessages: true,
18130
18185
  cwd: ctx.cwd,
18131
18186
  env: buildClaudeSdkEnv(authEnv),
@@ -18248,6 +18303,9 @@ async function resumeClaudeTask(taskId) {
18248
18303
  options: {
18249
18304
  resume: task.sessionId,
18250
18305
  abortController,
18306
+ // Same model resolution as the original run — a resume must not
18307
+ // silently hop tiers (profile pin > preference > quality default).
18308
+ model: ctx.modelId,
18251
18309
  includePartialMessages: true,
18252
18310
  cwd: ctx.cwd,
18253
18311
  env: buildClaudeSdkEnv(authEnv),
@@ -18366,6 +18424,7 @@ var init_claude_agent = __esm({
18366
18424
  init_context_builder2();
18367
18425
  init_output_scanner();
18368
18426
  init_registry2();
18427
+ init_model_preference();
18369
18428
  init_compatibility();
18370
18429
  init_claude_sdk();
18371
18430
  init_types2();
@@ -18384,9 +18443,8 @@ var init_claude_agent = __esm({
18384
18443
  // src/lib/agents/runtime/claude.ts
18385
18444
  import { query as query2 } from "@anthropic-ai/claude-agent-sdk";
18386
18445
  import { eq as eq34 } from "drizzle-orm";
18387
- function claudeCodeModelAlias() {
18388
- const models = getRuntimeCatalogEntry("claude-code").models;
18389
- return models.tiers?.quality ?? models.default;
18446
+ async function claudeCodeModelAlias() {
18447
+ return (await resolvePreferredModel("claude-code")).modelId;
18390
18448
  }
18391
18449
  function buildTaskAssistSystemPrompt(profileIds) {
18392
18450
  const profileList = profileIds.length > 0 ? `Available agent profiles: ${profileIds.join(", ")}
@@ -18480,7 +18538,7 @@ Provide a brief analysis (2-3 paragraphs max). Include specific terminology rele
18480
18538
  prompt,
18481
18539
  options: {
18482
18540
  abortController,
18483
- model: claudeCodeModelAlias(),
18541
+ model: await claudeCodeModelAlias(),
18484
18542
  includePartialMessages: true,
18485
18543
  env: buildClaudeSdkEnv(authEnv),
18486
18544
  allowedTools: []
@@ -18621,7 +18679,7 @@ async function runMetaCompletion(input) {
18621
18679
  prompt: input.prompt,
18622
18680
  options: {
18623
18681
  abortController,
18624
- model: claudeCodeModelAlias(),
18682
+ model: await claudeCodeModelAlias(),
18625
18683
  includePartialMessages: true,
18626
18684
  cwd: getLaunchCwd(),
18627
18685
  env: buildClaudeSdkEnv(authEnv),
@@ -18801,7 +18859,7 @@ ${userMessage}`;
18801
18859
  prompt,
18802
18860
  options: {
18803
18861
  abortController,
18804
- model: claudeCodeModelAlias(),
18862
+ model: await claudeCodeModelAlias(),
18805
18863
  includePartialMessages: true,
18806
18864
  cwd: getLaunchCwd(),
18807
18865
  env: buildClaudeSdkEnv(authEnv),
@@ -18876,7 +18934,7 @@ ${userMessage}`;
18876
18934
  prompt,
18877
18935
  options: {
18878
18936
  abortController,
18879
- model: claudeCodeModelAlias(),
18937
+ model: await claudeCodeModelAlias(),
18880
18938
  includePartialMessages: true,
18881
18939
  cwd: getLaunchCwd(),
18882
18940
  env: buildClaudeSdkEnv(authEnv),
@@ -18941,7 +18999,7 @@ async function testClaudeConnection() {
18941
18999
  prompt: "Reply with exactly: OK",
18942
19000
  options: {
18943
19001
  abortController,
18944
- model: claudeCodeModelAlias(),
19002
+ model: await claudeCodeModelAlias(),
18945
19003
  maxTurns: 1,
18946
19004
  includePartialMessages: false,
18947
19005
  cwd: getLaunchCwd(),
@@ -18988,6 +19046,7 @@ var init_claude = __esm({
18988
19046
  init_compatibility();
18989
19047
  init_claude_agent();
18990
19048
  init_catalog2();
19049
+ init_model_preference();
18991
19050
  init_claude_sdk();
18992
19051
  init_workspace_context();
18993
19052
  init_helpers();
@@ -20703,6 +20762,24 @@ async function withAnthropicDirectMcpServers(profileServers, browserServers, ext
20703
20762
  relay: relayServer
20704
20763
  };
20705
20764
  }
20765
+ function mcpServersToAnthropicConnectors(mergedServers) {
20766
+ const connectors = [];
20767
+ for (const [name, config] of Object.entries(mergedServers)) {
20768
+ if (name === "relay") continue;
20769
+ const cfg = config;
20770
+ const url = cfg.url ?? cfg.server_url;
20771
+ if (typeof url !== "string" || url.length === 0) {
20772
+ continue;
20773
+ }
20774
+ const connector = { type: "url", url, name };
20775
+ for (const [key, value] of Object.entries(cfg)) {
20776
+ if (["url", "server_url", "command", "args", "env"].includes(key)) continue;
20777
+ if (typeof value === "string") connector[key] = value;
20778
+ }
20779
+ connectors.push(connector);
20780
+ }
20781
+ return connectors;
20782
+ }
20706
20783
  async function getAnthropicSDK() {
20707
20784
  if (!_sdk) {
20708
20785
  _sdk = await import("@anthropic-ai/sdk");
@@ -20769,7 +20846,7 @@ async function callAnthropicModel(client, systemPrompt, messages, tools, signal,
20769
20846
  budget_tokens: options.extendedThinking.budgetTokens ?? 1e4
20770
20847
  };
20771
20848
  }
20772
- if (options.mcpServers && Object.keys(options.mcpServers).length > 0) {
20849
+ if (options.mcpServers && options.mcpServers.length > 0) {
20773
20850
  params.mcp_servers = options.mcpServers;
20774
20851
  }
20775
20852
  const stream = client.messages.stream(params, { signal });
@@ -20874,6 +20951,7 @@ ${outputInstructions}`;
20874
20951
  pluginServers,
20875
20952
  task.projectId
20876
20953
  );
20954
+ const mcpConnectors = mcpServersToAnthropicConnectors(mergedMcpServers);
20877
20955
  let initialMessages;
20878
20956
  if (isResume) {
20879
20957
  const snapshot = await loadSessionSnapshot(taskId);
@@ -20882,7 +20960,8 @@ ${outputInstructions}`;
20882
20960
  initialMessages = [{ role: "user", content: ctx.userPrompt }];
20883
20961
  }
20884
20962
  const { getSetting: getSetting2 } = await Promise.resolve().then(() => (init_helpers(), helpers_exports));
20885
- const modelId = await getSetting2("anthropic_direct_model") ?? getRuntimeCatalogEntry("anthropic-direct").models.default;
20963
+ const { resolvePreferredModel: resolvePreferredModel2 } = await Promise.resolve().then(() => (init_model_preference(), model_preference_exports));
20964
+ const modelId = await getSetting2("anthropic_direct_model") ?? (await resolvePreferredModel2("anthropic-direct")).modelId;
20886
20965
  const maxTurns = ctx.maxTurns ?? DEFAULT_MAX_TURNS;
20887
20966
  await db.insert(agentLogs).values({
20888
20967
  id: crypto.randomUUID(),
@@ -20921,7 +21000,7 @@ ${outputInstructions}`;
20921
21000
  enableCaching: true,
20922
21001
  profileInstructions: profile?.skillMd,
20923
21002
  extendedThinking: capOverrides?.extendedThinking,
20924
- mcpServers: mergedMcpServers
21003
+ mcpServers: mcpConnectors
20925
21004
  }
20926
21005
  );
20927
21006
  await saveSessionSnapshot(taskId, agentProfileId, [
@@ -21353,7 +21432,8 @@ ${outputInstructions}`;
21353
21432
  );
21354
21433
  const pluginMcpTools = mcpServersToOpenAiTools(mergedMcpServers);
21355
21434
  const { getSetting: getSetting2 } = await Promise.resolve().then(() => (init_helpers(), helpers_exports));
21356
- const modelId = await getSetting2("openai_direct_model") ?? getRuntimeCatalogEntry("openai-direct").models.default;
21435
+ const { resolvePreferredModel: resolvePreferredModel2 } = await Promise.resolve().then(() => (init_model_preference(), model_preference_exports));
21436
+ const modelId = await getSetting2("openai_direct_model") ?? (await resolvePreferredModel2("openai-direct")).modelId;
21357
21437
  const maxTurns = ctx.maxTurns ?? DEFAULT_MAX_TURNS;
21358
21438
  let previousResponseId = null;
21359
21439
  if (isResume) {
@@ -22193,6 +22273,17 @@ async function getUsageAggregates(policy, runtimeStates, now = /* @__PURE__ */ n
22193
22273
  runtimes[runtimeId].daily.totalTokens += row.totalTokens ?? 0;
22194
22274
  }
22195
22275
  });
22276
+ const metered = {
22277
+ daily: { costMicros: 0, totalTokens: 0 },
22278
+ monthly: { costMicros: 0, totalTokens: 0 }
22279
+ };
22280
+ for (const runtimeId of SUPPORTED_AGENT_RUNTIMES) {
22281
+ metered.daily.costMicros += runtimes[runtimeId].daily.costMicros;
22282
+ metered.daily.totalTokens += runtimes[runtimeId].daily.totalTokens;
22283
+ metered.monthly.costMicros += runtimes[runtimeId].monthly.costMicros;
22284
+ metered.monthly.totalTokens += runtimes[runtimeId].monthly.totalTokens;
22285
+ }
22286
+ let planPricedMonthlyMicros = null;
22196
22287
  if (runtimeStates["claude-code"].billingMode === "subscription") {
22197
22288
  const planPriceUsd = await getClaudeOAuthPlanPrice(
22198
22289
  policy.runtimes["claude-code"].claudeOAuthPlan
@@ -22201,6 +22292,7 @@ async function getUsageAggregates(policy, runtimeStates, now = /* @__PURE__ */ n
22201
22292
  const dailyMicros = Math.round(monthlyMicros / daysInMonth(now));
22202
22293
  runtimes["claude-code"].monthly.costMicros = monthlyMicros;
22203
22294
  runtimes["claude-code"].daily.costMicros = dailyMicros;
22295
+ planPricedMonthlyMicros = monthlyMicros;
22204
22296
  }
22205
22297
  const overall = {
22206
22298
  daily: { costMicros: 0, totalTokens: 0 },
@@ -22218,6 +22310,8 @@ async function getUsageAggregates(policy, runtimeStates, now = /* @__PURE__ */ n
22218
22310
  return {
22219
22311
  overall,
22220
22312
  runtimes,
22313
+ metered,
22314
+ planPricedMonthlyMicros,
22221
22315
  ...getBudgetWindowBounds(now)
22222
22316
  };
22223
22317
  }
@@ -25681,8 +25775,8 @@ import { execFileSync as execFileSync3 } from "child_process";
25681
25775
  import yaml12 from "js-yaml";
25682
25776
  import semver from "semver";
25683
25777
  function relayCoreVersion() {
25684
- if (semver.valid("0.22.0")) {
25685
- return "0.22.0";
25778
+ if (semver.valid("0.23.0")) {
25779
+ return "0.23.0";
25686
25780
  }
25687
25781
  try {
25688
25782
  const root = getAppRoot(import.meta.dirname, 3);
@@ -26925,6 +27019,10 @@ function hasSqliteHeader(path24) {
26925
27019
  return false;
26926
27020
  }
26927
27021
  }
27022
+ var MIGRATION_CHAIN = [
27023
+ { fromDir: ".stagent", toDir: ".ainative", fromDb: "stagent.db", toDb: "ainative.db" },
27024
+ { fromDir: ".ainative", toDir: ".relay", fromDb: "ainative.db", toDb: "relay.db" }
27025
+ ];
26928
27026
  async function migrateLegacyData(options = {}) {
26929
27027
  const home = options.home ?? homedir2();
26930
27028
  const gitDir = options.gitDir ?? join6(process.cwd(), ".git");
@@ -26937,46 +27035,50 @@ async function migrateLegacyData(options = {}) {
26937
27035
  keychainMigrated: false,
26938
27036
  errors: []
26939
27037
  };
26940
- const oldDir = join6(home, ".stagent");
26941
- const newDir = join6(home, ".ainative");
26942
- if (existsSync4(oldDir) && !existsSync4(newDir)) {
26943
- try {
26944
- renameSync(oldDir, newDir);
26945
- report.dirMigrated = true;
26946
- log(`renamed ${oldDir} -> ${newDir}`);
26947
- } catch (err2) {
26948
- const e = err2;
26949
- if (e.code === "EXDEV") {
26950
- try {
26951
- cpSync(oldDir, newDir, { recursive: true });
26952
- rmSync2(oldDir, { recursive: true, force: true });
26953
- report.dirMigrated = true;
26954
- log(`copied ${oldDir} -> ${newDir} (cross-device fallback)`);
26955
- } catch (copyErr) {
26956
- report.errors.push(`dir copy failed: ${String(copyErr)}`);
27038
+ const finalDir = join6(home, MIGRATION_CHAIN[MIGRATION_CHAIN.length - 1].toDir);
27039
+ const finalDbName = MIGRATION_CHAIN[MIGRATION_CHAIN.length - 1].toDb;
27040
+ for (const hop of MIGRATION_CHAIN) {
27041
+ const oldDir = join6(home, hop.fromDir);
27042
+ const newDir = join6(home, hop.toDir);
27043
+ if (existsSync4(oldDir) && !existsSync4(newDir)) {
27044
+ try {
27045
+ renameSync(oldDir, newDir);
27046
+ report.dirMigrated = true;
27047
+ log(`renamed ${oldDir} -> ${newDir}`);
27048
+ } catch (err2) {
27049
+ const e = err2;
27050
+ if (e.code === "EXDEV") {
27051
+ try {
27052
+ cpSync(oldDir, newDir, { recursive: true });
27053
+ rmSync2(oldDir, { recursive: true, force: true });
27054
+ report.dirMigrated = true;
27055
+ log(`copied ${oldDir} -> ${newDir} (cross-device fallback)`);
27056
+ } catch (copyErr) {
27057
+ report.errors.push(`dir copy failed: ${String(copyErr)}`);
27058
+ return report;
27059
+ }
27060
+ } else {
27061
+ report.errors.push(`dir rename failed: ${String(err2)}`);
26957
27062
  return report;
26958
27063
  }
26959
- } else {
26960
- report.errors.push(`dir rename failed: ${String(err2)}`);
26961
- return report;
26962
27064
  }
26963
27065
  }
26964
- }
26965
- if (existsSync4(newDir)) {
26966
- for (const suffix of ["", "-shm", "-wal"]) {
26967
- const oldName = join6(newDir, `stagent.db${suffix}`);
26968
- const newName = join6(newDir, `ainative.db${suffix}`);
26969
- if (existsSync4(oldName) && !existsSync4(newName)) {
26970
- try {
26971
- renameSync(oldName, newName);
26972
- report.dbFilesRenamed++;
26973
- } catch (err2) {
26974
- report.errors.push(`db file rename failed (${suffix}): ${String(err2)}`);
27066
+ if (existsSync4(newDir)) {
27067
+ for (const suffix of ["", "-shm", "-wal"]) {
27068
+ const oldName = join6(newDir, `${hop.fromDb}${suffix}`);
27069
+ const newName = join6(newDir, `${hop.toDb}${suffix}`);
27070
+ if (existsSync4(oldName) && !existsSync4(newName)) {
27071
+ try {
27072
+ renameSync(oldName, newName);
27073
+ report.dbFilesRenamed++;
27074
+ } catch (err2) {
27075
+ report.errors.push(`db file rename failed (${suffix}): ${String(err2)}`);
27076
+ }
26975
27077
  }
26976
27078
  }
26977
27079
  }
26978
27080
  }
26979
- const dbPath4 = join6(newDir, "ainative.db");
27081
+ const dbPath4 = join6(finalDir, finalDbName);
26980
27082
  if (existsSync4(dbPath4) && !hasSqliteHeader(dbPath4)) {
26981
27083
  log(`skipping SQL migration \u2014 ${dbPath4} exists but lacks SQLite header`);
26982
27084
  }
package/drizzle.config.ts CHANGED
@@ -2,13 +2,13 @@ import { defineConfig } from "drizzle-kit";
2
2
  import { homedir } from "os";
3
3
  import { join } from "path";
4
4
 
5
- const dataDir = process.env.AINATIVE_DATA_DIR || join(homedir(), ".ainative");
5
+ const dataDir = process.env.RELAY_DATA_DIR || join(homedir(), ".relay");
6
6
 
7
7
  export default defineConfig({
8
8
  schema: "./src/lib/db/schema.ts",
9
9
  out: "./src/lib/db/migrations",
10
10
  dialect: "sqlite",
11
11
  dbCredentials: {
12
- url: join(dataDir, "ainative.db"),
12
+ url: join(dataDir, "relay.db"),
13
13
  },
14
14
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "orionfold-relay",
3
- "version": "0.22.0",
3
+ "version": "0.23.0",
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",
@@ -14,7 +14,7 @@ import type { SignedLicense } from "@/lib/licensing/verify";
14
14
  * paste/upload activation path for UI-first users: the browser reads the
15
15
  * fulfilment file client-side and ships the `{ payload, signature }` envelope
16
16
  * as JSON. Verification is offline Ed25519 inside saveLicense; nothing here
17
- * phones home.
17
+ * sends user data to Orionfold.
18
18
  */
19
19
 
20
20
  const BodySchema = z.object({
@@ -175,9 +175,6 @@ export async function GET() {
175
175
  getFailuresByDay(7),
176
176
  ]);
177
177
 
178
- const overallDaily = budget.statuses.find(
179
- (s) => s.scopeId === "overall" && s.window === "daily",
180
- );
181
178
  const overallMonthly = budget.statuses.find(
182
179
  (s) => s.scopeId === "overall" && s.window === "monthly",
183
180
  );
@@ -196,8 +193,12 @@ export async function GET() {
196
193
  activeProjects: activeProjects?.count ?? 0,
197
194
  activeWorkflows: activeWorkflows?.count ?? 0,
198
195
  reviewPending,
199
- costTodayMicros: overallDaily?.currentValue ?? 0,
200
- costToDateMicros: overallMonthly?.currentValue ?? 0,
196
+ // Metered ledger sums only — the guardrail statuses' plan-priced budget
197
+ // basis must never render as "cost" (fix-dashboard-budget-vs-cost-labeling).
198
+ costTodayMicros: budget.meteredSpend.dailyMicros,
199
+ costToDateMicros: budget.meteredSpend.monthlyMicros,
200
+ budgetMonthlyCapMicros: overallMonthly?.limitValue ?? null,
201
+ planPricedMonthlyMicros: budget.planPricedMonthlyMicros,
201
202
  runtimeLabel,
202
203
  providerId,
203
204
  runtimeSdkVersion,