oasis_test_v2 2.2.26 → 2.2.27

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 +692 -450
  2. package/package.json +2 -2
package/dist/index.js CHANGED
@@ -1399,6 +1399,13 @@ var init_chat_turn = __esm({
1399
1399
  });
1400
1400
 
1401
1401
  // ../contract/src/delegation.ts
1402
+ function isLegacyDelegationOutboundPath(path41) {
1403
+ return path41.startsWith(`${LEGACY_DELEGATION_OUTBOUND_DIR}/`);
1404
+ }
1405
+ function delegationOutboundRoot(roundSummaries) {
1406
+ const legacy = roundSummaries.some((round) => (round.files ?? []).some((file) => isLegacyDelegationOutboundPath(file.name)));
1407
+ return legacy ? LEGACY_DELEGATION_OUTBOUND_DIR : DELEGATION_OUTBOUND_DIR;
1408
+ }
1402
1409
  function trimRoundSummariesForReturn(summaries, recentRounds = DELEGATION_RETURN_RECENT_ROUNDS) {
1403
1410
  const keep = Math.max(0, recentRounds);
1404
1411
  const cut = Math.max(0, summaries.length - keep);
@@ -1439,11 +1446,12 @@ function touchAnchorMessageId(messageId) {
1439
1446
  const hash2 = messageId.indexOf("#");
1440
1447
  return hash2 >= 0 ? messageId.slice(0, hash2) : messageId;
1441
1448
  }
1442
- var DelegationLabelConflictError, DELEGATION_FILE_LIMITS, DELEGATION_INBOUND_DIR, DELEGATION_OUTBOUND_DIR, DELEGATION_RETURN_RECENT_ROUNDS;
1449
+ var DelegationLabelConflictError, DELEGATION_FILE_LIMITS, DELEGATION_INBOUND_DIR, DELEGATION_OUTBOUND_DIR, LEGACY_DELEGATION_OUTBOUND_DIR, DELEGATION_RETURN_RECENT_ROUNDS;
1443
1450
  var init_delegation = __esm({
1444
1451
  "../contract/src/delegation.ts"() {
1445
1452
  "use strict";
1446
1453
  init_session_outputs();
1454
+ init_session_outputs();
1447
1455
  DelegationLabelConflictError = class extends Error {
1448
1456
  constructor(parentSessionId, label) {
1449
1457
  super(`delegation label conflict in parent session: parent=${parentSessionId} label=${label ?? "<null>"}`);
@@ -1461,7 +1469,8 @@ var init_delegation = __esm({
1461
1469
  maxTotalBytes: 20 * 1024 * 1024
1462
1470
  };
1463
1471
  DELEGATION_INBOUND_DIR = "inputs/attachments";
1464
- DELEGATION_OUTBOUND_DIR = "delegations";
1472
+ DELEGATION_OUTBOUND_DIR = `${SESSION_OUTPUTS_DIR}/delegations`;
1473
+ LEGACY_DELEGATION_OUTBOUND_DIR = "delegations";
1465
1474
  DELEGATION_RETURN_RECENT_ROUNDS = 3;
1466
1475
  }
1467
1476
  });
@@ -196277,7 +196286,7 @@ async function collectInboundFiles(port, parent, paths, limits, lenient) {
196277
196286
  }
196278
196287
  return { prepared, skipped, totalBytes, unchanged: [] };
196279
196288
  }
196280
- async function prepareOutboundFiles(port, child, dirName, limits = DELEGATION_FILE_LIMITS, known) {
196289
+ async function prepareOutboundFiles(port, child, dirName, limits = DELEGATION_FILE_LIMITS, known, outboundRoot = DELEGATION_OUTBOUND_DIR) {
196281
196290
  const out = { prepared: [], skipped: [], totalBytes: 0, unchanged: [] };
196282
196291
  if (!port) return out;
196283
196292
  if (port.locate) {
@@ -196354,7 +196363,7 @@ async function prepareOutboundFiles(port, child, dirName, limits = DELEGATION_FI
196354
196363
  out.skipped.push({ source: file.rel, code: "PATH_REJECTED", message: "\u8DEF\u5F84\u4E0D\u5408\u6CD5\uFF0C\u6CA1\u642C" });
196355
196364
  continue;
196356
196365
  }
196357
- const target = `${DELEGATION_OUTBOUND_DIR}/${dirName}/${relative5}`;
196366
+ const target = `${outboundRoot}/${dirName}/${relative5}`;
196358
196367
  const seen = known?.get(target);
196359
196368
  if (seen && file.mtime && seen.size === file.size && seen.mtime === file.mtime) {
196360
196369
  out.unchanged.push(target);
@@ -196435,6 +196444,12 @@ function digestOf(text5) {
196435
196444
  const line = text5.trim().replace(/\s+/g, " ");
196436
196445
  return line.length > DIGEST_CHARS ? `${line.slice(0, DIGEST_CHARS)}\u2026` : line;
196437
196446
  }
196447
+ function delegationArtifactsView(record8) {
196448
+ return collectDelegationArtifacts(record8.roundSummaries).map((a) => ({
196449
+ ...a,
196450
+ childMessageId: childRoundMessageIds(record8.childSessionId, a.round).placeholderId
196451
+ }));
196452
+ }
196438
196453
  function childRoundMessageIds(childSessionId, round) {
196439
196454
  const suffix = round <= 1 ? "" : `:${round}`;
196440
196455
  return {
@@ -197560,7 +197575,7 @@ ${inboundBundle.paths.map((path41) => `- ${path41}`).join("\n")}` : text5,
197560
197575
  ...record8.label ? { label: record8.label } : {},
197561
197576
  ...record8.taskDigest ? { taskDigest: record8.taskDigest } : {},
197562
197577
  roundSummaries: record8.roundSummaries,
197563
- artifacts: collectDelegationArtifacts(record8.roundSummaries),
197578
+ artifacts: delegationArtifactsView(record8),
197564
197579
  consecutiveFails: record8.consecutiveFails,
197565
197580
  settledSeq: record8.settledSeq,
197566
197581
  pendingInputs: await this.pendingInputs(store, record8.id),
@@ -198188,7 +198203,9 @@ async function deliverDelegationReturn(deps, terminal) {
198188
198203
  { chatSessionId: record8.childSessionId, ...parentSession.companyId ? { companyId: parentSession.companyId } : {} },
198189
198204
  dirName,
198190
198205
  void 0,
198191
- known
198206
+ known,
198207
+ // 已经往老落点(工作区根的 `delegations/`)交过文件的委派继续落那儿——协调者手里的是老路径。
198208
+ delegationOutboundRoot(record8.roundSummaries)
198192
198209
  ).catch((err) => {
198193
198210
  log3(`[delegation-return] \u4EA7\u7269\u642C\u8FD0\u5931\u8D25\uFF08delegation=${record8.id}\uFF09: ${String(err)}`);
198194
198211
  return {
@@ -211852,6 +211869,415 @@ var init_uncategorized_project = __esm({
211852
211869
  }
211853
211870
  });
211854
211871
 
211872
+ // ../server/src/domains/actors/model-pricing.ts
211873
+ function lookupInMap(map, prefixKeys, candidates) {
211874
+ for (const candidate of candidates) {
211875
+ const exact = map instanceof Map ? map.get(candidate) : map[candidate];
211876
+ if (exact) return exact;
211877
+ }
211878
+ const prefix = prefixKeys.find((p2) => candidates.some((candidate) => candidate.startsWith(`${p2}-`) || candidate.startsWith(`${p2}:`)));
211879
+ if (!prefix) return null;
211880
+ return map instanceof Map ? map.get(prefix) ?? null : map[prefix] ?? null;
211881
+ }
211882
+ function stripBracketTier(model) {
211883
+ return model.replace(/\[[^\]]*\]/g, "").trim();
211884
+ }
211885
+ function modelCandidates(model) {
211886
+ const normalized4 = model.trim().toLowerCase();
211887
+ if (!normalized4) return [];
211888
+ const base = [
211889
+ normalized4,
211890
+ normalized4.includes(":") ? normalized4.slice(normalized4.lastIndexOf(":") + 1) : "",
211891
+ normalized4.includes("/") ? normalized4.slice(normalized4.lastIndexOf("/") + 1) : ""
211892
+ ].filter(Boolean);
211893
+ const stripped = base.map(stripBracketTier).filter((m2) => m2 && !base.includes(m2));
211894
+ return [...base, ...new Set(stripped)];
211895
+ }
211896
+ function setModelPriceCacheLoader(loader) {
211897
+ cacheLoader = loader;
211898
+ }
211899
+ async function warmModelPriceCache() {
211900
+ if (!cacheLoader) return 0;
211901
+ try {
211902
+ const next = await cacheLoader();
211903
+ dbPrices = next;
211904
+ dbPrefixKeys = [...next.keys()].sort((a, b2) => b2.length - a.length);
211905
+ cacheLoadedAt = Date.now();
211906
+ return next.size;
211907
+ } catch (err) {
211908
+ console.warn(`[model-pricing] \u9884\u70ED\u4EF7\u683C\u7F13\u5B58\u5931\u8D25\uFF0C\u6CBF\u7528\u9759\u6001\u5FEB\u7167\uFF1A${String(err)}`);
211909
+ return 0;
211910
+ }
211911
+ }
211912
+ function maybeRefreshCache() {
211913
+ if (!cacheLoader) return;
211914
+ if (dbPrices && Date.now() - cacheLoadedAt < CACHE_TTL_MS2) return;
211915
+ if (cacheRefreshInFlight) return;
211916
+ cacheRefreshInFlight = warmModelPriceCache().catch(() => 0).then(() => {
211917
+ cacheRefreshInFlight = null;
211918
+ });
211919
+ }
211920
+ function priceForModel(model) {
211921
+ if (!model) return null;
211922
+ const candidates = modelCandidates(model);
211923
+ if (candidates.length === 0) return null;
211924
+ maybeRefreshCache();
211925
+ if (dbPrices && dbPrices.size > 0) {
211926
+ const fromDb = lookupInMap(dbPrices, dbPrefixKeys, candidates);
211927
+ if (fromDb) return fromDb;
211928
+ }
211929
+ return lookupInMap(MODEL_PRICES, STATIC_PREFIX_PRICES, candidates);
211930
+ }
211931
+ function estimateUsageCostBreakdown(model, usage) {
211932
+ const price = priceForModel(model);
211933
+ if (!price) return null;
211934
+ const inputTokens = Math.max(usage.inputTokens ?? 0, 0);
211935
+ const outputTokens = Math.max(usage.outputTokens ?? 0, 0);
211936
+ const cacheReadTokens = Math.max(usage.cacheReadTokens ?? 0, 0);
211937
+ const cacheCreationTokens = Math.max(usage.cacheCreationTokens ?? 0, 0);
211938
+ const cacheReadPrice = price.cachedInputPerMillion ?? price.inputPerMillion * 0.1;
211939
+ return {
211940
+ currency: price.currency,
211941
+ inputMicros: Math.round(inputTokens * price.inputPerMillion / 1e6 * 1e6),
211942
+ cacheMicros: Math.round(
211943
+ (cacheCreationTokens * price.inputPerMillion + cacheReadTokens * cacheReadPrice) / 1e6 * 1e6
211944
+ ),
211945
+ outputMicros: Math.round(outputTokens * price.outputPerMillion / 1e6 * 1e6)
211946
+ };
211947
+ }
211948
+ function estimateUsageCost(model, usage) {
211949
+ const breakdown = estimateUsageCostBreakdown(model, usage);
211950
+ if (!breakdown) return null;
211951
+ return {
211952
+ currency: breakdown.currency,
211953
+ micros: breakdown.inputMicros + breakdown.cacheMicros + breakdown.outputMicros
211954
+ };
211955
+ }
211956
+ function displayCostUsdMicros(model, usage) {
211957
+ const stored = Math.max(usage.costUsdMicros ?? 0, 0);
211958
+ if (stored > 0) return stored;
211959
+ const estimated = estimateUsageCost(model, usage);
211960
+ if (!estimated || estimated.micros <= 0) return 0;
211961
+ if (estimated.currency === "USD") return estimated.micros;
211962
+ return Math.round(estimated.micros / DISPLAY_CNY_PER_USD);
211963
+ }
211964
+ var FOREIGN_MODEL_MARKUP, usd, cny, MODEL_PRICES, STATIC_PREFIX_PRICES, CACHE_TTL_MS2, dbPrices, dbPrefixKeys, cacheLoadedAt, cacheLoader, cacheRefreshInFlight;
211965
+ var init_model_pricing = __esm({
211966
+ "../server/src/domains/actors/model-pricing.ts"() {
211967
+ "use strict";
211968
+ init_src();
211969
+ FOREIGN_MODEL_MARKUP = 1.08;
211970
+ usd = (inputPerMillion, outputPerMillion, cachedInputPerMillion) => ({
211971
+ currency: "USD",
211972
+ inputPerMillion: inputPerMillion * FOREIGN_MODEL_MARKUP,
211973
+ outputPerMillion: outputPerMillion * FOREIGN_MODEL_MARKUP,
211974
+ ...cachedInputPerMillion !== void 0 ? { cachedInputPerMillion: cachedInputPerMillion * FOREIGN_MODEL_MARKUP } : {}
211975
+ });
211976
+ cny = (inputPerMillion, outputPerMillion, cachedInputPerMillion) => ({
211977
+ currency: "CNY",
211978
+ inputPerMillion,
211979
+ outputPerMillion,
211980
+ ...cachedInputPerMillion !== void 0 ? { cachedInputPerMillion } : {}
211981
+ });
211982
+ MODEL_PRICES = {
211983
+ "gpt-5.5": usd(5, 30, 0.5),
211984
+ "gpt-5.5-pro": usd(30, 180),
211985
+ "chat-latest": usd(5, 30, 0.5),
211986
+ "gpt-5.4-mini": usd(0.75, 4.5, 0.075),
211987
+ "gpt-5.4-nano": usd(0.2, 1.25, 0.02),
211988
+ "gpt-5.4": usd(2.5, 15, 0.25),
211989
+ "gpt-5.4-pro": usd(30, 180),
211990
+ "gpt-5.3-codex": usd(1.75, 14, 0.175),
211991
+ "gpt-5.2": usd(1.75, 14, 0.175),
211992
+ "gpt-5.2-chat-latest": usd(1.75, 14, 0.175),
211993
+ "gpt-5.2-pro": usd(21, 168),
211994
+ "gpt-5.2-codex": usd(1.75, 14, 0.175),
211995
+ "gpt-5.1": usd(1.25, 10, 0.125),
211996
+ "gpt-5.1-chat-latest": usd(1.25, 10, 0.125),
211997
+ "gpt-5.1-codex": usd(1.25, 10, 0.125),
211998
+ "gpt-5.1-codex-max": usd(1.25, 10, 0.125),
211999
+ "gpt-5.1-codex-mini": usd(0.25, 2, 0.025),
212000
+ "gpt-5-pro": usd(15, 120),
212001
+ "gpt-5-codex": usd(1.25, 10, 0.125),
212002
+ "gpt-5-search-api": usd(1.25, 10, 0.125),
212003
+ "gpt-5": usd(1.25, 10, 0.125),
212004
+ "gpt-5-mini": usd(0.25, 2, 0.025),
212005
+ "gpt-5-nano": usd(0.05, 0.4, 5e-3),
212006
+ "gpt-5-chat-latest": usd(1.25, 10, 0.125),
212007
+ "gpt-4.1": usd(2, 8, 0.5),
212008
+ "gpt-4.1-2025-04-14": usd(2, 8, 0.5),
212009
+ "gpt-4.1-mini": usd(0.4, 1.6, 0.1),
212010
+ "gpt-4.1-mini-2025-04-14": usd(0.4, 1.6, 0.1),
212011
+ "gpt-4.1-nano": usd(0.1, 0.4, 0.025),
212012
+ "gpt-4.1-nano-2025-04-14": usd(0.1, 0.4, 0.025),
212013
+ "o4-mini": usd(1.1, 4.4, 0.275),
212014
+ // o3 / o3-mini 在 codex 静态目录里可选,却一直不在价目快照里(费用记 0)。
212015
+ // 这两行按 OpenAI 公开价填,不是智增增快照里的行;供应商价目表同步到时以 DB 为准。
212016
+ "o3": usd(2, 8),
212017
+ "o3-mini": usd(1.1, 4.4),
212018
+ "claude-fable-5-1": usd(10, 50),
212019
+ "claude-fable-5": usd(10, 50),
212020
+ "claude-mythos-5-1": usd(10, 50),
212021
+ "claude-mythos-5": usd(10, 50),
212022
+ // claude-opus-5 是 Claude Code 当代默认款,却一直不在这张快照里:查不到价 → 费用记 0
212023
+ // (与 2026-09-04 那次 `claude-opus-5[1m]` 记 0 同一类坑)。按 Opus 档 $5/$25 补上。
212024
+ "claude-opus-5": usd(5, 25),
212025
+ "claude-opus-4-8": usd(5, 25),
212026
+ "claude-opus-4-7": usd(5, 25),
212027
+ "claude-sonnet-5": usd(2, 10),
212028
+ "claude-sonnet-4-6": usd(3, 15),
212029
+ "claude-opus-4-6": usd(5, 25),
212030
+ "claude-opus-4-5-20251101": usd(5, 25),
212031
+ "claude-opus-4-5": usd(5, 25),
212032
+ "claude-sonnet-4-5-20250929": usd(3, 15),
212033
+ // 短名同样要有:前缀比对是「表里的键是模型名的前缀」,带日期的那行匹配不上不带日期的短名。
212034
+ // claude-sonnet-4-5 从 2026-07-04 起就在目录里可选,却一直查不到价(费用记 0),本次一并补。
212035
+ "claude-sonnet-4-5": usd(3, 15),
212036
+ "claude-opus-4-1-20250805": usd(15, 75),
212037
+ "claude-opus-4-20250514": usd(15, 75),
212038
+ "claude-sonnet-4-20250514": usd(3, 15),
212039
+ "claude-3-7-sonnet-20250219": usd(3, 15),
212040
+ "claude-3-5-sonnet-20241022": usd(3, 15),
212041
+ "claude-3-5-sonnet-20240620": usd(3, 15),
212042
+ "claude-3-sonnet-20240229": usd(3, 15),
212043
+ "claude-haiku-4-5-20251001": usd(1, 5),
212044
+ // codebuddy 目录用点号写法(claude-sonnet-4.6),与上面的连字符写法是同一批模型,
212045
+ // 但字符串对不上就查不到价 → 记 0。同价另起别名行。
212046
+ "claude-sonnet-4.6": usd(3, 15),
212047
+ "claude-opus-4.7": usd(5, 25),
212048
+ "gemini-3.1-pro": usd(2, 12),
212049
+ "gemini-3-pro-preview": usd(2, 12),
212050
+ // Claude Code 回报的可能是不带日期的短名,短名匹配不上带日期的那行(前缀比对是「表里的键是模型名的前缀」)。
212051
+ "claude-haiku-4-5": usd(1, 5),
212052
+ "gemini-3.5-flash": usd(1.5, 9),
212053
+ "gemini-3.1-pro-preview": usd(2, 12),
212054
+ "gemini-3.1-pro-preview-customtools": usd(2, 12),
212055
+ "gemini-3.1-flash-lite-preview": usd(0.25, 1.5),
212056
+ "gemini-3-flash-preview": usd(0.5, 3),
212057
+ "gemini-2.5-pro": usd(1.25, 10),
212058
+ "gemini-2.5-flash": usd(0.3, 2.5),
212059
+ "gemini-2.5-flash-lite": usd(0.1, 0.4),
212060
+ "gemini-2.5-flash-preview-09-2025": usd(0.3, 2.5),
212061
+ "gemini-2.0-flash": usd(0.1, 0.4),
212062
+ "deepseek-v4-flash": cny(1, 2, 0.02),
212063
+ "deepseek-v4-pro": cny(3, 6, 0.025),
212064
+ "deepseek-chat": cny(2, 3),
212065
+ "deepseek-reasoner": cny(2, 3),
212066
+ "deepseek-v3.2": cny(2, 3),
212067
+ "deepseek-coder": cny(2, 3),
212068
+ "deepseek-r1": cny(4, 16),
212069
+ "deepseek-r1-0528": cny(4, 16),
212070
+ "deepseek-v3": cny(2, 8),
212071
+ "deepseek-v3-0324": cny(2, 8),
212072
+ "deepseek-v3.1": cny(4, 12),
212073
+ "qwen3-max": cny(6, 24),
212074
+ "qwen3-max-preview": cny(6, 24),
212075
+ "qwen3-coder-plus": cny(4, 16),
212076
+ "qwen3-235b-a22b": cny(4, 40),
212077
+ "qwen3-32b": cny(2, 20),
212078
+ "qwen3-30b-a3b": cny(1.5, 15),
212079
+ "qwen3-14b": cny(1, 10),
212080
+ "qwen3-8b": cny(0.5, 5),
212081
+ "qwen3-4b": cny(0.3, 3),
212082
+ "qwen3-1.7b": cny(0.3, 3),
212083
+ "qwen3-0.6b": cny(0.3, 3),
212084
+ "qwq-plus": cny(1.6, 4),
212085
+ "qwq-plus-latest": cny(1.6, 4),
212086
+ "qwq-plus-2025-03-05": cny(1.6, 4),
212087
+ "kimi-k2.7-code": cny(6.5, 27),
212088
+ "kimi-k2.6": cny(6.5, 27),
212089
+ "kimi-k2.5": cny(4, 21),
212090
+ "kimi-k2-0905-preview": cny(4, 16),
212091
+ "kimi-k2-0711-preview": cny(4, 16),
212092
+ "kimi-k2-turbo-preview": cny(8, 58)
212093
+ };
212094
+ STATIC_PREFIX_PRICES = Object.keys(MODEL_PRICES).sort((a, b2) => b2.length - a.length);
212095
+ CACHE_TTL_MS2 = 5 * 6e4;
212096
+ dbPrices = null;
212097
+ dbPrefixKeys = [];
212098
+ cacheLoadedAt = 0;
212099
+ cacheLoader = null;
212100
+ cacheRefreshInFlight = null;
212101
+ }
212102
+ });
212103
+
212104
+ // ../server/src/domains/usage/accounting.ts
212105
+ function nonNegative(v2) {
212106
+ return Math.max(v2 ?? 0, 0);
212107
+ }
212108
+ function resolvedModel(model) {
212109
+ const trimmed = model?.trim();
212110
+ return trimmed ? trimmed : null;
212111
+ }
212112
+ function splitByWeight(total, w2) {
212113
+ const sum = w2.input + w2.cache + w2.output;
212114
+ if (total === 0) return { input: 0, cache: 0, output: 0 };
212115
+ if (sum <= 0) return { input: 0, cache: 0, output: total };
212116
+ const input = Math.round(total * w2.input / sum);
212117
+ const cache = Math.round(total * w2.cache / sum);
212118
+ return { input, cache, output: total - input - cache };
212119
+ }
212120
+ function toDisplayUsd(currency, micros) {
212121
+ return currency === "USD" ? micros : Math.round(micros / DISPLAY_CNY_PER_USD);
212122
+ }
212123
+ function accountUsage(model, usage) {
212124
+ const resolved = resolvedModel(model);
212125
+ if (!resolved || !usage) return null;
212126
+ const input = nonNegative(usage.inputTokens);
212127
+ const output = nonNegative(usage.outputTokens);
212128
+ const cache = nonNegative(usage.cacheReadTokens) + nonNegative(usage.cacheCreationTokens);
212129
+ const tokens = { input, cache, output, total: input + cache + output };
212130
+ const estimated = estimateUsageCostBreakdown(resolved, usage);
212131
+ const weights = estimated ? { input: estimated.inputMicros, cache: estimated.cacheMicros, output: estimated.outputMicros } : null;
212132
+ const reported = Math.max(usage.costUsdMicros ?? 0, 0);
212133
+ if (reported > 0) {
212134
+ const parts = weights && estimated?.currency === "USD" ? splitByWeight(reported, weights) : { input: 0, cache: 0, output: reported };
212135
+ return {
212136
+ tokens,
212137
+ currency: "USD",
212138
+ amountMicros: reported,
212139
+ parts,
212140
+ displayUsdMicros: reported,
212141
+ displayParts: parts,
212142
+ source: "reported"
212143
+ };
212144
+ }
212145
+ if (estimated && weights) {
212146
+ const amount = weights.input + weights.cache + weights.output;
212147
+ const displayUsdMicros = toDisplayUsd(estimated.currency, amount);
212148
+ return {
212149
+ tokens,
212150
+ currency: estimated.currency,
212151
+ amountMicros: amount,
212152
+ parts: weights,
212153
+ displayUsdMicros,
212154
+ displayParts: estimated.currency === "USD" ? weights : splitByWeight(displayUsdMicros, weights),
212155
+ source: "estimated"
212156
+ };
212157
+ }
212158
+ return {
212159
+ tokens,
212160
+ currency: "USD",
212161
+ amountMicros: 0,
212162
+ parts: { input: 0, cache: 0, output: 0 },
212163
+ displayUsdMicros: 0,
212164
+ displayParts: { input: 0, cache: 0, output: 0 },
212165
+ source: "unpriced"
212166
+ };
212167
+ }
212168
+ function isUnpriced(account) {
212169
+ return account.source === "unpriced" && account.tokens.total > 0;
212170
+ }
212171
+ var init_accounting = __esm({
212172
+ "../server/src/domains/usage/accounting.ts"() {
212173
+ "use strict";
212174
+ init_model_pricing();
212175
+ }
212176
+ });
212177
+
212178
+ // ../server/src/domains/actors/stats.ts
212179
+ function emptyCostBreakdown() {
212180
+ return {
212181
+ inputUsdMicros: 0,
212182
+ cacheUsdMicros: 0,
212183
+ outputUsdMicros: 0,
212184
+ inputCnyMicros: 0,
212185
+ cacheCnyMicros: 0,
212186
+ outputCnyMicros: 0
212187
+ };
212188
+ }
212189
+ function emptySummary() {
212190
+ return {
212191
+ tokenWeek: 0,
212192
+ tokenWeekBreakdown: { input: 0, cache: 0, output: 0 },
212193
+ costWeekUsdMicros: 0,
212194
+ costWeekCnyMicros: 0,
212195
+ costWeekBreakdown: emptyCostBreakdown(),
212196
+ unpricedModels: []
212197
+ };
212198
+ }
212199
+ function addAccount(target, account) {
212200
+ target.tokenWeekBreakdown.input += account.tokens.input;
212201
+ target.tokenWeekBreakdown.cache += account.tokens.cache;
212202
+ target.tokenWeekBreakdown.output += account.tokens.output;
212203
+ target.tokenWeek += account.tokens.total;
212204
+ if (account.currency === "USD") {
212205
+ target.costWeekUsdMicros += account.amountMicros;
212206
+ target.costWeekBreakdown.inputUsdMicros += account.parts.input;
212207
+ target.costWeekBreakdown.cacheUsdMicros += account.parts.cache;
212208
+ target.costWeekBreakdown.outputUsdMicros += account.parts.output;
212209
+ } else {
212210
+ target.costWeekCnyMicros += account.amountMicros;
212211
+ target.costWeekBreakdown.inputCnyMicros += account.parts.input;
212212
+ target.costWeekBreakdown.cacheCnyMicros += account.parts.cache;
212213
+ target.costWeekBreakdown.outputCnyMicros += account.parts.output;
212214
+ }
212215
+ }
212216
+ function summarize2(rows) {
212217
+ const out = emptySummary();
212218
+ const unpriced = /* @__PURE__ */ new Set();
212219
+ for (const row of rows) {
212220
+ const account = accountUsage(row.model, row.usage);
212221
+ if (!account) continue;
212222
+ addAccount(out, account);
212223
+ if (isUnpriced(account)) unpriced.add(resolvedModel(row.model));
212224
+ }
212225
+ out.unpricedModels = [...unpriced].sort();
212226
+ return out;
212227
+ }
212228
+ async function actorStats(actorId, opts = {}) {
212229
+ const now = opts.now ?? /* @__PURE__ */ new Date();
212230
+ const from = new Date(now.getTime() - 7 * 24 * 60 * 60 * 1e3).toISOString();
212231
+ const to = now.toISOString();
212232
+ 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({
212233
+ actorIds: [actorId],
212234
+ from,
212235
+ to,
212236
+ granularity: "hour"
212237
+ }) : []);
212238
+ return {
212239
+ actorId,
212240
+ usageAvailable: !!(opts.usage?.listRuns || !opts.artifactIds && opts.usage?.queryUsageStats),
212241
+ ...opts.model ? { participatingTaskCount: participatingTaskCount(opts.model, actorId) } : {},
212242
+ tokenWeek: weeklyUsage.tokenWeek,
212243
+ tokenWeekBreakdown: weeklyUsage.tokenWeekBreakdown,
212244
+ costWeekUsdMicros: weeklyUsage.costWeekUsdMicros,
212245
+ costWeekCnyMicros: weeklyUsage.costWeekCnyMicros,
212246
+ costWeekBreakdown: weeklyUsage.costWeekBreakdown,
212247
+ usageWindow: { from, to },
212248
+ unpricedModels: weeklyUsage.unpricedModels
212249
+ };
212250
+ }
212251
+ async function listWeeklyRuns(trace, actorId, from, to, artifactIds) {
212252
+ const out = [];
212253
+ let cursor;
212254
+ do {
212255
+ const page = await trace.listRuns({ actorId, from, to, limit: 500, ...cursor ? { cursor } : {} });
212256
+ out.push(...page.items.filter((run) => !artifactIds || run.artifactId != null && artifactIds.has(run.artifactId)));
212257
+ cursor = page.nextCursor ?? void 0;
212258
+ } while (cursor);
212259
+ return out;
212260
+ }
212261
+ function summarizeWeeklyRuns(runs) {
212262
+ return summarize2(runs.map((run) => ({ model: run.effectiveModel, usage: run.usage })));
212263
+ }
212264
+ function summarizeWeeklyUsage(rows) {
212265
+ return summarize2(rows.map((row) => ({ model: row.effectiveModel, usage: row })));
212266
+ }
212267
+ function participatingTaskCount(model, actorId) {
212268
+ const workorders = /* @__PURE__ */ new Set();
212269
+ for (const artifact of model.artifacts.values()) {
212270
+ if (artifact.owner === actorId) workorders.add(artifact.workspace);
212271
+ }
212272
+ return workorders.size;
212273
+ }
212274
+ var init_stats = __esm({
212275
+ "../server/src/domains/actors/stats.ts"() {
212276
+ "use strict";
212277
+ init_accounting();
212278
+ }
212279
+ });
212280
+
211855
212281
  // ../server/src/domains/projects/service.ts
211856
212282
  function sanitizeCreatorActorId(raw) {
211857
212283
  const id = raw?.trim();
@@ -212132,6 +212558,7 @@ var init_service4 = __esm({
212132
212558
  init_projection();
212133
212559
  init_uncategorized_project();
212134
212560
  init_workorders();
212561
+ init_stats();
212135
212562
  init_node_state();
212136
212563
  DEFAULT_AGENT_ACTOR = "actor:agent:system";
212137
212564
  MemoryProjectStateStore = class {
@@ -212491,6 +212918,17 @@ var init_service4 = __esm({
212491
212918
  artifacts.sort((a, b2) => b2.updatedAt.localeCompare(a.updatedAt));
212492
212919
  return { artifacts, summary: summarizeArtifacts(artifacts) };
212493
212920
  }
212921
+ /** 成员概览只读本公司的项目记录与当前任务模型,不加载项目文件/用量旁账。 */
212922
+ async getActorParticipationStats(actorId) {
212923
+ if (!this.actorDirectory) throw new Error("participation source unavailable");
212924
+ if (!await this.actorDirectory.getActor(actorId)) return null;
212925
+ const projects = await this.projects.listProjects();
212926
+ return {
212927
+ actorId,
212928
+ createdProjectCount: projects.filter((project) => !isUncategorizedProjectId(project.id) && project.createdBy?.id === actorId).length,
212929
+ participatingTaskCount: participatingTaskCount(this.kernel.model, actorId)
212930
+ };
212931
+ }
212494
212932
  /**
212495
212933
  * 项目列表(带派生态)。
212496
212934
  *
@@ -215491,17 +215929,30 @@ function joinRel(dir, name) {
215491
215929
  return dir ? `${dir}/${name}` : name;
215492
215930
  }
215493
215931
  async function scanSessionOutputs(bridge, loc, now = Date.now) {
215932
+ const locator = { workdirKey: loc.workdirKey, runtimeKind: loc.runtimeKind, rtId: loc.rtId };
215933
+ if (typeof bridge.walk === "function") {
215934
+ const walked = await bridge.walk(loc.nodeId, { ...locator, path: SESSION_OUTPUTS_DIR }, WALK_MAX_ENTRIES).catch(() => null);
215935
+ if (!walked) return null;
215936
+ if (walked.ok) {
215937
+ const files2 = walked.entries.filter((entry) => entry.type === "file").map((entry) => ({ path: entry.path, size: entry.size, mtime: entry.mtime }));
215938
+ return { files: files2, via: "walk", tree: walked.entries };
215939
+ }
215940
+ if (walked.code === "NOT_FOUND") return { files: [], via: "walk", tree: [] };
215941
+ if (walked.code !== "UNSUPPORTED") return null;
215942
+ }
215943
+ const files = await scanLayered(
215944
+ (path41) => bridge.list(loc.nodeId, { ...locator, path: path41 }),
215945
+ now
215946
+ );
215947
+ return files === null ? null : { files, via: "layered" };
215948
+ }
215949
+ async function scanLayered(list2, now) {
215494
215950
  const deadline = now() + BUDGET_MS;
215495
215951
  const out = [];
215496
215952
  let listCalls = 0;
215497
215953
  const listDir = (path41) => {
215498
215954
  listCalls += 1;
215499
- return bridge.list(loc.nodeId, {
215500
- workdirKey: loc.workdirKey,
215501
- runtimeKind: loc.runtimeKind,
215502
- rtId: loc.rtId,
215503
- path: path41
215504
- });
215955
+ return list2(path41);
215505
215956
  };
215506
215957
  const root = await listDir(SESSION_OUTPUTS_DIR).catch(() => null);
215507
215958
  if (!root) return null;
@@ -215534,23 +215985,44 @@ async function scanSessionOutputs(bridge, loc, now = Date.now) {
215534
215985
  }
215535
215986
  return out;
215536
215987
  }
215988
+ async function legacyReach(tree) {
215989
+ const children = /* @__PURE__ */ new Map();
215990
+ for (const entry of tree) {
215991
+ const slash = entry.path.lastIndexOf("/");
215992
+ const parent = slash >= 0 ? entry.path.slice(0, slash) : "";
215993
+ const siblings = children.get(parent) ?? [];
215994
+ siblings.push({ name: entry.path.slice(slash + 1), type: entry.type, size: entry.size, mtime: entry.mtime });
215995
+ children.set(parent, siblings);
215996
+ }
215997
+ const files = await scanLayered(async (path41) => ({ ok: true, entries: children.get(path41) ?? [], absPath: "" }), () => 0);
215998
+ return new Set((files ?? []).map((file) => file.path));
215999
+ }
215537
216000
  async function settleSessionOutputs(deps) {
215538
- const scanned = await scanSessionOutputs(deps.bridge, deps.loc, deps.now);
215539
- if (scanned === null) return null;
216001
+ const scan2 = await scanSessionOutputs(deps.bridge, deps.loc, deps.now);
216002
+ if (scan2 === null) return null;
215540
216003
  const previous3 = await deps.readFingerprints(deps.sessionId).catch(() => null);
215541
- const diff = diffSessionOutputs(scanned, previous3 ?? void 0);
216004
+ const diff = diffSessionOutputs(scan2.files, previous3 ?? void 0);
216005
+ if (scan2.via === "walk") {
216006
+ if (previous3 && previous3[WALK_TABLE_MARK] === void 0) {
216007
+ const reach = await legacyReach(scan2.tree ?? []);
216008
+ diff.changed = diff.changed.filter((file) => previous3[file.path] !== void 0 || reach.has(file.path));
216009
+ }
216010
+ diff.fingerprints[WALK_TABLE_MARK] = { size: 0, mtime: "" };
216011
+ }
215542
216012
  await deps.writeFingerprints(deps.sessionId, diff.fingerprints).catch(() => void 0);
215543
216013
  return diff;
215544
216014
  }
215545
- var MAX_DEPTH, MAX_ENTRIES, MAX_LIST_CALLS, BUDGET_MS;
216015
+ var WALK_MAX_ENTRIES, MAX_DEPTH, MAX_ENTRIES, MAX_LIST_CALLS, BUDGET_MS, WALK_TABLE_MARK;
215546
216016
  var init_outputs_scan = __esm({
215547
216017
  "../server/src/domains/chat-sessions/outputs-scan.ts"() {
215548
216018
  "use strict";
215549
216019
  init_src();
216020
+ WALK_MAX_ENTRIES = 2e3;
215550
216021
  MAX_DEPTH = 3;
215551
216022
  MAX_ENTRIES = 100;
215552
216023
  MAX_LIST_CALLS = 12;
215553
216024
  BUDGET_MS = 5e3;
216025
+ WALK_TABLE_MARK = "#walk";
215554
216026
  }
215555
216027
  });
215556
216028
 
@@ -221536,415 +222008,6 @@ var init_skill_bundle = __esm({
221536
222008
  }
221537
222009
  });
221538
222010
 
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
222011
  // ../server/src/domains/actors/list-usage.ts
221949
222012
  function aggregateUsageByActor(runs) {
221950
222013
  const out = /* @__PURE__ */ new Map();
@@ -224235,6 +224298,20 @@ ${errs.join("\n")}`);
224235
224298
  });
224236
224299
  return { status: 200, body: toApiProjectArtifacts(list2, req.query) };
224237
224300
  });
224301
+ router.get("/api/actors/:id/participation-stats", async (req) => {
224302
+ const service = await svc(req);
224303
+ let stats;
224304
+ try {
224305
+ stats = await service.getActorParticipationStats(req.params.id);
224306
+ } catch (error2) {
224307
+ if (error2 instanceof Error && error2.message === "participation source unavailable") {
224308
+ throw new ApiError(503, "UNAVAILABLE", "\u6210\u5458\u7EDF\u8BA1\u6570\u636E\u6E90\u672A\u63A5\u5165");
224309
+ }
224310
+ throw error2;
224311
+ }
224312
+ if (!stats) throw new ApiError(404, "NOT_FOUND", "\u8FD9\u4E2A\u7EC4\u7EC7\u91CC\u6CA1\u6709\u8FD9\u4F4D\u6210\u5458");
224313
+ return { status: 200, body: stats };
224314
+ });
224238
224315
  router.get("/api/actors/:id/files-view", async (req) => {
224239
224316
  const items = await (await svc(req)).listActorFiles(req.params.id);
224240
224317
  return {
@@ -229122,9 +229199,50 @@ function reasonFromWorkdirCode(code2) {
229122
229199
  function joinRel2(dir, name) {
229123
229200
  return dir ? `${dir}/${name}` : name;
229124
229201
  }
229202
+ function workdirFileItem(rel, name, size, mtime) {
229203
+ return {
229204
+ id: `workdir:${rel}`,
229205
+ name,
229206
+ kind: "file",
229207
+ // 节点侧列目录只报 name/type/size/mtime,没有 MIME——诚实留 null,
229208
+ // 不拿后缀猜(红线:取不到就是取不到,别拿别的字段顶)。
229209
+ content_type: null,
229210
+ size,
229211
+ updated_at: mtime,
229212
+ source: "workdir",
229213
+ blob_ref: null,
229214
+ path: rel,
229215
+ message_id: null,
229216
+ // 工作区文件本期不开写回(brief §4①:要动 node↔server 协议帧,超预算)。
229217
+ // 这里是**按能力不渲染**的判据,不是置灰假钮。
229218
+ editable: false
229219
+ };
229220
+ }
229125
229221
  async function collectWorkdirFiles(bridge, loc, now = Date.now) {
229126
- const deadline = now() + WORKDIR_WALK_BUDGET_MS;
229127
229222
  const items = [];
229223
+ if (typeof bridge.walk === "function") {
229224
+ const walked = await bridge.walk(
229225
+ loc.nodeId,
229226
+ { workdirKey: loc.workdirKey, runtimeKind: loc.runtimeKind, rtId: loc.rtId, path: "" },
229227
+ WORKDIR_TREE_MAX_ENTRIES
229228
+ );
229229
+ if (walked.ok) {
229230
+ let truncated2 = walked.truncated === true;
229231
+ for (const entry of walked.entries) {
229232
+ if (entry.type !== "file") continue;
229233
+ if (items.length >= WORKDIR_WALK_MAX_ENTRIES) {
229234
+ truncated2 = true;
229235
+ break;
229236
+ }
229237
+ const slash = entry.path.lastIndexOf("/");
229238
+ items.push(workdirFileItem(entry.path, entry.path.slice(slash + 1), entry.size, entry.mtime));
229239
+ }
229240
+ return { ok: true, items, truncated: truncated2 };
229241
+ }
229242
+ if (walked.code === "NOT_FOUND") return { ok: true, items: [], truncated: false };
229243
+ if (walked.code !== "UNSUPPORTED") return { ok: false, reason: reasonFromWorkdirCode(walked.code) };
229244
+ }
229245
+ const deadline = now() + WORKDIR_WALK_BUDGET_MS;
229128
229246
  let listCalls = 0;
229129
229247
  let truncated = false;
229130
229248
  const listDir = (path41) => {
@@ -229159,24 +229277,7 @@ async function collectWorkdirFiles(bridge, loc, now = Date.now) {
229159
229277
  truncated = true;
229160
229278
  continue;
229161
229279
  }
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
- });
229280
+ items.push(workdirFileItem(joinRel2(dir, entry.name), entry.name, entry.size, entry.mtime));
229180
229281
  }
229181
229282
  }
229182
229283
  if (depth === WORKDIR_WALK_MAX_DEPTH || nextDirs.length === 0) {
@@ -229298,7 +229399,7 @@ async function findMaterializedAttachmentCopies(messageItems, workdirItems, read
229298
229399
  }
229299
229400
  return copies;
229300
229401
  }
229301
- var import_node_crypto51, WORKDIR_WALK_MAX_DEPTH, WORKDIR_WALK_MAX_ENTRIES, WORKDIR_WALK_MAX_LIST_CALLS, WORKDIR_WALK_BUDGET_MS;
229402
+ 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
229403
  var init_files_view = __esm({
229303
229404
  "../server/src/domains/chat-sessions/files-view.ts"() {
229304
229405
  "use strict";
@@ -229309,6 +229410,7 @@ var init_files_view = __esm({
229309
229410
  WORKDIR_WALK_MAX_ENTRIES = 200;
229310
229411
  WORKDIR_WALK_MAX_LIST_CALLS = 20;
229311
229412
  WORKDIR_WALK_BUDGET_MS = 1e4;
229413
+ WORKDIR_TREE_MAX_ENTRIES = 2e3;
229312
229414
  }
229313
229415
  });
229314
229416
 
@@ -231752,6 +231854,19 @@ function nodesDomain(deps) {
231752
231854
  hubWired = true;
231753
231855
  const { nodeStore } = deps;
231754
231856
  const now = () => (/* @__PURE__ */ new Date()).toISOString();
231857
+ const HEARTBEAT_PERSIST_MS = 6e4;
231858
+ const heartbeatPersistedAt = /* @__PURE__ */ new Map();
231859
+ hub.addMessageListener((daemonId) => {
231860
+ if (!nodeStore.touchNodeSeen) return;
231861
+ const ms = Date.now();
231862
+ const prev = heartbeatPersistedAt.get(daemonId) ?? 0;
231863
+ if (ms - prev < HEARTBEAT_PERSIST_MS) return;
231864
+ heartbeatPersistedAt.set(daemonId, ms);
231865
+ void nodeStore.touchNodeSeen(daemonId, new Date(ms).toISOString()).catch((err) => {
231866
+ heartbeatPersistedAt.delete(daemonId);
231867
+ console.warn(`[nodes] \u5FC3\u8DF3\u843D\u5E93\u5931\u8D25(node=${daemonId})\uFF1A${err instanceof Error ? err.message : String(err)}`);
231868
+ });
231869
+ });
231755
231870
  hub.addMessageListener(async (daemonId, msg) => {
231756
231871
  if (msg.type !== "hello") return;
231757
231872
  const d = hub.connectedDaemons().find((c) => c.daemonId === daemonId);
@@ -231820,6 +231935,15 @@ function nodesDomain(deps) {
231820
231935
  };
231821
231936
  tryWire();
231822
231937
  if (!hubWired) wirePoll = setInterval(tryWire, 100);
231938
+ async function forbidUnlessOrgManager(req, verb) {
231939
+ if (!deps.resolveMemberRole) return null;
231940
+ const role = await deps.resolveMemberRole(req.auth.companyId, req.auth.actor);
231941
+ if (role === "owner" || role === "admin") return null;
231942
+ return {
231943
+ status: 403,
231944
+ 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` }
231945
+ };
231946
+ }
231823
231947
  async function syncConnectedDaemon(daemonId, hub) {
231824
231948
  const d = hub.connectedDaemons().find((c) => c.daemonId === daemonId);
231825
231949
  if (!d) return;
@@ -231848,6 +231972,13 @@ function nodesDomain(deps) {
231848
231972
  const outOfSync = nodes.filter((n) => onlineIds.has(n.id) && (n.status !== "online" || (runtimesByNode.get(n.id) ?? []).some((r) => r.status !== "online")));
231849
231973
  await Promise.all(outOfSync.map((n) => syncConnectedDaemon(n.id, hub)));
231850
231974
  }
231975
+ function heartbeatMsOf(n, offline) {
231976
+ const live = hub?.lastSeenAt(n.id);
231977
+ if (live !== void 0) return live;
231978
+ if (!offline) return void 0;
231979
+ const ms = Date.parse(n.lastSeenAt);
231980
+ return Number.isNaN(ms) ? void 0 : ms;
231981
+ }
231851
231982
  const items = await Promise.all(nodes.map(async (n) => {
231852
231983
  const rts = runtimesByNode.get(n.id) ?? [];
231853
231984
  const daemonVersion = daemonVersionById.get(n.id);
@@ -231877,7 +232008,16 @@ function nodesDomain(deps) {
231877
232008
  ...deps.nodeHealth ? {
231878
232009
  health: deriveNodeHealth({
231879
232010
  online: onlineIds.has(n.id),
231880
- ...hub?.lastSeenAt(n.id) !== void 0 ? { lastHeartbeatMs: hub.lastSeenAt(n.id) } : {},
232011
+ /*
232012
+ * 心跳时钟:在线节点读 hub 内存(每帧刷新,最准);**离线节点回落到落库的
232013
+ * `lastSeenAt`**——那是这台机器最后一次跟服务端说上话的时刻(心跳每分钟落一次库,
232014
+ * 见上面的 `HEARTBEAT_PERSIST_MS`;断开那一刻 `setNodeOnline(false)` 也会写一次)。
232015
+ * 运行时页「工作情况」列的离线行显示的「断连时间 X」读的就是它。
232016
+ *
232017
+ * 不影响健康判定:`healthy = online && 心跳新鲜 && 连败未超阈`,离线节点 `online=false`
232018
+ * 已经把结论钉死成不健康,这里补进来的只是**解释**用的时刻(设计稿 4.5 ⑤ 的口径)。
232019
+ */
232020
+ ...heartbeatMsOf(n, nodeOffline) !== void 0 ? { lastHeartbeatMs: heartbeatMsOf(n, nodeOffline) } : {},
231881
232021
  activeRunCount,
231882
232022
  ...deps.nodeHealth.snapshot(n.id) ? { snapshot: deps.nodeHealth.snapshot(n.id) } : {},
231883
232023
  now: healthNow
@@ -232047,6 +232187,8 @@ function nodesDomain(deps) {
232047
232187
  router.delete("/api/runtimes/:runtimeId", async (req) => {
232048
232188
  const runtimeId = req.params["runtimeId"];
232049
232189
  if (!runtimeId) return { status: 400, body: { error: "missing runtimeId" } };
232190
+ const forbidden = await forbidUnlessOrgManager(req, "\u5220\u9664\u8FD0\u884C\u65F6");
232191
+ if (forbidden) return forbidden;
232050
232192
  const runtime = (await deps.nodeStore.listRuntimes()).find((r) => r.id === runtimeId);
232051
232193
  const { nodeDeleted } = await deps.nodeStore.deleteRuntime(runtimeId);
232052
232194
  if (nodeDeleted && runtime) revokeNodeAccess(runtime.nodeId);
@@ -232114,6 +232256,8 @@ function nodesDomain(deps) {
232114
232256
  router.put("/api/runtimes/:runtimeId/config", async (req) => {
232115
232257
  const runtimeId = req.params["runtimeId"];
232116
232258
  if (!runtimeId) return { status: 400, body: { error: "missing runtimeId" } };
232259
+ const forbidden = await forbidUnlessOrgManager(req, "\u4FEE\u6539\u56E2\u961F\u9ED8\u8BA4\u6A21\u578B");
232260
+ if (forbidden) return forbidden;
232117
232261
  const body2 = req.body ?? {};
232118
232262
  const cfg = {
232119
232263
  runtimeId,
@@ -232325,6 +232469,10 @@ var init_node_store = __esm({
232325
232469
  n.lastSeenAt = (/* @__PURE__ */ new Date()).toISOString();
232326
232470
  }
232327
232471
  }
232472
+ async touchNodeSeen(id, atIso) {
232473
+ const n = this.nodes.get(id);
232474
+ if (n && n.status !== "deleted") n.lastSeenAt = atIso;
232475
+ }
232328
232476
  async setNodeRuntimesOnline(nodeId, online) {
232329
232477
  const now = (/* @__PURE__ */ new Date()).toISOString();
232330
232478
  for (const rt of this.runtimes.values()) {
@@ -232444,6 +232592,10 @@ var init_node_store = __esm({
232444
232592
  await super.setNodeOnline(id, online);
232445
232593
  this.flush();
232446
232594
  }
232595
+ async touchNodeSeen(id, atIso) {
232596
+ await super.touchNodeSeen(id, atIso);
232597
+ this.flush();
232598
+ }
232447
232599
  async setNodeRuntimesOnline(nodeId, online) {
232448
232600
  await super.setNodeRuntimesOnline(nodeId, online);
232449
232601
  this.flush();
@@ -235958,7 +236110,9 @@ function createChatSessionsDomain(opts) {
235958
236110
  的去重装置,直接发给前端等于给了一条 `messages[]` 里不存在的 id ——追加转达卡全部掉进
235959
236111
  floating 堆在流末尾,同一条消息上的两条委派还会因后缀不同被拆成两张单专家卡。 */
235960
236112
  touches: touches.map((t) => ({ messageId: touchAnchorMessageId(t.messageId), kind: t.kind, at: t.at })),
235961
- artifacts: collectDelegationArtifacts(record8.roundSummaries),
236113
+ /* 与协调者读口**同一个函数**——两侧 artifacts 有「逐字同源」的既有断言。它顺带给每份产物
236114
+ 填上 `childMessageId`(子会话面板把产出卡挂到产出它的那一轮下面要用)。 */
236115
+ artifacts: delegationArtifactsView(record8),
235962
236116
  createdAt: record8.createdAt,
235963
236117
  ...record8.settledAt ? { settledAt: record8.settledAt } : {}
235964
236118
  });
@@ -236246,6 +236400,7 @@ var init_chat_sessions = __esm({
236246
236400
  init_escalation_opening();
236247
236401
  init_workorders();
236248
236402
  init_chat_parts();
236403
+ init_service3();
236249
236404
  init_injected_context();
236250
236405
  init_deletion();
236251
236406
  init_chat_turn_gate();
@@ -240644,6 +240799,29 @@ var init_daemon_adapter = __esm({
240644
240799
  maxBytes
240645
240800
  }));
240646
240801
  }
240802
+ /**
240803
+ * 一次列全 `loc.path` 下的整棵子树(ADR 0554)。节点没自报能力位 → 不发帧、直接回 `UNSUPPORTED`,
240804
+ * 调用方退回逐层 `list`。判据取 hello 里的能力位,不靠「发了看回不回」:老节点收到不认识的帧
240805
+ * 只会静默丢掉,那样要白等满 30 秒超时。
240806
+ */
240807
+ walk(nodeId, loc, maxEntries) {
240808
+ if (!this.nodeSupportsWalk(nodeId)) return Promise.resolve({ ok: false, code: "UNSUPPORTED" });
240809
+ return this.request(nodeId, (requestId) => ({
240810
+ type: "workdir_walk",
240811
+ requestId,
240812
+ workdirKey: loc.workdirKey,
240813
+ runtimeKind: loc.runtimeKind,
240814
+ rtId: loc.rtId ?? null,
240815
+ path: loc.path,
240816
+ maxEntries
240817
+ }));
240818
+ }
240819
+ nodeSupportsWalk(nodeId) {
240820
+ const connectedDaemons = this.hub.connectedDaemons;
240821
+ if (typeof connectedDaemons !== "function") return false;
240822
+ const daemon = connectedDaemons.call(this.hub).find((candidate) => candidate.daemonId === nodeId);
240823
+ return daemon?.meta.supportsWorkdirWalk === true;
240824
+ }
240647
240825
  request(nodeId, buildFrame) {
240648
240826
  const requestId = (0, import_node_crypto65.randomUUID)();
240649
240827
  const sent = this.hub.dispatch(nodeId, buildFrame(requestId));
@@ -240658,7 +240836,7 @@ var init_daemon_adapter = __esm({
240658
240836
  });
240659
240837
  }
240660
240838
  onDaemonMessage(msg) {
240661
- if (msg.type !== "workdir_list_result" && msg.type !== "workdir_read_result") return;
240839
+ if (msg.type !== "workdir_list_result" && msg.type !== "workdir_read_result" && msg.type !== "workdir_walk_result") return;
240662
240840
  const entry = this.pending.get(msg.requestId);
240663
240841
  if (!entry) return;
240664
240842
  this.pending.delete(msg.requestId);
@@ -250669,6 +250847,13 @@ var init_postgres_nodes = __esm({
250669
250847
  async setNodeOnline(id, online) {
250670
250848
  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
250849
  }
250850
+ /** 只推进 last_seen_at,不动 status(心跳落库;节流由调用方做,见 nodes/routes.ts)。 */
250851
+ async touchNodeSeen(id, atIso) {
250852
+ await this.pool.query(
250853
+ `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)`,
250854
+ [id, atIso]
250855
+ );
250856
+ }
250672
250857
  async setNodeRuntimesOnline(nodeId, online) {
250673
250858
  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
250859
  }
@@ -262119,7 +262304,13 @@ async function startServe(opts) {
262119
262304
  activeRunsOf,
262120
262305
  nodeReportedModels,
262121
262306
  refreshNodeModels,
262122
- fetchLatestNpmDaemonVersion
262307
+ fetchLatestNpmDaemonVersion,
262308
+ // 「删除运行时 / 改团队默认模型仅组织管理者」的判据(Runtime PRD「权限设计」)。
262309
+ // 与 projectsDomain 那条 resolveMemberRole 同一实现:闭包在请求时才读成员表。
262310
+ resolveMemberRole: async (companyId, actorId) => {
262311
+ const members = await listCompanyMembers(companyId);
262312
+ return members.find((m2) => m2.actorId === actorId)?.role;
262313
+ }
262123
262314
  }),
262124
262315
  collabDomain({
262125
262316
  kernel: defaultCompanyKernel,
@@ -266455,6 +266646,8 @@ init_src6();
266455
266646
  init_src();
266456
266647
  var WORKDIR_READ_MAX_BYTES2 = 2 * 1024 * 1024;
266457
266648
  var WORKDIR_LIST_MAX_ENTRIES = 500;
266649
+ var WORKDIR_WALK_MAX_ENTRIES2 = 1e4;
266650
+ var WORKDIR_WALK_BUDGET_MS2 = 1e4;
266458
266651
  var CREDENTIAL_WORDS = ["token", "secret", "credential", "password", "passwd", "apikey"];
266459
266652
  var SSH_KEY_PREFIX = /^id_(rsa|dsa|ecdsa|ed25519)/;
266460
266653
  function isSensitiveSegment(segment) {
@@ -266578,6 +266771,41 @@ async function handleWorkdirList(req) {
266578
266771
  return { ok: false, code: "INTERNAL", message: String(e?.message ?? e) };
266579
266772
  }
266580
266773
  }
266774
+ async function handleWorkdirWalk(req, now = Date.now) {
266775
+ const cap = Math.max(1, Math.min(req.maxEntries ?? WORKDIR_WALK_MAX_ENTRIES2, WORKDIR_WALK_MAX_ENTRIES2));
266776
+ const deadline = now() + WORKDIR_WALK_BUDGET_MS2;
266777
+ const start = normalizeRel(req.path).replace(/[/\\]+$/, "");
266778
+ const root = await handleWorkdirList({ ...req, path: start });
266779
+ if (!root.ok) return root;
266780
+ const entries = [];
266781
+ let truncated = root.truncated === true;
266782
+ let level = [{ dir: start, listed: root }];
266783
+ while (level.length > 0) {
266784
+ const pending = [];
266785
+ for (const { dir, listed } of level) {
266786
+ if (!listed.ok) {
266787
+ if (listed.code !== "NOT_FOUND") truncated = true;
266788
+ continue;
266789
+ }
266790
+ if (listed.truncated) truncated = true;
266791
+ for (const entry of listed.entries) {
266792
+ if (entries.length >= cap) return { ok: true, entries, truncated: true, absPath: root.absPath };
266793
+ const rel = dir ? `${dir}/${entry.name}` : entry.name;
266794
+ entries.push({ path: rel, type: entry.type, size: entry.size, mtime: entry.mtime });
266795
+ if (entry.type === "dir") pending.push(rel);
266796
+ }
266797
+ }
266798
+ level = [];
266799
+ for (const dir of pending) {
266800
+ if (entries.length >= cap || now() >= deadline) {
266801
+ truncated = true;
266802
+ break;
266803
+ }
266804
+ level.push({ dir, listed: await handleWorkdirList({ ...req, path: dir }) });
266805
+ }
266806
+ }
266807
+ return { ok: true, entries, absPath: root.absPath, ...truncated ? { truncated: true } : {} };
266808
+ }
266581
266809
  async function fdCanonicalPath(fh) {
266582
266810
  try {
266583
266811
  return await import_node_fs19.default.promises.readlink(`/proc/self/fd/${fh.fd}`);
@@ -267068,6 +267296,18 @@ var DaemonWsClient = class {
267068
267296
  this.send({ type: "workdir_read_result", requestId: msg.requestId, result });
267069
267297
  break;
267070
267298
  }
267299
+ case "workdir_walk": {
267300
+ const result = await handleWorkdirWalk({
267301
+ workRoot: this.workRoot,
267302
+ runtimeKind: msg.runtimeKind,
267303
+ workdirKey: msg.workdirKey,
267304
+ rtId: msg.rtId,
267305
+ path: msg.path,
267306
+ maxEntries: msg.maxEntries
267307
+ });
267308
+ this.send({ type: "workdir_walk_result", requestId: msg.requestId, result });
267309
+ break;
267310
+ }
267071
267311
  case "list_models": {
267072
267312
  void this.reportRuntimeModels({ force: true, requestId: msg.requestId });
267073
267313
  break;
@@ -267247,6 +267487,8 @@ var DaemonWsClient = class {
267247
267487
  // 服务端据此决定是用节点归一还是自己就地归一;判据取能力位而不是「见过帧没有」,
267248
267488
  // 否则首个 chunk 之前那段窗口两边都不归一,正文会静默丢一截。
267249
267489
  supportsNormalizedEvents: true,
267490
+ // ADR 0554:本 daemon 认 `workdir_walk`。恒 true——handler 随包发,装了这版就一定有。
267491
+ supportsWorkdirWalk: true,
267250
267492
  ...activeSessions.length > 0 ? { activeSessions } : {},
267251
267493
  // 始终带字段(空数组也带)作为新协议能力标志;服务端仅对支持该字段的节点做缺席确认。
267252
267494
  pendingDispatchIds: [...this.pendingDispatchIds]
@@ -273151,7 +273393,7 @@ function shimScript() {
273151
273393
  }
273152
273394
 
273153
273395
  // src/index.ts
273154
- var PKG_VERSION = true ? "2.2.26" : "dev";
273396
+ var PKG_VERSION = true ? "2.2.27" : "dev";
273155
273397
  var LOCAL_BIN = localBin();
273156
273398
  var NPM_PREFIX = npmPrefix();
273157
273399
  var INSTANCE = DEFAULT_INSTANCE;