orionfold-relay 0.21.0 → 0.22.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.
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.21";
1189
+ CURRENT_PLUGIN_API_VERSION = "0.22";
1190
1190
  CAPABILITY_VALUES = ["fs", "net", "child_process", "env"];
1191
1191
  ORIGIN_VALUES = ["ainative-internal", "third-party"];
1192
1192
  PrimitivesBundleManifestSchema = z.object({
@@ -1232,16 +1232,16 @@ function computeHash(content) {
1232
1232
  function safePreview(content) {
1233
1233
  return content.slice(0, MAX_PREVIEW_CHARS).trim();
1234
1234
  }
1235
- function safeStat(path23) {
1235
+ function safeStat(path24) {
1236
1236
  try {
1237
- return statSync2(path23);
1237
+ return statSync2(path24);
1238
1238
  } catch {
1239
1239
  return null;
1240
1240
  }
1241
1241
  }
1242
- function safeReadFile(path23) {
1242
+ function safeReadFile(path24) {
1243
1243
  try {
1244
- return readFileSync4(path23, "utf-8");
1244
+ return readFileSync4(path24, "utf-8");
1245
1245
  } catch {
1246
1246
  return null;
1247
1247
  }
@@ -3744,6 +3744,13 @@ var init_format = __esm({
3744
3744
  price: z3.string().min(1).optional(),
3745
3745
  /** Get-license CTA target on the locked card. */
3746
3746
  purchaseUrl: z3.url().optional(),
3747
+ /**
3748
+ * Per-version customer-voice recap, version → one line. The single source
3749
+ * for every renewal value-recap surface (`license status`, the 402 update
3750
+ * refusal, the /packs update card, the Website renewal email). Optional —
3751
+ * but paid packs should carry it, or their renewal case argues generically.
3752
+ */
3753
+ changelog: z3.record(z3.string(), z3.string().min(1)).optional(),
3747
3754
  /** Customer slugs seeded via ensureCustomer at install. */
3748
3755
  customers: z3.array(z3.string()).default([])
3749
3756
  }).strict();
@@ -6903,6 +6910,34 @@ var init_output_scanner = __esm({
6903
6910
  }
6904
6911
  });
6905
6912
 
6913
+ // src/lib/agents/runtime/model-preference.ts
6914
+ var model_preference_exports = {};
6915
+ __export(model_preference_exports, {
6916
+ resolvePreferredModel: () => resolvePreferredModel
6917
+ });
6918
+ async function resolvePreferredModel(runtimeId, options) {
6919
+ if (options?.pinnedModelId) {
6920
+ return { modelId: options.pinnedModelId, source: "pin" };
6921
+ }
6922
+ const models = getRuntimeCatalogEntry(runtimeId).models;
6923
+ const preference = await getModelPreference();
6924
+ const tierModel = preference === "balanced" ? models.tiers?.balanced : preference === "cost" ? models.tiers?.fast : preference === "quality" ? models.tiers?.quality : void 0;
6925
+ if (tierModel) {
6926
+ return { modelId: tierModel, source: "preference" };
6927
+ }
6928
+ return {
6929
+ modelId: models.tiers?.quality ?? models.default,
6930
+ source: "default"
6931
+ };
6932
+ }
6933
+ var init_model_preference = __esm({
6934
+ "src/lib/agents/runtime/model-preference.ts"() {
6935
+ "use strict";
6936
+ init_catalog2();
6937
+ init_helpers();
6938
+ }
6939
+ });
6940
+
6906
6941
  // src/lib/agents/runtime/claude-sdk.ts
6907
6942
  function buildClaudeSdkEnv(authEnv) {
6908
6943
  const { CLAUDECODE, ANTHROPIC_API_KEY, ...cleanEnv } = process.env;
@@ -7672,6 +7707,9 @@ async function deriveUsageCostMicros(input) {
7672
7707
  if (!input.modelId) {
7673
7708
  return { costMicros: null, pricingVersion: null };
7674
7709
  }
7710
+ if (input.providerId === "ollama") {
7711
+ return { costMicros: 0, pricingVersion: "local-free" };
7712
+ }
7675
7713
  if (input.providerId !== "anthropic" && input.providerId !== "openai") {
7676
7714
  return { costMicros: null, pricingVersion: null };
7677
7715
  }
@@ -11951,9 +11989,9 @@ function buildPermissionSummary(toolName, input) {
11951
11989
  }
11952
11990
  }
11953
11991
  if (toolName === "Read" || toolName === "Write" || toolName === "Edit" || toolName === "read" || toolName === "write" || toolName === "edit") {
11954
- const path23 = input.file_path ?? input.path;
11955
- if (typeof path23 === "string" && path23.trim().length > 0) {
11956
- return truncate(path23.trim());
11992
+ const path24 = input.file_path ?? input.path;
11993
+ if (typeof path24 === "string" && path24.trim().length > 0) {
11994
+ return truncate(path24.trim());
11957
11995
  }
11958
11996
  }
11959
11997
  if (toolName?.startsWith("mcp__")) {
@@ -11987,9 +12025,9 @@ function getPermissionDetailEntries(toolName, input) {
11987
12025
  }
11988
12026
  }
11989
12027
  if (toolName === "Read" || toolName === "Write" || toolName === "Edit" || toolName === "read" || toolName === "write" || toolName === "edit") {
11990
- const path23 = input.file_path ?? input.path;
11991
- if (typeof path23 === "string") {
11992
- return [{ label: "Path", value: path23 }];
12028
+ const path24 = input.file_path ?? input.path;
12029
+ if (typeof path24 === "string") {
12030
+ return [{ label: "Path", value: path24 }];
11993
12031
  }
11994
12032
  }
11995
12033
  return Object.entries(input).slice(0, 6).map(([key, value]) => ({
@@ -12891,7 +12929,7 @@ var init_registry6 = __esm({
12891
12929
  init_registry5();
12892
12930
  init_installer();
12893
12931
  init_schedule_spec();
12894
- SUPPORTED_API_VERSIONS = /* @__PURE__ */ new Set([CURRENT_PLUGIN_API_VERSION, "0.20"]);
12932
+ SUPPORTED_API_VERSIONS = /* @__PURE__ */ new Set([CURRENT_PLUGIN_API_VERSION, "0.21"]);
12895
12933
  pluginCache = null;
12896
12934
  lastLoadedPluginIds = /* @__PURE__ */ new Set();
12897
12935
  PluginTableSchema = z16.object({
@@ -12934,8 +12972,8 @@ function pluginTools(_ctx) {
12934
12972
  Promise.resolve().then(() => (init_ainative_paths(), ainative_paths_exports)),
12935
12973
  Promise.resolve().then(() => (init_types(), types_exports))
12936
12974
  ]);
12937
- const fs22 = await import("fs");
12938
- const path23 = await import("path");
12975
+ const fs23 = await import("fs");
12976
+ const path24 = await import("path");
12939
12977
  const yaml13 = await import("js-yaml");
12940
12978
  const kind5 = listPlugins2();
12941
12979
  const registrations = await listPluginMcpRegistrations2();
@@ -12952,13 +12990,13 @@ function pluginTools(_ctx) {
12952
12990
  let manifestHash;
12953
12991
  let capabilityAcceptStatus = "pending";
12954
12992
  try {
12955
- const pluginYamlPath = path23.join(
12993
+ const pluginYamlPath = path24.join(
12956
12994
  pluginsDir,
12957
12995
  pluginId,
12958
12996
  "plugin.yaml"
12959
12997
  );
12960
- if (fs22.existsSync(pluginYamlPath)) {
12961
- const content = fs22.readFileSync(pluginYamlPath, "utf-8");
12998
+ if (fs23.existsSync(pluginYamlPath)) {
12999
+ const content = fs23.readFileSync(pluginYamlPath, "utf-8");
12962
13000
  const rawManifest = yaml13.load(content);
12963
13001
  if (rawManifest !== null && typeof rawManifest === "object" && !Array.isArray(rawManifest)) {
12964
13002
  const record = rawManifest;
@@ -12971,7 +13009,7 @@ function pluginTools(_ctx) {
12971
13009
  try {
12972
13010
  manifestHash = deriveManifestHash2(content);
12973
13011
  const parsed = PluginManifestSchema2.safeParse(rawManifest);
12974
- const pluginRootDir = path23.join(pluginsDir, pluginId);
13012
+ const pluginRootDir = path24.join(pluginsDir, pluginId);
12975
13013
  const check = isCapabilityAccepted2(
12976
13014
  pluginId,
12977
13015
  manifestHash,
@@ -18067,13 +18105,17 @@ ${learnedCtx}
18067
18105
  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.` : "";
18068
18106
  const systemInstructions = [worktreeNote, profileInstructions, learnedCtxBlock, docContext, tableContext, outputInstructions].filter(Boolean).join("\n\n");
18069
18107
  const maxTurns = profile?.maxTurns ?? DEFAULT_MAX_TURNS;
18108
+ const { modelId } = await resolvePreferredModel("claude-code", {
18109
+ pinnedModelId: profile?.capabilityOverrides?.["claude-code"]?.modelId
18110
+ });
18070
18111
  return {
18071
18112
  userPrompt: basePrompt,
18072
18113
  systemInstructions,
18073
18114
  cwd,
18074
18115
  payload,
18075
18116
  maxTurns,
18076
- canUseToolPolicy: payload?.canUseToolPolicy
18117
+ canUseToolPolicy: payload?.canUseToolPolicy,
18118
+ modelId
18077
18119
  };
18078
18120
  }
18079
18121
  async function executeClaudeTask(taskId) {
@@ -18119,6 +18161,10 @@ async function executeClaudeTask(taskId) {
18119
18161
  prompt: ctx.userPrompt,
18120
18162
  options: {
18121
18163
  abortController,
18164
+ // Explicit model: profile pin > onboarding preference > quality
18165
+ // default. Omitting this let the SDK pick ITS default (Opus) and
18166
+ // silently bill the wrong tier.
18167
+ model: ctx.modelId,
18122
18168
  includePartialMessages: true,
18123
18169
  cwd: ctx.cwd,
18124
18170
  env: buildClaudeSdkEnv(authEnv),
@@ -18241,6 +18287,9 @@ async function resumeClaudeTask(taskId) {
18241
18287
  options: {
18242
18288
  resume: task.sessionId,
18243
18289
  abortController,
18290
+ // Same model resolution as the original run — a resume must not
18291
+ // silently hop tiers (profile pin > preference > quality default).
18292
+ model: ctx.modelId,
18244
18293
  includePartialMessages: true,
18245
18294
  cwd: ctx.cwd,
18246
18295
  env: buildClaudeSdkEnv(authEnv),
@@ -18359,6 +18408,7 @@ var init_claude_agent = __esm({
18359
18408
  init_context_builder2();
18360
18409
  init_output_scanner();
18361
18410
  init_registry2();
18411
+ init_model_preference();
18362
18412
  init_compatibility();
18363
18413
  init_claude_sdk();
18364
18414
  init_types2();
@@ -18377,9 +18427,8 @@ var init_claude_agent = __esm({
18377
18427
  // src/lib/agents/runtime/claude.ts
18378
18428
  import { query as query2 } from "@anthropic-ai/claude-agent-sdk";
18379
18429
  import { eq as eq34 } from "drizzle-orm";
18380
- function claudeCodeModelAlias() {
18381
- const models = getRuntimeCatalogEntry("claude-code").models;
18382
- return models.tiers?.quality ?? models.default;
18430
+ async function claudeCodeModelAlias() {
18431
+ return (await resolvePreferredModel("claude-code")).modelId;
18383
18432
  }
18384
18433
  function buildTaskAssistSystemPrompt(profileIds) {
18385
18434
  const profileList = profileIds.length > 0 ? `Available agent profiles: ${profileIds.join(", ")}
@@ -18473,7 +18522,7 @@ Provide a brief analysis (2-3 paragraphs max). Include specific terminology rele
18473
18522
  prompt,
18474
18523
  options: {
18475
18524
  abortController,
18476
- model: claudeCodeModelAlias(),
18525
+ model: await claudeCodeModelAlias(),
18477
18526
  includePartialMessages: true,
18478
18527
  env: buildClaudeSdkEnv(authEnv),
18479
18528
  allowedTools: []
@@ -18614,7 +18663,7 @@ async function runMetaCompletion(input) {
18614
18663
  prompt: input.prompt,
18615
18664
  options: {
18616
18665
  abortController,
18617
- model: claudeCodeModelAlias(),
18666
+ model: await claudeCodeModelAlias(),
18618
18667
  includePartialMessages: true,
18619
18668
  cwd: getLaunchCwd(),
18620
18669
  env: buildClaudeSdkEnv(authEnv),
@@ -18794,7 +18843,7 @@ ${userMessage}`;
18794
18843
  prompt,
18795
18844
  options: {
18796
18845
  abortController,
18797
- model: claudeCodeModelAlias(),
18846
+ model: await claudeCodeModelAlias(),
18798
18847
  includePartialMessages: true,
18799
18848
  cwd: getLaunchCwd(),
18800
18849
  env: buildClaudeSdkEnv(authEnv),
@@ -18869,7 +18918,7 @@ ${userMessage}`;
18869
18918
  prompt,
18870
18919
  options: {
18871
18920
  abortController,
18872
- model: claudeCodeModelAlias(),
18921
+ model: await claudeCodeModelAlias(),
18873
18922
  includePartialMessages: true,
18874
18923
  cwd: getLaunchCwd(),
18875
18924
  env: buildClaudeSdkEnv(authEnv),
@@ -18934,7 +18983,7 @@ async function testClaudeConnection() {
18934
18983
  prompt: "Reply with exactly: OK",
18935
18984
  options: {
18936
18985
  abortController,
18937
- model: claudeCodeModelAlias(),
18986
+ model: await claudeCodeModelAlias(),
18938
18987
  maxTurns: 1,
18939
18988
  includePartialMessages: false,
18940
18989
  cwd: getLaunchCwd(),
@@ -18981,6 +19030,7 @@ var init_claude = __esm({
18981
19030
  init_compatibility();
18982
19031
  init_claude_agent();
18983
19032
  init_catalog2();
19033
+ init_model_preference();
18984
19034
  init_claude_sdk();
18985
19035
  init_workspace_context();
18986
19036
  init_helpers();
@@ -19387,13 +19437,13 @@ var init_codex_app_server_client = __esm({
19387
19437
  await syncPluginMcpToCodex2();
19388
19438
  } catch (err2) {
19389
19439
  try {
19390
- const fs22 = await import("fs");
19391
- const path23 = await import("path");
19440
+ const fs23 = await import("fs");
19441
+ const path24 = await import("path");
19392
19442
  const { getAinativeLogsDir: getAinativeLogsDir2 } = await Promise.resolve().then(() => (init_ainative_paths(), ainative_paths_exports));
19393
19443
  const dir = getAinativeLogsDir2();
19394
- fs22.mkdirSync(dir, { recursive: true });
19395
- fs22.appendFileSync(
19396
- path23.join(dir, "plugins.log"),
19444
+ fs23.mkdirSync(dir, { recursive: true });
19445
+ fs23.appendFileSync(
19446
+ path24.join(dir, "plugins.log"),
19397
19447
  `${(/* @__PURE__ */ new Date()).toISOString()} codex-sync-failed: ${err2 instanceof Error ? err2.message : String(err2)}
19398
19448
  `
19399
19449
  );
@@ -20875,7 +20925,8 @@ ${outputInstructions}`;
20875
20925
  initialMessages = [{ role: "user", content: ctx.userPrompt }];
20876
20926
  }
20877
20927
  const { getSetting: getSetting2 } = await Promise.resolve().then(() => (init_helpers(), helpers_exports));
20878
- const modelId = await getSetting2("anthropic_direct_model") ?? getRuntimeCatalogEntry("anthropic-direct").models.default;
20928
+ const { resolvePreferredModel: resolvePreferredModel2 } = await Promise.resolve().then(() => (init_model_preference(), model_preference_exports));
20929
+ const modelId = await getSetting2("anthropic_direct_model") ?? (await resolvePreferredModel2("anthropic-direct")).modelId;
20879
20930
  const maxTurns = ctx.maxTurns ?? DEFAULT_MAX_TURNS;
20880
20931
  await db.insert(agentLogs).values({
20881
20932
  id: crypto.randomUUID(),
@@ -21346,7 +21397,8 @@ ${outputInstructions}`;
21346
21397
  );
21347
21398
  const pluginMcpTools = mcpServersToOpenAiTools(mergedMcpServers);
21348
21399
  const { getSetting: getSetting2 } = await Promise.resolve().then(() => (init_helpers(), helpers_exports));
21349
- const modelId = await getSetting2("openai_direct_model") ?? getRuntimeCatalogEntry("openai-direct").models.default;
21400
+ const { resolvePreferredModel: resolvePreferredModel2 } = await Promise.resolve().then(() => (init_model_preference(), model_preference_exports));
21401
+ const modelId = await getSetting2("openai_direct_model") ?? (await resolvePreferredModel2("openai-direct")).modelId;
21350
21402
  const maxTurns = ctx.maxTurns ?? DEFAULT_MAX_TURNS;
21351
21403
  let previousResponseId = null;
21352
21404
  if (isResume) {
@@ -22186,6 +22238,17 @@ async function getUsageAggregates(policy, runtimeStates, now = /* @__PURE__ */ n
22186
22238
  runtimes[runtimeId].daily.totalTokens += row.totalTokens ?? 0;
22187
22239
  }
22188
22240
  });
22241
+ const metered = {
22242
+ daily: { costMicros: 0, totalTokens: 0 },
22243
+ monthly: { costMicros: 0, totalTokens: 0 }
22244
+ };
22245
+ for (const runtimeId of SUPPORTED_AGENT_RUNTIMES) {
22246
+ metered.daily.costMicros += runtimes[runtimeId].daily.costMicros;
22247
+ metered.daily.totalTokens += runtimes[runtimeId].daily.totalTokens;
22248
+ metered.monthly.costMicros += runtimes[runtimeId].monthly.costMicros;
22249
+ metered.monthly.totalTokens += runtimes[runtimeId].monthly.totalTokens;
22250
+ }
22251
+ let planPricedMonthlyMicros = null;
22189
22252
  if (runtimeStates["claude-code"].billingMode === "subscription") {
22190
22253
  const planPriceUsd = await getClaudeOAuthPlanPrice(
22191
22254
  policy.runtimes["claude-code"].claudeOAuthPlan
@@ -22194,6 +22257,7 @@ async function getUsageAggregates(policy, runtimeStates, now = /* @__PURE__ */ n
22194
22257
  const dailyMicros = Math.round(monthlyMicros / daysInMonth(now));
22195
22258
  runtimes["claude-code"].monthly.costMicros = monthlyMicros;
22196
22259
  runtimes["claude-code"].daily.costMicros = dailyMicros;
22260
+ planPricedMonthlyMicros = monthlyMicros;
22197
22261
  }
22198
22262
  const overall = {
22199
22263
  daily: { costMicros: 0, totalTokens: 0 },
@@ -22211,6 +22275,8 @@ async function getUsageAggregates(policy, runtimeStates, now = /* @__PURE__ */ n
22211
22275
  return {
22212
22276
  overall,
22213
22277
  runtimes,
22278
+ metered,
22279
+ planPricedMonthlyMicros,
22214
22280
  ...getBudgetWindowBounds(now)
22215
22281
  };
22216
22282
  }
@@ -22985,14 +23051,14 @@ function resolvePostAction(action, row, itemVariable) {
22985
23051
  function substituteRowPath(template, row, itemVariable) {
22986
23052
  const escaped = itemVariable.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
22987
23053
  const pattern = new RegExp(`\\{\\{\\s*${escaped}\\.([\\w.]+)\\s*\\}\\}`, "g");
22988
- return template.replace(pattern, (_match, path23) => {
22989
- const value = readPath(row, path23);
23054
+ return template.replace(pattern, (_match, path24) => {
23055
+ const value = readPath(row, path24);
22990
23056
  if (value === void 0 || value === null) return "";
22991
23057
  return String(value);
22992
23058
  });
22993
23059
  }
22994
- function readPath(obj, path23) {
22995
- const parts = path23.split(".");
23060
+ function readPath(obj, path24) {
23061
+ const parts = path24.split(".");
22996
23062
  let current = obj;
22997
23063
  for (const part of parts) {
22998
23064
  if (current === null || current === void 0) return void 0;
@@ -23179,14 +23245,14 @@ ${resolvedTemplate}`);
23179
23245
  return parts.join("");
23180
23246
  }
23181
23247
  function resolveRowTemplate(template, context) {
23182
- return template.replace(/\{\{\s*([^}]+)\s*\}\}/g, (_match, path23) => {
23183
- const value = readContextPath(context, path23.trim());
23248
+ return template.replace(/\{\{\s*([^}]+)\s*\}\}/g, (_match, path24) => {
23249
+ const value = readContextPath(context, path24.trim());
23184
23250
  if (value === void 0 || value === null) return "";
23185
23251
  return typeof value === "string" ? value : JSON.stringify(value);
23186
23252
  });
23187
23253
  }
23188
- function readContextPath(value, path23) {
23189
- const parts = path23.split(".");
23254
+ function readContextPath(value, path24) {
23255
+ const parts = path24.split(".");
23190
23256
  let current = value;
23191
23257
  for (const part of parts) {
23192
23258
  if (current === null || current === void 0) return void 0;
@@ -25674,8 +25740,8 @@ import { execFileSync as execFileSync3 } from "child_process";
25674
25740
  import yaml12 from "js-yaml";
25675
25741
  import semver from "semver";
25676
25742
  function relayCoreVersion() {
25677
- if (semver.valid("0.21.0")) {
25678
- return "0.21.0";
25743
+ if (semver.valid("0.22.1")) {
25744
+ return "0.22.1";
25679
25745
  }
25680
25746
  try {
25681
25747
  const root = getAppRoot(import.meta.dirname, 3);
@@ -26014,6 +26080,68 @@ var init_install = __esm({
26014
26080
  }
26015
26081
  });
26016
26082
 
26083
+ // src/lib/licensing/recap.ts
26084
+ var recap_exports = {};
26085
+ __export(recap_exports, {
26086
+ changelogWindow: () => changelogWindow,
26087
+ entitledPackRecaps: () => entitledPackRecaps
26088
+ });
26089
+ import fs21 from "fs";
26090
+ import path22 from "path";
26091
+ import semver2 from "semver";
26092
+ function changelogWindow(changelog, fromExclusive, toInclusive) {
26093
+ if (!changelog || !toInclusive || !semver2.valid(toInclusive)) return [];
26094
+ const from = fromExclusive && semver2.valid(fromExclusive) ? fromExclusive : null;
26095
+ return Object.entries(changelog).filter(([version]) => semver2.valid(version)).filter(
26096
+ ([version]) => semver2.compare(version, toInclusive) <= 0 && (from === null || semver2.compare(version, from) > 0)
26097
+ ).sort(([a], [b]) => semver2.compare(a, b)).map(([version, note]) => ({ version, note }));
26098
+ }
26099
+ function entitledPackRecaps(entitlements, opts2 = {}) {
26100
+ try {
26101
+ const appsDir = opts2.appsDir ?? getAinativeAppsDir();
26102
+ const covered = new Set(entitlements);
26103
+ const out = [];
26104
+ for (const tpl of listPackTemplates({ templatesDir: opts2.templatesDir })) {
26105
+ if (tpl.error || !tpl.meta?.entitlement) continue;
26106
+ if (!covered.has(tpl.meta.entitlement)) continue;
26107
+ if (!fs21.existsSync(path22.join(appsDir, tpl.id))) continue;
26108
+ const avail = packUpdateAvailability(tpl.id, {
26109
+ appsDir,
26110
+ templatesDir: opts2.templatesDir
26111
+ });
26112
+ const state = readInstallState(appsDir, tpl.id);
26113
+ const changelog = tpl.meta.changelog;
26114
+ out.push({
26115
+ packId: tpl.id,
26116
+ packName: tpl.meta.name,
26117
+ installedVersion: avail.installedVersion,
26118
+ ...state?.installedAt ? { installedAt: state.installedAt } : {},
26119
+ availableVersion: avail.availableVersion,
26120
+ updateAvailable: avail.updateAvailable,
26121
+ ...avail.installedVersion && changelog?.[avail.installedVersion] ? { received: changelog[avail.installedVersion] } : {},
26122
+ pending: avail.updateAvailable ? changelogWindow(
26123
+ changelog,
26124
+ avail.installedVersion,
26125
+ avail.availableVersion
26126
+ ) : [],
26127
+ ...tpl.meta.purchaseUrl ? { purchaseUrl: tpl.meta.purchaseUrl } : {}
26128
+ });
26129
+ }
26130
+ return out;
26131
+ } catch {
26132
+ return [];
26133
+ }
26134
+ }
26135
+ var init_recap = __esm({
26136
+ "src/lib/licensing/recap.ts"() {
26137
+ "use strict";
26138
+ init_ainative_paths();
26139
+ init_catalog();
26140
+ init_update();
26141
+ init_install_state();
26142
+ }
26143
+ });
26144
+
26017
26145
  // src/lib/packs/update.ts
26018
26146
  var update_exports = {};
26019
26147
  __export(update_exports, {
@@ -26021,15 +26149,15 @@ __export(update_exports, {
26021
26149
  packUpdateAvailability: () => packUpdateAvailability,
26022
26150
  updatePack: () => updatePack
26023
26151
  });
26024
- import fs21 from "fs";
26025
- import path22 from "path";
26026
- import semver2 from "semver";
26152
+ import fs22 from "fs";
26153
+ import path23 from "path";
26154
+ import semver3 from "semver";
26027
26155
  function packUpdateAvailability(appId, opts2 = {}) {
26028
26156
  const appsDir = opts2.appsDir ?? getAinativeAppsDir();
26029
26157
  const installedVersion = readInstallState(appsDir, appId)?.packVersion ?? null;
26030
26158
  const template = findPackTemplate(appId, { templatesDir: opts2.templatesDir });
26031
26159
  const availableVersion = template?.meta?.version ?? null;
26032
- const updateAvailable = availableVersion !== null && semver2.valid(availableVersion) !== null && (installedVersion === null || semver2.valid(installedVersion) === null || semver2.compare(availableVersion, installedVersion) > 0);
26160
+ const updateAvailable = availableVersion !== null && semver3.valid(availableVersion) !== null && (installedVersion === null || semver3.valid(installedVersion) === null || semver3.compare(availableVersion, installedVersion) > 0);
26033
26161
  return { installedVersion, availableVersion, updateAvailable };
26034
26162
  }
26035
26163
  async function updatePack(id, options = {}) {
@@ -26057,13 +26185,13 @@ async function updatePack(id, options = {}) {
26057
26185
  );
26058
26186
  }
26059
26187
  const coreVersion = options.coreVersion ?? relayCoreVersion();
26060
- if (pack.meta.relayCore && !semver2.satisfies(coreVersion, pack.meta.relayCore)) {
26188
+ if (pack.meta.relayCore && !semver3.satisfies(coreVersion, pack.meta.relayCore)) {
26061
26189
  throw new PackValidationError(
26062
26190
  `Pack ${pack.meta.id}@${pack.meta.version} requires relay-core ${pack.meta.relayCore}, but this install is ${coreVersion}.`
26063
26191
  );
26064
26192
  }
26065
26193
  const newVersion = pack.meta.version;
26066
- if (previousVersion !== null && semver2.valid(previousVersion) && semver2.valid(newVersion) && semver2.compare(newVersion, previousVersion) <= 0) {
26194
+ if (previousVersion !== null && semver3.valid(previousVersion) && semver3.valid(newVersion) && semver3.compare(newVersion, previousVersion) <= 0) {
26067
26195
  return {
26068
26196
  packId: id,
26069
26197
  previousVersion,
@@ -26088,8 +26216,21 @@ async function updatePack(id, options = {}) {
26088
26216
  } catch (err2) {
26089
26217
  if (err2 instanceof PackLicenseError2) {
26090
26218
  const renew = pack.meta.purchaseUrl ? `renew at ${pack.meta.purchaseUrl}` : `redeem one with: relay license add <path-or-url to your .license.json>`;
26219
+ let withheld = "";
26220
+ try {
26221
+ const { changelogWindow: changelogWindow2 } = await Promise.resolve().then(() => (init_recap(), recap_exports));
26222
+ const pending = changelogWindow2(
26223
+ pack.meta.changelog,
26224
+ previousVersion,
26225
+ newVersion
26226
+ );
26227
+ if (pending.length > 0) {
26228
+ withheld = `This update includes: ` + pending.map((p) => `v${p.version} \u2014 ${p.note}`).join("; ") + ` `;
26229
+ }
26230
+ } catch {
26231
+ }
26091
26232
  throw new PackLicenseError2(
26092
- `Your installed ${id} keeps working \u2014 nothing is locked. Updating to v${newVersion} needs an active license: ${renew}. (${err2.message})`,
26233
+ `Your installed ${id} keeps working \u2014 nothing is locked. Updating to v${newVersion} needs an active license: ${renew}. ` + withheld + `(${err2.message})`,
26093
26234
  err2.reason
26094
26235
  );
26095
26236
  }
@@ -26097,7 +26238,7 @@ async function updatePack(id, options = {}) {
26097
26238
  }
26098
26239
  }
26099
26240
  const resolved = resolvePackLayer(pack);
26100
- const backupRoot = path22.join(
26241
+ const backupRoot = path23.join(
26101
26242
  appsDir,
26102
26243
  id,
26103
26244
  "backup",
@@ -26106,12 +26247,12 @@ async function updatePack(id, options = {}) {
26106
26247
  const backedUp = [];
26107
26248
  for (const file of resolved.files) {
26108
26249
  const dest = artifactDestPath(file.relPath, profilesDir, blueprintsDir);
26109
- if (!dest || !fs21.existsSync(dest)) continue;
26250
+ if (!dest || !fs22.existsSync(dest)) continue;
26110
26251
  const recorded = state?.files[file.relPath];
26111
26252
  if (recorded !== void 0 && hashFileSha256(dest) === recorded) continue;
26112
- const backupPath = path22.join(backupRoot, file.relPath);
26113
- fs21.mkdirSync(path22.dirname(backupPath), { recursive: true });
26114
- fs21.copyFileSync(dest, backupPath);
26253
+ const backupPath = path23.join(backupRoot, file.relPath);
26254
+ fs22.mkdirSync(path23.dirname(backupPath), { recursive: true });
26255
+ fs22.copyFileSync(dest, backupPath);
26115
26256
  backedUp.push(file.relPath);
26116
26257
  }
26117
26258
  const install = await installPack(packDir, {
@@ -26395,6 +26536,17 @@ async function runAdd2(source, io) {
26395
26536
  return 1;
26396
26537
  }
26397
26538
  }
26539
+ async function pendingRecaps(entitlements, io) {
26540
+ try {
26541
+ const { entitledPackRecaps: entitledPackRecaps2 } = await Promise.resolve().then(() => (init_recap(), recap_exports));
26542
+ return entitledPackRecaps2(entitlements, {
26543
+ appsDir: io.appsDir,
26544
+ templatesDir: io.templatesDir
26545
+ }).filter((r) => r.pending.length > 0);
26546
+ } catch {
26547
+ return [];
26548
+ }
26549
+ }
26398
26550
  async function runStatus(io) {
26399
26551
  try {
26400
26552
  const { listLicenses: listLicenses2 } = await Promise.resolve().then(() => (init_store(), store_exports));
@@ -26418,6 +26570,16 @@ async function runStatus(io) {
26418
26570
  io.log(
26419
26571
  ` Status: ${lic.valid ? "valid" : `invalid \u2014 ${lic.reason ?? "corrupt entry"}`}`
26420
26572
  );
26573
+ const recaps = await pendingRecaps(lic.entitlements, io);
26574
+ if (lic.valid && recaps.length > 0) {
26575
+ io.log(` Included in your term, waiting to install:`);
26576
+ for (const r of recaps) {
26577
+ for (const p of r.pending) {
26578
+ io.log(` ${r.packName} v${p.version} \u2014 ${p.note}`);
26579
+ }
26580
+ io.log(` \u2192 relay pack update ${r.packId}`);
26581
+ }
26582
+ }
26421
26583
  if (lic.valid && lic.expiresAt) {
26422
26584
  const daysLeft = Math.floor(
26423
26585
  (new Date(lic.expiresAt).getTime() - now.getTime()) / DAY_MS
@@ -26426,6 +26588,23 @@ async function runStatus(io) {
26426
26588
  io.log(
26427
26589
  ` \u26A0 Renewal: expires in ${daysLeft} day${daysLeft === 1 ? "" : "s"}. Your installed packs are yours forever; renewing keeps new premium packs and updates flowing.`
26428
26590
  );
26591
+ const latest = recaps.map((r) => ({ r, p: r.pending[r.pending.length - 1] })).filter((x) => x.p);
26592
+ for (const { r, p } of latest) {
26593
+ io.log(
26594
+ ` This license year delivered ${r.packName} v${p.version} \u2014 ${p.note}`
26595
+ );
26596
+ }
26597
+ }
26598
+ }
26599
+ if (!lic.valid && /expired/i.test(lic.reason ?? "") && recaps.length > 0) {
26600
+ io.log(
26601
+ ` Your installed packs keep working \u2014 nothing is locked. Renewing unlocks:`
26602
+ );
26603
+ for (const r of recaps) {
26604
+ for (const p of r.pending) {
26605
+ io.log(` ${r.packName} v${p.version} \u2014 ${p.note}`);
26606
+ }
26607
+ if (r.purchaseUrl) io.log(` \u2192 renew at ${r.purchaseUrl}`);
26429
26608
  }
26430
26609
  }
26431
26610
  io.log("");
@@ -26796,10 +26975,10 @@ import { existsSync as existsSync4, renameSync, cpSync, rmSync as rmSync2, readF
26796
26975
  import { join as join6 } from "path";
26797
26976
  import { homedir as homedir2 } from "os";
26798
26977
  import Database from "better-sqlite3";
26799
- function hasSqliteHeader(path23) {
26978
+ function hasSqliteHeader(path24) {
26800
26979
  const SQLITE_MAGIC = "SQLite format 3\0";
26801
26980
  try {
26802
- const header = readFileSync3(path23, { encoding: null });
26981
+ const header = readFileSync3(path24, { encoding: null });
26803
26982
  return header.length >= 16 && header.subarray(0, 16).toString("binary") === SQLITE_MAGIC;
26804
26983
  } catch {
26805
26984
  return false;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "orionfold-relay",
3
- "version": "0.21.0",
3
+ "version": "0.22.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",
@@ -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,