oasis_test 0.1.133 → 0.1.135

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 +851 -307
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -971,9 +971,11 @@ var init_telemetry = __esm({
971
971
  });
972
972
 
973
973
  // ../contract/src/trace.ts
974
+ var DISPLAY_CNY_PER_USD;
974
975
  var init_trace = __esm({
975
976
  "../contract/src/trace.ts"() {
976
977
  "use strict";
978
+ DISPLAY_CNY_PER_USD = 7.2;
977
979
  }
978
980
  });
979
981
 
@@ -1558,7 +1560,7 @@ var init_src2 = __esm({
1558
1560
  RESOLVED_GAP_AFTER_HEAD_EFFECTS = effects(WAKE, NEUTRAL, VETO, WAKE);
1559
1561
  PENDING_REVIEW_EFFECTS = effects(NEUTRAL, NEUTRAL, VETO, NEUTRAL);
1560
1562
  GAP_SOLUTION_DELIVERY_SINCE = "2026-09-01T00:00:00.000Z";
1561
- REPLY_ORIGIN_SINCE = "2026-09-04T00:00:00.000Z";
1563
+ REPLY_ORIGIN_SINCE = "2026-09-05T00:00:00.000Z";
1562
1564
  ROUND_LIMIT_MARKER = "\u26A0\uFE0F \u56DE\u5408\u4E0A\u9650";
1563
1565
  LETTER_ROUND_LIMIT = 8;
1564
1566
  }
@@ -7210,7 +7212,7 @@ ${supplemental.taskAppend.trim()}
7210
7212
  ...provisioned?.env ?? {},
7211
7213
  ...spec.part !== void 0 ? { OASIS_PART: spec.part } : {}
7212
7214
  };
7213
- const resolvedModel3 = this.opts.resolveModel ? await this.opts.resolveModel(dispatchSpec.actor) : void 0;
7215
+ const resolvedModel2 = this.opts.resolveModel ? await this.opts.resolveModel(dispatchSpec.actor) : void 0;
7214
7216
  const resolvedExtraArgs = this.opts.resolveExtraArgs ? await this.opts.resolveExtraArgs(dispatchSpec.actor) : void 0;
7215
7217
  this.assertSpawnAttemptCurrent(jobKey, attempt);
7216
7218
  const freshWorkdir = workdirKey !== void 0 && !priorConsistent;
@@ -7227,7 +7229,7 @@ ${supplemental.taskAppend.trim()}
7227
7229
  server: { url: serverUrl },
7228
7230
  limits,
7229
7231
  ...this.resilienceFields(),
7230
- ...resolvedModel3 ? { model: resolvedModel3 } : {},
7232
+ ...resolvedModel2 ? { model: resolvedModel2 } : {},
7231
7233
  ...resolvedExtraArgs && resolvedExtraArgs.length > 0 ? { extraArgs: resolvedExtraArgs } : {},
7232
7234
  ...binding !== void 0 ? { binding } : {},
7233
7235
  ...runtimeSessionId !== void 0 ? { runtimeSessionId } : {},
@@ -154464,6 +154466,45 @@ var init_chat_item_ledger = __esm({
154464
154466
  }
154465
154467
  });
154466
154468
  }
154469
+ /**
154470
+ * B4:POST /api/chat 开轮时把 user 的**首条**提交也记进账本——桶按 itemId 索引的合并
154471
+ * 模型这才闭环(否则空闲会话刷新看不到 user 历史,且服务端从不回填 clientSubmitId)。
154472
+ *
154473
+ * - `itemId` 用 `oasis-user-optim:${clientSubmitId}`(与 FE `beginOptimisticUser` 同规则):
154474
+ * 同一次点击的乐观桶位和服务端权威条自然对齐,不需要「双条+去重」的过渡态。
154475
+ * 没给 clientSubmitId 时回落随机 id——旧客户端仍可用,只是 FE 侧没有乐观替换的钩子。
154476
+ * - `payload.clientSubmitId` 也一并写进去——`chat-items-merge` 在 itemId 恰好不同时(跨版本
154477
+ * 混部/异常路径)走 `retractSiblingOptimistic` 兜底,只剩一条 user 气泡。
154478
+ * - `message_id` 绑到本轮建 assistant 行之前的 user 行——但这里没有 messageId 可给,
154479
+ * 落 NULL 由 `bindMessage` 那侧不动它(它只回填 role='assistant' 的行)。
154480
+ */
154481
+ recordUserSubmission(input) {
154482
+ const payload = {
154483
+ role: "user",
154484
+ text: input.text
154485
+ };
154486
+ if (input.attachments?.length) payload.attachments = input.attachments;
154487
+ if (input.clientSubmitId) payload.clientSubmitId = input.clientSubmitId;
154488
+ const deterministicId = input.clientSubmitId ? `oasis-user-optim:${input.clientSubmitId}` : void 0;
154489
+ return this.insert({
154490
+ kind: "text",
154491
+ role: "user",
154492
+ status: "completed",
154493
+ origin: "server",
154494
+ text: input.text,
154495
+ attrs: {},
154496
+ inContent: false,
154497
+ persist: true,
154498
+ forceNullMessage: true,
154499
+ payload,
154500
+ metadata: {
154501
+ [CHAT_ITEM_META_LEGACY_CONTENT]: false,
154502
+ ...input.clientSubmitId ? { clientSubmitId: input.clientSubmitId } : {},
154503
+ ...input.attachments?.length ? { attachments: input.attachments } : {}
154504
+ },
154505
+ ...deterministicId ? { deterministicId } : {}
154506
+ });
154507
+ }
154467
154508
  /** 服务端合成的错误帧(session.done 抛 / exit 判失败)。不入正文,归 control。 */
154468
154509
  recordError(text5, extra) {
154469
154510
  this.insert({
@@ -154657,7 +154698,7 @@ var init_chat_item_ledger = __esm({
154657
154698
  }
154658
154699
  }
154659
154700
  insert(input) {
154660
- const id = this.newId();
154701
+ const id = input.deterministicId ?? this.newId();
154661
154702
  const row = {
154662
154703
  id,
154663
154704
  seq: ++this.seqCounter,
@@ -157933,8 +157974,36 @@ function parseCodexSessionId(lines) {
157933
157974
  const id = o.payload?.id;
157934
157975
  return typeof id === "string" && id ? id : void 0;
157935
157976
  }
157936
- function parseCodexTokenUsage(lines) {
157937
- let last;
157977
+ function bucketsOf(usage) {
157978
+ if (!usage) return void 0;
157979
+ return {
157980
+ input: codexNum(usage["input_tokens"]),
157981
+ // cache 字段名跨版本有两种
157982
+ cacheRead: codexNum(usage["cached_input_tokens"]) || codexNum(usage["cache_read_input_tokens"]),
157983
+ cacheWrite: codexNum(usage["cache_write_input_tokens"]),
157984
+ // reasoning 计入 output(对齐 Multica:codex 的 output_tokens 不含 reasoning)。
157985
+ output: codexNum(usage["output_tokens"]) + codexNum(usage["reasoning_output_tokens"])
157986
+ };
157987
+ }
157988
+ function usageFromBuckets(b2) {
157989
+ const inputFresh = Math.max(b2.input - b2.cacheRead, 0);
157990
+ return {
157991
+ inputTokens: inputFresh,
157992
+ outputTokens: b2.output,
157993
+ totalTokens: inputFresh + b2.output,
157994
+ // = input+output(不含 cache,与 claude/OTLP 口径一致)
157995
+ cacheReadTokens: b2.cacheRead,
157996
+ cacheCreationTokens: b2.cacheWrite || null,
157997
+ costUsdMicros: null
157998
+ // codex rollout 无成本(订阅计划)
157999
+ };
158000
+ }
158001
+ function parseCodexTokenUsage(lines, sinceMs) {
158002
+ let lastTotal;
158003
+ let lastTurn;
158004
+ const windowed = { input: 0, cacheRead: 0, cacheWrite: 0, output: 0 };
158005
+ let sawWindowed = false;
158006
+ let sawTimestamp = false;
157938
158007
  for (const line of lines) {
157939
158008
  if (!line || line.indexOf("token_count") === -1) continue;
157940
158009
  let o;
@@ -157943,25 +158012,29 @@ function parseCodexTokenUsage(lines) {
157943
158012
  } catch {
157944
158013
  continue;
157945
158014
  }
157946
- if (o?.type === "event_msg" && o.payload?.type === "token_count" && o.payload.info?.total_token_usage) {
157947
- last = o.payload.info.total_token_usage;
157948
- }
157949
- }
157950
- if (!last) return void 0;
157951
- const cacheRead = codexNum(last["cached_input_tokens"]) || codexNum(last["cache_read_input_tokens"]);
157952
- const inputFresh = Math.max(codexNum(last["input_tokens"]) - cacheRead, 0);
157953
- const output = codexNum(last["output_tokens"]) + codexNum(last["reasoning_output_tokens"]);
157954
- return {
157955
- inputTokens: inputFresh,
157956
- outputTokens: output,
157957
- totalTokens: inputFresh + output,
157958
- // = input+output(不含 cache,与 claude/OTLP 口径一致)
157959
- cacheReadTokens: cacheRead,
157960
- cacheCreationTokens: null,
157961
- // codex 不报 cache 写入
157962
- costUsdMicros: null
157963
- // codex rollout 无成本
157964
- };
158015
+ if (o?.type !== "event_msg" || o.payload?.type !== "token_count") continue;
158016
+ const info = o.payload.info;
158017
+ if (!info) continue;
158018
+ const total = bucketsOf(info.total_token_usage);
158019
+ if (total) lastTotal = total;
158020
+ const turn = bucketsOf(info.last_token_usage);
158021
+ if (turn) lastTurn = turn;
158022
+ if (sinceMs === void 0) continue;
158023
+ const ts3 = typeof o.timestamp === "string" ? Date.parse(o.timestamp) : Number.NaN;
158024
+ if (!Number.isFinite(ts3)) continue;
158025
+ sawTimestamp = true;
158026
+ if (ts3 < sinceMs || !turn) continue;
158027
+ sawWindowed = true;
158028
+ windowed.input += turn.input;
158029
+ windowed.cacheRead += turn.cacheRead;
158030
+ windowed.cacheWrite += turn.cacheWrite;
158031
+ windowed.output += turn.output;
158032
+ }
158033
+ if (sinceMs !== void 0 && sawTimestamp) {
158034
+ return sawWindowed ? usageFromBuckets(windowed) : void 0;
158035
+ }
158036
+ if (sinceMs !== void 0 && lastTurn) return usageFromBuckets(lastTurn);
158037
+ return lastTotal ? usageFromBuckets(lastTotal) : void 0;
157965
158038
  }
157966
158039
  function parseCodexModel(lines) {
157967
158040
  let model;
@@ -158019,8 +158092,27 @@ function listRecentRollouts(root, sinceMs) {
158019
158092
  walk(root);
158020
158093
  return found.sort((a, b2) => b2.m - a.m).map((x2) => x2.f);
158021
158094
  }
158022
- function telemetryFromRolloutLines(lines) {
158023
- const usage = parseCodexTokenUsage(lines);
158095
+ function findRolloutsBySessionId(root, sessionId) {
158096
+ const suffix = `-${sessionId}.jsonl`;
158097
+ const found = [];
158098
+ const walk = (d) => {
158099
+ let ents;
158100
+ try {
158101
+ ents = fs10.readdirSync(d, { withFileTypes: true });
158102
+ } catch {
158103
+ return;
158104
+ }
158105
+ for (const ent of ents) {
158106
+ const p2 = path11.join(d, ent.name);
158107
+ if (ent.isDirectory()) walk(p2);
158108
+ else if (ent.isFile() && ent.name.startsWith("rollout-") && ent.name.endsWith(suffix)) found.push(p2);
158109
+ }
158110
+ };
158111
+ walk(root);
158112
+ return found;
158113
+ }
158114
+ function telemetryFromRolloutLines(lines, sinceMs) {
158115
+ const usage = parseCodexTokenUsage(lines, sinceMs);
158024
158116
  const model = parseCodexModel(lines);
158025
158117
  const sessionId = parseCodexSessionId(lines);
158026
158118
  return {
@@ -158049,7 +158141,7 @@ function scanCodexTelemetry(workdir, sinceMs, sessionsRoot = codexSessionsRoot()
158049
158141
  continue;
158050
158142
  }
158051
158143
  if (meta?.type === "session_meta" && meta.payload?.cwd === workdir) {
158052
- return telemetryFromRolloutLines(lines);
158144
+ return telemetryFromRolloutLines(lines, sinceMs);
158053
158145
  }
158054
158146
  }
158055
158147
  if (preferSessionId) {
@@ -158060,9 +158152,13 @@ function scanCodexTelemetry(workdir, sinceMs, sessionsRoot = codexSessionsRoot()
158060
158152
  if (!lines) continue;
158061
158153
  const sid = parseCodexSessionId(lines);
158062
158154
  if (byName || sid === preferSessionId) {
158063
- return telemetryFromRolloutLines(lines);
158155
+ return telemetryFromRolloutLines(lines, sinceMs);
158064
158156
  }
158065
158157
  }
158158
+ for (const f2 of findRolloutsBySessionId(sessionsRoot, preferSessionId)) {
158159
+ const lines = readLines(f2);
158160
+ if (lines) return telemetryFromRolloutLines(lines, sinceMs);
158161
+ }
158066
158162
  }
158067
158163
  } catch {
158068
158164
  }
@@ -158337,7 +158433,7 @@ var init_codex = __esm({
158337
158433
  return this.spawnExec(job);
158338
158434
  }
158339
158435
  async spawnAppServer(job) {
158340
- const resolvedModel3 = job.model ?? this.opts.model;
158436
+ const resolvedModel2 = job.model ?? this.opts.model;
158341
158437
  const reasoningSummary = this.opts.reasoningSummary;
158342
158438
  const reasoningEffort = this.opts.reasoningEffort;
158343
158439
  return runProtocolSession(job, {
@@ -158356,7 +158452,7 @@ var init_codex = __esm({
158356
158452
  params: { clientInfo: { name: "oasis-adapter", version: "0.1.0" }, capabilities: {} }
158357
158453
  }),
158358
158454
  async start(send, currentJob, dir, task, state) {
158359
- const threadParams = currentJob.resumeRuntimeSession && currentJob.runtimeSessionId ? { threadId: currentJob.runtimeSessionId, cwd: dir, model: resolvedModel3 ?? null, approvalPolicy: "never", sandbox: "danger-full-access" } : { cwd: dir, model: resolvedModel3 ?? null, approvalPolicy: "never", sandbox: "danger-full-access" };
158455
+ const threadParams = currentJob.resumeRuntimeSession && currentJob.runtimeSessionId ? { threadId: currentJob.runtimeSessionId, cwd: dir, model: resolvedModel2 ?? null, approvalPolicy: "never", sandbox: "danger-full-access" } : { cwd: dir, model: resolvedModel2 ?? null, approvalPolicy: "never", sandbox: "danger-full-access" };
158360
158456
  let thread;
158361
158457
  try {
158362
158458
  thread = await send({
@@ -158366,7 +158462,7 @@ var init_codex = __esm({
158366
158462
  });
158367
158463
  } catch (error2) {
158368
158464
  if (!(currentJob.resumeRuntimeSession && currentJob.runtimeSessionId)) throw error2;
158369
- thread = await send({ jsonrpc: "2.0", method: "thread/start", params: { cwd: dir, model: resolvedModel3 ?? null, approvalPolicy: "never", sandbox: "danger-full-access" } });
158465
+ thread = await send({ jsonrpc: "2.0", method: "thread/start", params: { cwd: dir, model: resolvedModel2 ?? null, approvalPolicy: "never", sandbox: "danger-full-access" } });
158370
158466
  }
158371
158467
  const threadId = thread.thread?.id ?? thread.id;
158372
158468
  if (typeof threadId !== "string" || !threadId) throw new Error("codex app-server returned no thread id");
@@ -158383,7 +158479,7 @@ ${task}` : task;
158383
158479
  params: {
158384
158480
  threadId,
158385
158481
  input: [{ type: "text", text: text5 }],
158386
- ...resolvedModel3 ? { model: resolvedModel3 } : {},
158482
+ ...resolvedModel2 ? { model: resolvedModel2 } : {},
158387
158483
  ...reasoningSummary ? { summary: reasoningSummary } : {},
158388
158484
  ...reasoningEffort ? { effort: reasoningEffort } : {}
158389
158485
  }
@@ -158646,7 +158742,7 @@ ${task}` : task;
158646
158742
  child.on("exit", (code2) => {
158647
158743
  clearTimeout(timer);
158648
158744
  const resumeSessionId = job.resumeRuntimeSession ? job.runtimeSessionId : void 0;
158649
- const telemetry = scanCodexTelemetry(dir, startedAtMs, codexSessionsRoot(), resumeSessionId);
158745
+ const telemetry = scanCodexTelemetry(dir, startedAtMs, codexSessionsRoot(), job.runtimeSessionId);
158650
158746
  const model = telemetry.model ?? job.model ?? this.opts.model;
158651
158747
  const runtimeSessionId = telemetry.sessionId ?? resumeSessionId;
158652
158748
  const sawStreamError = streamErrorMessages.length > 0;
@@ -159437,7 +159533,7 @@ ${note}` : note };
159437
159533
  clientCapabilities: {}
159438
159534
  });
159439
159535
  const advertisedLoadSession = initRes?.agentCapabilities?.loadSession === true;
159440
- const resolvedModel3 = job.model ?? cfg.model;
159536
+ const resolvedModel2 = job.model ?? cfg.model;
159441
159537
  let sessionId = "";
159442
159538
  let ambiguousResume = false;
159443
159539
  if (job.resumeRuntimeSession && job.runtimeSessionId && advertisedLoadSession) {
@@ -159447,10 +159543,10 @@ ${note}` : note };
159447
159543
  cwd: dir,
159448
159544
  sessionId: job.runtimeSessionId,
159449
159545
  mcpServers: [],
159450
- ...resolvedModel3 ? { model: resolvedModel3 } : {}
159546
+ ...resolvedModel2 ? { model: resolvedModel2 } : {}
159451
159547
  });
159452
159548
  sessionId = extractSessionID(resumeRes);
159453
- actualModel = resolvedModel3 ?? extractCurrentModelID(resumeRes) ?? actualModel;
159549
+ actualModel = resolvedModel2 ?? extractCurrentModelID(resumeRes) ?? actualModel;
159454
159550
  if (!sessionId) {
159455
159551
  sessionId = job.runtimeSessionId;
159456
159552
  ambiguousResume = true;
@@ -159463,22 +159559,22 @@ ${note}` : note };
159463
159559
  const sessionRes = await client.request("session/new", {
159464
159560
  cwd: dir,
159465
159561
  mcpServers: [],
159466
- ...resolvedModel3 ? { model: resolvedModel3 } : {}
159562
+ ...resolvedModel2 ? { model: resolvedModel2 } : {}
159467
159563
  });
159468
159564
  sessionId = extractSessionID(sessionRes);
159469
- actualModel = resolvedModel3 ?? extractCurrentModelID(sessionRes) ?? actualModel;
159565
+ actualModel = resolvedModel2 ?? extractCurrentModelID(sessionRes) ?? actualModel;
159470
159566
  if (!sessionId) throw new Error("session/new returned no sessionId");
159471
159567
  }
159472
159568
  capturedSessionId = sessionId;
159473
- if (resolvedModel3) {
159569
+ if (resolvedModel2) {
159474
159570
  try {
159475
- await client.request("session/set_model", { sessionId, modelId: resolvedModel3 });
159571
+ await client.request("session/set_model", { sessionId, modelId: resolvedModel2 });
159476
159572
  } catch (err) {
159477
159573
  throw new Error(
159478
159574
  `\u8BE5 ACP \u8FD0\u884C\u65F6\u4E0D\u652F\u6301\u5207\u6362\u6A21\u578B\uFF08session/set_model \u5931\u8D25\uFF09\uFF1A${err instanceof Error ? err.message : String(err)}`
159479
159575
  );
159480
159576
  }
159481
- actualModel = resolvedModel3;
159577
+ actualModel = resolvedModel2;
159482
159578
  }
159483
159579
  const userText = job.systemPrompt ? `${job.systemPrompt}
159484
159580
 
@@ -159514,10 +159610,10 @@ ${task}` : task;
159514
159610
  const sessionRes = await client.request("session/new", {
159515
159611
  cwd: dir,
159516
159612
  mcpServers: [],
159517
- ...resolvedModel3 ? { model: resolvedModel3 } : {}
159613
+ ...resolvedModel2 ? { model: resolvedModel2 } : {}
159518
159614
  });
159519
159615
  sessionId = extractSessionID(sessionRes);
159520
- actualModel = resolvedModel3 ?? extractCurrentModelID(sessionRes) ?? actualModel;
159616
+ actualModel = resolvedModel2 ?? extractCurrentModelID(sessionRes) ?? actualModel;
159521
159617
  if (!sessionId) throw new Error("session/new returned no sessionId");
159522
159618
  capturedSessionId = sessionId;
159523
159619
  promptResult = await sendPrompt(userText);
@@ -195518,7 +195614,13 @@ function createAppJwt(appId, privateKeyPem, nowMs) {
195518
195614
  const signer = (0, import_node_crypto33.createSign)("RSA-SHA256");
195519
195615
  signer.update(`${header}.${payload}`);
195520
195616
  signer.end();
195521
- return `${header}.${payload}.${b64url(signer.sign(privateKeyPem))}`;
195617
+ try {
195618
+ return `${header}.${payload}.${b64url(signer.sign(privateKeyPem))}`;
195619
+ } catch (e) {
195620
+ throw new Error(
195621
+ "github app: \u79C1\u94A5\u89E3\u6790\u5931\u8D25\u2014\u2014\u8FD9\u4E2A .pem \u8BFB\u4E0D\u51FA\u6765\uFF08\u591A\u534A\u662F\u4E0B\u8F7D\u4E0D\u5B8C\u6574\u6216\u88AB\u6539\u8FC7\uFF09\uFF0C\u8BF7\u5728 App \u8BBE\u7F6E\u9875\u91CD\u65B0\u751F\u6210\u4E00\u628A"
195622
+ );
195623
+ }
195522
195624
  }
195523
195625
  async function mintInstallationToken(args, deps = {}) {
195524
195626
  const d = { ...defaultDeps2, ...deps };
@@ -200290,7 +200392,7 @@ async function startOasisServer(opts) {
200290
200392
  const discussSessions = opts.discussSessions ?? /* @__PURE__ */ new Map();
200291
200393
  const reviewWaiters = /* @__PURE__ */ new Map();
200292
200394
  const terminalReviews = /* @__PURE__ */ new Map();
200293
- const notifyReviewResolved = (draftId, r, resume) => {
200395
+ const notifyReviewResolved = (draftId, r, resume, storeFor) => {
200294
200396
  if (terminalReviews.has(draftId)) return;
200295
200397
  terminalReviews.set(draftId, r);
200296
200398
  if (terminalReviews.size > 512) {
@@ -200305,7 +200407,7 @@ async function startOasisServer(opts) {
200305
200407
  }
200306
200408
  if (resume?.origin) {
200307
200409
  const needsAgentDelivery = resume.kind === "command" && !hadWaiters;
200308
- void settleAuthorizationStatusLine(resume.origin, r, resume, draftId, needsAgentDelivery);
200410
+ void settleAuthorizationStatusLine(resume.origin, r, resume, draftId, needsAgentDelivery, storeFor);
200309
200411
  }
200310
200412
  };
200311
200413
  const resolveEngine = opts.resolveEngine ?? (async () => ({ kernel: opts.kernel, oplog: opts.oplog, blobs: opts.blobs, ...opts.registry ? { registry: opts.registry } : {} }));
@@ -200382,8 +200484,13 @@ async function startOasisServer(opts) {
200382
200484
  }
200383
200485
  return void 0;
200384
200486
  }
200385
- const appendNowThroughQueue2 = async (sid, text5, clientKey, attachments) => {
200386
- const store = opts.chatSession;
200487
+ const appendNowThroughQueue2 = async (sid, text5, clientKey, attachments, perCompanyStore) => {
200488
+ if (perCompanyStore === null) {
200489
+ throw new Error(
200490
+ `appendNowThroughQueue: per-company chat store unresolved (sid=${sid})`
200491
+ );
200492
+ }
200493
+ const store = perCompanyStore ?? opts.chatSession;
200387
200494
  if (!store?.enqueuePendingMessage || !store.listPendingMessages || !store.removePendingMessage) {
200388
200495
  return liveChat.append(sid, text5);
200389
200496
  }
@@ -200425,8 +200532,8 @@ async function startOasisServer(opts) {
200425
200532
  const label = ctx.effectLabel || ctx.command || "";
200426
200533
  return approved ? `\u547D\u4EE4\u5DF2\u7531 ${by} \u6279\u51C6\u5E76\u4EE5\u5176\u540D\u4E49\u6267\u884C${label ? `\uFF1A${label}` : ""}\u3002` : `\u547D\u4EE4\u5DF2\u88AB ${by} \u9A73\u56DE${reason ? `\uFF1A${reason}` : ""}\u3002`;
200427
200534
  };
200428
- const settleAuthorizationStatusLine = async (origin, resolution, ctx, draftId, needsAgentDelivery) => {
200429
- const store = opts.chatSession;
200535
+ const settleAuthorizationStatusLine = async (origin, resolution, ctx, draftId, needsAgentDelivery, storeFor) => {
200536
+ const store = storeFor ? await storeFor().catch(() => null) : opts.chatSession;
200430
200537
  if (!store) return;
200431
200538
  const session = await store.getSession(origin).catch(() => null);
200432
200539
  if (!session) {
@@ -201404,7 +201511,8 @@ async function startOasisServer(opts) {
201404
201511
  notifyReviewResolved(
201405
201512
  draftId,
201406
201513
  { status: "approved", command, effectLabel: review.command.effectLabel, authorizedBy: actor, receipt },
201407
- { origin: review.origin, kind: "command", authorizedBy: actor, effectLabel: review.command.effectLabel, command }
201514
+ { origin: review.origin, kind: "command", authorizedBy: actor, effectLabel: review.command.effectLabel, command },
201515
+ chatStoreNowOrNull
201408
201516
  );
201409
201517
  res.writeHead(200, { "content-type": "application/json" }).end(
201410
201518
  JSON.stringify({ applied: 1, command, artifactId, draftId, draftedBy: review.proposedBy, authorizedBy: actor })
@@ -201420,7 +201528,8 @@ async function startOasisServer(opts) {
201420
201528
  // 状态条要的两样也得进 resume 上下文(不能只放在 resolution 里):`effectLabel` 是改图卡的
201421
201529
  // 「改动摘要」(= plan.reason,见 diagnosis 口径 1),`authorizedBy` 是「谁批的」。
201422
201530
  // kind 仍传下去让三分支自行判定;改图卡到落库那一步就收手,**不**打开 chat 续跑。
201423
- { origin: review.origin, kind: review.kind ?? "graph-edit", authorizedBy: actor, effectLabel: review.plan.reason }
201531
+ { origin: review.origin, kind: review.kind ?? "graph-edit", authorizedBy: actor, effectLabel: review.plan.reason },
201532
+ chatStoreNowOrNull
201424
201533
  );
201425
201534
  res.writeHead(200, { "content-type": "application/json" }).end(
201426
201535
  JSON.stringify({ ...result, draftId, draftedBy: review.proposedBy, authorizedBy: actor })
@@ -201463,7 +201572,8 @@ async function startOasisServer(opts) {
201463
201572
  ...review.command?.effectLabel || review.plan.reason ? { effectLabel: review.command?.effectLabel || review.plan.reason } : {},
201464
201573
  ...review.command?.command ? { command: review.command.command } : {},
201465
201574
  ...reason !== void 0 ? { reason } : {}
201466
- }
201575
+ },
201576
+ chatStoreNowOrNull
201467
201577
  );
201468
201578
  res.writeHead(200, { "content-type": "application/json" }).end(JSON.stringify({ rejected: true, draftId, annotated: touched.length }));
201469
201579
  } catch (err) {
@@ -202035,7 +202145,8 @@ async function startOasisServer(opts) {
202035
202145
  }
202036
202146
  if (req.method === "POST" && /^\/api\/chat-sessions\/[^/]+\/append$/.test(url.pathname)) {
202037
202147
  const sid = decodeURIComponent(url.pathname.split("/")[3] ?? "");
202038
- const cs = await (await chatStoreNowOrNull())?.getSession(sid).catch(() => null) ?? null;
202148
+ const perCompanyStore = await chatStoreNowOrNull();
202149
+ const cs = await perCompanyStore?.getSession(sid).catch(() => null) ?? null;
202039
202150
  if (!cs || cs.humanActorId !== actor) {
202040
202151
  res.writeHead(404, { "content-type": "application/json" }).end(JSON.stringify({ error: "CHAT_SESSION_NOT_FOUND" }));
202041
202152
  return;
@@ -202059,13 +202170,14 @@ async function startOasisServer(opts) {
202059
202170
  res.writeHead(400, { "content-type": "application/json" }).end(JSON.stringify({ error: "EMPTY_TEXT" }));
202060
202171
  return;
202061
202172
  }
202062
- const result = await appendNowThroughQueue2(sid, text5, clientKey, insertAttachments);
202173
+ const result = await appendNowThroughQueue2(sid, text5, clientKey, insertAttachments, perCompanyStore);
202063
202174
  res.writeHead(result.accepted ? 200 : 409, { "content-type": "application/json" }).end(JSON.stringify(result));
202064
202175
  return;
202065
202176
  }
202066
202177
  if (url.pathname === "/api/chat" && req.method === "POST") {
202067
202178
  try {
202068
202179
  const body2 = JSON.parse(rawBody);
202180
+ const clientSubmitId = typeof body2.clientSubmitId === "string" && body2.clientSubmitId ? body2.clientSubmitId : void 0;
202069
202181
  if (!body2.message) throw new Error("\u7F3A\u5C11 message");
202070
202182
  const requestedModel = typeof body2.model === "string" && body2.model.trim() ? body2.model.trim() : null;
202071
202183
  if (requestedModel && !body2.chatSessionId) {
@@ -202286,6 +202398,14 @@ async function startOasisServer(opts) {
202286
202398
  ...assistantMsgId ? { messageId: assistantMsgId } : {},
202287
202399
  log: (m2) => console.warn(m2)
202288
202400
  });
202401
+ try {
202402
+ itemLedger.recordUserSubmission({
202403
+ text: persistedUserMessage,
202404
+ ...clientSubmitId ? { clientSubmitId } : {},
202405
+ ...effectiveAttachments.length ? { attachments: effectiveAttachments } : {}
202406
+ });
202407
+ } catch {
202408
+ }
202289
202409
  const normalizedSource = typeof session.onNormalizedProviderEvent === "function" ? {
202290
202410
  onNormalizedProviderEvent: session.onNormalizedProviderEvent.bind(session),
202291
202411
  finishTurn: (signal) => session.finishNormalizedTurn?.(signal)
@@ -203078,7 +203198,11 @@ async function startOasisServer(opts) {
203078
203198
  try {
203079
203199
  meta = await fetchAppSelfMetadata({ appId, privateKeyPem });
203080
203200
  } catch (e) {
203081
- bad(400, /HTTP 401/.test(String(e)) ? "App ID \u4E0E\u79C1\u94A5\u5BF9\u4E0D\u4E0A\u2014\u2014\u786E\u8BA4 App ID \u6284\u81EA\u540C\u4E00\u4E2A App \u7684\u8BBE\u7F6E\u9875" : `GitHub \u62D2\u7EDD\u4E86\u8FD9\u5BF9\u51ED\u636E\uFF1A${String(e)}`);
203201
+ const msg = String(e);
203202
+ bad(
203203
+ 400,
203204
+ /HTTP 401/.test(msg) ? "App ID \u4E0E\u79C1\u94A5\u5BF9\u4E0D\u4E0A\u2014\u2014\u786E\u8BA4 App ID \u6284\u81EA\u540C\u4E00\u4E2A App \u7684\u8BBE\u7F6E\u9875" : /私钥解析失败/.test(msg) ? msg.replace(/^Error:\s*/, "").replace(/^github app:\s*/, "") : `GitHub \u62D2\u7EDD\u4E86\u8FD9\u5BF9\u51ED\u636E\uFF1A${msg}`
203205
+ );
203082
203206
  return;
203083
203207
  }
203084
203208
  const excess = excessAppPermissions(meta.permissions);
@@ -215077,14 +215201,19 @@ function lookupInMap(map, prefixKeys, candidates) {
215077
215201
  if (!prefix) return null;
215078
215202
  return map instanceof Map ? map.get(prefix) ?? null : map[prefix] ?? null;
215079
215203
  }
215204
+ function stripBracketTier(model) {
215205
+ return model.replace(/\[[^\]]*\]/g, "").trim();
215206
+ }
215080
215207
  function modelCandidates(model) {
215081
215208
  const normalized4 = model.trim().toLowerCase();
215082
215209
  if (!normalized4) return [];
215083
- return [
215210
+ const base = [
215084
215211
  normalized4,
215085
215212
  normalized4.includes(":") ? normalized4.slice(normalized4.lastIndexOf(":") + 1) : "",
215086
215213
  normalized4.includes("/") ? normalized4.slice(normalized4.lastIndexOf("/") + 1) : ""
215087
215214
  ].filter(Boolean);
215215
+ const stripped = base.map(stripBracketTier).filter((m2) => m2 && !base.includes(m2));
215216
+ return [...base, ...new Set(stripped)];
215088
215217
  }
215089
215218
  function setModelPriceCacheLoader(loader) {
215090
215219
  cacheLoader = loader;
@@ -215154,10 +215283,11 @@ function displayCostUsdMicros(model, usage) {
215154
215283
  if (estimated.currency === "USD") return estimated.micros;
215155
215284
  return Math.round(estimated.micros / DISPLAY_CNY_PER_USD);
215156
215285
  }
215157
- var FOREIGN_MODEL_MARKUP, usd, cny, MODEL_PRICES, STATIC_PREFIX_PRICES, CACHE_TTL_MS2, dbPrices, dbPrefixKeys, cacheLoadedAt, cacheLoader, cacheRefreshInFlight, DISPLAY_CNY_PER_USD;
215286
+ var FOREIGN_MODEL_MARKUP, usd, cny, MODEL_PRICES, STATIC_PREFIX_PRICES, CACHE_TTL_MS2, dbPrices, dbPrefixKeys, cacheLoadedAt, cacheLoader, cacheRefreshInFlight;
215158
215287
  var init_model_pricing = __esm({
215159
215288
  "../server/src/domains/actors/model-pricing.ts"() {
215160
215289
  "use strict";
215290
+ init_src();
215161
215291
  FOREIGN_MODEL_MARKUP = 1.08;
215162
215292
  usd = (inputPerMillion, outputPerMillion, cachedInputPerMillion) => ({
215163
215293
  currency: "USD",
@@ -215269,7 +215399,80 @@ var init_model_pricing = __esm({
215269
215399
  cacheLoadedAt = 0;
215270
215400
  cacheLoader = null;
215271
215401
  cacheRefreshInFlight = null;
215272
- DISPLAY_CNY_PER_USD = 7.2;
215402
+ }
215403
+ });
215404
+
215405
+ // ../server/src/domains/usage/accounting.ts
215406
+ function nonNegative(v2) {
215407
+ return Math.max(v2 ?? 0, 0);
215408
+ }
215409
+ function resolvedModel(model) {
215410
+ const trimmed = model?.trim();
215411
+ return trimmed ? trimmed : null;
215412
+ }
215413
+ function splitByWeight(total, w2) {
215414
+ const sum = w2.input + w2.cache + w2.output;
215415
+ if (total === 0) return { input: 0, cache: 0, output: 0 };
215416
+ if (sum <= 0) return { input: 0, cache: 0, output: total };
215417
+ const input = Math.round(total * w2.input / sum);
215418
+ const cache = Math.round(total * w2.cache / sum);
215419
+ return { input, cache, output: total - input - cache };
215420
+ }
215421
+ function toDisplayUsd(currency, micros) {
215422
+ return currency === "USD" ? micros : Math.round(micros / DISPLAY_CNY_PER_USD);
215423
+ }
215424
+ function accountUsage(model, usage) {
215425
+ const resolved = resolvedModel(model);
215426
+ if (!resolved || !usage) return null;
215427
+ const input = nonNegative(usage.inputTokens);
215428
+ const output = nonNegative(usage.outputTokens);
215429
+ const cache = nonNegative(usage.cacheReadTokens) + nonNegative(usage.cacheCreationTokens);
215430
+ const tokens = { input, cache, output, total: input + cache + output };
215431
+ const estimated = estimateUsageCostBreakdown(resolved, usage);
215432
+ const weights = estimated ? { input: estimated.inputMicros, cache: estimated.cacheMicros, output: estimated.outputMicros } : null;
215433
+ const reported = Math.max(usage.costUsdMicros ?? 0, 0);
215434
+ if (reported > 0) {
215435
+ const parts = weights && estimated?.currency === "USD" ? splitByWeight(reported, weights) : { input: 0, cache: 0, output: reported };
215436
+ return {
215437
+ tokens,
215438
+ currency: "USD",
215439
+ amountMicros: reported,
215440
+ parts,
215441
+ displayUsdMicros: reported,
215442
+ displayParts: parts,
215443
+ source: "reported"
215444
+ };
215445
+ }
215446
+ if (estimated && weights) {
215447
+ const amount = weights.input + weights.cache + weights.output;
215448
+ const displayUsdMicros = toDisplayUsd(estimated.currency, amount);
215449
+ return {
215450
+ tokens,
215451
+ currency: estimated.currency,
215452
+ amountMicros: amount,
215453
+ parts: weights,
215454
+ displayUsdMicros,
215455
+ displayParts: estimated.currency === "USD" ? weights : splitByWeight(displayUsdMicros, weights),
215456
+ source: "estimated"
215457
+ };
215458
+ }
215459
+ return {
215460
+ tokens,
215461
+ currency: "USD",
215462
+ amountMicros: 0,
215463
+ parts: { input: 0, cache: 0, output: 0 },
215464
+ displayUsdMicros: 0,
215465
+ displayParts: { input: 0, cache: 0, output: 0 },
215466
+ source: "unpriced"
215467
+ };
215468
+ }
215469
+ function isUnpriced(account) {
215470
+ return account.source === "unpriced" && account.tokens.total > 0;
215471
+ }
215472
+ var init_accounting = __esm({
215473
+ "../server/src/domains/usage/accounting.ts"() {
215474
+ "use strict";
215475
+ init_model_pricing();
215273
215476
  }
215274
215477
  });
215275
215478
 
@@ -215284,55 +215487,44 @@ function emptyCostBreakdown() {
215284
215487
  outputCnyMicros: 0
215285
215488
  };
215286
215489
  }
215287
- function resolvedModel(model) {
215288
- const trimmed = model?.trim();
215289
- return trimmed ? trimmed : null;
215290
- }
215291
- function addBreakdownParts(target, currency, parts) {
215292
- if (currency === "USD") {
215293
- target.inputUsdMicros += parts.inputMicros;
215294
- target.cacheUsdMicros += parts.cacheMicros;
215295
- target.outputUsdMicros += parts.outputMicros;
215490
+ function emptySummary() {
215491
+ return {
215492
+ tokenWeek: 0,
215493
+ tokenWeekBreakdown: { input: 0, cache: 0, output: 0 },
215494
+ costWeekUsdMicros: 0,
215495
+ costWeekCnyMicros: 0,
215496
+ costWeekBreakdown: emptyCostBreakdown(),
215497
+ unpricedModels: []
215498
+ };
215499
+ }
215500
+ function addAccount(target, account) {
215501
+ target.tokenWeekBreakdown.input += account.tokens.input;
215502
+ target.tokenWeekBreakdown.cache += account.tokens.cache;
215503
+ target.tokenWeekBreakdown.output += account.tokens.output;
215504
+ target.tokenWeek += account.tokens.total;
215505
+ if (account.currency === "USD") {
215506
+ target.costWeekUsdMicros += account.amountMicros;
215507
+ target.costWeekBreakdown.inputUsdMicros += account.parts.input;
215508
+ target.costWeekBreakdown.cacheUsdMicros += account.parts.cache;
215509
+ target.costWeekBreakdown.outputUsdMicros += account.parts.output;
215296
215510
  } else {
215297
- target.inputCnyMicros += parts.inputMicros;
215298
- target.cacheCnyMicros += parts.cacheMicros;
215299
- target.outputCnyMicros += parts.outputMicros;
215300
- }
215301
- }
215302
- function addAlignedCost(target, totals, model, usage) {
215303
- const storedCost = Math.max(usage.costUsdMicros ?? 0, 0);
215304
- const estimated = estimateUsageCostBreakdown(model, usage);
215305
- if (storedCost > 0) {
215306
- if (estimated && estimated.currency === "USD") {
215307
- const estTotal = estimated.inputMicros + estimated.cacheMicros + estimated.outputMicros;
215308
- let inputMicros = 0;
215309
- let cacheMicros = 0;
215310
- let outputMicros = storedCost;
215311
- if (estTotal > 0) {
215312
- const scale = storedCost / estTotal;
215313
- inputMicros = Math.round(estimated.inputMicros * scale);
215314
- cacheMicros = Math.round(estimated.cacheMicros * scale);
215315
- outputMicros = storedCost - inputMicros - cacheMicros;
215316
- }
215317
- addBreakdownParts(target, "USD", { inputMicros, cacheMicros, outputMicros });
215318
- } else {
215319
- addBreakdownParts(target, "USD", { inputMicros: 0, cacheMicros: 0, outputMicros: storedCost });
215320
- }
215321
- totals.usd += storedCost;
215322
- return true;
215511
+ target.costWeekCnyMicros += account.amountMicros;
215512
+ target.costWeekBreakdown.inputCnyMicros += account.parts.input;
215513
+ target.costWeekBreakdown.cacheCnyMicros += account.parts.cache;
215514
+ target.costWeekBreakdown.outputCnyMicros += account.parts.output;
215323
215515
  }
215324
- if (estimated) {
215325
- const estTotal = estimated.inputMicros + estimated.cacheMicros + estimated.outputMicros;
215326
- addBreakdownParts(target, estimated.currency, {
215327
- inputMicros: estimated.inputMicros,
215328
- cacheMicros: estimated.cacheMicros,
215329
- outputMicros: estimated.outputMicros
215330
- });
215331
- if (estimated.currency === "USD") totals.usd += estTotal;
215332
- else totals.cny += estTotal;
215333
- return true;
215516
+ }
215517
+ function summarize2(rows) {
215518
+ const out = emptySummary();
215519
+ const unpriced = /* @__PURE__ */ new Set();
215520
+ for (const row of rows) {
215521
+ const account = accountUsage(row.model, row.usage);
215522
+ if (!account) continue;
215523
+ addAccount(out, account);
215524
+ if (isUnpriced(account)) unpriced.add(resolvedModel(row.model));
215334
215525
  }
215335
- return false;
215526
+ out.unpricedModels = [...unpriced].sort();
215527
+ return out;
215336
215528
  }
215337
215529
  async function actorStats(actorId, opts = {}) {
215338
215530
  const now = opts.now ?? /* @__PURE__ */ new Date();
@@ -215366,118 +215558,43 @@ async function listWeeklyRuns(trace, actorId, from, to) {
215366
215558
  return out;
215367
215559
  }
215368
215560
  function summarizeWeeklyRuns(runs) {
215369
- let input = 0;
215370
- let cache = 0;
215371
- let output = 0;
215372
- const totals = { usd: 0, cny: 0 };
215373
- const costWeekBreakdown = emptyCostBreakdown();
215374
- const unpriced = /* @__PURE__ */ new Set();
215375
- for (const run of runs) {
215376
- const model = resolvedModel(run.effectiveModel);
215377
- if (!model) continue;
215378
- const usage = run.usage;
215379
- if (!usage) continue;
215380
- const inputTokens = Math.max(usage.inputTokens ?? 0, 0);
215381
- const outputTokens = Math.max(usage.outputTokens ?? 0, 0);
215382
- const cacheCreation = Math.max(usage.cacheCreationTokens ?? 0, 0);
215383
- const cacheRead = Math.max(usage.cacheReadTokens ?? 0, 0);
215384
- input += inputTokens;
215385
- cache += cacheCreation + cacheRead;
215386
- output += outputTokens;
215387
- const priced = addAlignedCost(costWeekBreakdown, totals, model, usage);
215388
- if (!priced && inputTokens + outputTokens + cacheCreation + cacheRead > 0) {
215389
- unpriced.add(model);
215390
- }
215391
- }
215392
- return {
215393
- tokenWeek: input + cache + output,
215394
- tokenWeekBreakdown: { input, cache, output },
215395
- costWeekUsdMicros: totals.usd,
215396
- costWeekCnyMicros: totals.cny,
215397
- costWeekBreakdown,
215398
- unpricedModels: [...unpriced].sort()
215399
- };
215561
+ return summarize2(runs.map((run) => ({ model: run.effectiveModel, usage: run.usage })));
215400
215562
  }
215401
215563
  function summarizeWeeklyUsage(rows) {
215402
- let input = 0;
215403
- let cache = 0;
215404
- let output = 0;
215405
- const totals = { usd: 0, cny: 0 };
215406
- const costWeekBreakdown = emptyCostBreakdown();
215407
- const unpriced = /* @__PURE__ */ new Set();
215408
- for (const row of rows) {
215409
- const model = resolvedModel(row.effectiveModel);
215410
- if (!model) continue;
215411
- const inputTokens = Math.max(row.inputTokens, 0);
215412
- const outputTokens = Math.max(row.outputTokens, 0);
215413
- const cacheCreation = Math.max(row.cacheCreationTokens, 0);
215414
- const cacheRead = Math.max(row.cacheReadTokens, 0);
215415
- input += inputTokens;
215416
- cache += cacheCreation + cacheRead;
215417
- output += outputTokens;
215418
- const priced = addAlignedCost(costWeekBreakdown, totals, model, row);
215419
- if (!priced && inputTokens + outputTokens + cacheCreation + cacheRead > 0) {
215420
- unpriced.add(model);
215421
- }
215422
- }
215423
- return {
215424
- tokenWeek: input + cache + output,
215425
- tokenWeekBreakdown: { input, cache, output },
215426
- costWeekUsdMicros: totals.usd,
215427
- costWeekCnyMicros: totals.cny,
215428
- costWeekBreakdown,
215429
- unpricedModels: [...unpriced].sort()
215430
- };
215564
+ return summarize2(rows.map((row) => ({ model: row.effectiveModel, usage: row })));
215431
215565
  }
215432
215566
  var init_stats = __esm({
215433
215567
  "../server/src/domains/actors/stats.ts"() {
215434
215568
  "use strict";
215435
- init_model_pricing();
215569
+ init_accounting();
215436
215570
  }
215437
215571
  });
215438
215572
 
215439
215573
  // ../server/src/domains/actors/list-usage.ts
215440
- function resolvedModel2(model) {
215441
- const m2 = (model ?? "").trim();
215442
- return m2 === "" ? null : m2;
215443
- }
215444
215574
  function aggregateUsageByActor(runs) {
215445
215575
  const out = /* @__PURE__ */ new Map();
215446
215576
  const unpriced = /* @__PURE__ */ new Map();
215447
215577
  for (const run of runs) {
215448
- const model = resolvedModel2(run.effectiveModel);
215449
- if (!model) continue;
215450
- const usage = run.usage;
215451
- if (!usage) continue;
215452
- const inputTokens = Math.max(usage.inputTokens ?? 0, 0);
215453
- const outputTokens = Math.max(usage.outputTokens ?? 0, 0);
215454
- const cacheCreation = Math.max(usage.cacheCreationTokens ?? 0, 0);
215455
- const cacheRead = Math.max(usage.cacheReadTokens ?? 0, 0);
215456
- const cacheTokens = cacheCreation + cacheRead;
215578
+ const account = accountUsage(run.effectiveModel, run.usage);
215579
+ if (!account) continue;
215457
215580
  let acc = out.get(run.actorId);
215458
215581
  if (!acc) {
215459
215582
  acc = emptyActorUsageWeek();
215460
215583
  out.set(run.actorId, acc);
215461
215584
  }
215462
- acc.tokenWeekBreakdown.input += inputTokens;
215463
- acc.tokenWeekBreakdown.cache += cacheTokens;
215464
- acc.tokenWeekBreakdown.output += outputTokens;
215465
- acc.tokenWeek += inputTokens + cacheTokens + outputTokens;
215466
- const stored = Math.max(usage.costUsdMicros ?? 0, 0);
215467
- const estimated = estimateUsageCostBreakdown(model, usage);
215468
- if (stored > 0) {
215469
- acc.costWeekUsdMicros += stored;
215470
- } else if (estimated) {
215471
- const total = estimated.inputMicros + estimated.cacheMicros + estimated.outputMicros;
215472
- if (estimated.currency === "USD") acc.costWeekUsdMicros += total;
215473
- else acc.costWeekCnyMicros += total;
215474
- } else if (inputTokens + outputTokens + cacheTokens > 0) {
215585
+ acc.tokenWeekBreakdown.input += account.tokens.input;
215586
+ acc.tokenWeekBreakdown.cache += account.tokens.cache;
215587
+ acc.tokenWeekBreakdown.output += account.tokens.output;
215588
+ acc.tokenWeek += account.tokens.total;
215589
+ if (account.currency === "USD") acc.costWeekUsdMicros += account.amountMicros;
215590
+ else acc.costWeekCnyMicros += account.amountMicros;
215591
+ if (isUnpriced(account)) {
215475
215592
  let set = unpriced.get(run.actorId);
215476
215593
  if (!set) {
215477
215594
  set = /* @__PURE__ */ new Set();
215478
215595
  unpriced.set(run.actorId, set);
215479
215596
  }
215480
- set.add(model);
215597
+ set.add(resolvedModel(run.effectiveModel));
215481
215598
  }
215482
215599
  }
215483
215600
  for (const [actorId, models] of unpriced) {
@@ -215502,7 +215619,7 @@ var emptyActorUsageWeek;
215502
215619
  var init_list_usage = __esm({
215503
215620
  "../server/src/domains/actors/list-usage.ts"() {
215504
215621
  "use strict";
215505
- init_model_pricing();
215622
+ init_accounting();
215506
215623
  emptyActorUsageWeek = () => ({
215507
215624
  tokenWeek: 0,
215508
215625
  tokenWeekBreakdown: { input: 0, cache: 0, output: 0 },
@@ -215626,7 +215743,7 @@ function eventToEntry(rec) {
215626
215743
  ...s2
215627
215744
  };
215628
215745
  }
215629
- function summarize2(kind, payload) {
215746
+ function summarize3(kind, payload) {
215630
215747
  const p2 = payload;
215631
215748
  switch (kind) {
215632
215749
  case "report_gap":
@@ -215686,7 +215803,7 @@ async function actorActivity(source, actorId, limit = 50) {
215686
215803
  for await (const { op } of source.oplog.readAll()) {
215687
215804
  if (op.actor !== actorId) continue;
215688
215805
  total++;
215689
- matched.push({ at: op.timestamp, kind: op.kind, target: op.target, ...summarize2(op.kind, op.payload) });
215806
+ matched.push({ at: op.timestamp, kind: op.kind, target: op.target, ...summarize3(op.kind, op.payload) });
215690
215807
  if (matched.length > capped) matched.shift();
215691
215808
  }
215692
215809
  } else {
@@ -222733,26 +222850,15 @@ function emptyUsage() {
222733
222850
  };
222734
222851
  }
222735
222852
  function usageOf(run) {
222736
- if (!run.effectiveModel?.trim()) return null;
222737
- const input = Math.max(run.usage.inputTokens ?? 0, 0);
222738
- const cache = Math.max(run.usage.cacheReadTokens ?? 0, 0) + Math.max(run.usage.cacheCreationTokens ?? 0, 0);
222739
- const output = Math.max(run.usage.outputTokens ?? 0, 0);
222740
- const tokens = input + cache + output;
222741
- const costUsdMicros = Math.max(run.usage.costUsdMicros ?? 0, 0);
222742
- const denominator = tokens || 1;
222743
- const inputCost = Math.round(costUsdMicros * input / denominator);
222744
- const cacheCost = Math.round(costUsdMicros * cache / denominator);
222853
+ const account = accountUsage(run.effectiveModel, run.usage);
222854
+ if (!account) return null;
222745
222855
  return {
222746
- input,
222747
- cache,
222748
- output,
222749
- tokens,
222750
- costUsdMicros,
222751
- costParts: {
222752
- input: inputCost,
222753
- cache: cacheCost,
222754
- output: costUsdMicros - inputCost - cacheCost
222755
- }
222856
+ input: account.tokens.input,
222857
+ cache: account.tokens.cache,
222858
+ output: account.tokens.output,
222859
+ tokens: account.tokens.total,
222860
+ costUsdMicros: account.displayUsdMicros,
222861
+ costParts: { ...account.displayParts }
222756
222862
  };
222757
222863
  }
222758
222864
  function addUsage(target, addition) {
@@ -222805,6 +222911,7 @@ function buildOrganizationUsageSummary(input) {
222805
222911
  const rows = /* @__PURE__ */ new Map();
222806
222912
  const total = emptyUsage();
222807
222913
  let unpricedRunCount = 0;
222914
+ const unpricedModels = /* @__PURE__ */ new Set();
222808
222915
  const orgEmployees = /* @__PURE__ */ new Map();
222809
222916
  const orgEmployeeWorkorders = /* @__PURE__ */ new Map();
222810
222917
  const runtimeTotals = /* @__PURE__ */ new Map();
@@ -222820,6 +222927,8 @@ function buildOrganizationUsageSummary(input) {
222820
222927
  unpricedRunCount += 1;
222821
222928
  continue;
222822
222929
  }
222930
+ const account = accountUsage(run.effectiveModel, run.usage);
222931
+ if (account && isUnpriced(account)) unpricedModels.add(resolvedModel(run.effectiveModel));
222823
222932
  addUsage(total, usage);
222824
222933
  const key = bucketKey(formatter, run.startedAt, input.bucketKind);
222825
222934
  if (key) {
@@ -223012,7 +223121,8 @@ function buildOrganizationUsageSummary(input) {
223012
223121
  workorders: normalizedWorkorderRows,
223013
223122
  employees: employeeRows,
223014
223123
  runtimes: runtimeRows,
223015
- unpricedRunCount
223124
+ unpricedRunCount,
223125
+ unpricedModels: [...unpricedModels].sort()
223016
223126
  };
223017
223127
  }
223018
223128
  var UNASSIGNED_ID;
@@ -223020,6 +223130,7 @@ var init_organization_usage = __esm({
223020
223130
  "../server/src/domains/collab/organization-usage.ts"() {
223021
223131
  "use strict";
223022
223132
  init_workorders();
223133
+ init_accounting();
223023
223134
  UNASSIGNED_ID = "__unassigned__";
223024
223135
  }
223025
223136
  });
@@ -225361,8 +225472,6 @@ function renderCoordinatorView(view, taskBackground) {
225361
225472
  "unavailable"
225362
225473
  ];
225363
225474
  const attemptStates = [
225364
- "waiting",
225365
- "ready",
225366
225475
  "running",
225367
225476
  "stalled",
225368
225477
  "succeeded",
@@ -225374,7 +225483,12 @@ function renderCoordinatorView(view, taskBackground) {
225374
225483
  workStates,
225375
225484
  (state) => view.nodes.filter((node2) => node2.work.state === state).length
225376
225485
  );
225377
- const attemptProgress = visibleCounts(attemptStates, (state) => view.progress[state]);
225486
+ const noAttemptCount = view.nodes.filter((node2) => node2.nodeState === null).length;
225487
+ const attemptCounts = visibleCounts(attemptStates, (state) => view.progress[state]);
225488
+ const attemptProgress = [
225489
+ attemptCounts === "none" ? "" : attemptCounts,
225490
+ noAttemptCount > 0 ? `\u65E0 attempt \u8BB0\u5F55 ${noAttemptCount}` : ""
225491
+ ].filter(Boolean).join("; ") || "none";
225378
225492
  const lines = [
225379
225493
  "# Coordinator View",
225380
225494
  "",
@@ -225390,12 +225504,16 @@ function renderCoordinatorView(view, taskBackground) {
225390
225504
  "## Nodes"
225391
225505
  ];
225392
225506
  for (const node2 of view.nodes) {
225507
+ lines.push(`### ${node2.title} (${node2.nodeId})`);
225508
+ if (node2.sealed) lines.push(`- \u8282\u70B9: sealed(${node2.sealed.reason})`);
225393
225509
  lines.push(
225394
- `### ${node2.title} (${node2.nodeId})`,
225395
225510
  `- Work: ${node2.work.state}; Version: ${renderWorkVersion(node2.work)}`,
225396
- `- Attempt: ${node2.status}; continuity: ${node2.nodeState}; number: ${node2.attemptNo || "none"}`,
225511
+ // nodeState === null 说的是「账本里没有这条记录」,不是「没在跑」——它和上面那行 Work 状态
225512
+ // 必须并排出现:线上确有 Work running 而执行账本零 attempt 行的工单(ws:wo-9dc06583)。
225513
+ node2.nodeState === null ? "- Attempt: \u6267\u884C\u8D26\u672C\u4E2D\u65E0 attempt \u8BB0\u5F55" : `- Attempt: ${node2.status}; continuity: ${node2.nodeState}; number: ${node2.attemptNo || "none"}`,
225397
225514
  `- Actor: ${node2.actorId ?? "unassigned"}; role: ${node2.role ?? "unassigned"}`
225398
225515
  );
225516
+ if (node2.dependsOn.length > 0) lines.push(`- Depends on: ${node2.dependsOn.join(", ")}`);
225399
225517
  if (node2.conclusion?.trim()) lines.push(`- Conclusion: ${node2.conclusion.trim()}`);
225400
225518
  if (node2.recoverySource) {
225401
225519
  const source = node2.recoverySource;
@@ -225413,7 +225531,6 @@ function renderCoordinatorView(view, taskBackground) {
225413
225531
  }
225414
225532
  for (const risk of source.recoveryHandoff?.risks ?? []) lines.push(` - Recovery risk: ${risk}`);
225415
225533
  }
225416
- if (node2.blockedBy.length > 0) lines.push(`- Blocked by: ${node2.blockedBy.join(", ")}`);
225417
225534
  if (node2.progress) {
225418
225535
  lines.push(`- Latest checkpoint: ${node2.progress.recoverability} at ${node2.progress.lastCheckpointAt}`);
225419
225536
  for (const step of node2.progress.pendingSteps) lines.push(` - Pending: ${step}`);
@@ -225430,7 +225547,12 @@ function renderCoordinatorView(view, taskBackground) {
225430
225547
  ...node2.unresolvedIssues.map((value2) => `[unresolved] ${value2}`),
225431
225548
  ...node2.risks.map((value2) => `[risk] ${value2}`),
225432
225549
  ...node2.verificationGaps.map((value2) => `[verification] ${value2}`),
225433
- ...node2.nextActions.map((value2) => `[next] ${value2}`)
225550
+ // [next] 只给 Agent 亲手写的下一步。system_reconstructed 的 nextActions 是系统从
225551
+ // checkpoint.pendingSteps 抄回来的残留:节点已经跑成功了,那几条早就做完,却和 Agent 的计划
225552
+ // 长得一模一样(ws:wo-ecce337b 的 smoke 节点五条「下一步」全是这么来的),协调者会当成还有活干。
225553
+ // 同一道 handoffProducer === "agent" 判断,上面的 Technical notes 本来就有,这里之前漏了。
225554
+ // 真正在跑/卡住的节点不会因此丢信息:那些 pendingSteps 由 node.progress 走 `- Pending:` 出。
225555
+ ...node2.handoffProducer === "agent" ? node2.nextActions.map((value2) => `[next] ${value2}`) : []
225434
225556
  ];
225435
225557
  if (openItems.length > 0) {
225436
225558
  lines.push("- Open items:");
@@ -225438,10 +225560,7 @@ function renderCoordinatorView(view, taskBackground) {
225438
225560
  }
225439
225561
  lines.push("");
225440
225562
  }
225441
- lines.push("## Dependency blocks (Attempt continuity)");
225442
- if (view.dependencyBlocks.length === 0) lines.push("- None");
225443
- for (const block of view.dependencyBlocks) lines.push(`- ${block.nodeId} blocks: ${block.blocks.join(", ")}`);
225444
- lines.push("", "## Attention items");
225563
+ lines.push("## Attention items");
225445
225564
  const unavailableWorkCount = view.nodes.filter((node2) => node2.work.state === "unavailable").length;
225446
225565
  const workAttention = view.nodes.filter((node2) => node2.work.state === "failed" || node2.work.state === "dead" || node2.work.state === "retry");
225447
225566
  if (unavailableWorkCount > 0) {
@@ -225452,8 +225571,7 @@ function renderCoordinatorView(view, taskBackground) {
225452
225571
  `- Work ${node2.nodeId} [${node2.work.state}]: current head ${node2.work.currentWorkId ?? "none"}; delivered ${node2.work.acceptedWorkId ?? "none"}.`
225453
225572
  );
225454
225573
  }
225455
- if (unavailableWorkCount === 0 && workAttention.length === 0 && view.todo.length === 0) lines.push("- None");
225456
- for (const item of view.todo) lines.push(`- Attempt P${item.priority} ${item.nodeId} [${item.nodeState}]: ${item.action}`);
225574
+ if (unavailableWorkCount === 0 && workAttention.length === 0) lines.push("- None");
225457
225575
  return `${lines.join("\n")}
225458
225576
  `;
225459
225577
  }
@@ -225541,50 +225659,18 @@ function stateFromAttempt(attempt, handoff, checkpoint, now, stalledAfterMs, ope
225541
225659
  if (attempt.status === "no-output") return handoff?.unresolvedIssues.length ? "blocked" : "no_change";
225542
225660
  return "failed";
225543
225661
  }
225544
- function reverseEdges(nodes) {
225545
- const out = /* @__PURE__ */ new Map();
225546
- for (const node2 of nodes) for (const upstream of node2.dependsOn) out.set(upstream, [...out.get(upstream) ?? [], node2.nodeId]);
225547
- return out;
225548
- }
225549
- function waitingDescendants(source, reverse, states) {
225550
- const found = /* @__PURE__ */ new Set();
225551
- const queue = [...reverse.get(source) ?? []];
225552
- while (queue.length) {
225553
- const nodeId = queue.shift();
225554
- if (found.has(nodeId) || states.get(nodeId) !== "waiting") continue;
225555
- found.add(nodeId);
225556
- queue.push(...reverse.get(nodeId) ?? []);
225557
- }
225558
- return [...found].sort();
225559
- }
225560
- function todoAction(node2) {
225561
- if (node2.handoffProducer === "agent" && node2.nextActions[0]) return `Agent Handoff: ${node2.nextActions[0]}`;
225562
- if (node2.nodeState === "ready") return "\u8282\u70B9 ready\uFF0C\u7B49\u5F85\u8C03\u5EA6";
225563
- if (node2.nodeState === "blocked" && node2.unresolvedIssues[0]) return `\u8282\u70B9 blocked\uFF1A${node2.unresolvedIssues[0]}`;
225564
- if (!node2.progress) return `\u8282\u70B9 ${node2.nodeState}\uFF0C\u65E0 checkpoint`;
225565
- if (node2.progress.recoverability === "full") return `\u8282\u70B9 ${node2.nodeState}\uFF0Ccheckpoint \u5B8C\u6574\u53EF\u6062\u590D`;
225566
- if (node2.progress.recoverability === "partial") return `\u8282\u70B9 ${node2.nodeState}\uFF0Ccheckpoint \u90E8\u5206\u53EF\u6062\u590D\uFF0C\u9700\u5173\u6CE8\u5DF2\u6709\u4EA7\u51FA`;
225567
- return `\u8282\u70B9 ${node2.nodeState}\uFF0Ccheckpoint \u4E0D\u53EF\u6062\u590D`;
225568
- }
225569
- var import_node_crypto56, DONE_STATES, BLOCKING_STATES, HANDOFF_OUTCOMES2, RESUME_EXTERNAL_EFFECT_LIMIT, RESUME_SNAPSHOT_PREFIX, RESUME_SNAPSHOT_SUFFIX, TODO_RANK, ExecutionContinuityService;
225662
+ var import_node_crypto56, BLOCKING_STATES, HANDOFF_OUTCOMES2, RESUME_EXTERNAL_EFFECT_LIMIT, RESUME_SNAPSHOT_PREFIX, RESUME_SNAPSHOT_SUFFIX, ExecutionContinuityService;
225570
225663
  var init_service8 = __esm({
225571
225664
  "../server/src/domains/execution-continuity/service.ts"() {
225572
225665
  "use strict";
225573
225666
  init_src();
225574
225667
  import_node_crypto56 = require("node:crypto");
225575
225668
  init_src5();
225576
- DONE_STATES = /* @__PURE__ */ new Set(["succeeded", "no_change"]);
225577
225669
  BLOCKING_STATES = /* @__PURE__ */ new Set(["stalled", "failed", "blocked"]);
225578
225670
  HANDOFF_OUTCOMES2 = /* @__PURE__ */ new Set(["succeeded", "no-output", "failed", "cancelled", "timeout", "orphaned"]);
225579
225671
  RESUME_EXTERNAL_EFFECT_LIMIT = 20;
225580
225672
  RESUME_SNAPSHOT_PREFIX = "<!-- oasis-resume-pack-snapshot:v1:";
225581
225673
  RESUME_SNAPSHOT_SUFFIX = " -->";
225582
- TODO_RANK = {
225583
- stalled: 0,
225584
- failed: 1,
225585
- blocked: 2,
225586
- ready: 3
225587
- };
225588
225674
  ExecutionContinuityService = class {
225589
225675
  mode;
225590
225676
  store;
@@ -225857,10 +225943,6 @@ var init_service8 = __esm({
225857
225943
  const attempt = latest.get(node2.nodeId);
225858
225944
  if (attempt) stateByNode.set(node2.nodeId, stateFromAttempt(attempt, handoffs.get(attempt.id) ?? null, checkpoints.get(attempt.id) ?? null, this.now(), this.stalledAfterMs));
225859
225945
  }
225860
- for (const node2 of graph) {
225861
- if (stateByNode.has(node2.nodeId)) continue;
225862
- stateByNode.set(node2.nodeId, node2.dependsOn.every((id) => DONE_STATES.has(stateByNode.get(id) ?? "waiting")) ? "ready" : "waiting");
225863
- }
225864
225946
  const nodes = graph.map((node2) => {
225865
225947
  const attempt = latest.get(node2.nodeId) ?? null;
225866
225948
  const handoff = attempt ? handoffs.get(attempt.id) ?? null : null;
@@ -225873,10 +225955,10 @@ var init_service8 = __esm({
225873
225955
  recoveryCheckpoint: recoverySnapshot.latestCheckpoint,
225874
225956
  recoveryHandoff: recoverySnapshot.previousHandoff
225875
225957
  } : null;
225876
- const nodeState = stateByNode.get(node2.nodeId);
225958
+ const nodeState = stateByNode.get(node2.nodeId) ?? null;
225877
225959
  const wasRecovered = recoverySource !== null;
225878
- const executionState = wasRecovered && nodeState === "failed" ? "recovery_failed" : BLOCKING_STATES.has(nodeState) ? "awaiting_coordinator" : wasRecovered ? "recovered" : nodeState === "running" ? "running" : null;
225879
- const includeProgress = checkpoint && ["running", "stalled", "blocked", "failed"].includes(nodeState);
225960
+ const executionState = wasRecovered && nodeState === "failed" ? "recovery_failed" : nodeState !== null && BLOCKING_STATES.has(nodeState) ? "awaiting_coordinator" : wasRecovered ? "recovered" : nodeState === "running" ? "running" : null;
225961
+ const includeProgress = checkpoint && nodeState !== null && ["running", "stalled", "blocked", "failed"].includes(nodeState);
225880
225962
  return {
225881
225963
  nodeId: node2.nodeId,
225882
225964
  title: node2.title,
@@ -225903,18 +225985,15 @@ var init_service8 = __esm({
225903
225985
  recoverability: checkpoint.recoverability,
225904
225986
  lastCheckpointAt: checkpoint.createdAt
225905
225987
  } } : {},
225906
- blockedBy: nodeState === "waiting" ? node2.dependsOn.filter((id) => !DONE_STATES.has(stateByNode.get(id) ?? "waiting")) : [],
225988
+ sealed: facts?.sealed ?? null,
225989
+ // 纯透传的图事实,零派生:不看 attempt、不判断谁在等谁。见 CoordinatorNodeView.dependsOn。
225990
+ dependsOn: node2.dependsOn,
225907
225991
  // 纯透传(展示层事实,见 CoordinatorNodeView.work/conclusion)。不参与本方法里任何
225908
- // Attempt nodeState / todo / dependencyBlocks / collaborationStatus 的派生。
225992
+ // Attempt nodeState 的派生。
225909
225993
  ...facts?.conclusion?.trim() ? { conclusion: facts.conclusion.trim() } : {}
225910
225994
  };
225911
225995
  });
225912
- const reverse = reverseEdges(graph);
225913
- const dependencyBlocks = nodes.filter((node2) => BLOCKING_STATES.has(node2.nodeState)).map((node2) => ({ nodeId: node2.nodeId, blocks: waitingDescendants(node2.nodeId, reverse, stateByNode) })).filter((item) => item.blocks.length > 0).sort((a, b2) => b2.blocks.length - a.blocks.length || a.nodeId.localeCompare(b2.nodeId));
225914
- const todos = nodes.filter((node2) => node2.nodeState === "stalled" || node2.nodeState === "failed" || node2.nodeState === "blocked" || node2.nodeState === "ready").sort((a, b2) => TODO_RANK[a.nodeState] - TODO_RANK[b2.nodeState] || a.nodeId.localeCompare(b2.nodeId)).map((node2, index2) => ({ priority: index2 + 1, nodeId: node2.nodeId, nodeState: node2.nodeState, action: todoAction(node2) }));
225915
225996
  const progress = Object.fromEntries([
225916
- "waiting",
225917
- "ready",
225918
225997
  "running",
225919
225998
  "stalled",
225920
225999
  "succeeded",
@@ -225922,8 +226001,7 @@ var init_service8 = __esm({
225922
226001
  "blocked",
225923
226002
  "failed"
225924
226003
  ].map((state) => [state, nodes.filter((node2) => node2.nodeState === state).length]));
225925
- const collaborationStatus = nodes.length > 0 && nodes.every((node2) => DONE_STATES.has(node2.nodeState)) ? "completed" : nodes.some((node2) => BLOCKING_STATES.has(node2.nodeState)) ? "needs_coordination" : "in_progress";
225926
- return { workOrderId, mode: this.mode, collaborationStatus, progress, nodes, dependencyBlocks, todo: todos };
226004
+ return { workOrderId, mode: this.mode, progress, nodes };
225927
226005
  }
225928
226006
  };
225929
226007
  }
@@ -226242,9 +226320,11 @@ function nodeCoordinatorFactsFromSnapshot(snapshot) {
226242
226320
  };
226243
226321
  const accepted = node2.latestAcceptId ? workById.get(node2.latestAcceptId) : void 0;
226244
226322
  const conclusion = accepted?.conclusion?.trim() || snapshot.works.filter((work2) => work2.nodeId === node2.id && work2.status === "success" && work2.conclusion?.trim()).sort((a, b2) => a.createdAt.localeCompare(b2.createdAt)).at(-1)?.conclusion?.trim();
226323
+ const sealed = node2.cancelledAt ? { reason: node2.sealReason ?? "cancelled", at: node2.cancelledAt } : null;
226245
226324
  out[node2.id] = {
226246
226325
  work,
226247
- ...conclusion ? { conclusion } : {}
226326
+ ...conclusion ? { conclusion } : {},
226327
+ sealed
226248
226328
  };
226249
226329
  }
226250
226330
  return out;
@@ -226660,6 +226740,33 @@ function mapChatItemToSnapshotEntry(row) {
226660
226740
  payload: row.payload
226661
226741
  };
226662
226742
  }
226743
+ function synthesizeMessageItem(msg, seq) {
226744
+ const attachments = Array.isArray(msg.attachments) ? msg.attachments.map((a) => ({
226745
+ name: a.name,
226746
+ ...a.blobRef !== void 0 ? { blobRef: a.blobRef } : {},
226747
+ ...a.contentType !== void 0 ? { contentType: a.contentType } : {}
226748
+ })) : void 0;
226749
+ const clientSubmitId = typeof msg.clientSubmitId === "string" ? msg.clientSubmitId : void 0;
226750
+ const role = msg.role === "user" || msg.role === "system" ? msg.role : "assistant";
226751
+ const status = msg.status === "running" ? "streaming" : msg.status === "error" ? "failed" : "completed";
226752
+ return {
226753
+ itemId: `chat-message:${msg.id}`,
226754
+ turnId: msg.runId ?? `chat-message-turn:${msg.id}`,
226755
+ itemType: "message",
226756
+ role,
226757
+ status,
226758
+ itemVersion: 0,
226759
+ seq,
226760
+ messageId: msg.id,
226761
+ updatedAt: msg.completedAt ?? msg.createdAt,
226762
+ payload: {
226763
+ role,
226764
+ text: msg.content ?? "",
226765
+ ...attachments?.length ? { attachments } : {},
226766
+ ...clientSubmitId ? { clientSubmitId } : {}
226767
+ }
226768
+ };
226769
+ }
226663
226770
  function throwWorkdirError(code2) {
226664
226771
  switch (code2) {
226665
226772
  case "PATH_ESCAPE":
@@ -226929,6 +227036,24 @@ function createChatSessionsDomain(opts) {
226929
227036
  }
226930
227037
  const snapshotEntries = [];
226931
227038
  let nextSinceVersion = sinceVersion;
227039
+ const HISTORY_TURNS = 12;
227040
+ const historicalEntries = [];
227041
+ if (sinceVersion === 0) {
227042
+ const listRecentTurns2 = store2.listRecentTurns;
227043
+ const messages = listRecentTurns2 ? (await listRecentTurns2.call(store2, chatSessionId, HISTORY_TURNS).catch(() => ({ messages: [], hasMoreBefore: false }))).messages : windowMessagesByTurns(await store2.listMessages(chatSessionId).catch(() => []), HISTORY_TURNS).messages;
227044
+ const coveredMessageIds = /* @__PURE__ */ new Set();
227045
+ for (const msg of messages) {
227046
+ if (!msg.id) continue;
227047
+ const rows = await items.listItemsByMessage(msg.id).catch(() => []);
227048
+ if (rows.length > 0) coveredMessageIds.add(msg.id);
227049
+ }
227050
+ let syntheticSeq = 0;
227051
+ for (const msg of messages) {
227052
+ if (!msg.id || coveredMessageIds.has(msg.id)) continue;
227053
+ syntheticSeq += 1;
227054
+ historicalEntries.push(synthesizeMessageItem(msg, syntheticSeq));
227055
+ }
227056
+ }
226932
227057
  if (turnId) {
226933
227058
  const rows = await items.listItemsByTurn(chatSessionId, turnId, { sinceTurnVersion: sinceVersion }).catch(() => []);
226934
227059
  for (const row of rows) {
@@ -226939,7 +227064,8 @@ function createChatSessionsDomain(opts) {
226939
227064
  const body2 = {
226940
227065
  chatSessionId,
226941
227066
  turnId,
226942
- items: snapshotEntries,
227067
+ // 历史合成条在前,active turn 的 chat_items 在后——顺序即桶排序,桶按 seq 单调排列。
227068
+ items: [...historicalEntries, ...snapshotEntries],
226943
227069
  nextSinceVersion,
226944
227070
  frameCursor: cursor ? { epoch: cursor.epoch, seq: cursor.seq } : { epoch: 0, seq: 0 },
226945
227071
  canAppend: cursor?.canAppend ?? false
@@ -227047,7 +227173,11 @@ function createChatSessionsDomain(opts) {
227047
227173
  };
227048
227174
  const wkKernel = opts.kernel;
227049
227175
  const allSummaries = buildWorkorderSummaries(wkKernel.model, resolver2, (wo) => byWo.get(wo) ?? null, (wo) => held.has(wo), void 0, void 0, (role) => wkKernel.actorForRole(role));
227050
- const items = allSummaries.filter((s2) => workOrderIds.includes(s2.id));
227176
+ const summaryById = new Map(allSummaries.map((s2) => [s2.id, s2]));
227177
+ const items = [...workOrderIds].reverse().flatMap((id) => {
227178
+ const s2 = summaryById.get(id);
227179
+ return s2 ? [s2] : [];
227180
+ });
227051
227181
  return { status: 200, body: { items } };
227052
227182
  });
227053
227183
  router.get("/api/chat-broadcasts/pending", async (req) => {
@@ -230065,7 +230195,7 @@ function automationsDomain(opts) {
230065
230195
  }
230066
230196
  return out;
230067
230197
  };
230068
- const summarize3 = async (automation, req) => {
230198
+ const summarize4 = async (automation, req) => {
230069
230199
  const [triggers, recent, canWrite] = await Promise.all([
230070
230200
  service.listTriggers(automation.id),
230071
230201
  service.listRuns(automation.id, { limit: RECENT_LIMIT2 }),
@@ -230158,13 +230288,13 @@ function automationsDomain(opts) {
230158
230288
  router.get("/api/automations", async (req) => {
230159
230289
  const includeArchived = req.query.get("includeArchived") === "1";
230160
230290
  const automations = await service.listAutomations(companyOf(req), { includeArchived });
230161
- const items = await Promise.all(automations.map((a) => summarize3(a, req)));
230291
+ const items = await Promise.all(automations.map((a) => summarize4(a, req)));
230162
230292
  const body2 = { items };
230163
230293
  return { status: 200, body: body2 };
230164
230294
  });
230165
230295
  router.get("/api/automations/:id", async (req) => {
230166
230296
  const automation = await mustGet(req);
230167
- return { status: 200, body: await summarize3(automation, req) };
230297
+ return { status: 200, body: await summarize4(automation, req) };
230168
230298
  });
230169
230299
  router.get("/api/automations/:id/runs", async (req) => {
230170
230300
  const automation = await mustGet(req);
@@ -230244,7 +230374,7 @@ function automationsDomain(opts) {
230244
230374
  ...triggers !== void 0 ? { triggers } : {}
230245
230375
  });
230246
230376
  const withTarget = createTargetSession ? await service.createChatTargetSession(created, req.auth.actor) : created;
230247
- return { status: 201, body: await summarize3(withTarget, req) };
230377
+ return { status: 201, body: await summarize4(withTarget, req) };
230248
230378
  });
230249
230379
  router.patch("/api/automations/:id", async (req) => {
230250
230380
  const automation = await mustGet(req);
@@ -230291,7 +230421,7 @@ function automationsDomain(opts) {
230291
230421
  },
230292
230422
  req.auth.actor
230293
230423
  );
230294
- return { status: 200, body: await summarize3(updated, req) };
230424
+ return { status: 200, body: await summarize4(updated, req) };
230295
230425
  });
230296
230426
  router.post("/api/automations/:id/chat-target-session", async (req) => {
230297
230427
  const automation = await mustGet(req);
@@ -230305,7 +230435,7 @@ function automationsDomain(opts) {
230305
230435
  if (!owner) throw new ApiError(400, "CHAT_TARGET_NO_OWNER", "\u89E3\u6790\u4E0D\u5230\u4EBA\u7C7B\u5F52\u5C5E\u8005\uFF0C\u65E0\u6CD5\u5EFA\u957F\u671F\u76EE\u6807\u4F1A\u8BDD");
230306
230436
  const withOwner = automation.ownerHumanActorId ? automation : await service.updateAutomation(automation.id, { ownerHumanActorId: owner }, req.auth.actor);
230307
230437
  const updated = await service.createChatTargetSession(withOwner, req.auth.actor);
230308
- return { status: 201, body: await summarize3(updated, req) };
230438
+ return { status: 201, body: await summarize4(updated, req) };
230309
230439
  });
230310
230440
  router.post("/api/automations/:id/enabled", async (req) => {
230311
230441
  const automation = await mustGet(req);
@@ -230313,7 +230443,7 @@ function automationsDomain(opts) {
230313
230443
  const b2 = req.body ?? {};
230314
230444
  if (typeof b2.enabled !== "boolean") throw new ApiError(400, "BAD_REQUEST", "\u7F3A\u5C11 enabled\uFF08boolean\uFF09");
230315
230445
  const updated = await service.setEnabled(automation.id, b2.enabled, req.auth.actor);
230316
- return { status: 200, body: await summarize3(updated, req) };
230446
+ return { status: 200, body: await summarize4(updated, req) };
230317
230447
  });
230318
230448
  router.post("/api/automations/:id/run-now", async (req) => {
230319
230449
  const automation = await mustGet(req);
@@ -232609,10 +232739,11 @@ ${ctx.nodeFault}
232609
232739
  ``,
232610
232740
  `## \u5B8C\u6574\u5DE5\u5355\u4E0A\u4E0B\u6587\u4E0E\u5168\u5C40\u6267\u884C\u72B6\u6001\uFF08\u5148\u8BFB \`execution/COORDINATOR_VIEW.md\`\uFF09`,
232611
232741
  `\u90A3\u4EFD\u6587\u4EF6\u5148\u7ED9\u51FA\u6743\u5A01\u4EFB\u52A1\u80CC\u666F\uFF08\u5B8C\u6574 brief \u6B63\u6587\u3001\u76EE\u6807\u3001\u9A8C\u6536\u6807\u51C6\u3001\u8303\u56F4/\u8303\u56F4\u5916\u4E8B\u9879\u3001\u7EA6\u675F\u548C\u7248\u672C\u6765\u6E90\uFF09\uFF0C`,
232612
- `\u518D\u7ED9\u51FA\u672C\u5DE5\u5355**\u6240\u6709\u8282\u70B9**\u7684\u4E00\u6B21\u6027\u5FEB\u7167\uFF1A\u5404\u8282\u70B9\u72B6\u6001\u3001\u5361\u5728\u8C01\u8EAB\u4E0A\u3001\u5DF2\u4EA4\u4ED8\u7684\u8282\u70B9\u4EA4\u4E86\u4EC0\u4E48`,
232613
- `\uFF08Conclusion = \u5DF2\u4EA4\u4ED8\u7248\u672C\u7684\u4E1A\u52A1\u5B8C\u6210\u6458\u8981\uFF09\u3001\u52A8\u4E86\u54EA\u4E9B\u4EA7\u7269\u4E0E\u7248\u672C\u3001\u4EE5\u53CA\u975E\u963B\u585E\u5F00\u653E\u9879\u3002`,
232742
+ `\u518D\u7ED9\u51FA\u672C\u5DE5\u5355**\u6240\u6709\u8282\u70B9**\u7684\u4E00\u6B21\u6027\u4E8B\u5B9E\u5FEB\u7167\uFF1A\u6BCF\u4E2A\u8282\u70B9\u7684 Work \u72B6\u6001\u4E0E\u4EA4\u4ED8\u7248\u672C\u3001\u6700\u8FD1\u4E00\u8D9F Attempt`,
232743
+ `\u8DD1\u6210\u4EC0\u4E48\u6837\u3001\u8282\u70B9\u662F\u5426\u5DF2\u79BB\u573A\uFF08sealed\uFF09\u3001\u5DF2\u4EA4\u4ED8\u7684\u8282\u70B9\u4EA4\u4E86\u4EC0\u4E48\uFF08Conclusion = \u5DF2\u4EA4\u4ED8\u7248\u672C\u7684\u4E1A\u52A1`,
232744
+ `\u5B8C\u6210\u6458\u8981\uFF09\u3001\u52A8\u4E86\u54EA\u4E9B\u4EA7\u7269\u4E0E\u7248\u672C\uFF0C\u4EE5\u53CA\u5F00\u653E\u9879\u3002`,
232614
232745
  `\u82E5\u6587\u4EF6\u660E\u786E\u5199\u7740\u67D0\u9879\u672A\u63D0\u4F9B\u6216\u6B63\u6587\u4E0D\u53EF\u8BFB\uFF0C\u4E0D\u8981\u81EA\u884C\u8865\u9020\uFF1B\u7248\u672C\u4E0D\u4E00\u81F4\u65F6\u4FDD\u7559\u5DEE\u5F02\u5E76\u5411\u4EBA\u8BF4\u660E\u3002`,
232615
- `\u5224\u300C\u771F\u6B7B\u9501\u8FD8\u662F\u5065\u5EB7\u7B49\u5F85\u300D\u4E4B\u524D\u5148\u770B\u5B83\u2014\u2014\u53EA\u770B\u672C\u8282\u70B9\u90BB\u57DF\u5BB9\u6613\u628A\u300C\u4E0A\u6E38\u6B63\u5E38\u5728\u4EA7\u300D\u8BEF\u5224\u6210\u5361\u4F4F\u3002`,
232746
+ `\u5B83\u53EA\u9648\u8FF0\u4E8B\u5B9E\uFF0C\u4E0D\u542B\u7CFB\u7EDF\u7ED9\u51FA\u7684\u4F18\u5148\u7EA7\u3001\u5F85\u529E\u6216\u5904\u7F6E\u5EFA\u8BAE\u2014\u2014\u600E\u4E48\u5904\u7F6E\u7531\u4F60\u5224\u65AD\u3002`,
232616
232747
  `Technical notes \u53EA\u8865\u5145 Agent \u7684\u6280\u672F\u4EA4\u63A5\uFF1BExecution diagnostic \u53EA\u8BF4\u660E\u4F1A\u8BDD\u600E\u4E48\u7ED3\u675F\uFF0C\u4E8C\u8005\u90FD\u4E0D\u66FF\u4EE3 Conclusion\u3002`,
232617
232748
  ``
232618
232749
  ].join("\n") : task;
@@ -243954,6 +244085,9 @@ var init_company_owned_registry_store = __esm({
243954
244085
  });
243955
244086
 
243956
244087
  // ../storage/src/tenant-cutover.ts
244088
+ function maxGate(a, b2) {
244089
+ return GATE_RANK[a] >= GATE_RANK[b2] ? a : b2;
244090
+ }
243957
244091
  function rowToCutover(row) {
243958
244092
  return {
243959
244093
  companyId: row.company_id,
@@ -243966,7 +244100,7 @@ function rowToCutover(row) {
243966
244100
  updatedAt: new Date(row.updated_at).toISOString()
243967
244101
  };
243968
244102
  }
243969
- var COMPANY_DATA_BUNDLES, LEGAL, CutoverIllegalTransitionError, CutoverGateOpenRollbackError, TenantCutoverStore;
244103
+ var COMPANY_DATA_BUNDLES, LEGAL, GATE_RANK, CutoverIllegalTransitionError, CutoverGateOpenRollbackError, TenantCutoverStore;
243970
244104
  var init_tenant_cutover = __esm({
243971
244105
  "../storage/src/tenant-cutover.ts"() {
243972
244106
  "use strict";
@@ -243989,6 +244123,12 @@ var init_tenant_cutover = __esm({
243989
244123
  frozen: ["company-primary", "legacy"],
243990
244124
  "company-primary": ["legacy"]
243991
244125
  };
244126
+ GATE_RANK = {
244127
+ "gate-closed": 0,
244128
+ "gate-open": 1,
244129
+ "contract-ready": 2,
244130
+ contracted: 3
244131
+ };
243992
244132
  CutoverIllegalTransitionError = class extends Error {
243993
244133
  constructor(from, to) {
243994
244134
  super(`\u975E\u6CD5\u5207\u6362 ${from} \u2192 ${to}`);
@@ -244090,19 +244230,37 @@ var init_tenant_cutover = __esm({
244090
244230
  }
244091
244231
  return rowToCutover(r.rows[0]);
244092
244232
  }
244233
+ /**
244234
+ * 该 bundle 的 **bundle-global** gate(ADR §7.1):取这个 bundle 所有公司行里到过的最高档。
244235
+ *
244236
+ * 为什么不能只看单行:`markBundlePrimary` 给新公司插的行硬编 `gate-closed`,于是
244237
+ * 「gate 已经全局打开、之后新建的公司」那一行是 gate-closed —— 拿它判回滚,就会
244238
+ * **放行一次本该被永久禁止的回滚**(2026-09-04 生产实测:21 家 09-03 之后建的公司
244239
+ * 在 skills 已 gate-open 的情况下,行上仍是 gate-closed)。
244240
+ */
244241
+ async bundleGate(bundle) {
244242
+ const r = await this.pool.query(
244243
+ `SELECT DISTINCT gate FROM ${this.s}.tenant_storage_cutover WHERE bundle=$1`,
244244
+ [bundle]
244245
+ );
244246
+ let g2 = "gate-closed";
244247
+ for (const row of r.rows) g2 = maxGate(g2, row.gate);
244248
+ return g2;
244249
+ }
244093
244250
  async casTransition(companyId, bundle, expectedEpoch, to, opts) {
244094
244251
  const cur = await this.get(companyId, bundle);
244095
244252
  if (cur.epoch !== expectedEpoch) {
244096
244253
  throw new Error(`cutover epoch \u4E0D\u5339\u914D\uFF1A\u671F\u671B ${expectedEpoch} \u5B9E\u9645 ${cur.epoch}`);
244097
244254
  }
244098
- if (to === "legacy" && (cur.gate === "gate-open" || cur.gate === "contract-ready" || cur.gate === "contracted")) {
244255
+ const globalGate = maxGate(cur.gate, await this.bundleGate(bundle));
244256
+ if (to === "legacy" && globalGate !== "gate-closed") {
244099
244257
  throw new CutoverGateOpenRollbackError();
244100
244258
  }
244101
244259
  if (cur.state !== to && !LEGAL[cur.state].includes(to)) {
244102
244260
  throw new CutoverIllegalTransitionError(cur.state, to);
244103
244261
  }
244104
244262
  const nextEpoch = cur.epoch + 1;
244105
- const gate = opts.gate ?? cur.gate;
244263
+ const gate = maxGate(opts.gate ?? cur.gate, globalGate);
244106
244264
  const now = (/* @__PURE__ */ new Date()).toISOString();
244107
244265
  const updated = await this.pool.query(
244108
244266
  `INSERT INTO ${this.s}.tenant_storage_cutover
@@ -244125,28 +244283,44 @@ var init_tenant_cutover = __esm({
244125
244283
  * 与 markNewCompanyPrimary 的区别:那个是整包切,只对全新空公司成立;这个按 bundle 逐个来。
244126
244284
  */
244127
244285
  async markBundlePrimary(companyId, bundle, operator) {
244286
+ const gate = await this.bundleGate(bundle);
244128
244287
  const r = await this.pool.query(
244129
244288
  `INSERT INTO ${this.s}.tenant_storage_cutover
244130
244289
  (company_id, bundle, state, epoch, gate, manifest_generation, journal_hwm, updated_at)
244131
- VALUES ($1,$2,'company-primary',1,'gate-closed',0,0,$3)
244290
+ VALUES ($1,$2,'company-primary',1,$4,0,0,$3)
244132
244291
  ON CONFLICT (company_id, bundle) DO NOTHING`,
244133
- [companyId, bundle, (/* @__PURE__ */ new Date()).toISOString()]
244292
+ [companyId, bundle, (/* @__PURE__ */ new Date()).toISOString(), gate]
244134
244293
  );
244135
244294
  return (r.rowCount ?? 0) > 0;
244136
244295
  }
244137
- /** 新公司:所有 4c bundle 直接 company-primary */
244296
+ /** 新公司:所有 4c bundle 直接 company-primary(各自继承该 bundle 的全局 gate)。 */
244138
244297
  async markNewCompanyPrimary(companyId, operator) {
244139
244298
  for (const bundle of COMPANY_DATA_BUNDLES) {
244140
244299
  const now = (/* @__PURE__ */ new Date()).toISOString();
244300
+ const gate = await this.bundleGate(bundle);
244141
244301
  await this.pool.query(
244142
244302
  `INSERT INTO ${this.s}.tenant_storage_cutover
244143
244303
  (company_id, bundle, state, epoch, gate, manifest_generation, journal_hwm, updated_at)
244144
- VALUES ($1,$2,'company-primary',1,'gate-closed',0,0,$3)
244304
+ VALUES ($1,$2,'company-primary',1,$4,0,0,$3)
244145
244305
  ON CONFLICT (company_id, bundle) DO NOTHING`,
244146
- [companyId, bundle, now]
244306
+ [companyId, bundle, now, gate]
244147
244307
  );
244148
244308
  }
244149
244309
  }
244310
+ /**
244311
+ * 把该 bundle 里落后于全局档的 gate 列一次性归一(修存量的 gate-closed 残行)。
244312
+ * 只推进、绝不回退;返回被改的行数。
244313
+ */
244314
+ async normalizeBundleGate(bundle) {
244315
+ const gate = await this.bundleGate(bundle);
244316
+ if (gate === "gate-closed") return 0;
244317
+ const r = await this.pool.query(
244318
+ `UPDATE ${this.s}.tenant_storage_cutover SET gate=$2, updated_at=$3
244319
+ WHERE bundle=$1 AND gate <> $2`,
244320
+ [bundle, gate, (/* @__PURE__ */ new Date()).toISOString()]
244321
+ );
244322
+ return r.rowCount ?? 0;
244323
+ }
244150
244324
  readsCompanySchema(row) {
244151
244325
  return row.state === "company-primary";
244152
244326
  }
@@ -245147,6 +245321,350 @@ var init_tenant_migration_operator = __esm({
245147
245321
  }
245148
245322
  });
245149
245323
 
245324
+ // ../storage/src/tenant-new-company-provision.ts
245325
+ async function provisionNewCompanyDataPlane(input) {
245326
+ const legacySchema = input.legacySchema ?? "public";
245327
+ const controlSchema = input.controlSchema ?? "public";
245328
+ const operator = input.operator ?? "serve-provision";
245329
+ const { pool, companyId, cutover } = input;
245330
+ const loc = await input.locations.provision(companyId);
245331
+ await provisionCompanyDataPlaneSchema(pool, loc.schemaName);
245332
+ const report = {
245333
+ companyId,
245334
+ schemaName: loc.schemaName,
245335
+ provisioned: [],
245336
+ alreadyPrimary: [],
245337
+ skipped: []
245338
+ };
245339
+ const eligible = async (b2) => {
245340
+ const row = await cutover.get(companyId, b2);
245341
+ if (row.state === "company-primary") {
245342
+ report.alreadyPrimary.push(b2);
245343
+ return false;
245344
+ }
245345
+ if (row.epoch !== 0) {
245346
+ report.skipped.push({ bundle: b2, reason: `epoch=${row.epoch}\uFF08\u6709\u4EBA\u663E\u5F0F\u52A8\u8FC7\uFF0C\u81EA\u52A8\u8DEF\u5F84\u4E0D\u63A5\u7BA1\uFF09` });
245347
+ return false;
245348
+ }
245349
+ return true;
245350
+ };
245351
+ const identityTodo = [];
245352
+ for (const b2 of IDENTITY_BUNDLES) if (await eligible(b2)) identityTodo.push(b2);
245353
+ if (identityTodo.length > 0) {
245354
+ const owned = await CompanyOwnedRegistryStore.open(pool, loc.schemaName, companyId);
245355
+ await runIdentityMigration({
245356
+ pool,
245357
+ target: owned,
245358
+ roster: await input.roster(),
245359
+ manifestId: `provision-i-${companyId}-${Date.now()}`,
245360
+ legacySchema,
245361
+ controlSchema
245362
+ });
245363
+ for (const b2 of identityTodo) {
245364
+ await advanceCutover(cutover, companyId, b2, "company-primary", operator);
245365
+ report.provisioned.push(b2);
245366
+ }
245367
+ }
245368
+ if (await eligible("types")) {
245369
+ await copyArtifactTypesIdempotent(
245370
+ pool,
245371
+ companyId,
245372
+ loc.schemaName,
245373
+ `provision-t-${companyId}-${Date.now()}`,
245374
+ legacySchema,
245375
+ controlSchema
245376
+ );
245377
+ await advanceCutover(
245378
+ cutover,
245379
+ companyId,
245380
+ "types",
245381
+ "company-primary",
245382
+ operator,
245383
+ { pool, companySchema: loc.schemaName, legacySchema }
245384
+ );
245385
+ report.provisioned.push("types");
245386
+ }
245387
+ if (await eligible("org-registry")) {
245388
+ await copyOrgRegistryIdempotent(
245389
+ pool,
245390
+ companyId,
245391
+ loc.schemaName,
245392
+ `provision-o-${companyId}-${Date.now()}`,
245393
+ { takeLegacyRows: false, legacySchema, controlSchema }
245394
+ );
245395
+ await advanceCutover(cutover, companyId, "org-registry", "company-primary", operator);
245396
+ report.provisioned.push("org-registry");
245397
+ }
245398
+ if (await eligible("skills")) {
245399
+ const n = await input.legacySkillCount();
245400
+ if (n > 0) {
245401
+ report.skipped.push({ bundle: "skills", reason: `legacy \u4FA7\u6709 ${n} \u6761\u6280\u80FD\u5B58\u91CF\uFF0C\u9700\u8D70 operator \u5168\u94FE` });
245402
+ } else {
245403
+ const marked = await cutover.markBundlePrimary(companyId, "skills", operator);
245404
+ if (!marked) await advanceCutover(cutover, companyId, "skills", "company-primary", operator);
245405
+ report.provisioned.push("skills");
245406
+ }
245407
+ }
245408
+ const known = /* @__PURE__ */ new Set([
245409
+ ...report.provisioned,
245410
+ ...report.alreadyPrimary,
245411
+ ...report.skipped.map((s2) => s2.bundle)
245412
+ ]);
245413
+ for (const b2 of COMPANY_DATA_BUNDLES) {
245414
+ if (!known.has(b2)) report.skipped.push({ bundle: b2, reason: "\u672A\u88AB provisioning \u8986\u76D6\uFF08\u65B0\u589E bundle \u5FD8\u4E86\u63A5\u7EBF\uFF1F\uFF09" });
245415
+ }
245416
+ return report;
245417
+ }
245418
+ var IDENTITY_BUNDLES;
245419
+ var init_tenant_new_company_provision = __esm({
245420
+ "../storage/src/tenant-new-company-provision.ts"() {
245421
+ "use strict";
245422
+ init_esm();
245423
+ init_company_owned_registry_store();
245424
+ init_company_data_plane_schema();
245425
+ init_tenant_schema_mapping();
245426
+ init_tenant_cutover();
245427
+ init_tenant_migration_operator();
245428
+ IDENTITY_BUNDLES = ["actors", "actor-config", "assistant"];
245429
+ }
245430
+ });
245431
+
245432
+ // ../storage/src/tenancy-attribution.ts
245433
+ function realCompanyId(v2) {
245434
+ return typeof v2 === "string" && v2.trim().length > 0 ? v2 : null;
245435
+ }
245436
+ async function buildActorRosterIndex(pool, controlSchema = "public") {
245437
+ const schemas = await pool.query(`
245438
+ SELECT n.nspname FROM pg_namespace n JOIN pg_class c ON c.relnamespace = n.oid
245439
+ WHERE n.nspname LIKE 'c\\_%' AND c.relname = 'actors' AND c.relkind = 'r'`);
245440
+ const seen = /* @__PURE__ */ new Map();
245441
+ const schemaToCompany = /* @__PURE__ */ new Map();
245442
+ for (const r of (await pool.query(
245443
+ `SELECT company_id, schema_name FROM ${quoteIdent2(controlSchema)}.tenant_storage_locations`
245444
+ )).rows) {
245445
+ schemaToCompany.set(r.schema_name, r.company_id);
245446
+ }
245447
+ for (const row of schemas.rows) {
245448
+ const ns = row.nspname;
245449
+ const company = schemaToCompany.get(ns);
245450
+ if (!company) continue;
245451
+ const hasRoster = await pool.query(
245452
+ `SELECT 1 FROM information_schema.columns
245453
+ WHERE table_schema=$1 AND table_name='actors' AND column_name='roster'`,
245454
+ [ns]
245455
+ );
245456
+ const q = hasRoster.rowCount ? `SELECT id FROM ${quoteIdent2(ns)}.actors WHERE roster IS TRUE` : `SELECT id FROM ${quoteIdent2(ns)}.actors`;
245457
+ for (const a of (await pool.query(q)).rows) {
245458
+ const id = a.id;
245459
+ if (!seen.has(id)) seen.set(id, /* @__PURE__ */ new Set());
245460
+ seen.get(id).add(company);
245461
+ }
245462
+ }
245463
+ const out = /* @__PURE__ */ new Map();
245464
+ for (const [id, set] of seen) out.set(id, set.size === 1 ? [...set][0] : null);
245465
+ return out;
245466
+ }
245467
+ function tally(rows, table) {
245468
+ const byRung = {};
245469
+ const byCompany = {};
245470
+ for (const r of rows) {
245471
+ byRung[r.rung] = (byRung[r.rung] ?? 0) + 1;
245472
+ byCompany[r.companyId] = (byCompany[r.companyId] ?? 0) + 1;
245473
+ }
245474
+ return {
245475
+ table,
245476
+ total: rows.length,
245477
+ byRung,
245478
+ byCompany,
245479
+ fallback: byRung["fallback-default"] ?? 0,
245480
+ conflicts: rows.filter((r) => r.conflict).length
245481
+ };
245482
+ }
245483
+ async function attributeAgentRuns(o) {
245484
+ const ls = quoteIdent2(o.legacySchema ?? "public");
245485
+ const roster = await buildActorRosterIndex(o.pool, o.legacySchema ?? "public");
245486
+ const rows = await o.pool.query(`
245487
+ SELECT r.id, r.actor_id, w.company_id AS wo_company, (w.id IS NOT NULL) AS wo_in_legacy
245488
+ FROM ${ls}.agent_runs r
245489
+ LEFT JOIN ${ls}.workorders w ON w.id = r.work_order_id`);
245490
+ const out = [];
245491
+ for (const r of rows.rows) {
245492
+ const key = `agent_runs:${r.id}`;
245493
+ const wo = realCompanyId(r.wo_company);
245494
+ const byActor2 = roster.get(r.actor_id) ?? null;
245495
+ const conflict = wo !== null && byActor2 !== null && wo !== byActor2 ? { rung: "actor-roster", companyId: byActor2 } : void 0;
245496
+ if (wo !== null) {
245497
+ out.push({ key, companyId: wo, rung: "workorder", ...conflict ? { conflict } : {} });
245498
+ continue;
245499
+ }
245500
+ if (byActor2 !== null) {
245501
+ out.push({ key, companyId: byActor2, rung: "actor-roster" });
245502
+ continue;
245503
+ }
245504
+ if (r.wo_in_legacy) {
245505
+ out.push({ key, companyId: o.defaultCompanyId, rung: "schema-identity" });
245506
+ continue;
245507
+ }
245508
+ out.push({ key, companyId: o.defaultCompanyId, rung: "fallback-default" });
245509
+ }
245510
+ return out;
245511
+ }
245512
+ async function attributeByRun(o, table, joinColumn, keyColumns) {
245513
+ const ls = quoteIdent2(o.legacySchema ?? "public");
245514
+ const runs = /* @__PURE__ */ new Map();
245515
+ for (const r of await attributeAgentRuns(o)) runs.set(r.key.slice("agent_runs:".length), r.companyId);
245516
+ const keyExpr = keyColumns.map((c) => `COALESCE(${quoteIdent2(c)}::text,'\u2205')`).join(` || ':' || `);
245517
+ const rows = await o.pool.query(
245518
+ `SELECT ${keyExpr} AS k, ${quoteIdent2(joinColumn)}::text AS run FROM ${ls}.${quoteIdent2(table)}`
245519
+ );
245520
+ return rows.rows.map((r) => {
245521
+ const c = runs.get(r.run);
245522
+ return c ? { key: `${table}:${r.k}`, companyId: c, rung: "run" } : { key: `${table}:${r.k}`, companyId: o.defaultCompanyId, rung: "fallback-default" };
245523
+ });
245524
+ }
245525
+ async function attributeDispatches(o) {
245526
+ const ls = quoteIdent2(o.legacySchema ?? "public");
245527
+ const roster = await buildActorRosterIndex(o.pool, o.legacySchema ?? "public");
245528
+ const rows = await o.pool.query(`
245529
+ SELECT d.id, d.actor_id, w.company_id AS wo_company, (w.id IS NOT NULL) AS wo_in_legacy
245530
+ FROM ${ls}.dispatches d LEFT JOIN ${ls}.workorders w ON w.id = d.workorder_id`);
245531
+ return rows.rows.map((r) => {
245532
+ const key = `dispatches:${r.id}`;
245533
+ const wo = realCompanyId(r.wo_company);
245534
+ const a = roster.get(r.actor_id) ?? null;
245535
+ const conflict = wo !== null && a !== null && wo !== a ? { rung: "actor-roster", companyId: a } : void 0;
245536
+ if (wo !== null) return { key, companyId: wo, rung: "workorder", ...conflict ? { conflict } : {} };
245537
+ if (a !== null) return { key, companyId: a, rung: "actor-roster" };
245538
+ if (r.wo_in_legacy) return { key, companyId: o.defaultCompanyId, rung: "schema-identity" };
245539
+ return { key, companyId: o.defaultCompanyId, rung: "fallback-default" };
245540
+ });
245541
+ }
245542
+ async function attributeWorkorderDrafts(o) {
245543
+ const ls = quoteIdent2(o.legacySchema ?? "public");
245544
+ const rows = await o.pool.query(`
245545
+ SELECT d.id, w.company_id, (w.id IS NOT NULL) AS wo_in_legacy FROM ${ls}.workorder_drafts d
245546
+ LEFT JOIN ${ls}.workorders w ON w.id = d.workspace`);
245547
+ return rows.rows.map((r) => {
245548
+ const key = `workorder_drafts:${r.id}`;
245549
+ const wo = realCompanyId(r.company_id);
245550
+ if (wo !== null) return { key, companyId: wo, rung: "workorder" };
245551
+ if (r.wo_in_legacy) return { key, companyId: o.defaultCompanyId, rung: "schema-identity" };
245552
+ return { key, companyId: o.defaultCompanyId, rung: "fallback-default" };
245553
+ });
245554
+ }
245555
+ async function attributeInboxReadMarkers(o) {
245556
+ const ls = quoteIdent2(o.legacySchema ?? "public");
245557
+ const roster = await buildActorRosterIndex(o.pool, o.legacySchema ?? "public");
245558
+ const rows = await o.pool.query(`
245559
+ SELECT i.actor_id, i.scope, s.company_id AS chat_company
245560
+ FROM ${ls}.inbox_read_markers i
245561
+ LEFT JOIN ${ls}.chat_sessions s
245562
+ ON i.scope LIKE 'chat:%' AND s.id = substring(i.scope from 6)`);
245563
+ return rows.rows.map((r) => {
245564
+ const key = `inbox_read_markers:${r.actor_id}:${r.scope}`;
245565
+ const chat = realCompanyId(r.chat_company);
245566
+ if (chat !== null) return { key, companyId: chat, rung: "chat-session" };
245567
+ const m2 = /(?:^|:)(company:[^:]+)$/.exec(String(r.scope));
245568
+ if (m2) return { key, companyId: m2[1], rung: "scope-literal" };
245569
+ const a = roster.get(r.actor_id);
245570
+ if (a) return { key, companyId: a, rung: "actor-roster" };
245571
+ return { key, companyId: o.defaultCompanyId, rung: "fallback-default" };
245572
+ });
245573
+ }
245574
+ async function attributeActorMemories(o) {
245575
+ const ls = quoteIdent2(o.legacySchema ?? "public");
245576
+ const roster = await buildActorRosterIndex(o.pool, o.legacySchema ?? "public");
245577
+ const rows = await o.pool.query(`SELECT mem_id, actor_id FROM ${ls}.actor_memories`);
245578
+ return rows.rows.map((r) => {
245579
+ const a = roster.get(r.actor_id);
245580
+ return a ? { key: `actor_memories:${r.mem_id}`, companyId: a, rung: "actor-roster" } : { key: `actor_memories:${r.mem_id}`, companyId: o.defaultCompanyId, rung: "fallback-default" };
245581
+ });
245582
+ }
245583
+ async function attributeBlobs(o) {
245584
+ const ls = quoteIdent2(o.legacySchema ?? "public");
245585
+ const refs = /* @__PURE__ */ new Map();
245586
+ const add = (hash2, company) => {
245587
+ if (!refs.has(hash2)) refs.set(hash2, /* @__PURE__ */ new Set());
245588
+ refs.get(hash2).add(company);
245589
+ };
245590
+ const schemas = await o.pool.query(`
245591
+ SELECT n.nspname FROM pg_namespace n JOIN pg_class c ON c.relnamespace = n.oid
245592
+ WHERE n.nspname LIKE 'c\\_%' AND c.relname = 'skill_files' AND c.relkind='r'`);
245593
+ const schemaToCompany = /* @__PURE__ */ new Map();
245594
+ for (const r of (await o.pool.query(
245595
+ `SELECT company_id, schema_name FROM ${ls}.tenant_storage_locations`
245596
+ )).rows) {
245597
+ schemaToCompany.set(r.schema_name, r.company_id);
245598
+ }
245599
+ for (const s2 of schemas.rows) {
245600
+ const company = schemaToCompany.get(s2.nspname);
245601
+ if (!company) continue;
245602
+ for (const f2 of (await o.pool.query(
245603
+ `SELECT DISTINCT blob_hash FROM ${quoteIdent2(s2.nspname)}.skill_files WHERE blob_hash IS NOT NULL`
245604
+ )).rows) {
245605
+ add(f2.blob_hash, company);
245606
+ }
245607
+ }
245608
+ const legacyRefs = [
245609
+ { sql: `SELECT f.blob_id AS h, w.company_id AS c FROM ${ls}.work_order_files f
245610
+ JOIN ${ls}.workorders w ON w.id = f.work_order_id WHERE f.blob_id IS NOT NULL` },
245611
+ { sql: `SELECT e.blob_ref AS h, w.company_id AS c FROM ${ls}.agent_run_events e
245612
+ JOIN ${ls}.agent_runs r ON r.id = e.run_id
245613
+ JOIN ${ls}.workorders w ON w.id = r.work_order_id WHERE e.blob_ref IS NOT NULL` }
245614
+ ];
245615
+ for (const q of legacyRefs) {
245616
+ let res;
245617
+ try {
245618
+ res = await o.pool.query(q.sql);
245619
+ } catch {
245620
+ continue;
245621
+ }
245622
+ for (const r of res.rows) if (r.h && r.c) add(r.h, r.c);
245623
+ }
245624
+ const all2 = await o.pool.query(`SELECT hash FROM ${ls}.blobs`);
245625
+ const out = [];
245626
+ for (const b2 of all2.rows) {
245627
+ const hash2 = b2.hash;
245628
+ const owners = refs.get(hash2);
245629
+ if (!owners || owners.size === 0) {
245630
+ out.push({ key: `blobs:${hash2}`, companyId: o.defaultCompanyId, rung: "fallback-default" });
245631
+ continue;
245632
+ }
245633
+ for (const c of owners) out.push({ key: `blobs:${hash2}@${c}`, companyId: c, rung: "referrer" });
245634
+ }
245635
+ return out;
245636
+ }
245637
+ async function inventoryZeroTenantTables(o) {
245638
+ const reports = [];
245639
+ reports.push(tally(await attributeAgentRuns(o), "agent_runs"));
245640
+ const traceFollowers = [
245641
+ ["agent_run_events", "run_id", ["run_id", "seq"]],
245642
+ ["agent_tool_calls", "run_id", ["id"]],
245643
+ ["agent_run_links", "run_id", ["run_id", "ref_type", "ref_id", "relation"]]
245644
+ ];
245645
+ for (const [t, j, k2] of traceFollowers) reports.push(tally(await attributeByRun(o, t, j, k2), t));
245646
+ for (const [t, j, k2] of [
245647
+ ["execution_checkpoints", "attempt_id", ["checkpoint_id"]],
245648
+ ["node_handoffs", "attempt_id", ["handoff_id"]],
245649
+ ["external_effects", "attempt_id", ["node_id", "attempt_id", "kind", "idempotency_key"]]
245650
+ ]) {
245651
+ reports.push(tally(await attributeByRun(o, t, j, k2), t));
245652
+ }
245653
+ reports.push(tally(await attributeDispatches(o), "dispatches"));
245654
+ reports.push(tally(await attributeWorkorderDrafts(o), "workorder_drafts"));
245655
+ reports.push(tally(await attributeInboxReadMarkers(o), "inbox_read_markers"));
245656
+ reports.push(tally(await attributeActorMemories(o), "actor_memories"));
245657
+ reports.push(tally(await attributeBlobs(o), "blobs"));
245658
+ return reports;
245659
+ }
245660
+ var init_tenancy_attribution = __esm({
245661
+ "../storage/src/tenancy-attribution.ts"() {
245662
+ "use strict";
245663
+ init_esm();
245664
+ init_pg_ident();
245665
+ }
245666
+ });
245667
+
245150
245668
  // ../storage/src/knowledge-snapshot-crypto.ts
245151
245669
  function sealSnapshot(snapshot, key) {
245152
245670
  if (snapshot.content.startsWith(PREFIX3)) return structuredClone(snapshot);
@@ -245812,8 +246330,16 @@ __export(src_exports, {
245812
246330
  advanceCutover: () => advanceCutover,
245813
246331
  assertInstallersPresent: () => assertInstallersPresent,
245814
246332
  assertTypeCatalogCovered: () => assertTypeCatalogCovered,
246333
+ attributeActorMemories: () => attributeActorMemories,
246334
+ attributeAgentRuns: () => attributeAgentRuns,
246335
+ attributeBlobs: () => attributeBlobs,
246336
+ attributeByRun: () => attributeByRun,
246337
+ attributeDispatches: () => attributeDispatches,
246338
+ attributeInboxReadMarkers: () => attributeInboxReadMarkers,
246339
+ attributeWorkorderDrafts: () => attributeWorkorderDrafts,
245815
246340
  backfillTenancyColumns: () => backfillTenancyColumns,
245816
246341
  backfillTypeRegistryFromFile: () => backfillTypeRegistryFromFile,
246342
+ buildActorRosterIndex: () => buildActorRosterIndex,
245817
246343
  buildTrigger: () => buildTrigger,
245818
246344
  canonicalTypeDefHash: () => canonicalTypeDefHash,
245819
246345
  companySchemaName: () => companySchemaName,
@@ -245833,12 +246359,15 @@ __export(src_exports, {
245833
246359
  inventoryActorsByIds: () => inventoryActorsByIds,
245834
246360
  inventoryPublicSkills: () => inventoryPublicSkills,
245835
246361
  inventoryTenancy: () => inventoryTenancy,
246362
+ inventoryZeroTenantTables: () => inventoryZeroTenantTables,
245836
246363
  isUniqueViolation: () => isUniqueViolation2,
245837
246364
  listTenancyCompanies: () => listTenancyCompanies,
246365
+ maxGate: () => maxGate,
245838
246366
  missingTypeNames: () => missingTypeNames,
245839
246367
  moveTenancyRows: () => moveTenancyRows,
245840
246368
  payloadChecksum: () => payloadChecksum,
245841
246369
  provisionCompanyDataPlaneSchema: () => provisionCompanyDataPlaneSchema,
246370
+ provisionNewCompanyDataPlane: () => provisionNewCompanyDataPlane,
245842
246371
  quoteIdent: () => quoteIdent2,
245843
246372
  renderTenancyInventoryMarkdown: () => renderTenancyInventoryMarkdown,
245844
246373
  renderTenancyMoveReport: () => renderTenancyMoveReport,
@@ -245883,6 +246412,8 @@ var init_src11 = __esm({
245883
246412
  init_cutover_aware_registry();
245884
246413
  init_tenant_cutover();
245885
246414
  init_tenant_migration_operator();
246415
+ init_tenant_new_company_provision();
246416
+ init_tenancy_attribution();
245886
246417
  init_postgres_knowledge();
245887
246418
  }
245888
246419
  });
@@ -248199,12 +248730,25 @@ async function startServe(opts) {
248199
248730
  companyId,
248200
248731
  ownedOrgRegistry
248201
248732
  });
248202
- const existing = await cutoverStore.get(companyId, "skills");
248203
- if (companyId !== defaultCompanyId && existing.state === "legacy" && existing.epoch === 0) {
248204
- const legacySkills = await scopedRegistryFor(companyId).listInstalledSkills();
248205
- if (legacySkills.length === 0) {
248206
- const marked = await cutoverStore.markBundlePrimary(companyId, "skills", "serve-provision");
248207
- if (marked) console.log(`[tenancy] ${companyId} \u7684 skills \u65E0 legacy \u5B58\u91CF\uFF0C\u5DF2\u76F4\u63A5\u7F6E\u4E3A company-primary`);
248733
+ if (companyId !== defaultCompanyId) {
248734
+ try {
248735
+ const scoped = scopedRegistryFor(companyId);
248736
+ const rep = await provisionNewCompanyDataPlane({
248737
+ pool: pgPool,
248738
+ companyId,
248739
+ cutover: cutoverStore,
248740
+ locations: locationStore,
248741
+ roster: () => scoped.listActors(),
248742
+ legacySkillCount: async () => (await scoped.listInstalledSkills()).length,
248743
+ legacySchema: pgSchema,
248744
+ controlSchema: pgSchema,
248745
+ operator: "serve-provision"
248746
+ });
248747
+ if (rep.provisioned.length > 0) {
248748
+ console.log(`[tenancy] ${companyId} provisioning\uFF1A\u5DF2\u7F6E company-primary=${rep.provisioned.join(",")}` + (rep.skipped.length > 0 ? ` \u8DF3\u8FC7=${rep.skipped.map((x2) => `${x2.bundle}(${x2.reason})`).join("; ")}` : ""));
248749
+ }
248750
+ } catch (e) {
248751
+ console.error(`[tenancy] ${companyId} provisioning \u5931\u8D25\uFF0C\u672C\u6B21\u9000\u56DE legacy \u8DEF\u5F84\uFF1A${e.message}`);
248208
248752
  }
248209
248753
  }
248210
248754
  const plane = { overlay, types: types2, assistants, humanPrefs };
@@ -258728,7 +259272,7 @@ function shimScript() {
258728
259272
  }
258729
259273
 
258730
259274
  // src/index.ts
258731
- var PKG_VERSION = true ? "0.1.133" : "dev";
259275
+ var PKG_VERSION = true ? "0.1.135" : "dev";
258732
259276
  var LOCAL_BIN = localBin();
258733
259277
  var NPM_PREFIX = npmPrefix();
258734
259278
  var INSTANCE = DEFAULT_INSTANCE;