oasis_test_v2 2.2.26 → 2.2.28

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 (2) hide show
  1. package/dist/index.js +1068 -464
  2. package/package.json +2 -2
package/dist/index.js CHANGED
@@ -1607,7 +1607,7 @@ var init_delegation_prompts = __esm({
1607
1607
  "### \u8981\u5E26\u6587\u4EF6\u5C31\u660E\u5199",
1608
1608
  "",
1609
1609
  "`--file <\u5DE5\u4F5C\u533A\u91CC\u7684\u8DEF\u5F84>`\uFF0C\u53EF\u4EE5\u7ED9\u591A\u6B21\u3002**\u4E0D\u5199\u5C31\u4E00\u4E2A\u6587\u4EF6\u90FD\u4E0D\u5E26**\u2014\u2014\u4E24\u8FB9\u5DE5\u4F5C\u533A\u662F\u5206\u5F00\u7684\u3002",
1610
- "\u5BF9\u65B9\u7684\u4EA4\u4ED8\u7269\u4F1A\u81EA\u52A8\u56DE\u5230\u8FD9\u4E2A\u5BF9\u8BDD\u7684\u5DE5\u4F5C\u533A\uFF0C\u4F60\u4E0D\u7528\u624B\u5DE5\u642C\u3002"
1610
+ "\u5BF9\u65B9\u4EA4\u56DE\u7684\u6587\u4EF6\u81EA\u52A8\u843D\u8FDB `delegations/<\u77ED\u540D>/`\uFF08\u7528\u6237\u770B\u4E0D\u5230\uFF09\uFF1B\u8981\u7ED9\u7528\u6237\u770B\u7684\uFF0C\u590D\u5236\u6216\u52A0\u5DE5\u540E\u653E\u8FDB `outputs/`\uFF0C\u539F\u4EF6\u522B\u52A8\u3002"
1611
1611
  ].join("\n");
1612
1612
  }
1613
1613
  });
@@ -196435,6 +196435,18 @@ function digestOf(text5) {
196435
196435
  const line = text5.trim().replace(/\s+/g, " ");
196436
196436
  return line.length > DIGEST_CHARS ? `${line.slice(0, DIGEST_CHARS)}\u2026` : line;
196437
196437
  }
196438
+ function delegationArtifactsView(record8) {
196439
+ const dirName = delegationDirName(record8.label, record8.expertActorId, record8.id);
196440
+ const prefixes = [`${DELEGATION_OUTBOUND_DIR}/${dirName}/`, `${SESSION_OUTPUTS_DIR}/${DELEGATION_OUTBOUND_DIR}/${dirName}/`];
196441
+ return collectDelegationArtifacts(record8.roundSummaries).map((a) => {
196442
+ const prefix = prefixes.find((p2) => a.name.startsWith(p2));
196443
+ return {
196444
+ ...a,
196445
+ childMessageId: childRoundMessageIds(record8.childSessionId, a.round).placeholderId,
196446
+ ...prefix ? { childPath: `${SESSION_OUTPUTS_DIR}/${a.name.slice(prefix.length)}` } : {}
196447
+ };
196448
+ });
196449
+ }
196438
196450
  function childRoundMessageIds(childSessionId, round) {
196439
196451
  const suffix = round <= 1 ? "" : `:${round}`;
196440
196452
  return {
@@ -197560,7 +197572,7 @@ ${inboundBundle.paths.map((path41) => `- ${path41}`).join("\n")}` : text5,
197560
197572
  ...record8.label ? { label: record8.label } : {},
197561
197573
  ...record8.taskDigest ? { taskDigest: record8.taskDigest } : {},
197562
197574
  roundSummaries: record8.roundSummaries,
197563
- artifacts: collectDelegationArtifacts(record8.roundSummaries),
197575
+ artifacts: delegationArtifactsView(record8),
197564
197576
  consecutiveFails: record8.consecutiveFails,
197565
197577
  settledSeq: record8.settledSeq,
197566
197578
  pendingInputs: await this.pendingInputs(store, record8.id),
@@ -198137,7 +198149,12 @@ function delegationReturnText(terminal, opts) {
198137
198149
  }
198138
198150
  }
198139
198151
  if (opts.movedPaths.length > 0) {
198140
- lines.push("", "\u4E13\u5BB6\u4EA4\u56DE\u7684\u6587\u4EF6\uFF08\u5DF2\u5728\u4F60\u7684\u5DE5\u4F5C\u76EE\u5F55\u4E0B\uFF0C\u53EF\u76F4\u63A5\u8BFB\u53D6\uFF09\uFF1A", ...opts.movedPaths.map((p2) => `- ${p2}`));
198152
+ const updated = new Set(opts.updatedPaths ?? []);
198153
+ lines.push(
198154
+ "",
198155
+ "\u4E13\u5BB6\u4EA4\u56DE\u7684\u6587\u4EF6\uFF08\u5DF2\u5728\u4F60\u7684\u5DE5\u4F5C\u76EE\u5F55\u4E0B\uFF0C\u53EF\u76F4\u63A5\u8BFB\u53D6\uFF09\uFF1A",
198156
+ ...opts.movedPaths.map((p2) => `- ${p2}\uFF08${updated.has(p2) ? "\u66F4\u65B0\uFF1A\u4E4B\u524D\u4EA4\u56DE\u8FC7\uFF0C\u8FD9\u4E00\u7248\u6709\u6539\u52A8" : "\u65B0\u4EA4"}\uFF09`)
198157
+ );
198141
198158
  }
198142
198159
  if ((opts.unchangedPaths?.length ?? 0) > 0) {
198143
198160
  lines.push("", `\u53E6\u6709 ${opts.unchangedPaths.length} \u4E2A\u6587\u4EF6\u4E0E\u4E0A\u6B21\u4EA4\u56DE\u65F6\u9010\u5B57\u4E00\u81F4\uFF0C\u672C\u8F6E\u6CA1\u6709\u91CD\u4F20\uFF08\u4ECD\u5728\u4F60\u7684\u5DE5\u4F5C\u76EE\u5F55\u4E0B\uFF0C\u8DEF\u5F84\u4E0D\u53D8\uFF09\u3002`);
@@ -198160,6 +198177,11 @@ function delegationReturnText(terminal, opts) {
198160
198177
  "",
198161
198178
  record8.state === "done" ? "\u8FD9\u4E9B\u662F\u6750\u6599\uFF0C\u4E0D\u662F\u7ED9\u7528\u6237\u7684\u6210\u54C1\uFF1A\u4F60\u6709\u8FD9\u6761\u5BF9\u8BDD\u7684\u5B8C\u6574\u4E0A\u4E0B\u6587\uFF0C\u81EA\u5DF1\u5224\u65AD\u8BE5\u8BB2\u54EA\u4E00\u53E5\u3001\u8981\u4E0D\u8981\u63A5\u7740\u5F80\u4E0B\u505A\u3002" : "\u5148\u81EA\u5DF1\u5224\u65AD\u80FD\u4E0D\u80FD\u63A5\u4F4F\uFF1A\u63A5\u7BA1\u3001\u6362\u4E2A\u505A\u6CD5\u3001\u8FD8\u662F\u91CD\u6D3E\u3002**\u522B\u628A\u4E13\u4E1A\u5931\u8D25\u539F\u6837\u4E22\u56DE\u7ED9\u7528\u6237**\u3002"
198162
198179
  );
198180
+ if (opts.movedPaths.length > 0 || (opts.unchangedPaths?.length ?? 0) > 0) {
198181
+ lines.push(
198182
+ "\u4EA4\u56DE\u7684\u6587\u4EF6\u653E\u5728 `delegations/` \u4E0B\uFF0C**\u7528\u6237\u770B\u4E0D\u5230**\uFF1B\u8981\u7ED9\u7528\u6237\u770B\u7684\uFF0C\u590D\u5236\u6216\u52A0\u5DE5\u540E\u653E\u8FDB `outputs/`\uFF08\u539F\u4EF6\u522B\u79FB\u52A8\u3001\u522B\u5220\u9664\uFF09\u3002" + ((opts.updatedPaths?.length ?? 0) > 0 ? "\u6807\u4E86\u300C\u66F4\u65B0\u300D\u7684\u6587\u4EF6\u5982\u679C\u4F60\u4E4B\u524D\u653E\u8FDB\u8FC7 `outputs/`\uFF0C\u8981\u4E0D\u8981\u6362\u6210\u65B0\u7248\u7531\u4F60\u5224\u65AD\u3002" : "")
198183
+ );
198184
+ }
198163
198185
  return lines.join("\n");
198164
198186
  }
198165
198187
  async function deliverDelegationReturn(deps, terminal) {
@@ -198212,11 +198234,14 @@ async function deliverDelegationReturn(deps, terminal) {
198212
198234
  );
198213
198235
  };
198214
198236
  const bundleFiles = toBundleFiles(outbound.prepared);
198237
+ const previouslyDelivered = new Set(record8.roundSummaries.flatMap((round) => (round.files ?? []).map((file) => file.name)));
198215
198238
  const text5 = delegationReturnText(terminal, {
198216
198239
  movedPaths: bundleFiles.paths,
198217
198240
  skipped: outbound.skipped,
198218
198241
  recentRounds: deps.recentRounds ?? DELEGATION_RETURN_RECENT_ROUNDS,
198219
- unchangedPaths: outbound.unchanged
198242
+ unchangedPaths: outbound.unchanged,
198243
+ // 「之前交回过」按台账里已经登记的文件名判——这一轮的文件要等投递之后才落账,此刻还不在里面。
198244
+ updatedPaths: bundleFiles.paths.filter((p2) => previouslyDelivered.has(p2))
198220
198245
  });
198221
198246
  const movedFileRefs = outbound.prepared.map((f2) => ({
198222
198247
  name: f2.rel,
@@ -209352,8 +209377,12 @@ var init_memory_registry_store = __esm({
209352
209377
  const keptName = v2.name ?? ex?.name;
209353
209378
  const keptCreatedBy = ex?.createdBy ?? v2.createdBy;
209354
209379
  const keptCreatedAt = ex?.createdAt ?? v2.createdAt;
209380
+ const keptAudience = v2.audience ?? ex?.audience;
209381
+ const keptEnabled = v2.enabledActorIds ?? ex?.enabledActorIds;
209355
209382
  this.variables.set(mk, {
209356
209383
  ...v2,
209384
+ ...keptAudience !== void 0 ? { audience: keptAudience } : {},
209385
+ ...keptEnabled !== void 0 ? { enabledActorIds: [...keptEnabled] } : {},
209357
209386
  ...ex?.lastUsedAt !== void 0 ? { lastUsedAt: ex.lastUsedAt } : {},
209358
209387
  ...keptName ? { name: keptName } : {},
209359
209388
  ...keptCreatedBy ? { createdBy: keptCreatedBy } : {},
@@ -209371,12 +209400,30 @@ var init_memory_registry_store = __esm({
209371
209400
  const ex = this.variables.get(mk);
209372
209401
  if (ex) this.variables.set(mk, { ...ex, valueEncrypted, updatedAt });
209373
209402
  }
209403
+ async patchVariableMeta(key, actorId, patch) {
209404
+ const mk = this._varKey(key, actorId);
209405
+ const ex = this.variables.get(mk);
209406
+ if (!ex) return false;
209407
+ const next = { ...ex, updatedAt: patch.updatedAt };
209408
+ if (patch.audience !== void 0) next.audience = patch.audience;
209409
+ if (patch.enabledActorIds !== void 0) next.enabledActorIds = [...patch.enabledActorIds];
209410
+ if (patch.deliveryMode !== void 0) next.deliveryMode = patch.deliveryMode;
209411
+ if (patch.expiresAt !== void 0) {
209412
+ if (patch.expiresAt === null) delete next.expiresAt;
209413
+ else next.expiresAt = patch.expiresAt;
209414
+ }
209415
+ if (patch.updatedBy !== void 0) next.updatedBy = patch.updatedBy;
209416
+ this.variables.set(mk, next);
209417
+ return true;
209418
+ }
209374
209419
  async getVariableCiphertext(key, actorId) {
209375
209420
  return this.variables.get(this._varKey(key, actorId))?.valueEncrypted ?? null;
209376
209421
  }
209377
209422
  async listVariables(actorId) {
209378
209423
  const all2 = [...this.variables.values()];
209379
- if (actorId !== void 0) return all2.filter((v2) => v2.scope === "global" || v2.actorId === actorId).map((v2) => ({ ...v2 }));
209424
+ if (actorId !== void 0) {
209425
+ return all2.filter((v2) => v2.actorId === actorId || v2.scope === "global" && (v2.audience !== "selected" || (v2.enabledActorIds ?? []).includes(actorId))).map((v2) => ({ ...v2 }));
209426
+ }
209380
209427
  return all2.map((v2) => ({ ...v2 }));
209381
209428
  }
209382
209429
  async resolveProjectVariables(projectId2) {
@@ -209815,6 +209862,27 @@ var init_memory_trace_store = __esm({
209815
209862
  usage: structuredClone(run.usage)
209816
209863
  }));
209817
209864
  }
209865
+ /** 与 PostgresTraceStore.runtimeRunActivity 同语义:按实例数全部 run(不看状态 / usage),取最近开跑。 */
209866
+ async runtimeRunActivity(runtimeInstanceId) {
209867
+ let runCount = 0;
209868
+ let lastRunStartedAt = null;
209869
+ for (const run of this.runs.values()) {
209870
+ if (run.runtimeInstanceId !== runtimeInstanceId) continue;
209871
+ runCount += 1;
209872
+ if (lastRunStartedAt === null || run.startedAt > lastRunStartedAt) lastRunStartedAt = run.startedAt;
209873
+ }
209874
+ return { runCount, lastRunStartedAt };
209875
+ }
209876
+ /** 与 PostgresTraceStore.listRuntimeRuns 同语义:时间窗内该实例的全部 run,按开跑正序。 */
209877
+ async listRuntimeRuns(runtimeInstanceId, range) {
209878
+ return [...this.runs.values()].filter((run) => run.runtimeInstanceId === runtimeInstanceId && run.startedAt >= range.from && run.startedAt <= range.to).sort((a, b2) => a.startedAt < b2.startedAt ? -1 : a.startedAt > b2.startedAt ? 1 : 0).map((run) => ({
209879
+ actorId: run.actorId,
209880
+ runtimeKind: run.runtimeKind,
209881
+ effectiveModel: run.effectiveModel,
209882
+ startedAt: run.startedAt,
209883
+ usage: run.usage ? structuredClone(run.usage) : null
209884
+ }));
209885
+ }
209818
209886
  /* ---------- execution continuity ---------- */
209819
209887
  async getAttempt(attemptId) {
209820
209888
  return this.getRun(attemptId);
@@ -211852,6 +211920,415 @@ var init_uncategorized_project = __esm({
211852
211920
  }
211853
211921
  });
211854
211922
 
211923
+ // ../server/src/domains/actors/model-pricing.ts
211924
+ function lookupInMap(map, prefixKeys, candidates) {
211925
+ for (const candidate of candidates) {
211926
+ const exact = map instanceof Map ? map.get(candidate) : map[candidate];
211927
+ if (exact) return exact;
211928
+ }
211929
+ const prefix = prefixKeys.find((p2) => candidates.some((candidate) => candidate.startsWith(`${p2}-`) || candidate.startsWith(`${p2}:`)));
211930
+ if (!prefix) return null;
211931
+ return map instanceof Map ? map.get(prefix) ?? null : map[prefix] ?? null;
211932
+ }
211933
+ function stripBracketTier(model) {
211934
+ return model.replace(/\[[^\]]*\]/g, "").trim();
211935
+ }
211936
+ function modelCandidates(model) {
211937
+ const normalized4 = model.trim().toLowerCase();
211938
+ if (!normalized4) return [];
211939
+ const base = [
211940
+ normalized4,
211941
+ normalized4.includes(":") ? normalized4.slice(normalized4.lastIndexOf(":") + 1) : "",
211942
+ normalized4.includes("/") ? normalized4.slice(normalized4.lastIndexOf("/") + 1) : ""
211943
+ ].filter(Boolean);
211944
+ const stripped = base.map(stripBracketTier).filter((m2) => m2 && !base.includes(m2));
211945
+ return [...base, ...new Set(stripped)];
211946
+ }
211947
+ function setModelPriceCacheLoader(loader) {
211948
+ cacheLoader = loader;
211949
+ }
211950
+ async function warmModelPriceCache() {
211951
+ if (!cacheLoader) return 0;
211952
+ try {
211953
+ const next = await cacheLoader();
211954
+ dbPrices = next;
211955
+ dbPrefixKeys = [...next.keys()].sort((a, b2) => b2.length - a.length);
211956
+ cacheLoadedAt = Date.now();
211957
+ return next.size;
211958
+ } catch (err) {
211959
+ console.warn(`[model-pricing] \u9884\u70ED\u4EF7\u683C\u7F13\u5B58\u5931\u8D25\uFF0C\u6CBF\u7528\u9759\u6001\u5FEB\u7167\uFF1A${String(err)}`);
211960
+ return 0;
211961
+ }
211962
+ }
211963
+ function maybeRefreshCache() {
211964
+ if (!cacheLoader) return;
211965
+ if (dbPrices && Date.now() - cacheLoadedAt < CACHE_TTL_MS2) return;
211966
+ if (cacheRefreshInFlight) return;
211967
+ cacheRefreshInFlight = warmModelPriceCache().catch(() => 0).then(() => {
211968
+ cacheRefreshInFlight = null;
211969
+ });
211970
+ }
211971
+ function priceForModel(model) {
211972
+ if (!model) return null;
211973
+ const candidates = modelCandidates(model);
211974
+ if (candidates.length === 0) return null;
211975
+ maybeRefreshCache();
211976
+ if (dbPrices && dbPrices.size > 0) {
211977
+ const fromDb = lookupInMap(dbPrices, dbPrefixKeys, candidates);
211978
+ if (fromDb) return fromDb;
211979
+ }
211980
+ return lookupInMap(MODEL_PRICES, STATIC_PREFIX_PRICES, candidates);
211981
+ }
211982
+ function estimateUsageCostBreakdown(model, usage) {
211983
+ const price = priceForModel(model);
211984
+ if (!price) return null;
211985
+ const inputTokens = Math.max(usage.inputTokens ?? 0, 0);
211986
+ const outputTokens = Math.max(usage.outputTokens ?? 0, 0);
211987
+ const cacheReadTokens = Math.max(usage.cacheReadTokens ?? 0, 0);
211988
+ const cacheCreationTokens = Math.max(usage.cacheCreationTokens ?? 0, 0);
211989
+ const cacheReadPrice = price.cachedInputPerMillion ?? price.inputPerMillion * 0.1;
211990
+ return {
211991
+ currency: price.currency,
211992
+ inputMicros: Math.round(inputTokens * price.inputPerMillion / 1e6 * 1e6),
211993
+ cacheMicros: Math.round(
211994
+ (cacheCreationTokens * price.inputPerMillion + cacheReadTokens * cacheReadPrice) / 1e6 * 1e6
211995
+ ),
211996
+ outputMicros: Math.round(outputTokens * price.outputPerMillion / 1e6 * 1e6)
211997
+ };
211998
+ }
211999
+ function estimateUsageCost(model, usage) {
212000
+ const breakdown = estimateUsageCostBreakdown(model, usage);
212001
+ if (!breakdown) return null;
212002
+ return {
212003
+ currency: breakdown.currency,
212004
+ micros: breakdown.inputMicros + breakdown.cacheMicros + breakdown.outputMicros
212005
+ };
212006
+ }
212007
+ function displayCostUsdMicros(model, usage) {
212008
+ const stored = Math.max(usage.costUsdMicros ?? 0, 0);
212009
+ if (stored > 0) return stored;
212010
+ const estimated = estimateUsageCost(model, usage);
212011
+ if (!estimated || estimated.micros <= 0) return 0;
212012
+ if (estimated.currency === "USD") return estimated.micros;
212013
+ return Math.round(estimated.micros / DISPLAY_CNY_PER_USD);
212014
+ }
212015
+ var FOREIGN_MODEL_MARKUP, usd, cny, MODEL_PRICES, STATIC_PREFIX_PRICES, CACHE_TTL_MS2, dbPrices, dbPrefixKeys, cacheLoadedAt, cacheLoader, cacheRefreshInFlight;
212016
+ var init_model_pricing = __esm({
212017
+ "../server/src/domains/actors/model-pricing.ts"() {
212018
+ "use strict";
212019
+ init_src();
212020
+ FOREIGN_MODEL_MARKUP = 1.08;
212021
+ usd = (inputPerMillion, outputPerMillion, cachedInputPerMillion) => ({
212022
+ currency: "USD",
212023
+ inputPerMillion: inputPerMillion * FOREIGN_MODEL_MARKUP,
212024
+ outputPerMillion: outputPerMillion * FOREIGN_MODEL_MARKUP,
212025
+ ...cachedInputPerMillion !== void 0 ? { cachedInputPerMillion: cachedInputPerMillion * FOREIGN_MODEL_MARKUP } : {}
212026
+ });
212027
+ cny = (inputPerMillion, outputPerMillion, cachedInputPerMillion) => ({
212028
+ currency: "CNY",
212029
+ inputPerMillion,
212030
+ outputPerMillion,
212031
+ ...cachedInputPerMillion !== void 0 ? { cachedInputPerMillion } : {}
212032
+ });
212033
+ MODEL_PRICES = {
212034
+ "gpt-5.5": usd(5, 30, 0.5),
212035
+ "gpt-5.5-pro": usd(30, 180),
212036
+ "chat-latest": usd(5, 30, 0.5),
212037
+ "gpt-5.4-mini": usd(0.75, 4.5, 0.075),
212038
+ "gpt-5.4-nano": usd(0.2, 1.25, 0.02),
212039
+ "gpt-5.4": usd(2.5, 15, 0.25),
212040
+ "gpt-5.4-pro": usd(30, 180),
212041
+ "gpt-5.3-codex": usd(1.75, 14, 0.175),
212042
+ "gpt-5.2": usd(1.75, 14, 0.175),
212043
+ "gpt-5.2-chat-latest": usd(1.75, 14, 0.175),
212044
+ "gpt-5.2-pro": usd(21, 168),
212045
+ "gpt-5.2-codex": usd(1.75, 14, 0.175),
212046
+ "gpt-5.1": usd(1.25, 10, 0.125),
212047
+ "gpt-5.1-chat-latest": usd(1.25, 10, 0.125),
212048
+ "gpt-5.1-codex": usd(1.25, 10, 0.125),
212049
+ "gpt-5.1-codex-max": usd(1.25, 10, 0.125),
212050
+ "gpt-5.1-codex-mini": usd(0.25, 2, 0.025),
212051
+ "gpt-5-pro": usd(15, 120),
212052
+ "gpt-5-codex": usd(1.25, 10, 0.125),
212053
+ "gpt-5-search-api": usd(1.25, 10, 0.125),
212054
+ "gpt-5": usd(1.25, 10, 0.125),
212055
+ "gpt-5-mini": usd(0.25, 2, 0.025),
212056
+ "gpt-5-nano": usd(0.05, 0.4, 5e-3),
212057
+ "gpt-5-chat-latest": usd(1.25, 10, 0.125),
212058
+ "gpt-4.1": usd(2, 8, 0.5),
212059
+ "gpt-4.1-2025-04-14": usd(2, 8, 0.5),
212060
+ "gpt-4.1-mini": usd(0.4, 1.6, 0.1),
212061
+ "gpt-4.1-mini-2025-04-14": usd(0.4, 1.6, 0.1),
212062
+ "gpt-4.1-nano": usd(0.1, 0.4, 0.025),
212063
+ "gpt-4.1-nano-2025-04-14": usd(0.1, 0.4, 0.025),
212064
+ "o4-mini": usd(1.1, 4.4, 0.275),
212065
+ // o3 / o3-mini 在 codex 静态目录里可选,却一直不在价目快照里(费用记 0)。
212066
+ // 这两行按 OpenAI 公开价填,不是智增增快照里的行;供应商价目表同步到时以 DB 为准。
212067
+ "o3": usd(2, 8),
212068
+ "o3-mini": usd(1.1, 4.4),
212069
+ "claude-fable-5-1": usd(10, 50),
212070
+ "claude-fable-5": usd(10, 50),
212071
+ "claude-mythos-5-1": usd(10, 50),
212072
+ "claude-mythos-5": usd(10, 50),
212073
+ // claude-opus-5 是 Claude Code 当代默认款,却一直不在这张快照里:查不到价 → 费用记 0
212074
+ // (与 2026-09-04 那次 `claude-opus-5[1m]` 记 0 同一类坑)。按 Opus 档 $5/$25 补上。
212075
+ "claude-opus-5": usd(5, 25),
212076
+ "claude-opus-4-8": usd(5, 25),
212077
+ "claude-opus-4-7": usd(5, 25),
212078
+ "claude-sonnet-5": usd(2, 10),
212079
+ "claude-sonnet-4-6": usd(3, 15),
212080
+ "claude-opus-4-6": usd(5, 25),
212081
+ "claude-opus-4-5-20251101": usd(5, 25),
212082
+ "claude-opus-4-5": usd(5, 25),
212083
+ "claude-sonnet-4-5-20250929": usd(3, 15),
212084
+ // 短名同样要有:前缀比对是「表里的键是模型名的前缀」,带日期的那行匹配不上不带日期的短名。
212085
+ // claude-sonnet-4-5 从 2026-07-04 起就在目录里可选,却一直查不到价(费用记 0),本次一并补。
212086
+ "claude-sonnet-4-5": usd(3, 15),
212087
+ "claude-opus-4-1-20250805": usd(15, 75),
212088
+ "claude-opus-4-20250514": usd(15, 75),
212089
+ "claude-sonnet-4-20250514": usd(3, 15),
212090
+ "claude-3-7-sonnet-20250219": usd(3, 15),
212091
+ "claude-3-5-sonnet-20241022": usd(3, 15),
212092
+ "claude-3-5-sonnet-20240620": usd(3, 15),
212093
+ "claude-3-sonnet-20240229": usd(3, 15),
212094
+ "claude-haiku-4-5-20251001": usd(1, 5),
212095
+ // codebuddy 目录用点号写法(claude-sonnet-4.6),与上面的连字符写法是同一批模型,
212096
+ // 但字符串对不上就查不到价 → 记 0。同价另起别名行。
212097
+ "claude-sonnet-4.6": usd(3, 15),
212098
+ "claude-opus-4.7": usd(5, 25),
212099
+ "gemini-3.1-pro": usd(2, 12),
212100
+ "gemini-3-pro-preview": usd(2, 12),
212101
+ // Claude Code 回报的可能是不带日期的短名,短名匹配不上带日期的那行(前缀比对是「表里的键是模型名的前缀」)。
212102
+ "claude-haiku-4-5": usd(1, 5),
212103
+ "gemini-3.5-flash": usd(1.5, 9),
212104
+ "gemini-3.1-pro-preview": usd(2, 12),
212105
+ "gemini-3.1-pro-preview-customtools": usd(2, 12),
212106
+ "gemini-3.1-flash-lite-preview": usd(0.25, 1.5),
212107
+ "gemini-3-flash-preview": usd(0.5, 3),
212108
+ "gemini-2.5-pro": usd(1.25, 10),
212109
+ "gemini-2.5-flash": usd(0.3, 2.5),
212110
+ "gemini-2.5-flash-lite": usd(0.1, 0.4),
212111
+ "gemini-2.5-flash-preview-09-2025": usd(0.3, 2.5),
212112
+ "gemini-2.0-flash": usd(0.1, 0.4),
212113
+ "deepseek-v4-flash": cny(1, 2, 0.02),
212114
+ "deepseek-v4-pro": cny(3, 6, 0.025),
212115
+ "deepseek-chat": cny(2, 3),
212116
+ "deepseek-reasoner": cny(2, 3),
212117
+ "deepseek-v3.2": cny(2, 3),
212118
+ "deepseek-coder": cny(2, 3),
212119
+ "deepseek-r1": cny(4, 16),
212120
+ "deepseek-r1-0528": cny(4, 16),
212121
+ "deepseek-v3": cny(2, 8),
212122
+ "deepseek-v3-0324": cny(2, 8),
212123
+ "deepseek-v3.1": cny(4, 12),
212124
+ "qwen3-max": cny(6, 24),
212125
+ "qwen3-max-preview": cny(6, 24),
212126
+ "qwen3-coder-plus": cny(4, 16),
212127
+ "qwen3-235b-a22b": cny(4, 40),
212128
+ "qwen3-32b": cny(2, 20),
212129
+ "qwen3-30b-a3b": cny(1.5, 15),
212130
+ "qwen3-14b": cny(1, 10),
212131
+ "qwen3-8b": cny(0.5, 5),
212132
+ "qwen3-4b": cny(0.3, 3),
212133
+ "qwen3-1.7b": cny(0.3, 3),
212134
+ "qwen3-0.6b": cny(0.3, 3),
212135
+ "qwq-plus": cny(1.6, 4),
212136
+ "qwq-plus-latest": cny(1.6, 4),
212137
+ "qwq-plus-2025-03-05": cny(1.6, 4),
212138
+ "kimi-k2.7-code": cny(6.5, 27),
212139
+ "kimi-k2.6": cny(6.5, 27),
212140
+ "kimi-k2.5": cny(4, 21),
212141
+ "kimi-k2-0905-preview": cny(4, 16),
212142
+ "kimi-k2-0711-preview": cny(4, 16),
212143
+ "kimi-k2-turbo-preview": cny(8, 58)
212144
+ };
212145
+ STATIC_PREFIX_PRICES = Object.keys(MODEL_PRICES).sort((a, b2) => b2.length - a.length);
212146
+ CACHE_TTL_MS2 = 5 * 6e4;
212147
+ dbPrices = null;
212148
+ dbPrefixKeys = [];
212149
+ cacheLoadedAt = 0;
212150
+ cacheLoader = null;
212151
+ cacheRefreshInFlight = null;
212152
+ }
212153
+ });
212154
+
212155
+ // ../server/src/domains/usage/accounting.ts
212156
+ function nonNegative(v2) {
212157
+ return Math.max(v2 ?? 0, 0);
212158
+ }
212159
+ function resolvedModel(model) {
212160
+ const trimmed = model?.trim();
212161
+ return trimmed ? trimmed : null;
212162
+ }
212163
+ function splitByWeight(total, w2) {
212164
+ const sum = w2.input + w2.cache + w2.output;
212165
+ if (total === 0) return { input: 0, cache: 0, output: 0 };
212166
+ if (sum <= 0) return { input: 0, cache: 0, output: total };
212167
+ const input = Math.round(total * w2.input / sum);
212168
+ const cache = Math.round(total * w2.cache / sum);
212169
+ return { input, cache, output: total - input - cache };
212170
+ }
212171
+ function toDisplayUsd(currency, micros) {
212172
+ return currency === "USD" ? micros : Math.round(micros / DISPLAY_CNY_PER_USD);
212173
+ }
212174
+ function accountUsage(model, usage) {
212175
+ const resolved = resolvedModel(model);
212176
+ if (!resolved || !usage) return null;
212177
+ const input = nonNegative(usage.inputTokens);
212178
+ const output = nonNegative(usage.outputTokens);
212179
+ const cache = nonNegative(usage.cacheReadTokens) + nonNegative(usage.cacheCreationTokens);
212180
+ const tokens = { input, cache, output, total: input + cache + output };
212181
+ const estimated = estimateUsageCostBreakdown(resolved, usage);
212182
+ const weights = estimated ? { input: estimated.inputMicros, cache: estimated.cacheMicros, output: estimated.outputMicros } : null;
212183
+ const reported = Math.max(usage.costUsdMicros ?? 0, 0);
212184
+ if (reported > 0) {
212185
+ const parts = weights && estimated?.currency === "USD" ? splitByWeight(reported, weights) : { input: 0, cache: 0, output: reported };
212186
+ return {
212187
+ tokens,
212188
+ currency: "USD",
212189
+ amountMicros: reported,
212190
+ parts,
212191
+ displayUsdMicros: reported,
212192
+ displayParts: parts,
212193
+ source: "reported"
212194
+ };
212195
+ }
212196
+ if (estimated && weights) {
212197
+ const amount = weights.input + weights.cache + weights.output;
212198
+ const displayUsdMicros = toDisplayUsd(estimated.currency, amount);
212199
+ return {
212200
+ tokens,
212201
+ currency: estimated.currency,
212202
+ amountMicros: amount,
212203
+ parts: weights,
212204
+ displayUsdMicros,
212205
+ displayParts: estimated.currency === "USD" ? weights : splitByWeight(displayUsdMicros, weights),
212206
+ source: "estimated"
212207
+ };
212208
+ }
212209
+ return {
212210
+ tokens,
212211
+ currency: "USD",
212212
+ amountMicros: 0,
212213
+ parts: { input: 0, cache: 0, output: 0 },
212214
+ displayUsdMicros: 0,
212215
+ displayParts: { input: 0, cache: 0, output: 0 },
212216
+ source: "unpriced"
212217
+ };
212218
+ }
212219
+ function isUnpriced(account) {
212220
+ return account.source === "unpriced" && account.tokens.total > 0;
212221
+ }
212222
+ var init_accounting = __esm({
212223
+ "../server/src/domains/usage/accounting.ts"() {
212224
+ "use strict";
212225
+ init_model_pricing();
212226
+ }
212227
+ });
212228
+
212229
+ // ../server/src/domains/actors/stats.ts
212230
+ function emptyCostBreakdown() {
212231
+ return {
212232
+ inputUsdMicros: 0,
212233
+ cacheUsdMicros: 0,
212234
+ outputUsdMicros: 0,
212235
+ inputCnyMicros: 0,
212236
+ cacheCnyMicros: 0,
212237
+ outputCnyMicros: 0
212238
+ };
212239
+ }
212240
+ function emptySummary() {
212241
+ return {
212242
+ tokenWeek: 0,
212243
+ tokenWeekBreakdown: { input: 0, cache: 0, output: 0 },
212244
+ costWeekUsdMicros: 0,
212245
+ costWeekCnyMicros: 0,
212246
+ costWeekBreakdown: emptyCostBreakdown(),
212247
+ unpricedModels: []
212248
+ };
212249
+ }
212250
+ function addAccount(target, account) {
212251
+ target.tokenWeekBreakdown.input += account.tokens.input;
212252
+ target.tokenWeekBreakdown.cache += account.tokens.cache;
212253
+ target.tokenWeekBreakdown.output += account.tokens.output;
212254
+ target.tokenWeek += account.tokens.total;
212255
+ if (account.currency === "USD") {
212256
+ target.costWeekUsdMicros += account.amountMicros;
212257
+ target.costWeekBreakdown.inputUsdMicros += account.parts.input;
212258
+ target.costWeekBreakdown.cacheUsdMicros += account.parts.cache;
212259
+ target.costWeekBreakdown.outputUsdMicros += account.parts.output;
212260
+ } else {
212261
+ target.costWeekCnyMicros += account.amountMicros;
212262
+ target.costWeekBreakdown.inputCnyMicros += account.parts.input;
212263
+ target.costWeekBreakdown.cacheCnyMicros += account.parts.cache;
212264
+ target.costWeekBreakdown.outputCnyMicros += account.parts.output;
212265
+ }
212266
+ }
212267
+ function summarize2(rows) {
212268
+ const out = emptySummary();
212269
+ const unpriced = /* @__PURE__ */ new Set();
212270
+ for (const row of rows) {
212271
+ const account = accountUsage(row.model, row.usage);
212272
+ if (!account) continue;
212273
+ addAccount(out, account);
212274
+ if (isUnpriced(account)) unpriced.add(resolvedModel(row.model));
212275
+ }
212276
+ out.unpricedModels = [...unpriced].sort();
212277
+ return out;
212278
+ }
212279
+ async function actorStats(actorId, opts = {}) {
212280
+ const now = opts.now ?? /* @__PURE__ */ new Date();
212281
+ const from = new Date(now.getTime() - 7 * 24 * 60 * 60 * 1e3).toISOString();
212282
+ const to = now.toISOString();
212283
+ const weeklyUsage = opts.usage?.listRuns ? summarizeWeeklyRuns(await listWeeklyRuns(opts.usage, actorId, from, to, opts.artifactIds)) : summarizeWeeklyUsage(!opts.artifactIds && opts.usage?.queryUsageStats ? await opts.usage.queryUsageStats({
212284
+ actorIds: [actorId],
212285
+ from,
212286
+ to,
212287
+ granularity: "hour"
212288
+ }) : []);
212289
+ return {
212290
+ actorId,
212291
+ usageAvailable: !!(opts.usage?.listRuns || !opts.artifactIds && opts.usage?.queryUsageStats),
212292
+ ...opts.model ? { participatingTaskCount: participatingTaskCount(opts.model, actorId) } : {},
212293
+ tokenWeek: weeklyUsage.tokenWeek,
212294
+ tokenWeekBreakdown: weeklyUsage.tokenWeekBreakdown,
212295
+ costWeekUsdMicros: weeklyUsage.costWeekUsdMicros,
212296
+ costWeekCnyMicros: weeklyUsage.costWeekCnyMicros,
212297
+ costWeekBreakdown: weeklyUsage.costWeekBreakdown,
212298
+ usageWindow: { from, to },
212299
+ unpricedModels: weeklyUsage.unpricedModels
212300
+ };
212301
+ }
212302
+ async function listWeeklyRuns(trace, actorId, from, to, artifactIds) {
212303
+ const out = [];
212304
+ let cursor;
212305
+ do {
212306
+ const page = await trace.listRuns({ actorId, from, to, limit: 500, ...cursor ? { cursor } : {} });
212307
+ out.push(...page.items.filter((run) => !artifactIds || run.artifactId != null && artifactIds.has(run.artifactId)));
212308
+ cursor = page.nextCursor ?? void 0;
212309
+ } while (cursor);
212310
+ return out;
212311
+ }
212312
+ function summarizeWeeklyRuns(runs) {
212313
+ return summarize2(runs.map((run) => ({ model: run.effectiveModel, usage: run.usage })));
212314
+ }
212315
+ function summarizeWeeklyUsage(rows) {
212316
+ return summarize2(rows.map((row) => ({ model: row.effectiveModel, usage: row })));
212317
+ }
212318
+ function participatingTaskCount(model, actorId) {
212319
+ const workorders = /* @__PURE__ */ new Set();
212320
+ for (const artifact of model.artifacts.values()) {
212321
+ if (artifact.owner === actorId) workorders.add(artifact.workspace);
212322
+ }
212323
+ return workorders.size;
212324
+ }
212325
+ var init_stats = __esm({
212326
+ "../server/src/domains/actors/stats.ts"() {
212327
+ "use strict";
212328
+ init_accounting();
212329
+ }
212330
+ });
212331
+
211855
212332
  // ../server/src/domains/projects/service.ts
211856
212333
  function sanitizeCreatorActorId(raw) {
211857
212334
  const id = raw?.trim();
@@ -212132,6 +212609,7 @@ var init_service4 = __esm({
212132
212609
  init_projection();
212133
212610
  init_uncategorized_project();
212134
212611
  init_workorders();
212612
+ init_stats();
212135
212613
  init_node_state();
212136
212614
  DEFAULT_AGENT_ACTOR = "actor:agent:system";
212137
212615
  MemoryProjectStateStore = class {
@@ -212491,6 +212969,17 @@ var init_service4 = __esm({
212491
212969
  artifacts.sort((a, b2) => b2.updatedAt.localeCompare(a.updatedAt));
212492
212970
  return { artifacts, summary: summarizeArtifacts(artifacts) };
212493
212971
  }
212972
+ /** 成员概览只读本公司的项目记录与当前任务模型,不加载项目文件/用量旁账。 */
212973
+ async getActorParticipationStats(actorId) {
212974
+ if (!this.actorDirectory) throw new Error("participation source unavailable");
212975
+ if (!await this.actorDirectory.getActor(actorId)) return null;
212976
+ const projects = await this.projects.listProjects();
212977
+ return {
212978
+ actorId,
212979
+ createdProjectCount: projects.filter((project) => !isUncategorizedProjectId(project.id) && project.createdBy?.id === actorId).length,
212980
+ participatingTaskCount: participatingTaskCount(this.kernel.model, actorId)
212981
+ };
212982
+ }
212494
212983
  /**
212495
212984
  * 项目列表(带派生态)。
212496
212985
  *
@@ -215467,6 +215956,9 @@ var init_company_scoped_registry = __esm({
215467
215956
  async patchVariableValue(...args) {
215468
215957
  return this.base.patchVariableValue(...args);
215469
215958
  }
215959
+ async patchVariableMeta(...args) {
215960
+ return this.base.patchVariableMeta(...args);
215961
+ }
215470
215962
  async getVariableCiphertext(...args) {
215471
215963
  return this.base.getVariableCiphertext(...args);
215472
215964
  }
@@ -215491,17 +215983,30 @@ function joinRel(dir, name) {
215491
215983
  return dir ? `${dir}/${name}` : name;
215492
215984
  }
215493
215985
  async function scanSessionOutputs(bridge, loc, now = Date.now) {
215986
+ const locator = { workdirKey: loc.workdirKey, runtimeKind: loc.runtimeKind, rtId: loc.rtId };
215987
+ if (typeof bridge.walk === "function") {
215988
+ const walked = await bridge.walk(loc.nodeId, { ...locator, path: SESSION_OUTPUTS_DIR }, WALK_MAX_ENTRIES).catch(() => null);
215989
+ if (!walked) return null;
215990
+ if (walked.ok) {
215991
+ const files2 = walked.entries.filter((entry) => entry.type === "file").map((entry) => ({ path: entry.path, size: entry.size, mtime: entry.mtime }));
215992
+ return { files: files2, via: "walk", tree: walked.entries };
215993
+ }
215994
+ if (walked.code === "NOT_FOUND") return { files: [], via: "walk", tree: [] };
215995
+ if (walked.code !== "UNSUPPORTED") return null;
215996
+ }
215997
+ const files = await scanLayered(
215998
+ (path41) => bridge.list(loc.nodeId, { ...locator, path: path41 }),
215999
+ now
216000
+ );
216001
+ return files === null ? null : { files, via: "layered" };
216002
+ }
216003
+ async function scanLayered(list2, now) {
215494
216004
  const deadline = now() + BUDGET_MS;
215495
216005
  const out = [];
215496
216006
  let listCalls = 0;
215497
216007
  const listDir = (path41) => {
215498
216008
  listCalls += 1;
215499
- return bridge.list(loc.nodeId, {
215500
- workdirKey: loc.workdirKey,
215501
- runtimeKind: loc.runtimeKind,
215502
- rtId: loc.rtId,
215503
- path: path41
215504
- });
216009
+ return list2(path41);
215505
216010
  };
215506
216011
  const root = await listDir(SESSION_OUTPUTS_DIR).catch(() => null);
215507
216012
  if (!root) return null;
@@ -215534,23 +216039,44 @@ async function scanSessionOutputs(bridge, loc, now = Date.now) {
215534
216039
  }
215535
216040
  return out;
215536
216041
  }
216042
+ async function legacyReach(tree) {
216043
+ const children = /* @__PURE__ */ new Map();
216044
+ for (const entry of tree) {
216045
+ const slash = entry.path.lastIndexOf("/");
216046
+ const parent = slash >= 0 ? entry.path.slice(0, slash) : "";
216047
+ const siblings = children.get(parent) ?? [];
216048
+ siblings.push({ name: entry.path.slice(slash + 1), type: entry.type, size: entry.size, mtime: entry.mtime });
216049
+ children.set(parent, siblings);
216050
+ }
216051
+ const files = await scanLayered(async (path41) => ({ ok: true, entries: children.get(path41) ?? [], absPath: "" }), () => 0);
216052
+ return new Set((files ?? []).map((file) => file.path));
216053
+ }
215537
216054
  async function settleSessionOutputs(deps) {
215538
- const scanned = await scanSessionOutputs(deps.bridge, deps.loc, deps.now);
215539
- if (scanned === null) return null;
216055
+ const scan2 = await scanSessionOutputs(deps.bridge, deps.loc, deps.now);
216056
+ if (scan2 === null) return null;
215540
216057
  const previous3 = await deps.readFingerprints(deps.sessionId).catch(() => null);
215541
- const diff = diffSessionOutputs(scanned, previous3 ?? void 0);
216058
+ const diff = diffSessionOutputs(scan2.files, previous3 ?? void 0);
216059
+ if (scan2.via === "walk") {
216060
+ if (previous3 && previous3[WALK_TABLE_MARK] === void 0) {
216061
+ const reach = await legacyReach(scan2.tree ?? []);
216062
+ diff.changed = diff.changed.filter((file) => previous3[file.path] !== void 0 || reach.has(file.path));
216063
+ }
216064
+ diff.fingerprints[WALK_TABLE_MARK] = { size: 0, mtime: "" };
216065
+ }
215542
216066
  await deps.writeFingerprints(deps.sessionId, diff.fingerprints).catch(() => void 0);
215543
216067
  return diff;
215544
216068
  }
215545
- var MAX_DEPTH, MAX_ENTRIES, MAX_LIST_CALLS, BUDGET_MS;
216069
+ var WALK_MAX_ENTRIES, MAX_DEPTH, MAX_ENTRIES, MAX_LIST_CALLS, BUDGET_MS, WALK_TABLE_MARK;
215546
216070
  var init_outputs_scan = __esm({
215547
216071
  "../server/src/domains/chat-sessions/outputs-scan.ts"() {
215548
216072
  "use strict";
215549
216073
  init_src();
216074
+ WALK_MAX_ENTRIES = 2e3;
215550
216075
  MAX_DEPTH = 3;
215551
216076
  MAX_ENTRIES = 100;
215552
216077
  MAX_LIST_CALLS = 12;
215553
216078
  BUDGET_MS = 5e3;
216079
+ WALK_TABLE_MARK = "#walk";
215554
216080
  }
215555
216081
  });
215556
216082
 
@@ -220014,6 +220540,9 @@ function variableKeyFromEnv(env = process.env) {
220014
220540
  if (env.NODE_ENV === "production") throw new Error("OASIS_VAR_KEY is required in production");
220015
220541
  return (0, import_node_crypto47.createHash)("sha256").update("oasis-dev-only-variable-key").digest();
220016
220542
  }
220543
+ function dedupe(ids2) {
220544
+ return [...new Set(ids2.filter((x2) => typeof x2 === "string" && x2.length > 0))];
220545
+ }
220017
220546
  var import_node_crypto47, REDACTED_MARKER, ActorsService, mask, changedKeys;
220018
220547
  var init_service5 = __esm({
220019
220548
  "../server/src/domains/actors/service.ts"() {
@@ -220997,7 +221526,31 @@ ${input.description}
220997
221526
  ...v2.by ? { createdBy: v2.by, createdAt: this.now(), updatedBy: v2.by } : {},
220998
221527
  // ADR 凭据保险库补章 §D2.2:expiresAt 写侧穿透——**显式带 key**(含 undefined),使清空(传空→undefined)
220999
221528
  // 也落到底(store 两实现都按此覆盖)。lastUsedAt 从不由写侧传,取用时另刷(§D2.3)。
221000
- expiresAt: v2.expiresAt
221529
+ expiresAt: v2.expiresAt,
221530
+ // 决策 0556:发给谁只对全局行有意义;个人档 / 项目档一律不带(存储层 COALESCE:不带 = 不动)。
221531
+ ...v2.scope === "global" && v2.audience !== void 0 ? { audience: v2.audience } : {},
221532
+ ...v2.scope === "global" && v2.enabledActorIds !== void 0 ? { enabledActorIds: dedupe(v2.enabledActorIds) } : {}
221533
+ });
221534
+ }
221535
+ /**
221536
+ * 只改一行变量的元数据、不碰值(决策 0556)。
221537
+ *
221538
+ * 两个用处:① 管理页「配置Agent」改发给谁(audience / enabledActorIds)——不用为了改名单去取明文重写;
221539
+ * ② 改到期时间 / 注入方式不用连值一起重填(此前 `putVariable` 空值即静默不写,界面只能逼人重输一遍值)。
221540
+ *
221541
+ * 行不存在返回 false,**不会凭空造一条没有值的行**。audience / enabledActorIds 只允许写在全局行上。
221542
+ */
221543
+ async patchVariableMeta(key, selector, patch) {
221544
+ if (selector.actorId !== void 0 && (patch.audience !== void 0 || patch.enabledActorIds !== void 0)) {
221545
+ throw new ApiError(400, "BAD_REQUEST", "audience / enabledActorIds \u53EA\u80FD\u5199\u5728\u7EC4\u7EC7\u9ED8\u8BA4\u503C\uFF08\u5168\u5C40\u884C\uFF09\u4E0A\uFF0C\u4E2A\u4EBA\u6863\u6CA1\u6709\u300C\u53D1\u7ED9\u8C01\u300D");
221546
+ }
221547
+ return this.opts.store.patchVariableMeta(key, selector.actorId, {
221548
+ ...patch.audience !== void 0 ? { audience: patch.audience } : {},
221549
+ ...patch.enabledActorIds !== void 0 ? { enabledActorIds: dedupe(patch.enabledActorIds) } : {},
221550
+ ...patch.deliveryMode !== void 0 ? { deliveryMode: patch.deliveryMode } : {},
221551
+ ...patch.expiresAt !== void 0 ? { expiresAt: patch.expiresAt } : {},
221552
+ ...patch.by ? { updatedBy: patch.by } : {},
221553
+ updatedAt: this.now()
221001
221554
  });
221002
221555
  }
221003
221556
  /** ADR 凭据保险库补章 §D2.3:某一行变量「明文真被取用」→ 刷 lastUsedAt。行按 (key, actorId?, projectId?)
@@ -221072,7 +221625,10 @@ ${input.description}
221072
221625
  // 存量行没有这三格,原样不带出去——展示侧出「—」,不拿 updatedAt 冒充 createdAt。
221073
221626
  ...r.createdBy !== void 0 ? { createdBy: r.createdBy } : {},
221074
221627
  ...r.createdAt !== void 0 ? { createdAt: r.createdAt } : {},
221075
- ...r.updatedBy !== void 0 ? { updatedBy: r.updatedBy } : {}
221628
+ ...r.updatedBy !== void 0 ? { updatedBy: r.updatedBy } : {},
221629
+ // 决策 0556:发给谁。存量行没有 → 不带出去,读侧按「全员生效」理解。
221630
+ ...r.audience !== void 0 ? { audience: r.audience } : {},
221631
+ ...r.enabledActorIds !== void 0 ? { enabledActorIds: r.enabledActorIds } : {}
221076
221632
  };
221077
221633
  });
221078
221634
  }
@@ -221536,415 +222092,6 @@ var init_skill_bundle = __esm({
221536
222092
  }
221537
222093
  });
221538
222094
 
221539
- // ../server/src/domains/actors/model-pricing.ts
221540
- function lookupInMap(map, prefixKeys, candidates) {
221541
- for (const candidate of candidates) {
221542
- const exact = map instanceof Map ? map.get(candidate) : map[candidate];
221543
- if (exact) return exact;
221544
- }
221545
- const prefix = prefixKeys.find((p2) => candidates.some((candidate) => candidate.startsWith(`${p2}-`) || candidate.startsWith(`${p2}:`)));
221546
- if (!prefix) return null;
221547
- return map instanceof Map ? map.get(prefix) ?? null : map[prefix] ?? null;
221548
- }
221549
- function stripBracketTier(model) {
221550
- return model.replace(/\[[^\]]*\]/g, "").trim();
221551
- }
221552
- function modelCandidates(model) {
221553
- const normalized4 = model.trim().toLowerCase();
221554
- if (!normalized4) return [];
221555
- const base = [
221556
- normalized4,
221557
- normalized4.includes(":") ? normalized4.slice(normalized4.lastIndexOf(":") + 1) : "",
221558
- normalized4.includes("/") ? normalized4.slice(normalized4.lastIndexOf("/") + 1) : ""
221559
- ].filter(Boolean);
221560
- const stripped = base.map(stripBracketTier).filter((m2) => m2 && !base.includes(m2));
221561
- return [...base, ...new Set(stripped)];
221562
- }
221563
- function setModelPriceCacheLoader(loader) {
221564
- cacheLoader = loader;
221565
- }
221566
- async function warmModelPriceCache() {
221567
- if (!cacheLoader) return 0;
221568
- try {
221569
- const next = await cacheLoader();
221570
- dbPrices = next;
221571
- dbPrefixKeys = [...next.keys()].sort((a, b2) => b2.length - a.length);
221572
- cacheLoadedAt = Date.now();
221573
- return next.size;
221574
- } catch (err) {
221575
- console.warn(`[model-pricing] \u9884\u70ED\u4EF7\u683C\u7F13\u5B58\u5931\u8D25\uFF0C\u6CBF\u7528\u9759\u6001\u5FEB\u7167\uFF1A${String(err)}`);
221576
- return 0;
221577
- }
221578
- }
221579
- function maybeRefreshCache() {
221580
- if (!cacheLoader) return;
221581
- if (dbPrices && Date.now() - cacheLoadedAt < CACHE_TTL_MS2) return;
221582
- if (cacheRefreshInFlight) return;
221583
- cacheRefreshInFlight = warmModelPriceCache().catch(() => 0).then(() => {
221584
- cacheRefreshInFlight = null;
221585
- });
221586
- }
221587
- function priceForModel(model) {
221588
- if (!model) return null;
221589
- const candidates = modelCandidates(model);
221590
- if (candidates.length === 0) return null;
221591
- maybeRefreshCache();
221592
- if (dbPrices && dbPrices.size > 0) {
221593
- const fromDb = lookupInMap(dbPrices, dbPrefixKeys, candidates);
221594
- if (fromDb) return fromDb;
221595
- }
221596
- return lookupInMap(MODEL_PRICES, STATIC_PREFIX_PRICES, candidates);
221597
- }
221598
- function estimateUsageCostBreakdown(model, usage) {
221599
- const price = priceForModel(model);
221600
- if (!price) return null;
221601
- const inputTokens = Math.max(usage.inputTokens ?? 0, 0);
221602
- const outputTokens = Math.max(usage.outputTokens ?? 0, 0);
221603
- const cacheReadTokens = Math.max(usage.cacheReadTokens ?? 0, 0);
221604
- const cacheCreationTokens = Math.max(usage.cacheCreationTokens ?? 0, 0);
221605
- const cacheReadPrice = price.cachedInputPerMillion ?? price.inputPerMillion * 0.1;
221606
- return {
221607
- currency: price.currency,
221608
- inputMicros: Math.round(inputTokens * price.inputPerMillion / 1e6 * 1e6),
221609
- cacheMicros: Math.round(
221610
- (cacheCreationTokens * price.inputPerMillion + cacheReadTokens * cacheReadPrice) / 1e6 * 1e6
221611
- ),
221612
- outputMicros: Math.round(outputTokens * price.outputPerMillion / 1e6 * 1e6)
221613
- };
221614
- }
221615
- function estimateUsageCost(model, usage) {
221616
- const breakdown = estimateUsageCostBreakdown(model, usage);
221617
- if (!breakdown) return null;
221618
- return {
221619
- currency: breakdown.currency,
221620
- micros: breakdown.inputMicros + breakdown.cacheMicros + breakdown.outputMicros
221621
- };
221622
- }
221623
- function displayCostUsdMicros(model, usage) {
221624
- const stored = Math.max(usage.costUsdMicros ?? 0, 0);
221625
- if (stored > 0) return stored;
221626
- const estimated = estimateUsageCost(model, usage);
221627
- if (!estimated || estimated.micros <= 0) return 0;
221628
- if (estimated.currency === "USD") return estimated.micros;
221629
- return Math.round(estimated.micros / DISPLAY_CNY_PER_USD);
221630
- }
221631
- var FOREIGN_MODEL_MARKUP, usd, cny, MODEL_PRICES, STATIC_PREFIX_PRICES, CACHE_TTL_MS2, dbPrices, dbPrefixKeys, cacheLoadedAt, cacheLoader, cacheRefreshInFlight;
221632
- var init_model_pricing = __esm({
221633
- "../server/src/domains/actors/model-pricing.ts"() {
221634
- "use strict";
221635
- init_src();
221636
- FOREIGN_MODEL_MARKUP = 1.08;
221637
- usd = (inputPerMillion, outputPerMillion, cachedInputPerMillion) => ({
221638
- currency: "USD",
221639
- inputPerMillion: inputPerMillion * FOREIGN_MODEL_MARKUP,
221640
- outputPerMillion: outputPerMillion * FOREIGN_MODEL_MARKUP,
221641
- ...cachedInputPerMillion !== void 0 ? { cachedInputPerMillion: cachedInputPerMillion * FOREIGN_MODEL_MARKUP } : {}
221642
- });
221643
- cny = (inputPerMillion, outputPerMillion, cachedInputPerMillion) => ({
221644
- currency: "CNY",
221645
- inputPerMillion,
221646
- outputPerMillion,
221647
- ...cachedInputPerMillion !== void 0 ? { cachedInputPerMillion } : {}
221648
- });
221649
- MODEL_PRICES = {
221650
- "gpt-5.5": usd(5, 30, 0.5),
221651
- "gpt-5.5-pro": usd(30, 180),
221652
- "chat-latest": usd(5, 30, 0.5),
221653
- "gpt-5.4-mini": usd(0.75, 4.5, 0.075),
221654
- "gpt-5.4-nano": usd(0.2, 1.25, 0.02),
221655
- "gpt-5.4": usd(2.5, 15, 0.25),
221656
- "gpt-5.4-pro": usd(30, 180),
221657
- "gpt-5.3-codex": usd(1.75, 14, 0.175),
221658
- "gpt-5.2": usd(1.75, 14, 0.175),
221659
- "gpt-5.2-chat-latest": usd(1.75, 14, 0.175),
221660
- "gpt-5.2-pro": usd(21, 168),
221661
- "gpt-5.2-codex": usd(1.75, 14, 0.175),
221662
- "gpt-5.1": usd(1.25, 10, 0.125),
221663
- "gpt-5.1-chat-latest": usd(1.25, 10, 0.125),
221664
- "gpt-5.1-codex": usd(1.25, 10, 0.125),
221665
- "gpt-5.1-codex-max": usd(1.25, 10, 0.125),
221666
- "gpt-5.1-codex-mini": usd(0.25, 2, 0.025),
221667
- "gpt-5-pro": usd(15, 120),
221668
- "gpt-5-codex": usd(1.25, 10, 0.125),
221669
- "gpt-5-search-api": usd(1.25, 10, 0.125),
221670
- "gpt-5": usd(1.25, 10, 0.125),
221671
- "gpt-5-mini": usd(0.25, 2, 0.025),
221672
- "gpt-5-nano": usd(0.05, 0.4, 5e-3),
221673
- "gpt-5-chat-latest": usd(1.25, 10, 0.125),
221674
- "gpt-4.1": usd(2, 8, 0.5),
221675
- "gpt-4.1-2025-04-14": usd(2, 8, 0.5),
221676
- "gpt-4.1-mini": usd(0.4, 1.6, 0.1),
221677
- "gpt-4.1-mini-2025-04-14": usd(0.4, 1.6, 0.1),
221678
- "gpt-4.1-nano": usd(0.1, 0.4, 0.025),
221679
- "gpt-4.1-nano-2025-04-14": usd(0.1, 0.4, 0.025),
221680
- "o4-mini": usd(1.1, 4.4, 0.275),
221681
- // o3 / o3-mini 在 codex 静态目录里可选,却一直不在价目快照里(费用记 0)。
221682
- // 这两行按 OpenAI 公开价填,不是智增增快照里的行;供应商价目表同步到时以 DB 为准。
221683
- "o3": usd(2, 8),
221684
- "o3-mini": usd(1.1, 4.4),
221685
- "claude-fable-5-1": usd(10, 50),
221686
- "claude-fable-5": usd(10, 50),
221687
- "claude-mythos-5-1": usd(10, 50),
221688
- "claude-mythos-5": usd(10, 50),
221689
- // claude-opus-5 是 Claude Code 当代默认款,却一直不在这张快照里:查不到价 → 费用记 0
221690
- // (与 2026-09-04 那次 `claude-opus-5[1m]` 记 0 同一类坑)。按 Opus 档 $5/$25 补上。
221691
- "claude-opus-5": usd(5, 25),
221692
- "claude-opus-4-8": usd(5, 25),
221693
- "claude-opus-4-7": usd(5, 25),
221694
- "claude-sonnet-5": usd(2, 10),
221695
- "claude-sonnet-4-6": usd(3, 15),
221696
- "claude-opus-4-6": usd(5, 25),
221697
- "claude-opus-4-5-20251101": usd(5, 25),
221698
- "claude-opus-4-5": usd(5, 25),
221699
- "claude-sonnet-4-5-20250929": usd(3, 15),
221700
- // 短名同样要有:前缀比对是「表里的键是模型名的前缀」,带日期的那行匹配不上不带日期的短名。
221701
- // claude-sonnet-4-5 从 2026-07-04 起就在目录里可选,却一直查不到价(费用记 0),本次一并补。
221702
- "claude-sonnet-4-5": usd(3, 15),
221703
- "claude-opus-4-1-20250805": usd(15, 75),
221704
- "claude-opus-4-20250514": usd(15, 75),
221705
- "claude-sonnet-4-20250514": usd(3, 15),
221706
- "claude-3-7-sonnet-20250219": usd(3, 15),
221707
- "claude-3-5-sonnet-20241022": usd(3, 15),
221708
- "claude-3-5-sonnet-20240620": usd(3, 15),
221709
- "claude-3-sonnet-20240229": usd(3, 15),
221710
- "claude-haiku-4-5-20251001": usd(1, 5),
221711
- // codebuddy 目录用点号写法(claude-sonnet-4.6),与上面的连字符写法是同一批模型,
221712
- // 但字符串对不上就查不到价 → 记 0。同价另起别名行。
221713
- "claude-sonnet-4.6": usd(3, 15),
221714
- "claude-opus-4.7": usd(5, 25),
221715
- "gemini-3.1-pro": usd(2, 12),
221716
- "gemini-3-pro-preview": usd(2, 12),
221717
- // Claude Code 回报的可能是不带日期的短名,短名匹配不上带日期的那行(前缀比对是「表里的键是模型名的前缀」)。
221718
- "claude-haiku-4-5": usd(1, 5),
221719
- "gemini-3.5-flash": usd(1.5, 9),
221720
- "gemini-3.1-pro-preview": usd(2, 12),
221721
- "gemini-3.1-pro-preview-customtools": usd(2, 12),
221722
- "gemini-3.1-flash-lite-preview": usd(0.25, 1.5),
221723
- "gemini-3-flash-preview": usd(0.5, 3),
221724
- "gemini-2.5-pro": usd(1.25, 10),
221725
- "gemini-2.5-flash": usd(0.3, 2.5),
221726
- "gemini-2.5-flash-lite": usd(0.1, 0.4),
221727
- "gemini-2.5-flash-preview-09-2025": usd(0.3, 2.5),
221728
- "gemini-2.0-flash": usd(0.1, 0.4),
221729
- "deepseek-v4-flash": cny(1, 2, 0.02),
221730
- "deepseek-v4-pro": cny(3, 6, 0.025),
221731
- "deepseek-chat": cny(2, 3),
221732
- "deepseek-reasoner": cny(2, 3),
221733
- "deepseek-v3.2": cny(2, 3),
221734
- "deepseek-coder": cny(2, 3),
221735
- "deepseek-r1": cny(4, 16),
221736
- "deepseek-r1-0528": cny(4, 16),
221737
- "deepseek-v3": cny(2, 8),
221738
- "deepseek-v3-0324": cny(2, 8),
221739
- "deepseek-v3.1": cny(4, 12),
221740
- "qwen3-max": cny(6, 24),
221741
- "qwen3-max-preview": cny(6, 24),
221742
- "qwen3-coder-plus": cny(4, 16),
221743
- "qwen3-235b-a22b": cny(4, 40),
221744
- "qwen3-32b": cny(2, 20),
221745
- "qwen3-30b-a3b": cny(1.5, 15),
221746
- "qwen3-14b": cny(1, 10),
221747
- "qwen3-8b": cny(0.5, 5),
221748
- "qwen3-4b": cny(0.3, 3),
221749
- "qwen3-1.7b": cny(0.3, 3),
221750
- "qwen3-0.6b": cny(0.3, 3),
221751
- "qwq-plus": cny(1.6, 4),
221752
- "qwq-plus-latest": cny(1.6, 4),
221753
- "qwq-plus-2025-03-05": cny(1.6, 4),
221754
- "kimi-k2.7-code": cny(6.5, 27),
221755
- "kimi-k2.6": cny(6.5, 27),
221756
- "kimi-k2.5": cny(4, 21),
221757
- "kimi-k2-0905-preview": cny(4, 16),
221758
- "kimi-k2-0711-preview": cny(4, 16),
221759
- "kimi-k2-turbo-preview": cny(8, 58)
221760
- };
221761
- STATIC_PREFIX_PRICES = Object.keys(MODEL_PRICES).sort((a, b2) => b2.length - a.length);
221762
- CACHE_TTL_MS2 = 5 * 6e4;
221763
- dbPrices = null;
221764
- dbPrefixKeys = [];
221765
- cacheLoadedAt = 0;
221766
- cacheLoader = null;
221767
- cacheRefreshInFlight = null;
221768
- }
221769
- });
221770
-
221771
- // ../server/src/domains/usage/accounting.ts
221772
- function nonNegative(v2) {
221773
- return Math.max(v2 ?? 0, 0);
221774
- }
221775
- function resolvedModel(model) {
221776
- const trimmed = model?.trim();
221777
- return trimmed ? trimmed : null;
221778
- }
221779
- function splitByWeight(total, w2) {
221780
- const sum = w2.input + w2.cache + w2.output;
221781
- if (total === 0) return { input: 0, cache: 0, output: 0 };
221782
- if (sum <= 0) return { input: 0, cache: 0, output: total };
221783
- const input = Math.round(total * w2.input / sum);
221784
- const cache = Math.round(total * w2.cache / sum);
221785
- return { input, cache, output: total - input - cache };
221786
- }
221787
- function toDisplayUsd(currency, micros) {
221788
- return currency === "USD" ? micros : Math.round(micros / DISPLAY_CNY_PER_USD);
221789
- }
221790
- function accountUsage(model, usage) {
221791
- const resolved = resolvedModel(model);
221792
- if (!resolved || !usage) return null;
221793
- const input = nonNegative(usage.inputTokens);
221794
- const output = nonNegative(usage.outputTokens);
221795
- const cache = nonNegative(usage.cacheReadTokens) + nonNegative(usage.cacheCreationTokens);
221796
- const tokens = { input, cache, output, total: input + cache + output };
221797
- const estimated = estimateUsageCostBreakdown(resolved, usage);
221798
- const weights = estimated ? { input: estimated.inputMicros, cache: estimated.cacheMicros, output: estimated.outputMicros } : null;
221799
- const reported = Math.max(usage.costUsdMicros ?? 0, 0);
221800
- if (reported > 0) {
221801
- const parts = weights && estimated?.currency === "USD" ? splitByWeight(reported, weights) : { input: 0, cache: 0, output: reported };
221802
- return {
221803
- tokens,
221804
- currency: "USD",
221805
- amountMicros: reported,
221806
- parts,
221807
- displayUsdMicros: reported,
221808
- displayParts: parts,
221809
- source: "reported"
221810
- };
221811
- }
221812
- if (estimated && weights) {
221813
- const amount = weights.input + weights.cache + weights.output;
221814
- const displayUsdMicros = toDisplayUsd(estimated.currency, amount);
221815
- return {
221816
- tokens,
221817
- currency: estimated.currency,
221818
- amountMicros: amount,
221819
- parts: weights,
221820
- displayUsdMicros,
221821
- displayParts: estimated.currency === "USD" ? weights : splitByWeight(displayUsdMicros, weights),
221822
- source: "estimated"
221823
- };
221824
- }
221825
- return {
221826
- tokens,
221827
- currency: "USD",
221828
- amountMicros: 0,
221829
- parts: { input: 0, cache: 0, output: 0 },
221830
- displayUsdMicros: 0,
221831
- displayParts: { input: 0, cache: 0, output: 0 },
221832
- source: "unpriced"
221833
- };
221834
- }
221835
- function isUnpriced(account) {
221836
- return account.source === "unpriced" && account.tokens.total > 0;
221837
- }
221838
- var init_accounting = __esm({
221839
- "../server/src/domains/usage/accounting.ts"() {
221840
- "use strict";
221841
- init_model_pricing();
221842
- }
221843
- });
221844
-
221845
- // ../server/src/domains/actors/stats.ts
221846
- function emptyCostBreakdown() {
221847
- return {
221848
- inputUsdMicros: 0,
221849
- cacheUsdMicros: 0,
221850
- outputUsdMicros: 0,
221851
- inputCnyMicros: 0,
221852
- cacheCnyMicros: 0,
221853
- outputCnyMicros: 0
221854
- };
221855
- }
221856
- function emptySummary() {
221857
- return {
221858
- tokenWeek: 0,
221859
- tokenWeekBreakdown: { input: 0, cache: 0, output: 0 },
221860
- costWeekUsdMicros: 0,
221861
- costWeekCnyMicros: 0,
221862
- costWeekBreakdown: emptyCostBreakdown(),
221863
- unpricedModels: []
221864
- };
221865
- }
221866
- function addAccount(target, account) {
221867
- target.tokenWeekBreakdown.input += account.tokens.input;
221868
- target.tokenWeekBreakdown.cache += account.tokens.cache;
221869
- target.tokenWeekBreakdown.output += account.tokens.output;
221870
- target.tokenWeek += account.tokens.total;
221871
- if (account.currency === "USD") {
221872
- target.costWeekUsdMicros += account.amountMicros;
221873
- target.costWeekBreakdown.inputUsdMicros += account.parts.input;
221874
- target.costWeekBreakdown.cacheUsdMicros += account.parts.cache;
221875
- target.costWeekBreakdown.outputUsdMicros += account.parts.output;
221876
- } else {
221877
- target.costWeekCnyMicros += account.amountMicros;
221878
- target.costWeekBreakdown.inputCnyMicros += account.parts.input;
221879
- target.costWeekBreakdown.cacheCnyMicros += account.parts.cache;
221880
- target.costWeekBreakdown.outputCnyMicros += account.parts.output;
221881
- }
221882
- }
221883
- function summarize2(rows) {
221884
- const out = emptySummary();
221885
- const unpriced = /* @__PURE__ */ new Set();
221886
- for (const row of rows) {
221887
- const account = accountUsage(row.model, row.usage);
221888
- if (!account) continue;
221889
- addAccount(out, account);
221890
- if (isUnpriced(account)) unpriced.add(resolvedModel(row.model));
221891
- }
221892
- out.unpricedModels = [...unpriced].sort();
221893
- return out;
221894
- }
221895
- async function actorStats(actorId, opts = {}) {
221896
- const now = opts.now ?? /* @__PURE__ */ new Date();
221897
- const from = new Date(now.getTime() - 7 * 24 * 60 * 60 * 1e3).toISOString();
221898
- const to = now.toISOString();
221899
- const weeklyUsage = opts.usage?.listRuns ? summarizeWeeklyRuns(await listWeeklyRuns(opts.usage, actorId, from, to, opts.artifactIds)) : summarizeWeeklyUsage(!opts.artifactIds && opts.usage?.queryUsageStats ? await opts.usage.queryUsageStats({
221900
- actorIds: [actorId],
221901
- from,
221902
- to,
221903
- granularity: "hour"
221904
- }) : []);
221905
- return {
221906
- actorId,
221907
- usageAvailable: !!(opts.usage?.listRuns || !opts.artifactIds && opts.usage?.queryUsageStats),
221908
- ...opts.model ? { participatingTaskCount: participatingTaskCount(opts.model, actorId) } : {},
221909
- tokenWeek: weeklyUsage.tokenWeek,
221910
- tokenWeekBreakdown: weeklyUsage.tokenWeekBreakdown,
221911
- costWeekUsdMicros: weeklyUsage.costWeekUsdMicros,
221912
- costWeekCnyMicros: weeklyUsage.costWeekCnyMicros,
221913
- costWeekBreakdown: weeklyUsage.costWeekBreakdown,
221914
- usageWindow: { from, to },
221915
- unpricedModels: weeklyUsage.unpricedModels
221916
- };
221917
- }
221918
- async function listWeeklyRuns(trace, actorId, from, to, artifactIds) {
221919
- const out = [];
221920
- let cursor;
221921
- do {
221922
- const page = await trace.listRuns({ actorId, from, to, limit: 500, ...cursor ? { cursor } : {} });
221923
- out.push(...page.items.filter((run) => !artifactIds || run.artifactId != null && artifactIds.has(run.artifactId)));
221924
- cursor = page.nextCursor ?? void 0;
221925
- } while (cursor);
221926
- return out;
221927
- }
221928
- function summarizeWeeklyRuns(runs) {
221929
- return summarize2(runs.map((run) => ({ model: run.effectiveModel, usage: run.usage })));
221930
- }
221931
- function summarizeWeeklyUsage(rows) {
221932
- return summarize2(rows.map((row) => ({ model: row.effectiveModel, usage: row })));
221933
- }
221934
- function participatingTaskCount(model, actorId) {
221935
- const workorders = /* @__PURE__ */ new Set();
221936
- for (const artifact of model.artifacts.values()) {
221937
- if (artifact.owner === actorId) workorders.add(artifact.workspace);
221938
- }
221939
- return workorders.size;
221940
- }
221941
- var init_stats = __esm({
221942
- "../server/src/domains/actors/stats.ts"() {
221943
- "use strict";
221944
- init_accounting();
221945
- }
221946
- });
221947
-
221948
222095
  // ../server/src/domains/actors/list-usage.ts
221949
222096
  function aggregateUsageByActor(runs) {
221950
222097
  const out = /* @__PURE__ */ new Map();
@@ -222929,6 +223076,10 @@ function actorsDomain(opts) {
222929
223076
  const b2 = req.body;
222930
223077
  if (!b2?.key) throw new ApiError(400, "BAD_REQUEST", "\u7F3A\u5C11 key");
222931
223078
  const expiresAt2 = normalizeExpiresAt(b2.expiresAt);
223079
+ const audienceMeta = parseAudienceMeta(b2);
223080
+ if ((audienceMeta.audience !== void 0 || audienceMeta.enabledActorIds !== void 0) && b2.scope !== "global") {
223081
+ throw new ApiError(400, "BAD_REQUEST", "audience / enabledActorIds \u53EA\u80FD\u5199\u5728 scope=global \u7684\u7EC4\u7EC7\u9ED8\u8BA4\u503C\u4E0A");
223082
+ }
222932
223083
  if (b2.scope !== "global" && b2.scope !== "personal" && b2.scope !== "project") {
222933
223084
  throw new ApiError(
222934
223085
  400,
@@ -222973,6 +223124,7 @@ function actorsDomain(opts) {
222973
223124
  ...b2.connectorId !== void 0 ? { connectorId: b2.connectorId } : {},
222974
223125
  ...b2.overrides !== void 0 ? { overrides: b2.overrides } : {},
222975
223126
  ...b2.encrypted !== void 0 ? { encrypted: b2.encrypted } : {},
223127
+ ...audienceMeta,
222976
223128
  // 创建者/更新者取**当前登录人**,不接受 body 传入——自报的「谁建的」等于没有这一格。
222977
223129
  by: req.auth.actor,
222978
223130
  expiresAt: expiresAt2
@@ -222985,6 +223137,18 @@ function actorsDomain(opts) {
222985
223137
  );
222986
223138
  return { status: 201, body: { ok: true } };
222987
223139
  });
223140
+ router.patch("/api/variables/:key", async (req) => {
223141
+ requireVariableManager(req);
223142
+ const { service } = await resolveCtx(req.auth.companyId);
223143
+ const key = req.params.key;
223144
+ const b2 = req.body ?? {};
223145
+ const patch = parseMetaPatch(b2, { allowAudience: true });
223146
+ await requireSecretOwner(req, service, key, {});
223147
+ const ok3 = await service.patchVariableMeta(key, {}, { ...patch, by: req.auth.actor });
223148
+ if (!ok3) throw new ApiError(404, "VARIABLE_NOT_FOUND", `\u6CA1\u6709\u53EB ${key} \u7684\u7EC4\u7EC7\u9ED8\u8BA4\u503C`);
223149
+ await auditVariableChange(req, "credential_write", key, "global");
223150
+ return { status: 200, body: { ok: true } };
223151
+ });
222988
223152
  router.delete("/api/variables/:key", async (req) => {
222989
223153
  requireVariableManager(req);
222990
223154
  const { service } = await resolveCtx(req.auth.companyId);
@@ -223055,6 +223219,19 @@ function actorsDomain(opts) {
223055
223219
  await auditVariableChange(req, "credential_write", b2.key, `personal:${actorId}`);
223056
223220
  return { status: 201, body: { ok: true } };
223057
223221
  });
223222
+ router.patch("/api/actors/:id/variables/:key", async (req) => {
223223
+ requireVariableManager(req);
223224
+ const { service } = await resolveCtx(req.auth.companyId);
223225
+ const actorId = req.params.id;
223226
+ const key = req.params.key;
223227
+ const b2 = req.body ?? {};
223228
+ const patch = parseMetaPatch(b2, { allowAudience: false });
223229
+ await requireSecretOwner(req, service, key, { actorId });
223230
+ const ok3 = await service.patchVariableMeta(key, { actorId }, { ...patch, by: req.auth.actor });
223231
+ if (!ok3) throw new ApiError(404, "VARIABLE_NOT_FOUND", `\u8FD9\u4F4D Agent \u6CA1\u6709\u5355\u72EC\u8BBE\u7F6E ${key}`);
223232
+ await auditVariableChange(req, "credential_write", key, `personal:${actorId}`);
223233
+ return { status: 200, body: { ok: true } };
223234
+ });
223058
223235
  router.delete("/api/actors/:id/variables/:key", async (req) => {
223059
223236
  requireVariableManager(req);
223060
223237
  const { service } = await resolveCtx(req.auth.companyId);
@@ -223657,6 +223834,43 @@ function actorsDomain(opts) {
223657
223834
  });
223658
223835
  };
223659
223836
  }
223837
+ function parseAudienceMeta(b2) {
223838
+ const out = {};
223839
+ if (b2.audience !== void 0) {
223840
+ if (b2.audience !== "all" && b2.audience !== "selected") {
223841
+ throw new ApiError(400, "BAD_REQUEST", "audience \u53EA\u80FD\u662F all \u6216 selected");
223842
+ }
223843
+ out.audience = b2.audience;
223844
+ }
223845
+ if (b2.enabledActorIds !== void 0) {
223846
+ if (!Array.isArray(b2.enabledActorIds) || b2.enabledActorIds.some((x2) => typeof x2 !== "string" || !x2)) {
223847
+ throw new ApiError(400, "BAD_REQUEST", "enabledActorIds \u5FC5\u987B\u662F\u975E\u7A7A\u5B57\u7B26\u4E32\u6570\u7EC4");
223848
+ }
223849
+ out.enabledActorIds = b2.enabledActorIds;
223850
+ }
223851
+ return out;
223852
+ }
223853
+ function parseMetaPatch(b2, opts) {
223854
+ const out = {};
223855
+ const aud = parseAudienceMeta(b2);
223856
+ if ((aud.audience !== void 0 || aud.enabledActorIds !== void 0) && !opts.allowAudience) {
223857
+ throw new ApiError(400, "BAD_REQUEST", "\u4E2A\u4EBA\u6863\u6CA1\u6709\u300C\u53D1\u7ED9\u8C01\u300D\u2014\u2014audience / enabledActorIds \u8BF7\u5199\u5728\u7EC4\u7EC7\u9ED8\u8BA4\u503C\u4E0A");
223858
+ }
223859
+ Object.assign(out, aud);
223860
+ if (b2.deliveryMode !== void 0) {
223861
+ if (b2.deliveryMode !== "env" && b2.deliveryMode !== "ref" && b2.deliveryMode !== "broker") {
223862
+ throw new ApiError(400, "BAD_REQUEST", "deliveryMode \u53EA\u80FD\u662F env\u3001ref \u6216 broker");
223863
+ }
223864
+ out.deliveryMode = b2.deliveryMode;
223865
+ }
223866
+ if (b2.expiresAt !== void 0) {
223867
+ if (b2.expiresAt === null || b2.expiresAt === "") out.expiresAt = null;
223868
+ else if (typeof b2.expiresAt === "string") out.expiresAt = normalizeExpiresAt(b2.expiresAt) ?? null;
223869
+ else throw new ApiError(400, "BAD_REQUEST", "expiresAt \u5FC5\u987B\u662F ISO \u65F6\u95F4\u5B57\u7B26\u4E32\u6216 null");
223870
+ }
223871
+ if (Object.keys(out).length === 0) throw new ApiError(400, "BAD_REQUEST", "\u81F3\u5C11\u8981\u6539\u4E00\u6837\uFF1Aaudience / enabledActorIds / deliveryMode / expiresAt");
223872
+ return out;
223873
+ }
223660
223874
  var CREDENTIAL_REVEAL_SOURCES, RevealRateLimiter;
223661
223875
  var init_routes3 = __esm({
223662
223876
  "../server/src/domains/actors/routes.ts"() {
@@ -224235,6 +224449,20 @@ ${errs.join("\n")}`);
224235
224449
  });
224236
224450
  return { status: 200, body: toApiProjectArtifacts(list2, req.query) };
224237
224451
  });
224452
+ router.get("/api/actors/:id/participation-stats", async (req) => {
224453
+ const service = await svc(req);
224454
+ let stats;
224455
+ try {
224456
+ stats = await service.getActorParticipationStats(req.params.id);
224457
+ } catch (error2) {
224458
+ if (error2 instanceof Error && error2.message === "participation source unavailable") {
224459
+ throw new ApiError(503, "UNAVAILABLE", "\u6210\u5458\u7EDF\u8BA1\u6570\u636E\u6E90\u672A\u63A5\u5165");
224460
+ }
224461
+ throw error2;
224462
+ }
224463
+ if (!stats) throw new ApiError(404, "NOT_FOUND", "\u8FD9\u4E2A\u7EC4\u7EC7\u91CC\u6CA1\u6709\u8FD9\u4F4D\u6210\u5458");
224464
+ return { status: 200, body: stats };
224465
+ });
224238
224466
  router.get("/api/actors/:id/files-view", async (req) => {
224239
224467
  const items = await (await svc(req)).listActorFiles(req.params.id);
224240
224468
  return {
@@ -229122,9 +229350,50 @@ function reasonFromWorkdirCode(code2) {
229122
229350
  function joinRel2(dir, name) {
229123
229351
  return dir ? `${dir}/${name}` : name;
229124
229352
  }
229353
+ function workdirFileItem(rel, name, size, mtime) {
229354
+ return {
229355
+ id: `workdir:${rel}`,
229356
+ name,
229357
+ kind: "file",
229358
+ // 节点侧列目录只报 name/type/size/mtime,没有 MIME——诚实留 null,
229359
+ // 不拿后缀猜(红线:取不到就是取不到,别拿别的字段顶)。
229360
+ content_type: null,
229361
+ size,
229362
+ updated_at: mtime,
229363
+ source: "workdir",
229364
+ blob_ref: null,
229365
+ path: rel,
229366
+ message_id: null,
229367
+ // 工作区文件本期不开写回(brief §4①:要动 node↔server 协议帧,超预算)。
229368
+ // 这里是**按能力不渲染**的判据,不是置灰假钮。
229369
+ editable: false
229370
+ };
229371
+ }
229125
229372
  async function collectWorkdirFiles(bridge, loc, now = Date.now) {
229126
- const deadline = now() + WORKDIR_WALK_BUDGET_MS;
229127
229373
  const items = [];
229374
+ if (typeof bridge.walk === "function") {
229375
+ const walked = await bridge.walk(
229376
+ loc.nodeId,
229377
+ { workdirKey: loc.workdirKey, runtimeKind: loc.runtimeKind, rtId: loc.rtId, path: "" },
229378
+ WORKDIR_TREE_MAX_ENTRIES
229379
+ );
229380
+ if (walked.ok) {
229381
+ let truncated2 = walked.truncated === true;
229382
+ for (const entry of walked.entries) {
229383
+ if (entry.type !== "file") continue;
229384
+ if (items.length >= WORKDIR_WALK_MAX_ENTRIES) {
229385
+ truncated2 = true;
229386
+ break;
229387
+ }
229388
+ const slash = entry.path.lastIndexOf("/");
229389
+ items.push(workdirFileItem(entry.path, entry.path.slice(slash + 1), entry.size, entry.mtime));
229390
+ }
229391
+ return { ok: true, items, truncated: truncated2 };
229392
+ }
229393
+ if (walked.code === "NOT_FOUND") return { ok: true, items: [], truncated: false };
229394
+ if (walked.code !== "UNSUPPORTED") return { ok: false, reason: reasonFromWorkdirCode(walked.code) };
229395
+ }
229396
+ const deadline = now() + WORKDIR_WALK_BUDGET_MS;
229128
229397
  let listCalls = 0;
229129
229398
  let truncated = false;
229130
229399
  const listDir = (path41) => {
@@ -229159,24 +229428,7 @@ async function collectWorkdirFiles(bridge, loc, now = Date.now) {
229159
229428
  truncated = true;
229160
229429
  continue;
229161
229430
  }
229162
- const rel = joinRel2(dir, entry.name);
229163
- items.push({
229164
- id: `workdir:${rel}`,
229165
- name: entry.name,
229166
- kind: "file",
229167
- // 节点侧列目录只报 name/type/size/mtime,没有 MIME——诚实留 null,
229168
- // 不拿后缀猜(红线:取不到就是取不到,别拿别的字段顶)。
229169
- content_type: null,
229170
- size: entry.size,
229171
- updated_at: entry.mtime,
229172
- source: "workdir",
229173
- blob_ref: null,
229174
- path: rel,
229175
- message_id: null,
229176
- // 工作区文件本期不开写回(brief §4①:要动 node↔server 协议帧,超预算)。
229177
- // 这里是**按能力不渲染**的判据,不是置灰假钮。
229178
- editable: false
229179
- });
229431
+ items.push(workdirFileItem(joinRel2(dir, entry.name), entry.name, entry.size, entry.mtime));
229180
229432
  }
229181
229433
  }
229182
229434
  if (depth === WORKDIR_WALK_MAX_DEPTH || nextDirs.length === 0) {
@@ -229298,7 +229550,7 @@ async function findMaterializedAttachmentCopies(messageItems, workdirItems, read
229298
229550
  }
229299
229551
  return copies;
229300
229552
  }
229301
- var import_node_crypto51, WORKDIR_WALK_MAX_DEPTH, WORKDIR_WALK_MAX_ENTRIES, WORKDIR_WALK_MAX_LIST_CALLS, WORKDIR_WALK_BUDGET_MS;
229553
+ var import_node_crypto51, WORKDIR_WALK_MAX_DEPTH, WORKDIR_WALK_MAX_ENTRIES, WORKDIR_WALK_MAX_LIST_CALLS, WORKDIR_WALK_BUDGET_MS, WORKDIR_TREE_MAX_ENTRIES;
229302
229554
  var init_files_view = __esm({
229303
229555
  "../server/src/domains/chat-sessions/files-view.ts"() {
229304
229556
  "use strict";
@@ -229309,6 +229561,7 @@ var init_files_view = __esm({
229309
229561
  WORKDIR_WALK_MAX_ENTRIES = 200;
229310
229562
  WORKDIR_WALK_MAX_LIST_CALLS = 20;
229311
229563
  WORKDIR_WALK_BUDGET_MS = 1e4;
229564
+ WORKDIR_TREE_MAX_ENTRIES = 2e3;
229312
229565
  }
229313
229566
  });
229314
229567
 
@@ -231543,6 +231796,84 @@ var init_connect_script = __esm({
231543
231796
  }
231544
231797
  });
231545
231798
 
231799
+ // ../server/src/domains/nodes/runtime-activity.ts
231800
+ function isRuntimeRunReader(store) {
231801
+ const s2 = store;
231802
+ return !!s2 && typeof s2.runtimeRunActivity === "function" && typeof s2.listRuntimeRuns === "function";
231803
+ }
231804
+ function parseRuntimeUsageWindow(query, granularity, nowMs) {
231805
+ const toRaw = query.get("to");
231806
+ const fromRaw = query.get("from");
231807
+ const toMs = toRaw ? Date.parse(toRaw) : nowMs;
231808
+ if (!Number.isFinite(toMs)) return "to \u4E0D\u662F\u5408\u6CD5\u7684 ISO \u65F6\u95F4";
231809
+ const fromMs = fromRaw ? Date.parse(fromRaw) : toMs - DEFAULT_WINDOW_MS[granularity];
231810
+ if (!Number.isFinite(fromMs)) return "from \u4E0D\u662F\u5408\u6CD5\u7684 ISO \u65F6\u95F4";
231811
+ if (fromMs > toMs) return "from \u4E0D\u80FD\u665A\u4E8E to";
231812
+ if (toMs - fromMs > MAX_WINDOW_MS) return "\u65F6\u95F4\u7A97\u6700\u957F 92 \u5929";
231813
+ return { from: new Date(fromMs).toISOString(), to: new Date(toMs).toISOString() };
231814
+ }
231815
+ function runtimeUsageBucket(startedAt, granularity) {
231816
+ const ms = Date.parse(startedAt);
231817
+ if (!Number.isFinite(ms)) return null;
231818
+ const d = new Date(ms);
231819
+ if (granularity === "day") return d.toISOString().slice(0, 10);
231820
+ return new Date(Date.UTC(d.getUTCFullYear(), d.getUTCMonth(), d.getUTCDate(), d.getUTCHours())).toISOString();
231821
+ }
231822
+ function bucketRuntimeRuns(runtimeInstanceId, runs, granularity) {
231823
+ const rows = /* @__PURE__ */ new Map();
231824
+ for (const run of runs) {
231825
+ const bucket = runtimeUsageBucket(run.startedAt, granularity);
231826
+ if (!bucket) continue;
231827
+ const model = resolvedModel(run.effectiveModel);
231828
+ const key = `${bucket}\0${run.actorId}\0${model ?? ""}`;
231829
+ let row = rows.get(key);
231830
+ if (!row) {
231831
+ row = {
231832
+ actorId: run.actorId,
231833
+ runtimeInstanceId,
231834
+ runtimeKind: run.runtimeKind,
231835
+ effectiveModel: model,
231836
+ bucket,
231837
+ runCount: 0,
231838
+ inputTokens: 0,
231839
+ outputTokens: 0,
231840
+ totalTokens: 0,
231841
+ cacheReadTokens: 0,
231842
+ cacheCreationTokens: 0,
231843
+ costUsdMicros: 0
231844
+ };
231845
+ rows.set(key, row);
231846
+ }
231847
+ row.runCount += 1;
231848
+ const usage = run.usage;
231849
+ if (!usage) continue;
231850
+ const input = nonNegative2(usage.inputTokens);
231851
+ const output = nonNegative2(usage.outputTokens);
231852
+ row.inputTokens += input;
231853
+ row.outputTokens += output;
231854
+ row.totalTokens += input + output;
231855
+ row.cacheReadTokens += nonNegative2(usage.cacheReadTokens);
231856
+ row.cacheCreationTokens += nonNegative2(usage.cacheCreationTokens);
231857
+ const account = accountUsage(model, usage);
231858
+ row.costUsdMicros += account ? account.displayUsdMicros : nonNegative2(usage.costUsdMicros);
231859
+ }
231860
+ return [...rows.values()].sort((a, b2) => {
231861
+ if (a.bucket !== b2.bucket) return a.bucket < b2.bucket ? 1 : -1;
231862
+ if (a.actorId !== b2.actorId) return a.actorId < b2.actorId ? -1 : 1;
231863
+ return (a.effectiveModel ?? "").localeCompare(b2.effectiveModel ?? "");
231864
+ });
231865
+ }
231866
+ var DEFAULT_WINDOW_MS, MAX_WINDOW_MS, nonNegative2;
231867
+ var init_runtime_activity = __esm({
231868
+ "../server/src/domains/nodes/runtime-activity.ts"() {
231869
+ "use strict";
231870
+ init_accounting();
231871
+ DEFAULT_WINDOW_MS = { hour: 24 * 36e5, day: 30 * 864e5 };
231872
+ MAX_WINDOW_MS = 92 * 864e5;
231873
+ nonNegative2 = (v2) => Math.max(v2 ?? 0, 0);
231874
+ }
231875
+ });
231876
+
231546
231877
  // ../server/src/node-health.ts
231547
231878
  function deriveNodeHealth(input) {
231548
231879
  const staleMs = input.staleMs ?? 9e4;
@@ -231752,6 +232083,19 @@ function nodesDomain(deps) {
231752
232083
  hubWired = true;
231753
232084
  const { nodeStore } = deps;
231754
232085
  const now = () => (/* @__PURE__ */ new Date()).toISOString();
232086
+ const HEARTBEAT_PERSIST_MS = 6e4;
232087
+ const heartbeatPersistedAt = /* @__PURE__ */ new Map();
232088
+ hub.addMessageListener((daemonId) => {
232089
+ if (!nodeStore.touchNodeSeen) return;
232090
+ const ms = Date.now();
232091
+ const prev = heartbeatPersistedAt.get(daemonId) ?? 0;
232092
+ if (ms - prev < HEARTBEAT_PERSIST_MS) return;
232093
+ heartbeatPersistedAt.set(daemonId, ms);
232094
+ void nodeStore.touchNodeSeen(daemonId, new Date(ms).toISOString()).catch((err) => {
232095
+ heartbeatPersistedAt.delete(daemonId);
232096
+ console.warn(`[nodes] \u5FC3\u8DF3\u843D\u5E93\u5931\u8D25(node=${daemonId})\uFF1A${err instanceof Error ? err.message : String(err)}`);
232097
+ });
232098
+ });
231755
232099
  hub.addMessageListener(async (daemonId, msg) => {
231756
232100
  if (msg.type !== "hello") return;
231757
232101
  const d = hub.connectedDaemons().find((c) => c.daemonId === daemonId);
@@ -231820,6 +232164,23 @@ function nodesDomain(deps) {
231820
232164
  };
231821
232165
  tryWire();
231822
232166
  if (!hubWired) wirePoll = setInterval(tryWire, 100);
232167
+ async function forbidUnlessOrgManager(req, verb) {
232168
+ if (!deps.resolveMemberRole) return null;
232169
+ const role = await deps.resolveMemberRole(req.auth.companyId, req.auth.actor);
232170
+ if (role === "owner" || role === "admin") return null;
232171
+ return {
232172
+ status: 403,
232173
+ body: { error: "ORG_MANAGER_REQUIRED", message: `${verb}\u4EC5\u7EC4\u7EC7\u7BA1\u7406\u8005\u53EF\u64CD\u4F5C\uFF0C\u8BF7\u8054\u7CFB\u7EC4\u7EC7\u7BA1\u7406\u5458` }
232174
+ };
232175
+ }
232176
+ async function runtimeInScope(req) {
232177
+ const runtimeId = req.params["runtimeId"];
232178
+ if (!runtimeId) return { denied: { status: 400, body: { error: "missing runtimeId" } } };
232179
+ const scope = req.auth.companyId !== void 0 ? { companyId: req.auth.companyId } : void 0;
232180
+ const rt = (await deps.nodeStore.listRuntimes(void 0, scope)).find((r) => r.id === runtimeId);
232181
+ if (!rt) return { denied: { status: 404, body: { error: "runtime not found" } } };
232182
+ return { runtimeId };
232183
+ }
231823
232184
  async function syncConnectedDaemon(daemonId, hub) {
231824
232185
  const d = hub.connectedDaemons().find((c) => c.daemonId === daemonId);
231825
232186
  if (!d) return;
@@ -231848,6 +232209,13 @@ function nodesDomain(deps) {
231848
232209
  const outOfSync = nodes.filter((n) => onlineIds.has(n.id) && (n.status !== "online" || (runtimesByNode.get(n.id) ?? []).some((r) => r.status !== "online")));
231849
232210
  await Promise.all(outOfSync.map((n) => syncConnectedDaemon(n.id, hub)));
231850
232211
  }
232212
+ function heartbeatMsOf(n, offline) {
232213
+ const live = hub?.lastSeenAt(n.id);
232214
+ if (live !== void 0) return live;
232215
+ if (!offline) return void 0;
232216
+ const ms = Date.parse(n.lastSeenAt);
232217
+ return Number.isNaN(ms) ? void 0 : ms;
232218
+ }
231851
232219
  const items = await Promise.all(nodes.map(async (n) => {
231852
232220
  const rts = runtimesByNode.get(n.id) ?? [];
231853
232221
  const daemonVersion = daemonVersionById.get(n.id);
@@ -231877,7 +232245,16 @@ function nodesDomain(deps) {
231877
232245
  ...deps.nodeHealth ? {
231878
232246
  health: deriveNodeHealth({
231879
232247
  online: onlineIds.has(n.id),
231880
- ...hub?.lastSeenAt(n.id) !== void 0 ? { lastHeartbeatMs: hub.lastSeenAt(n.id) } : {},
232248
+ /*
232249
+ * 心跳时钟:在线节点读 hub 内存(每帧刷新,最准);**离线节点回落到落库的
232250
+ * `lastSeenAt`**——那是这台机器最后一次跟服务端说上话的时刻(心跳每分钟落一次库,
232251
+ * 见上面的 `HEARTBEAT_PERSIST_MS`;断开那一刻 `setNodeOnline(false)` 也会写一次)。
232252
+ * 运行时页「工作情况」列的离线行显示的「断连时间 X」读的就是它。
232253
+ *
232254
+ * 不影响健康判定:`healthy = online && 心跳新鲜 && 连败未超阈`,离线节点 `online=false`
232255
+ * 已经把结论钉死成不健康,这里补进来的只是**解释**用的时刻(设计稿 4.5 ⑤ 的口径)。
232256
+ */
232257
+ ...heartbeatMsOf(n, nodeOffline) !== void 0 ? { lastHeartbeatMs: heartbeatMsOf(n, nodeOffline) } : {},
231881
232258
  activeRunCount,
231882
232259
  ...deps.nodeHealth.snapshot(n.id) ? { snapshot: deps.nodeHealth.snapshot(n.id) } : {},
231883
232260
  now: healthNow
@@ -232047,6 +232424,8 @@ function nodesDomain(deps) {
232047
232424
  router.delete("/api/runtimes/:runtimeId", async (req) => {
232048
232425
  const runtimeId = req.params["runtimeId"];
232049
232426
  if (!runtimeId) return { status: 400, body: { error: "missing runtimeId" } };
232427
+ const forbidden = await forbidUnlessOrgManager(req, "\u5220\u9664\u8FD0\u884C\u65F6");
232428
+ if (forbidden) return forbidden;
232050
232429
  const runtime = (await deps.nodeStore.listRuntimes()).find((r) => r.id === runtimeId);
232051
232430
  const { nodeDeleted } = await deps.nodeStore.deleteRuntime(runtimeId);
232052
232431
  if (nodeDeleted && runtime) revokeNodeAccess(runtime.nodeId);
@@ -232105,6 +232484,23 @@ function nodesDomain(deps) {
232105
232484
  await deps.registry.upsertRuntimeModelOverride(override);
232106
232485
  return { status: 200, body: { override } };
232107
232486
  });
232487
+ router.get("/api/runtimes/:runtimeId/activity", async (req) => {
232488
+ const found = await runtimeInScope(req);
232489
+ if (found.denied) return found.denied;
232490
+ const reader = await deps.runtimeRuns?.(req.auth.companyId);
232491
+ const activity = reader ? await reader.runtimeRunActivity(found.runtimeId) : null;
232492
+ return { status: 200, body: { activity } };
232493
+ });
232494
+ router.get("/api/runtimes/:runtimeId/usage", async (req) => {
232495
+ const found = await runtimeInScope(req);
232496
+ if (found.denied) return found.denied;
232497
+ const granularity = req.query.get("granularity") === "day" ? "day" : "hour";
232498
+ const window2 = parseRuntimeUsageWindow(req.query, granularity, Date.now());
232499
+ if (typeof window2 === "string") return { status: 400, body: { error: window2 } };
232500
+ const reader = await deps.runtimeRuns?.(req.auth.companyId);
232501
+ const runs = reader ? await reader.listRuntimeRuns(found.runtimeId, window2) : null;
232502
+ return { status: 200, body: { items: bucketRuntimeRuns(found.runtimeId, runs ?? [], granularity) } };
232503
+ });
232108
232504
  router.get("/api/runtimes/:runtimeId/config", async (req) => {
232109
232505
  const runtimeId = req.params["runtimeId"];
232110
232506
  if (!runtimeId) return { status: 400, body: { error: "missing runtimeId" } };
@@ -232114,6 +232510,8 @@ function nodesDomain(deps) {
232114
232510
  router.put("/api/runtimes/:runtimeId/config", async (req) => {
232115
232511
  const runtimeId = req.params["runtimeId"];
232116
232512
  if (!runtimeId) return { status: 400, body: { error: "missing runtimeId" } };
232513
+ const forbidden = await forbidUnlessOrgManager(req, "\u4FEE\u6539\u56E2\u961F\u9ED8\u8BA4\u6A21\u578B");
232514
+ if (forbidden) return forbidden;
232117
232515
  const body2 = req.body ?? {};
232118
232516
  const cfg = {
232119
232517
  runtimeId,
@@ -232151,6 +232549,7 @@ var init_routes7 = __esm({
232151
232549
  init_src();
232152
232550
  init_src6();
232153
232551
  init_connect_script();
232552
+ init_runtime_activity();
232154
232553
  init_node_health();
232155
232554
  }
232156
232555
  });
@@ -232325,6 +232724,10 @@ var init_node_store = __esm({
232325
232724
  n.lastSeenAt = (/* @__PURE__ */ new Date()).toISOString();
232326
232725
  }
232327
232726
  }
232727
+ async touchNodeSeen(id, atIso) {
232728
+ const n = this.nodes.get(id);
232729
+ if (n && n.status !== "deleted") n.lastSeenAt = atIso;
232730
+ }
232328
232731
  async setNodeRuntimesOnline(nodeId, online) {
232329
232732
  const now = (/* @__PURE__ */ new Date()).toISOString();
232330
232733
  for (const rt of this.runtimes.values()) {
@@ -232444,6 +232847,10 @@ var init_node_store = __esm({
232444
232847
  await super.setNodeOnline(id, online);
232445
232848
  this.flush();
232446
232849
  }
232850
+ async touchNodeSeen(id, atIso) {
232851
+ await super.touchNodeSeen(id, atIso);
232852
+ this.flush();
232853
+ }
232447
232854
  async setNodeRuntimesOnline(nodeId, online) {
232448
232855
  await super.setNodeRuntimesOnline(nodeId, online);
232449
232856
  this.flush();
@@ -235958,7 +236365,9 @@ function createChatSessionsDomain(opts) {
235958
236365
  的去重装置,直接发给前端等于给了一条 `messages[]` 里不存在的 id ——追加转达卡全部掉进
235959
236366
  floating 堆在流末尾,同一条消息上的两条委派还会因后缀不同被拆成两张单专家卡。 */
235960
236367
  touches: touches.map((t) => ({ messageId: touchAnchorMessageId(t.messageId), kind: t.kind, at: t.at })),
235961
- artifacts: collectDelegationArtifacts(record8.roundSummaries),
236368
+ /* 与协调者读口**同一个函数**——两侧 artifacts 有「逐字同源」的既有断言。它顺带给每份产物
236369
+ 填上 `childMessageId`(子会话面板把产出卡挂到产出它的那一轮下面要用)。 */
236370
+ artifacts: delegationArtifactsView(record8),
235962
236371
  createdAt: record8.createdAt,
235963
236372
  ...record8.settledAt ? { settledAt: record8.settledAt } : {}
235964
236373
  });
@@ -236246,6 +236655,7 @@ var init_chat_sessions = __esm({
236246
236655
  init_escalation_opening();
236247
236656
  init_workorders();
236248
236657
  init_chat_parts();
236658
+ init_service3();
236249
236659
  init_injected_context();
236250
236660
  init_deletion();
236251
236661
  init_chat_turn_gate();
@@ -240644,6 +241054,29 @@ var init_daemon_adapter = __esm({
240644
241054
  maxBytes
240645
241055
  }));
240646
241056
  }
241057
+ /**
241058
+ * 一次列全 `loc.path` 下的整棵子树(ADR 0554)。节点没自报能力位 → 不发帧、直接回 `UNSUPPORTED`,
241059
+ * 调用方退回逐层 `list`。判据取 hello 里的能力位,不靠「发了看回不回」:老节点收到不认识的帧
241060
+ * 只会静默丢掉,那样要白等满 30 秒超时。
241061
+ */
241062
+ walk(nodeId, loc, maxEntries) {
241063
+ if (!this.nodeSupportsWalk(nodeId)) return Promise.resolve({ ok: false, code: "UNSUPPORTED" });
241064
+ return this.request(nodeId, (requestId) => ({
241065
+ type: "workdir_walk",
241066
+ requestId,
241067
+ workdirKey: loc.workdirKey,
241068
+ runtimeKind: loc.runtimeKind,
241069
+ rtId: loc.rtId ?? null,
241070
+ path: loc.path,
241071
+ maxEntries
241072
+ }));
241073
+ }
241074
+ nodeSupportsWalk(nodeId) {
241075
+ const connectedDaemons = this.hub.connectedDaemons;
241076
+ if (typeof connectedDaemons !== "function") return false;
241077
+ const daemon = connectedDaemons.call(this.hub).find((candidate) => candidate.daemonId === nodeId);
241078
+ return daemon?.meta.supportsWorkdirWalk === true;
241079
+ }
240647
241080
  request(nodeId, buildFrame) {
240648
241081
  const requestId = (0, import_node_crypto65.randomUUID)();
240649
241082
  const sent = this.hub.dispatch(nodeId, buildFrame(requestId));
@@ -240658,7 +241091,7 @@ var init_daemon_adapter = __esm({
240658
241091
  });
240659
241092
  }
240660
241093
  onDaemonMessage(msg) {
240661
- if (msg.type !== "workdir_list_result" && msg.type !== "workdir_read_result") return;
241094
+ if (msg.type !== "workdir_list_result" && msg.type !== "workdir_read_result" && msg.type !== "workdir_walk_result") return;
240662
241095
  const entry = this.pending.get(msg.requestId);
240663
241096
  if (!entry) return;
240664
241097
  this.pending.delete(msg.requestId);
@@ -242145,7 +242578,10 @@ function rowToVariable(row) {
242145
242578
  // 创建者 / 创建时间 / 更新者:2026-09-10 之前的行没有 → 不带这一格,展示侧出「—」。
242146
242579
  ...row.created_by ? { createdBy: row.created_by } : {},
242147
242580
  ...row.created_at ? { createdAt: row.created_at } : {},
242148
- ...row.updated_by ? { updatedBy: row.updated_by } : {}
242581
+ ...row.updated_by ? { updatedBy: row.updated_by } : {},
242582
+ // 决策 0556:NULL(存量行)不带出去——读侧按「全员生效」理解,与决策之前一致。
242583
+ ...row.audience === "all" || row.audience === "selected" ? { audience: row.audience } : {},
242584
+ ...Array.isArray(row.enabled_actor_ids) ? { enabledActorIds: row.enabled_actor_ids } : {}
242149
242585
  };
242150
242586
  }
242151
242587
  var ident4, PostgresRegistryStore, rowToActor, rowToConfig, rowToAgentPrefs;
@@ -242320,6 +242756,8 @@ var init_postgres_registry = __esm({
242320
242756
  await pool.query(`ALTER TABLE IF EXISTS "${s2}".variables ADD COLUMN IF NOT EXISTS created_by text`);
242321
242757
  await pool.query(`ALTER TABLE IF EXISTS "${s2}".variables ADD COLUMN IF NOT EXISTS created_at text`);
242322
242758
  await pool.query(`ALTER TABLE IF EXISTS "${s2}".variables ADD COLUMN IF NOT EXISTS updated_by text`);
242759
+ await pool.query(`ALTER TABLE IF EXISTS "${s2}".variables ADD COLUMN IF NOT EXISTS audience text`);
242760
+ await pool.query(`ALTER TABLE IF EXISTS "${s2}".variables ADD COLUMN IF NOT EXISTS enabled_actor_ids text[]`);
242323
242761
  await pool.query(`DROP INDEX IF EXISTS "${s2}"."${s2}_variables_key_actor_uq"`);
242324
242762
  await pool.query(`DROP INDEX IF EXISTS "${s2}"."${s2}_variables_scope_uq"`);
242325
242763
  await pool.query(`
@@ -242825,14 +243263,18 @@ var init_postgres_registry = __esm({
242825
243263
  /* created_by / created_at 走 COALESCE(**旧值**, 新值):**第一次落行才写**,之后每次 upsert
242826
243264
  都保留原值——否则「谁建的」会被最后一次改的人顶掉,这一格就没有意义了。
242827
243265
  updated_by 反过来:每次都覆盖。三者与 name 的 COALESCE 方向相反,别照抄。 */
242828
- `INSERT INTO ${this.s}.variables (key, scope, actor_id, project_id, connector_id, overrides, delivery_mode, value_encrypted, updated_at, expires_at, name, created_by, created_at, updated_by)
242829
- VALUES ($1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11,$12,$13,$14)
243266
+ /* audience / enabled_actor_ids(决策 0556)走 COALESCE(**新值**, 旧值):不传 = 不动——
243267
+ 改默认值时手上不带「发给谁」,普通赋值会把「仅指定 Agent」悄悄打回全员生效。 */
243268
+ `INSERT INTO ${this.s}.variables (key, scope, actor_id, project_id, connector_id, overrides, delivery_mode, value_encrypted, updated_at, expires_at, name, created_by, created_at, updated_by, audience, enabled_actor_ids)
243269
+ VALUES ($1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11,$12,$13,$14,$15,$16)
242830
243270
  ON CONFLICT (key, COALESCE(actor_id, ''), COALESCE(project_id, '')) DO UPDATE
242831
243271
  SET scope=$2, actor_id=$3, project_id=$4, connector_id=$5, overrides=$6, delivery_mode=$7, value_encrypted=$8, updated_at=$9, expires_at=$10,
242832
243272
  name=COALESCE($11, ${this.s}.variables.name),
242833
243273
  created_by=COALESCE(${this.s}.variables.created_by, $12),
242834
243274
  created_at=COALESCE(${this.s}.variables.created_at, $13),
242835
- updated_by=$14`,
243275
+ updated_by=$14,
243276
+ audience=COALESCE($15, ${this.s}.variables.audience),
243277
+ enabled_actor_ids=COALESCE($16, ${this.s}.variables.enabled_actor_ids)`,
242836
243278
  [
242837
243279
  v2.key,
242838
243280
  v2.scope,
@@ -242847,7 +243289,9 @@ var init_postgres_registry = __esm({
242847
243289
  v2.name ?? null,
242848
243290
  v2.createdBy ?? null,
242849
243291
  v2.createdAt ?? null,
242850
- v2.updatedBy ?? null
243292
+ v2.updatedBy ?? null,
243293
+ v2.audience ?? null,
243294
+ v2.enabledActorIds ?? null
242851
243295
  ]
242852
243296
  );
242853
243297
  }
@@ -242866,6 +243310,34 @@ var init_postgres_registry = __esm({
242866
243310
  [key, aid, valueEncrypted, updatedAt]
242867
243311
  );
242868
243312
  }
243313
+ async patchVariableMeta(key, actorId, patch) {
243314
+ const r = await this.pool.query(
243315
+ `UPDATE ${this.s}.variables SET
243316
+ audience = CASE WHEN $3::boolean THEN $4::text ELSE audience END,
243317
+ enabled_actor_ids = CASE WHEN $5::boolean THEN $6::text[] ELSE enabled_actor_ids END,
243318
+ delivery_mode = CASE WHEN $7::boolean THEN $8::text ELSE delivery_mode END,
243319
+ expires_at = CASE WHEN $9::boolean THEN $10::text ELSE expires_at END,
243320
+ updated_by = CASE WHEN $11::boolean THEN $12::text ELSE updated_by END,
243321
+ updated_at = $13
243322
+ WHERE key=$1 AND COALESCE(actor_id,'')=COALESCE($2::text,'') AND COALESCE(project_id,'')=''`,
243323
+ [
243324
+ key,
243325
+ actorId ?? null,
243326
+ patch.audience !== void 0,
243327
+ patch.audience ?? null,
243328
+ patch.enabledActorIds !== void 0,
243329
+ patch.enabledActorIds ?? null,
243330
+ patch.deliveryMode !== void 0,
243331
+ patch.deliveryMode ?? null,
243332
+ patch.expiresAt !== void 0,
243333
+ patch.expiresAt ?? null,
243334
+ patch.updatedBy !== void 0,
243335
+ patch.updatedBy ?? null,
243336
+ patch.updatedAt
243337
+ ]
243338
+ );
243339
+ return (r.rowCount ?? 0) > 0;
243340
+ }
242869
243341
  async getVariableCiphertext(key, actorId) {
242870
243342
  const aid = actorId ?? null;
242871
243343
  const r = await this.pool.query(
@@ -242878,7 +243350,14 @@ var init_postgres_registry = __esm({
242878
243350
  let r;
242879
243351
  if (actorId !== void 0) {
242880
243352
  r = await this.pool.query(
242881
- `SELECT * FROM ${this.s}.variables WHERE scope='global' OR actor_id=$1 ORDER BY key`,
243353
+ /* 决策 0556:`audience='selected'` 的全局行**只给 enabled_actor_ids 里那几位**。
243354
+ 注入(resolveActorEnv / resolveActorProvisionEntries)、agent 侧 reveal、组织页 Agent 的密钥列表
243355
+ 全都读这一条查询——语义只改在这一处,别在上层各自再滤一遍(滤漏一处就是泄漏)。
243356
+ audience 为 NULL(存量行)或 'all' 时行为与决策之前逐字一致。 */
243357
+ `SELECT * FROM ${this.s}.variables
243358
+ WHERE actor_id=$1
243359
+ OR (scope='global' AND (audience IS DISTINCT FROM 'selected' OR $1 = ANY(COALESCE(enabled_actor_ids, '{}'::text[]))))
243360
+ ORDER BY key`,
242882
243361
  [actorId]
242883
243362
  );
242884
243363
  } else {
@@ -245355,6 +245834,7 @@ var init_postgres_trace = __esm({
245355
245834
  AND left(job_key, char_length('produce::' || node_id || '::')) = 'produce::' || node_id || '::'`);
245356
245835
  await pool.query(`CREATE INDEX IF NOT EXISTS agent_runs_effective_model_idx ON "${s2}".agent_runs (effective_model)`);
245357
245836
  await pool.query(`CREATE INDEX IF NOT EXISTS agent_runs_actor_started_idx ON "${s2}".agent_runs (actor_id, started_at DESC)`);
245837
+ await pool.query(`CREATE INDEX IF NOT EXISTS agent_runs_runtime_started_idx ON "${s2}".agent_runs (runtime_instance_id, started_at DESC)`);
245358
245838
  await pool.query(`CREATE INDEX IF NOT EXISTS agent_runs_workorder_idx ON "${s2}".agent_runs (work_order_id, node_id, attempt_no DESC)`);
245359
245839
  await pool.query(`CREATE UNIQUE INDEX IF NOT EXISTS agent_runs_node_attempt_uq ON "${s2}".agent_runs (node_id, attempt_no) WHERE node_id IS NOT NULL AND attempt_no IS NOT NULL`);
245360
245840
  await pool.query(`
@@ -246041,6 +246521,41 @@ var init_postgres_trace = __esm({
246041
246521
  usage: row.usage
246042
246522
  }));
246043
246523
  }
246524
+ /* ---------- 运行时详情页:按实例直接读 run 行 ---------- */
246525
+ /**
246526
+ * 这个运行时实例上一共跑过几轮、最近一轮什么时候开跑。
246527
+ *
246528
+ * 读 `agent_runs` 而不是 `agent_usage_*` 聚合表:chat、工单节点(produce / integrate / review)、
246529
+ * 协调者每一轮**开跑时**就先落一行 run,所以开跑即计入;聚合表只在 run 结束且带 usage 时才 +1,
246530
+ * 失败 / 取消没 usage 的轮次永远不算。状态不过滤——每一轮都是一次运行。
246531
+ */
246532
+ async runtimeRunActivity(runtimeInstanceId) {
246533
+ const r = await this.pool.query(
246534
+ `SELECT count(*)::int AS run_count, max(started_at) AS last_started_at
246535
+ FROM ${this.s}.agent_runs WHERE runtime_instance_id = $1`,
246536
+ [runtimeInstanceId]
246537
+ );
246538
+ const row = r.rows[0] ?? {};
246539
+ return { runCount: Number(row.run_count ?? 0), lastRunStartedAt: row.last_started_at ?? null };
246540
+ }
246541
+ /** 该实例在时间窗内开跑的全部 run(含还没有 usage 的在途 / 失败轮次),按开跑时刻正序。 */
246542
+ async listRuntimeRuns(runtimeInstanceId, range) {
246543
+ const r = await this.pool.query(
246544
+ `SELECT actor_id, runtime_kind, effective_model, started_at, usage
246545
+ FROM ${this.s}.agent_runs
246546
+ WHERE runtime_instance_id = $1 AND started_at >= $2 AND started_at <= $3
246547
+ ORDER BY started_at`,
246548
+ [runtimeInstanceId, range.from, range.to]
246549
+ );
246550
+ return r.rows.map((row) => ({
246551
+ actorId: row.actor_id,
246552
+ runtimeKind: row.runtime_kind ?? "shell",
246553
+ effectiveModel: row.effective_model ?? null,
246554
+ startedAt: row.started_at,
246555
+ // 历史行里有 JSONB 字面量 null,与 SQL NULL 一样当「还没有 usage」。
246556
+ usage: row.usage && typeof row.usage === "object" ? row.usage : null
246557
+ }));
246558
+ }
246044
246559
  /* ---------- execution continuity ---------- */
246045
246560
  async getAttempt(attemptId) {
246046
246561
  return this.getRun(attemptId);
@@ -250669,6 +251184,13 @@ var init_postgres_nodes = __esm({
250669
251184
  async setNodeOnline(id, online) {
250670
251185
  await this.pool.query(`UPDATE ${this.s}.nodes SET status=$2,last_seen_at=$3 WHERE id=$1 AND status <> 'deleted'`, [id, online ? "online" : "offline", (/* @__PURE__ */ new Date()).toISOString()]);
250671
251186
  }
251187
+ /** 只推进 last_seen_at,不动 status(心跳落库;节流由调用方做,见 nodes/routes.ts)。 */
251188
+ async touchNodeSeen(id, atIso) {
251189
+ await this.pool.query(
251190
+ `UPDATE ${this.s}.nodes SET last_seen_at=$2 WHERE id=$1 AND status <> 'deleted' AND (last_seen_at IS NULL OR last_seen_at < $2)`,
251191
+ [id, atIso]
251192
+ );
251193
+ }
250672
251194
  async setNodeRuntimesOnline(nodeId, online) {
250673
251195
  await this.pool.query(`UPDATE ${this.s}.node_runtimes SET status=$2,last_seen_at=$3 WHERE node_id=$1`, [nodeId, online ? "online" : "offline", (/* @__PURE__ */ new Date()).toISOString()]);
250674
251196
  }
@@ -254049,6 +254571,9 @@ var init_cutover_aware_registry = __esm({
254049
254571
  async patchVariableValue(...args) {
254050
254572
  return (await this.orgStoreForWrite()).patchVariableValue(...args);
254051
254573
  }
254574
+ async patchVariableMeta(...args) {
254575
+ return (await this.orgStoreForWrite()).patchVariableMeta(...args);
254576
+ }
254052
254577
  async getVariableCiphertext(...args) {
254053
254578
  return (await this.orgStore()).getVariableCiphertext(...args);
254054
254579
  }
@@ -254430,6 +254955,15 @@ var init_cutover_aware_trace = __esm({
254430
254955
  async listUsageRuns(filter) {
254431
254956
  return (await this.pick()).listUsageRuns(filter);
254432
254957
  }
254958
+ // ── 运行时详情页的按实例读(不在 TraceStore 契约上:落点 store 没实现就当没有数据源)──────────
254959
+ async runtimeRunActivity(runtimeInstanceId) {
254960
+ const s2 = await this.pick();
254961
+ return typeof s2.runtimeRunActivity === "function" ? s2.runtimeRunActivity(runtimeInstanceId) : null;
254962
+ }
254963
+ async listRuntimeRuns(runtimeInstanceId, range) {
254964
+ const s2 = await this.pick();
254965
+ return typeof s2.listRuntimeRuns === "function" ? s2.listRuntimeRuns(runtimeInstanceId, range) : null;
254966
+ }
254433
254967
  // ── 事件 / 工具调用 / 链接 ────────────────────────────────────────────────
254434
254968
  async appendEvent(runId, event) {
254435
254969
  return (await this.pickWritable()).appendEvent(runId, event);
@@ -258519,7 +259053,7 @@ var init_run_settlement_wiring = __esm({
258519
259053
  });
258520
259054
 
258521
259055
  // ../server/src/deadline-clock.ts
258522
- async function deadlineClockTick(deps, dedupe) {
259056
+ async function deadlineClockTick(deps, dedupe2) {
258523
259057
  const limit = deps.batchLimit ?? DEADLINE_CLOCK_BATCH_LIMIT;
258524
259058
  const lookahead = deps.lookaheadMs ?? DEADLINE_CLOCK_LOOKAHEAD_MS;
258525
259059
  const policy = deps.policy();
@@ -258548,14 +259082,14 @@ async function deadlineClockTick(deps, dedupe) {
258548
259082
  if (overdue > result.oldestDueDelayMs) result.oldestDueDelayMs = overdue;
258549
259083
  const phase = overdue >= 0 ? "due" : "early";
258550
259084
  const bucket = `${c.workId}@${deadline}@${phase}`;
258551
- if (dedupe.has(c.workorderId, bucket)) {
259085
+ if (dedupe2.has(c.workorderId, bucket)) {
258552
259086
  result.deduped++;
258553
259087
  continue;
258554
259088
  }
258555
259089
  const buckets = toPing.get(c.workorderId) ?? [];
258556
259090
  buckets.push(bucket);
258557
259091
  toPing.set(c.workorderId, buckets);
258558
- dedupe.mark(c.workorderId, bucket);
259092
+ dedupe2.mark(c.workorderId, bucket);
258559
259093
  }
258560
259094
  if (page.nextCursor) result.backlog = toPing.size + result.deduped;
258561
259095
  for (const [workorderId, buckets] of toPing) {
@@ -258564,7 +259098,7 @@ async function deadlineClockTick(deps, dedupe) {
258564
259098
  result.pinged++;
258565
259099
  } catch (err) {
258566
259100
  result.failed++;
258567
- for (const b2 of buckets) dedupe.unmark(workorderId, b2);
259101
+ for (const b2 of buckets) dedupe2.unmark(workorderId, b2);
258568
259102
  deps.log?.(`[deadline-clock] ping ${workorderId} \u5931\u8D25: ${String(err)}`);
258569
259103
  }
258570
259104
  }
@@ -258728,6 +259262,7 @@ var init_src11 = __esm({
258728
259262
  init_auth();
258729
259263
  init_sender();
258730
259264
  init_routes7();
259265
+ init_runtime_activity();
258731
259266
  init_runtime_drift();
258732
259267
  init_runtime_tenancy();
258733
259268
  init_types4();
@@ -261543,6 +262078,11 @@ async function startServe(opts) {
261543
262078
  }) : dispatchLedger;
261544
262079
  const companyRegistryFor = async (companyId) => pgPool ? (await openCompanyPlane(companyId)).overlay : scopedRegistryFor(companyId);
261545
262080
  const defaultRegistry = await companyRegistryFor(defaultCompanyId);
262081
+ const companyLiveTypeDefs = async (companyId) => {
262082
+ if (!pgPool || !cutoverStore) return null;
262083
+ if (!cutoverStore.readsCompanySchema(await cutoverStore.get(companyId, "types"))) return null;
262084
+ return buildLiveSchema(await (await openCompanyPlane(companyId)).types.list());
262085
+ };
261546
262086
  const engineRouter = new CompanyEngineRouter({
261547
262087
  // ★ 字节柜按公司取:`gc` 用的就是 `engine.blobs`,共享柜 + 单公司可达集 = 删光别家。
261548
262088
  defaultEngine: { kernel: defaultCompanyKernel, oplog, blobs: await blobStoreFor(defaultCompanyId), registry: defaultRegistry, assets },
@@ -261558,6 +262098,7 @@ async function startServe(opts) {
261558
262098
  // ★ 同默认公司:漏了这里,非默认公司的引擎照旧按代码默认 30min 判超时(策略文件形同虚设)。
261559
262099
  policy: () => activeTimeoutPolicy
261560
262100
  });
262101
+ for (const d of await companyLiveTypeDefs(companyId) ?? []) engine2.kernel.setType(d);
261561
262102
  const registry2 = await companyRegistryFor(companyId);
261562
262103
  await ensureCompanyActors(controlPlaneStore, companyId, registry2);
261563
262104
  await ensureDefaultExpertTeam(registry2);
@@ -262094,7 +262635,7 @@ async function startServe(opts) {
262094
262635
  () => [engine2.kernel],
262095
262636
  async () => {
262096
262637
  if (primary) {
262097
- for (const d of await store.list()) engine2.kernel.setType(d);
262638
+ for (const d of await companyLiveTypeDefs(cid) ?? []) engine2.kernel.setType(d);
262098
262639
  return;
262099
262640
  }
262100
262641
  await rebuildLiveSchema();
@@ -262119,7 +262660,19 @@ async function startServe(opts) {
262119
262660
  activeRunsOf,
262120
262661
  nodeReportedModels,
262121
262662
  refreshNodeModels,
262122
- fetchLatestNpmDaemonVersion
262663
+ fetchLatestNpmDaemonVersion,
262664
+ // 「删除运行时 / 改团队默认模型仅组织管理者」的判据(Runtime PRD「权限设计」)。
262665
+ // 与 projectsDomain 那条 resolveMemberRole 同一实现:闭包在请求时才读成员表。
262666
+ resolveMemberRole: async (companyId, actorId) => {
262667
+ const members = await listCompanyMembers(companyId);
262668
+ return members.find((m2) => m2.actorId === actorId)?.role;
262669
+ },
262670
+ // 详情页「运行次数 / 最近工作时间」与用量 tab 按实例读 run 行:落到这家公司 trace 当前的读落点
262671
+ //(切走了读公司 schema,没切走读 legacy)。dev 文件态的 FileTraceStore 同样实现了这两个读。
262672
+ runtimeRuns: async (companyId) => {
262673
+ const store = await traceStoreFor(companyId);
262674
+ return isRuntimeRunReader(store) ? store : null;
262675
+ }
262123
262676
  }),
262124
262677
  collabDomain({
262125
262678
  kernel: defaultCompanyKernel,
@@ -264734,7 +265287,7 @@ async function startServe(opts) {
264734
265287
  } else if (!deadlineStoreOf(newEngine.kernel)?.listOpenWorkDeadlines) {
264735
265288
  console.warn("[deadline-clock] engine store \u672A\u5B9E\u73B0 listOpenWorkDeadlines\uFF0C\u65F6\u949F\u672A\u542F\u52A8\u2014\u2014deadline \u65E0\u6700\u8FDF\u89E6\u53D1\u4FDD\u8BC1");
264736
265289
  } else {
264737
- const dedupe = new DeadlineDedupe();
265290
+ const dedupe2 = new DeadlineDedupe();
264738
265291
  const clockCursors = /* @__PURE__ */ new Map();
264739
265292
  let clockTicking = false;
264740
265293
  void (async () => {
@@ -264784,7 +265337,7 @@ async function startServe(opts) {
264784
265337
  batchLimit: DEADLINE_CLOCK_BATCH_LIMIT,
264785
265338
  ...cursor ? { cursor } : {},
264786
265339
  log: (m2) => console.warn(m2)
264787
- }, dedupe);
265340
+ }, dedupe2);
264788
265341
  clockCursors.set(companyId, r.nextCursor);
264789
265342
  if (r.pinged > 0) {
264790
265343
  console.log(`[deadline-clock] [${companyId}] \u6295\u51FA ${r.pinged} \u4E2A workorder.ping\uFF08\u53BB\u91CD ${r.deduped}\u3001\u8C41\u514D ${r.exempt}\uFF09`);
@@ -266455,6 +267008,8 @@ init_src6();
266455
267008
  init_src();
266456
267009
  var WORKDIR_READ_MAX_BYTES2 = 2 * 1024 * 1024;
266457
267010
  var WORKDIR_LIST_MAX_ENTRIES = 500;
267011
+ var WORKDIR_WALK_MAX_ENTRIES2 = 1e4;
267012
+ var WORKDIR_WALK_BUDGET_MS2 = 1e4;
266458
267013
  var CREDENTIAL_WORDS = ["token", "secret", "credential", "password", "passwd", "apikey"];
266459
267014
  var SSH_KEY_PREFIX = /^id_(rsa|dsa|ecdsa|ed25519)/;
266460
267015
  function isSensitiveSegment(segment) {
@@ -266578,6 +267133,41 @@ async function handleWorkdirList(req) {
266578
267133
  return { ok: false, code: "INTERNAL", message: String(e?.message ?? e) };
266579
267134
  }
266580
267135
  }
267136
+ async function handleWorkdirWalk(req, now = Date.now) {
267137
+ const cap = Math.max(1, Math.min(req.maxEntries ?? WORKDIR_WALK_MAX_ENTRIES2, WORKDIR_WALK_MAX_ENTRIES2));
267138
+ const deadline = now() + WORKDIR_WALK_BUDGET_MS2;
267139
+ const start = normalizeRel(req.path).replace(/[/\\]+$/, "");
267140
+ const root = await handleWorkdirList({ ...req, path: start });
267141
+ if (!root.ok) return root;
267142
+ const entries = [];
267143
+ let truncated = root.truncated === true;
267144
+ let level = [{ dir: start, listed: root }];
267145
+ while (level.length > 0) {
267146
+ const pending = [];
267147
+ for (const { dir, listed } of level) {
267148
+ if (!listed.ok) {
267149
+ if (listed.code !== "NOT_FOUND") truncated = true;
267150
+ continue;
267151
+ }
267152
+ if (listed.truncated) truncated = true;
267153
+ for (const entry of listed.entries) {
267154
+ if (entries.length >= cap) return { ok: true, entries, truncated: true, absPath: root.absPath };
267155
+ const rel = dir ? `${dir}/${entry.name}` : entry.name;
267156
+ entries.push({ path: rel, type: entry.type, size: entry.size, mtime: entry.mtime });
267157
+ if (entry.type === "dir") pending.push(rel);
267158
+ }
267159
+ }
267160
+ level = [];
267161
+ for (const dir of pending) {
267162
+ if (entries.length >= cap || now() >= deadline) {
267163
+ truncated = true;
267164
+ break;
267165
+ }
267166
+ level.push({ dir, listed: await handleWorkdirList({ ...req, path: dir }) });
267167
+ }
267168
+ }
267169
+ return { ok: true, entries, absPath: root.absPath, ...truncated ? { truncated: true } : {} };
267170
+ }
266581
267171
  async function fdCanonicalPath(fh) {
266582
267172
  try {
266583
267173
  return await import_node_fs19.default.promises.readlink(`/proc/self/fd/${fh.fd}`);
@@ -267068,6 +267658,18 @@ var DaemonWsClient = class {
267068
267658
  this.send({ type: "workdir_read_result", requestId: msg.requestId, result });
267069
267659
  break;
267070
267660
  }
267661
+ case "workdir_walk": {
267662
+ const result = await handleWorkdirWalk({
267663
+ workRoot: this.workRoot,
267664
+ runtimeKind: msg.runtimeKind,
267665
+ workdirKey: msg.workdirKey,
267666
+ rtId: msg.rtId,
267667
+ path: msg.path,
267668
+ maxEntries: msg.maxEntries
267669
+ });
267670
+ this.send({ type: "workdir_walk_result", requestId: msg.requestId, result });
267671
+ break;
267672
+ }
267071
267673
  case "list_models": {
267072
267674
  void this.reportRuntimeModels({ force: true, requestId: msg.requestId });
267073
267675
  break;
@@ -267247,6 +267849,8 @@ var DaemonWsClient = class {
267247
267849
  // 服务端据此决定是用节点归一还是自己就地归一;判据取能力位而不是「见过帧没有」,
267248
267850
  // 否则首个 chunk 之前那段窗口两边都不归一,正文会静默丢一截。
267249
267851
  supportsNormalizedEvents: true,
267852
+ // ADR 0554:本 daemon 认 `workdir_walk`。恒 true——handler 随包发,装了这版就一定有。
267853
+ supportsWorkdirWalk: true,
267250
267854
  ...activeSessions.length > 0 ? { activeSessions } : {},
267251
267855
  // 始终带字段(空数组也带)作为新协议能力标志;服务端仅对支持该字段的节点做缺席确认。
267252
267856
  pendingDispatchIds: [...this.pendingDispatchIds]
@@ -273151,7 +273755,7 @@ function shimScript() {
273151
273755
  }
273152
273756
 
273153
273757
  // src/index.ts
273154
- var PKG_VERSION = true ? "2.2.26" : "dev";
273758
+ var PKG_VERSION = true ? "2.2.28" : "dev";
273155
273759
  var LOCAL_BIN = localBin();
273156
273760
  var NPM_PREFIX = npmPrefix();
273157
273761
  var INSTANCE = DEFAULT_INSTANCE;