oasis_test_v2 2.2.67 → 2.2.69

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 +573 -44
  2. package/package.json +2 -2
package/dist/index.js CHANGED
@@ -444,13 +444,14 @@ function isUnread(eventAt, marker) {
444
444
  function isChatSessionUnread(session, marker) {
445
445
  return session.lastTurnQuality !== void 0 && isUnread(session.touchedAt, marker);
446
446
  }
447
- var chatScope, letterScope, INBOX_BASELINE_SCOPE;
447
+ var chatScope, letterScope, INBOX_BASELINE_SCOPE, INBOX_READ_ALL_SCOPE;
448
448
  var init_inbox_read = __esm({
449
449
  "../contract/src/inbox-read.ts"() {
450
450
  "use strict";
451
451
  chatScope = (sessionId) => `chat:${sessionId}`;
452
452
  letterScope = (annotationId) => `letter:${annotationId}`;
453
453
  INBOX_BASELINE_SCOPE = "inbox-baseline";
454
+ INBOX_READ_ALL_SCOPE = "inbox-read-all";
454
455
  }
455
456
  });
456
457
 
@@ -1303,6 +1304,15 @@ var init_telemetry = __esm({
1303
1304
  });
1304
1305
 
1305
1306
  // ../contract/src/trace.ts
1307
+ function legacyScopeIsEmpty(scope2) {
1308
+ return scope2.artifactIds.length === 0 && scope2.chatSessionIds.length === 0 && (scope2.runtimeInstanceIds?.length ?? 0) === 0;
1309
+ }
1310
+ function legacyScopeMatcher(scope2) {
1311
+ const artifacts = new Set(scope2.artifactIds);
1312
+ const sessions = new Set(scope2.chatSessionIds);
1313
+ const runtimes = new Set(scope2.runtimeInstanceIds ?? []);
1314
+ return (run) => run.artifactId != null && artifacts.has(run.artifactId) || run.chatSessionId != null && sessions.has(run.chatSessionId) || run.runtimeInstanceId != null && runtimes.has(run.runtimeInstanceId);
1315
+ }
1306
1316
  var DISPLAY_CNY_PER_USD, USAGE_POINT_FINAL_SEQ;
1307
1317
  var init_trace = __esm({
1308
1318
  "../contract/src/trace.ts"() {
@@ -125849,7 +125859,7 @@ function isReplyWork(w2) {
125849
125859
  function replyIssueIdOf(w2) {
125850
125860
  return w2.lane === "reply" ? w2.replyToIssueId ?? null : null;
125851
125861
  }
125852
- var BUSINESS_HANDOFF_POLICY_FIELD, BUSINESS_HANDOFF_POLICY_EXEMPT, DEFAULT_CONFIG, deriveWorkId, deriveReviewId, deriveReplyId;
125862
+ var BUSINESS_HANDOFF_POLICY_FIELD, BUSINESS_HANDOFF_POLICY_EXEMPT, DEFAULT_CONFIG, deriveWorkId, deriveReviewId, ENGINE_SYSTEM_ACTOR, REVIEW_EXHAUSTED_ESC_PREFIX, deriveReplyId;
125853
125863
  var init_model = __esm({
125854
125864
  "../engine/src/model.ts"() {
125855
125865
  "use strict";
@@ -125862,6 +125872,8 @@ var init_model = __esm({
125862
125872
  };
125863
125873
  deriveWorkId = (nodeId, version2) => `wk:${nodeId}:${version2}`;
125864
125874
  deriveReviewId = (workId, reviewer, group) => `rv:${workId}:${reviewer}:${group}`;
125875
+ ENGINE_SYSTEM_ACTOR = "actor:system:engine";
125876
+ REVIEW_EXHAUSTED_ESC_PREFIX = "esc:review-exhausted:";
125865
125877
  deriveReplyId = (issueId, salt) => `ir:${issueId}:${salt}`;
125866
125878
  }
125867
125879
  });
@@ -126405,6 +126417,7 @@ var init_kill = __esm({
126405
126417
  function scan(state, ctx) {
126406
126418
  if (state.workorder.dispatchPaused) return { events: [], terminated: false };
126407
126419
  const events = [];
126420
+ closeSettledReviewEscalations(state, events);
126408
126421
  for (const node2 of state.liveNodes()) {
126409
126422
  const lw = node2.latestWorkId ? state.work(node2.latestWorkId) : null;
126410
126423
  const continuationPolicy = node2.assigneeActorId ? ctx.continuationPolicies?.get(node2.assigneeActorId) : null;
@@ -126933,6 +126946,55 @@ function reviewMaxRetriesOf(policy) {
126933
126946
  const act = policy.resolve({ executor: "agent", scenario: "review" }).timeoutAction;
126934
126947
  return act.kind === "kill-rebuild" ? act.maxRetries : AGENT_REVIEW_MAX_RETRIES;
126935
126948
  }
126949
+ function pushReviewExhaustedEscalation(state, nodeId, workId, reviewId, reviewerActorId, rounds, events) {
126950
+ const node2 = state.node(nodeId);
126951
+ if (!node2) return;
126952
+ if (node2.latestAcceptId === workId) return;
126953
+ const escId = `${REVIEW_EXHAUSTED_ESC_PREFIX}${reviewId}`;
126954
+ if (state.allIssues().some((i) => i.id === escId)) return;
126955
+ events.push({
126956
+ kind: "issue.create",
126957
+ workorderId: state.workorder.id,
126958
+ issueId: escId,
126959
+ issueKind: "escalation",
126960
+ body: `\u300A${node2.title}\u300B\u7684\u8BC4\u5BA1\u8FDE\u7EED ${rounds} \u6B21\u5931\u8D25\uFF0C\u8FD9\u4E00\u8F6E\u8BC4\u5BA1\u6536\u4E0D\u9F50\u2014\u2014\u9700\u8981\u6709\u4EBA\u91CD\u5F00\u3002
126961
+
126962
+ \u672B\u6B21\u8BC4\u5BA1\u4F1A\u8BDD\u7ED3\u675F\u65F6\u6CA1\u6709\u6295\u7968\uFF1B\u62A5\u9519\u539F\u8BDD\u89C1\u8FD9\u4E2A\u8282\u70B9\u7684\u6267\u884C\u8F68\u8FF9\u3002`,
126963
+ aboutNodeId: nodeId,
126964
+ aboutWorkId: workId,
126965
+ raisedByNodeId: null,
126966
+ authorActorId: reviewerActorId,
126967
+ handActorId: null,
126968
+ recipients: [state.workorder.ownerActorId],
126969
+ attachments: [],
126970
+ intent: "advisory"
126971
+ });
126972
+ }
126973
+ function closeSettledReviewEscalations(state, events) {
126974
+ for (const issue2 of state.allIssues()) {
126975
+ if (issue2.resolvedAt !== null || issue2.kind !== "escalation") continue;
126976
+ const id = issue2.id;
126977
+ if (!id.startsWith(REVIEW_EXHAUSTED_ESC_PREFIX)) continue;
126978
+ const node2 = issue2.aboutNodeId ? state.node(issue2.aboutNodeId) : void 0;
126979
+ if (!node2 || node2.cancelledAt) continue;
126980
+ const lw = node2.latestWorkId ? state.work(node2.latestWorkId) : null;
126981
+ if (lw && workState(lw, hasOutput(lw, state.artifactsOf(lw.id).length)) === "dead") continue;
126982
+ const review = state.review(id.slice(REVIEW_EXHAUSTED_ESC_PREFIX.length));
126983
+ if (!review) continue;
126984
+ const head = state.requirementsOf(node2.id).find((r) => r.reviewGroup === review.reviewGroup)?.latestReviewId ?? null;
126985
+ const note = head !== review.id ? "\u8BC4\u5BA1\u5DF2\u91CD\u65B0\u5B89\u6392" : node2.latestAcceptId === review.targetWorkId ? "\u8FD9\u4E00\u7248\u5DF2\u9A8C\u6536" : null;
126986
+ if (note === null) continue;
126987
+ events.push({
126988
+ kind: "issue.resolve",
126989
+ workorderId: state.workorder.id,
126990
+ issueId: issue2.id,
126991
+ state: "resolved",
126992
+ by: SYSTEM_ENGINE_ACTOR,
126993
+ note,
126994
+ viaWorkId: null
126995
+ });
126996
+ }
126997
+ }
126936
126998
  function processReviews(state, nodeId, workId, ctx, events) {
126937
126999
  const policy = ctx.policy ?? CODE_DEFAULT_POLICY;
126938
127000
  const reqs = state.requirementsOf(nodeId);
@@ -126994,15 +127056,19 @@ function processReviews(state, nodeId, workId, ctx, events) {
126994
127056
  }
126995
127057
  } else if (info.status === "failed") {
126996
127058
  const req = reqs.find((r) => r.reviewGroup === g2);
126997
- const overLimit = reviewRetriesExhausted(g2, reviews, { policy }) !== null;
127059
+ const exhausted = reviewRetriesExhausted(g2, reviews, { policy });
127060
+ const overLimit = exhausted !== null;
126998
127061
  if (overLimit && classify(req.reviewerActorId) === "agent") {
126999
127062
  const lrId = req.latestReviewId;
127000
- if (lrId) events.push({
127001
- kind: "review.kill",
127002
- workorderId: state.workorder.id,
127003
- reviewId: lrId,
127004
- reason: "exhausted"
127005
- });
127063
+ if (lrId) {
127064
+ events.push({
127065
+ kind: "review.kill",
127066
+ workorderId: state.workorder.id,
127067
+ reviewId: lrId,
127068
+ reason: "exhausted"
127069
+ });
127070
+ pushReviewExhaustedEscalation(state, nodeId, workId, lrId, req.reviewerActorId, exhausted.rounds, events);
127071
+ }
127006
127072
  } else if (!overLimit) {
127007
127073
  events.push({
127008
127074
  kind: "review.create",
@@ -127193,6 +127259,7 @@ function assessConvergence(state, ctx) {
127193
127259
  const anyUnfinished = state.liveNodes().some((n) => !n.latestAcceptId);
127194
127260
  return { stuck: !anyRunning && !anyBlockingIssue && allIdle && anyUnfinished, codes };
127195
127261
  }
127262
+ var SYSTEM_ENGINE_ACTOR;
127196
127263
  var init_activate = __esm({
127197
127264
  "../engine/src/activate.ts"() {
127198
127265
  "use strict";
@@ -127207,6 +127274,7 @@ var init_activate = __esm({
127207
127274
  init_model();
127208
127275
  init_views2();
127209
127276
  init_kill();
127277
+ SYSTEM_ENGINE_ACTOR = ENGINE_SYSTEM_ACTOR;
127210
127278
  }
127211
127279
  });
127212
127280
 
@@ -135081,6 +135149,7 @@ var init_issue = __esm({
135081
135149
  "../engine/src/handlers/issue.ts"() {
135082
135150
  "use strict";
135083
135151
  init_plan();
135152
+ init_model();
135084
135153
  init_letters();
135085
135154
  issueCreate = {
135086
135155
  name: "issue/create",
@@ -135172,7 +135241,8 @@ var init_issue = __esm({
135172
135241
  }
135173
135242
  if (issue2.resolvedAt !== null) return;
135174
135243
  const humanResolution = e.by.startsWith("actor:human:") && e.by === ctx.actorId;
135175
- if (e.semanticsVersion === 2 && issue2.kind === "escalation" && !humanResolution) {
135244
+ const engineSelfClosure = e.by === ENGINE_SYSTEM_ACTOR && ctx.actorId === ENGINE_SYSTEM_ACTOR && issue2.id.startsWith(REVIEW_EXHAUSTED_ESC_PREFIX);
135245
+ if (e.semanticsVersion === 2 && issue2.kind === "escalation" && !humanResolution && !engineSelfClosure) {
135176
135246
  throw new Error("\u5173\u95ED\u5347\u7EA7\u9700\u8981\u4EBA\u660E\u786E\u786E\u8BA4\uFF1Bagent \u8BF7\u63D0\u4EA4\u5173\u95ED\u6388\u6743\u7533\u8BF7");
135177
135247
  }
135178
135248
  state.updateIssue(e.issueId, {
@@ -213891,7 +213961,10 @@ ${composed}`;
213891
213961
  auth: {
213892
213962
  actor,
213893
213963
  ...companyId !== void 0 ? { companyId } : {},
213894
- ...dispatch && Object.keys(dispatch).length > 0 ? { dispatch } : {}
213964
+ ...dispatch && Object.keys(dispatch).length > 0 ? { dispatch } : {},
213965
+ // 签名 scopes 原样透出(ADR 0609 D1):部署级 operator-token 靠它被认出来。
213966
+ // 只来自签名 token;cookie / 简单 token 没有这一项。
213967
+ ...domainClaims?.scopes ? { scopes: domainClaims.scopes } : {}
213895
213968
  },
213896
213969
  raw: req
213897
213970
  });
@@ -214342,6 +214415,18 @@ var init_tokens = __esm({
214342
214415
  }
214343
214416
  });
214344
214417
 
214418
+ // ../server/src/auth/operator.ts
214419
+ function isOperatorRequest(auth) {
214420
+ return auth.scopes?.includes(OPERATOR_SCOPE) === true;
214421
+ }
214422
+ var OPERATOR_SCOPE;
214423
+ var init_operator = __esm({
214424
+ "../server/src/auth/operator.ts"() {
214425
+ "use strict";
214426
+ OPERATOR_SCOPE = "oasis:operator";
214427
+ }
214428
+ });
214429
+
214345
214430
  // ../server/src/session-dataplane-token.ts
214346
214431
  function createDataplaneTokenIssuer(opts = {}) {
214347
214432
  const secret = readOrCreateSecret2(opts.secretFile);
@@ -215505,6 +215590,7 @@ var init_memory_trace_store = __esm({
215505
215590
  return run ? this.decorate(structuredClone(run)) : null;
215506
215591
  }
215507
215592
  async listRuns(filter) {
215593
+ const inScope2 = filter.legacyScope ? legacyScopeMatcher(filter.legacyScope) : null;
215508
215594
  let items = [...this.runs.values()].filter((r) => {
215509
215595
  if (filter.actorId && r.actorId !== filter.actorId) return false;
215510
215596
  if (filter.artifactId && r.artifactId !== filter.artifactId) return false;
@@ -215512,6 +215598,7 @@ var init_memory_trace_store = __esm({
215512
215598
  if (filter.runtimeKind && r.runtimeKind !== filter.runtimeKind) return false;
215513
215599
  if (filter.status && r.status !== filter.status) return false;
215514
215600
  if (filter.rootRunId && r.rootRunId !== filter.rootRunId) return false;
215601
+ if (inScope2 && !inScope2(r)) return false;
215515
215602
  if (filter.from && r.startedAt < filter.from) return false;
215516
215603
  if (filter.to && r.startedAt > filter.to) return false;
215517
215604
  if (filter.excludeAnalysis) {
@@ -216223,6 +216310,7 @@ var init_memory_trace_store = __esm({
216223
216310
  }
216224
216311
  /** 按维度查聚合用量。 */
216225
216312
  async queryUsageStats(filter) {
216313
+ if (filter.legacyScope) return this.queryUsageStatsScoped(filter, filter.legacyScope);
216226
216314
  const gran = filter.granularity ?? "hour";
216227
216315
  const out = [];
216228
216316
  for (const row of this.usageAggregates.values()) {
@@ -216250,6 +216338,59 @@ var init_memory_trace_store = __esm({
216250
216338
  }
216251
216339
  return out.sort((a, b2) => a.bucket < b2.bucket ? 1 : a.bucket > b2.bucket ? -1 : a.actorId.localeCompare(b2.actorId));
216252
216340
  }
216341
+ /**
216342
+ * legacy 共享账本上的按维度用量:绕开聚合行、按租户谓词从 runs 现算
216343
+ * (与 PostgresTraceStore.queryUsageStatsScoped 同口径——聚合行里没有 artifact / chat_session,
216344
+ * 表达不了归属;共用同一份 run 才能对上数)。
216345
+ */
216346
+ async queryUsageStatsScoped(filter, scope2) {
216347
+ if (legacyScopeIsEmpty(scope2)) return [];
216348
+ const inScope2 = legacyScopeMatcher(scope2);
216349
+ const gran = filter.granularity ?? "hour";
216350
+ const acc = /* @__PURE__ */ new Map();
216351
+ for (const run of this.runs.values()) {
216352
+ if (run.usage == null) continue;
216353
+ if (!inScope2(run)) continue;
216354
+ if (filter.actorIds?.length && !filter.actorIds.includes(run.actorId)) continue;
216355
+ if (filter.runtimeInstanceIds?.length && !filter.runtimeInstanceIds.includes(run.runtimeInstanceId ?? "")) continue;
216356
+ if (filter.runtimeKinds?.length && !filter.runtimeKinds.includes(run.runtimeKind)) continue;
216357
+ if (filter.effectiveModels?.length && !filter.effectiveModels.includes(run.effectiveModel ?? "")) continue;
216358
+ const started = new Date(run.startedAt);
216359
+ const bucket2 = gran === "day" ? this.truncDayISO(started) : this.truncHourISO(started);
216360
+ if (filter.from && bucket2 < (gran === "day" ? this.truncDayISO(new Date(filter.from)) : this.truncHourISO(new Date(filter.from)))) continue;
216361
+ if (filter.to && bucket2 > filter.to) continue;
216362
+ const u = run.usage;
216363
+ const input = u.inputTokens ?? 0;
216364
+ const output = u.outputTokens ?? 0;
216365
+ const key2 = `${run.actorId}|${run.runtimeInstanceId ?? ""}|${run.effectiveModel ?? ""}|${bucket2}`;
216366
+ const row = acc.get(key2);
216367
+ if (row) {
216368
+ row.runCount += 1;
216369
+ row.inputTokens += input;
216370
+ row.outputTokens += output;
216371
+ row.totalTokens += u.totalTokens ?? input + output;
216372
+ row.cacheReadTokens += u.cacheReadTokens ?? 0;
216373
+ row.cacheCreationTokens += u.cacheCreationTokens ?? 0;
216374
+ row.costUsdMicros += u.costUsdMicros ?? 0;
216375
+ } else {
216376
+ acc.set(key2, {
216377
+ actorId: run.actorId,
216378
+ runtimeInstanceId: run.runtimeInstanceId ?? null,
216379
+ runtimeKind: run.runtimeKind,
216380
+ effectiveModel: run.effectiveModel ?? null,
216381
+ bucket: bucket2,
216382
+ runCount: 1,
216383
+ inputTokens: input,
216384
+ outputTokens: output,
216385
+ totalTokens: u.totalTokens ?? input + output,
216386
+ cacheReadTokens: u.cacheReadTokens ?? 0,
216387
+ cacheCreationTokens: u.cacheCreationTokens ?? 0,
216388
+ costUsdMicros: u.costUsdMicros ?? 0
216389
+ });
216390
+ }
216391
+ }
216392
+ return [...acc.values()].sort((a, b2) => a.bucket < b2.bucket ? 1 : a.bucket > b2.bucket ? -1 : a.actorId.localeCompare(b2.actorId));
216393
+ }
216253
216394
  };
216254
216395
  }
216255
216396
  });
@@ -233138,6 +233279,7 @@ var init_review_requirement_activity = __esm({
233138
233279
  // ../server/src/domains/collab/activity.ts
233139
233280
  function escalationKindOf(issueId, authorActorId) {
233140
233281
  if (issueId.startsWith(RETRY_EXHAUSTED_ESC_PREFIX)) return "retry_exhausted";
233282
+ if (issueId.startsWith(REVIEW_EXHAUSTED_ESC_PREFIX2)) return "review_exhausted";
233141
233283
  return isSystemActorId(authorActorId) ? "output_stall" : "manual";
233142
233284
  }
233143
233285
  function parseStopLossFacts(kind, body2) {
@@ -234046,8 +234188,12 @@ function buildWorkorderActivity(input) {
234046
234188
  const gapId = issueById.get(id)?.gapId;
234047
234189
  return typeof gapId === "string" && !!issueById.get(gapId);
234048
234190
  };
234049
- const head = kind === "retry_exhausted" ? ACTIVITY_COPY.escalationHeadRetryExhausted(nodeName(latest.nodeId)) : kind === "output_stall" ? ACTIVITY_COPY.escalationHeadOutputStall(nodeName(latest.nodeId)) : gapBehind(latest.id) ? ACTIVITY_COPY.escalationHeadGapEscalated(nameOf(latest.authorId), nodeName(latest.nodeId)) : ACTIVITY_COPY.escalationHeadManual(nameOf(stuckActor));
234050
- const howTo = kind === "retry_exhausted" ? ACTIVITY_COPY.escalationHowToRetryExhausted : kind === "output_stall" ? ACTIVITY_COPY.escalationHowToOutputStall : void 0;
234191
+ const lastReviewFailure = kind === "review_exhausted" ? (input.runFailures ?? []).filter((f2) => f2.nodeId === latest.nodeId && f2.actorId === latest.authorId && f2.startedAt <= latest.at && f2.message.trim()).sort((a, b2) => a.startedAt.localeCompare(b2.startedAt)).at(-1)?.message : void 0;
234192
+ const head = kind === "retry_exhausted" ? ACTIVITY_COPY.escalationHeadRetryExhausted(nodeName(latest.nodeId)) : kind === "review_exhausted" ? ACTIVITY_COPY.escalationHeadReviewExhausted(
234193
+ nodeName(latest.nodeId),
234194
+ lastReviewFailure ? firstSentence(lastReviewFailure) : void 0
234195
+ ) : kind === "output_stall" ? ACTIVITY_COPY.escalationHeadOutputStall(nodeName(latest.nodeId)) : gapBehind(latest.id) ? ACTIVITY_COPY.escalationHeadGapEscalated(nameOf(latest.authorId), nodeName(latest.nodeId)) : ACTIVITY_COPY.escalationHeadManual(nameOf(stuckActor));
234196
+ const howTo = kind === "retry_exhausted" ? ACTIVITY_COPY.escalationHowToRetryExhausted : kind === "review_exhausted" ? ACTIVITY_COPY.escalationHowToReviewExhausted : kind === "output_stall" ? ACTIVITY_COPY.escalationHowToOutputStall : void 0;
234051
234197
  const closing = issueById.get(latest.id);
234052
234198
  const closeEvent = plainResolveEvents.get(latest.id);
234053
234199
  const closedBy = text5(closing?.resolvedBy) ?? closeEvent?.by ?? target;
@@ -234369,7 +234515,7 @@ function buildWorkorderActivity(input) {
234369
234515
  cards.sort((a, b2) => a.at.localeCompare(b2.at) || a.seq - b2.seq || a.id.localeCompare(b2.id));
234370
234516
  return { workorderId, cards, truncated };
234371
234517
  }
234372
- var ACTIVITY_EVENT_KINDS, LEGACY_PAUSE_REASON, ACTIVITY_EVENT_LIMIT, UPSTREAM_ACK_NOTE_CAP, ACTIVITY_COPY, REWORK_ANNOTATION_PREFIX, RECOVERY_ANNOTATION_PREFIX, ENGINE_RETRY_EXHAUSTED, BUTTON, PHASE_LABEL, COORDINATOR_CARD_PREFIX, RETRY_EXHAUSTED_ESC_PREFIX, isStopLoss;
234518
+ var ACTIVITY_EVENT_KINDS, LEGACY_PAUSE_REASON, ACTIVITY_EVENT_LIMIT, UPSTREAM_ACK_NOTE_CAP, ACTIVITY_COPY, REWORK_ANNOTATION_PREFIX, RECOVERY_ANNOTATION_PREFIX, ENGINE_RETRY_EXHAUSTED, BUTTON, PHASE_LABEL, COORDINATOR_CARD_PREFIX, RETRY_EXHAUSTED_ESC_PREFIX, REVIEW_EXHAUSTED_ESC_PREFIX2, isStopLoss;
234373
234519
  var init_activity2 = __esm({
234374
234520
  "../server/src/domains/collab/activity.ts"() {
234375
234521
  "use strict";
@@ -234513,6 +234659,8 @@ var init_activity2 = __esm({
234513
234659
  同一件事,只换后半句(「等 @X 处理」→「X 已处理:…」)。 */
234514
234660
  escalationHeadRetryExhausted: (node2) => `\u300A${node2}\u300B\u91CD\u8BD5\u5DF2\u7528\u5C3D\uFF0C`,
234515
234661
  escalationHeadOutputStall: (node2) => `\u7CFB\u7EDF\u6B62\u635F\uFF1A\u300A${node2}\u300B\u505C\u4F4F\u4E86\uFF0C`,
234662
+ /** 评审连败熔断(Bug 0212):卡住的是**这一轮评审**,不是那一版活——话不能写成「执行异常」。 */
234663
+ escalationHeadReviewExhausted: (node2, lastReason) => `\u300A${node2}\u300B\u7684\u8BC4\u5BA1\u8FDE\u7740\u6CA1\u8DD1\u6210\uFF0C\u8FD9\u4E00\u8F6E\u6536\u4E0D\u9F50${lastReason ? `\uFF08\u672B\u6B21\uFF1A${lastReason}\uFF09` : ""}\uFF0C`,
234516
234664
  /** 人工上报里**关联着缺口**的那一档:协调者接了缺口、补不了,于是升级给人(四种停法的第二种)。 */
234517
234665
  escalationHeadGapEscalated: (coordinator, node2) => `${coordinator} \u8865\u4E0D\u4E86\u300A${node2}\u300B\u7684\u7F3A\u53E3\uFF0C`,
234518
234666
  /** 其余人工上报:主语是卡住的那个执行人(异常是它的,不是上报作者的)。 */
@@ -234528,6 +234676,8 @@ var init_activity2 = __esm({
234528
234676
  /** 「你要做什么」那半句——两种系统止损各一份(F6 的立论就在这里:执行者一样,只有话不一样)。 */
234529
234677
  escalationHowToRetryExhausted: "\u5904\u7406\u5E76\u5173\u6389\u8FD9\u6761\u4E0A\u62A5\u540E\uFF0C\u8FD9\u4E00\u6B65\u4F1A\u81EA\u52A8\u91CD\u8BD5\uFF1B\u591A\u534A\u662F\u989D\u5EA6 / \u767B\u5F55\u8FC7\u671F / \u9650\u6D41\u8FD9\u7C7B\u73AF\u5883\u95EE\u9898\uFF0C\u5148\u770B\u8F68\u8FF9\u518D\u51B3\u5B9A\u91CD\u8BD5\u3002",
234530
234678
  escalationHowToOutputStall: "\u5904\u7406\u5E76\u5173\u6389\u8FD9\u6761\u4E0A\u62A5\u540E\uFF0C\u8FD9\u4E00\u6B65\u4F1A\u81EA\u52A8\u91CD\u8BD5\uFF1B\u4E0D\u67E5\u6E05\u539F\u56E0\u5C31\u91CD\u8BD5\u4F1A\u7EE7\u7EED\u7A7A\u70E7\uFF0C\u5148\u8FDB\u8BA8\u8BBA\u623F\u3002",
234679
+ /** 评审熔断那一份:关掉之后重开的是**评审**,不是这一步的活(活已经交了,等的是审)。 */
234680
+ escalationHowToReviewExhausted: "\u5904\u7406\u5E76\u5173\u6389\u8FD9\u6761\u4E0A\u62A5\u540E\uFF0C\u8FD9\u4E00\u8F6E\u8BC4\u5BA1\u4F1A\u81EA\u52A8\u91CD\u5F00\uFF1B\u591A\u534A\u662F\u8BC4\u5BA1\u5458\u7684\u8FD0\u884C\u73AF\u5883\u51FA\u4E86\u95EE\u9898\uFF08\u989D\u5EA6 / \u767B\u5F55\u8FC7\u671F / \u9650\u6D41\uFF09\uFF0C\u5148\u770B\u8F68\u8FF9\u518D\u51B3\u5B9A\u3002",
234531
234681
  /** 关掉了:接着开头那半句说「谁处理的、怎么处理的」。没写处置说明就只说「已处理」。
234532
234682
  * 处置说明常常是人自己写的一整段、末尾自带句号——**别再补一个**,否则卡面收在「。。」上。 */
234533
234683
  escalationHandled: (head, by, note) => `${head}${by} \u5DF2\u5904\u7406${note ? `\uFF1A${note}` : ""}${note && /[。!?.!?]$/.test(note) ? "" : "\u3002"}`,
@@ -234717,7 +234867,8 @@ var init_activity2 = __esm({
234717
234867
  };
234718
234868
  COORDINATOR_CARD_PREFIX = "coordinator:";
234719
234869
  RETRY_EXHAUSTED_ESC_PREFIX = "esc:retry-exhausted:";
234720
- isStopLoss = (kind) => kind !== "manual";
234870
+ REVIEW_EXHAUSTED_ESC_PREFIX2 = "esc:review-exhausted:";
234871
+ isStopLoss = (kind) => kind === "retry_exhausted" || kind === "output_stall";
234721
234872
  }
234722
234873
  });
234723
234874
 
@@ -235417,6 +235568,15 @@ function backfillInboxUnread(items, markers) {
235417
235568
  item.unread = !Number.isFinite(effective) ? true : sinceMs > effective;
235418
235569
  }
235419
235570
  }
235571
+ function applyReadAllWatermark(items, markers) {
235572
+ const at = markers.find((m2) => m2.scope === INBOX_READ_ALL_SCOPE)?.readAt;
235573
+ const atMs = at ? Date.parse(at) : Number.NaN;
235574
+ if (!Number.isFinite(atMs)) return;
235575
+ for (const item of items) {
235576
+ const sinceMs = Date.parse(item.since);
235577
+ if (Number.isFinite(sinceMs) && sinceMs <= atMs) item.unread = false;
235578
+ }
235579
+ }
235420
235580
  var C_ACTION_LABEL;
235421
235581
  var init_inbox = __esm({
235422
235582
  "../server/src/domains/collab/inbox.ts"() {
@@ -235995,6 +236155,16 @@ var init_notification_store = __esm({
235995
236155
  }
235996
236156
  if (changed) this.save();
235997
236157
  }
236158
+ async markAllRead(recipient, at) {
236159
+ let n = 0;
236160
+ for (const [id, r] of this.rows) {
236161
+ if (r.recipient !== recipient || r.readAt) continue;
236162
+ this.rows.set(id, { ...r, readAt: at });
236163
+ n++;
236164
+ }
236165
+ if (n > 0) this.save();
236166
+ return n;
236167
+ }
235998
236168
  async countUnread(recipient) {
235999
236169
  let pending = 0;
236000
236170
  let all2 = 0;
@@ -236185,6 +236355,13 @@ var init_notification_store = __esm({
236185
236355
  [recipient, [...ids2], at]
236186
236356
  );
236187
236357
  }
236358
+ async markAllRead(recipient, at) {
236359
+ const r = await this.sql.query(
236360
+ `UPDATE ${this.t} SET read_at = $2::timestamptz WHERE recipient = $1 AND read_at IS NULL`,
236361
+ [recipient, at]
236362
+ );
236363
+ return r.rowCount ?? 0;
236364
+ }
236188
236365
  async countUnread(recipient) {
236189
236366
  const r = await this.sql.query(
236190
236367
  `SELECT count(*) FILTER (WHERE status = 'pending')::int AS pending, count(*)::int AS "all"
@@ -236715,7 +236892,7 @@ var init_workorder_metrics = __esm({
236715
236892
  });
236716
236893
 
236717
236894
  // ../server/src/domains/usage/statistics-model.ts
236718
- var UsageQueryError;
236895
+ var UsageQueryError, ZERO_USAGE_AMOUNT;
236719
236896
  var init_statistics_model = __esm({
236720
236897
  "../server/src/domains/usage/statistics-model.ts"() {
236721
236898
  "use strict";
@@ -236726,6 +236903,13 @@ var init_statistics_model = __esm({
236726
236903
  this.name = "UsageQueryError";
236727
236904
  }
236728
236905
  };
236906
+ ZERO_USAGE_AMOUNT = {
236907
+ tokens: { input: 0, cache: 0, cacheRead: 0, cacheCreation: 0, output: 0, total: 0 },
236908
+ costUsdMicros: 0,
236909
+ costParts: { input: 0, cacheRead: 0, cacheCreation: 0, output: 0, unsplit: 0 },
236910
+ costKnown: "full",
236911
+ costUnknownTokens: 0
236912
+ };
236729
236913
  }
236730
236914
  });
236731
236915
 
@@ -237101,6 +237285,11 @@ function bucketIndex(starts, ms) {
237101
237285
  }
237102
237286
  return ans;
237103
237287
  }
237288
+ function pushView(map, key2, v2) {
237289
+ const list2 = map.get(key2);
237290
+ if (list2) list2.push(v2);
237291
+ else map.set(key2, [v2]);
237292
+ }
237104
237293
  function getCell(map, key2) {
237105
237294
  let c = map.get(key2);
237106
237295
  if (!c) {
@@ -237338,12 +237527,15 @@ function attributed(facts, relations) {
237338
237527
  const taskCells = /* @__PURE__ */ new Map();
237339
237528
  const taskActorCells = /* @__PURE__ */ new Map();
237340
237529
  const convSessionCells = /* @__PURE__ */ new Map();
237530
+ const convViews = /* @__PURE__ */ new Map();
237531
+ const taskViews = /* @__PURE__ */ new Map();
237341
237532
  const unlinked = new Cell();
237342
237533
  for (const v2 of facts.views) {
237343
237534
  if (!v2.relevant) continue;
237344
237535
  const attr = runAttribution(relations, v2.run, memo);
237345
237536
  if (attr.kind === "task") {
237346
237537
  getCell(taskCells, attr.workorderId).addView(v2);
237538
+ pushView(taskViews, attr.workorderId, v2);
237347
237539
  let byActor2 = taskActorCells.get(attr.workorderId);
237348
237540
  if (!byActor2) {
237349
237541
  byActor2 = /* @__PURE__ */ new Map();
@@ -237357,6 +237549,7 @@ function attributed(facts, relations) {
237357
237549
  convSessionCells.set(attr.rootSessionId, bySession);
237358
237550
  }
237359
237551
  getCell(bySession, attr.sessionId).addView(v2);
237552
+ pushView(convViews, attr.rootSessionId, v2);
237360
237553
  } else unlinked.addView(v2);
237361
237554
  }
237362
237555
  const taskAmount = /* @__PURE__ */ new Map();
@@ -237388,8 +237581,13 @@ function attributed(facts, relations) {
237388
237581
  }
237389
237582
  const conversations = [];
237390
237583
  const convGroupAcc = /* @__PURE__ */ new Map();
237584
+ const treeViews = /* @__PURE__ */ new Map();
237391
237585
  for (const rootId of /* @__PURE__ */ new Set([...convSessionCells.keys(), ...tasksByRoot.keys()])) {
237392
237586
  const root = relations.sessions.get(rootId);
237587
+ treeViews.set(rootId, [
237588
+ ...convViews.get(rootId) ?? [],
237589
+ ...(tasksByRoot.get(rootId) ?? []).flatMap((workorderId) => taskViews.get(workorderId) ?? [])
237590
+ ]);
237393
237591
  const group = new Cell();
237394
237592
  const rows = [];
237395
237593
  const sessionRows = [];
@@ -237511,9 +237709,83 @@ function attributed(facts, relations) {
237511
237709
  { amount: a.amount, name: a.name, id: a.projectId ?? "" },
237512
237710
  { amount: b2.amount, name: b2.name, id: b2.projectId ?? "" }
237513
237711
  ));
237514
- return { conversations, tasks, projects };
237712
+ return { conversations, tasks, projects, treeViews };
237713
+ }
237714
+ function deriveTreeDuration(views, now) {
237715
+ const nowMs = now.getTime();
237716
+ const spans = [];
237717
+ let countedRuns = 0;
237718
+ let unstartedRuns = 0;
237719
+ let inProgress = false;
237720
+ let firstMs = Infinity;
237721
+ let lastMs = -Infinity;
237722
+ let runtimeMs = 0;
237723
+ for (const v2 of views) {
237724
+ const startMs = v2.run.runtimeStartedAt ? Date.parse(v2.run.runtimeStartedAt) : NaN;
237725
+ if (!Number.isFinite(startMs)) {
237726
+ unstartedRuns += 1;
237727
+ continue;
237728
+ }
237729
+ const endedMs = v2.run.endedAt ? Date.parse(v2.run.endedAt) : NaN;
237730
+ const running = !Number.isFinite(endedMs) && !isRunEnded(v2.run);
237731
+ if (running) inProgress = true;
237732
+ const rawEnd = Number.isFinite(endedMs) ? endedMs : running ? nowMs : startMs;
237733
+ const end = Math.max(rawEnd, startMs);
237734
+ countedRuns += 1;
237735
+ runtimeMs += end - startMs;
237736
+ spans.push({ start: startMs, end });
237737
+ if (startMs < firstMs) firstMs = startMs;
237738
+ if (end > lastMs) lastMs = end;
237739
+ }
237740
+ if (countedRuns === 0) return { ...ZERO_DURATION, unstartedRuns };
237741
+ spans.sort((a, b2) => a.start - b2.start || a.end - b2.end);
237742
+ let activeMs = 0;
237743
+ let curStart = spans[0].start;
237744
+ let curEnd = spans[0].end;
237745
+ for (const span of spans.slice(1)) {
237746
+ if (span.start > curEnd) {
237747
+ activeMs += curEnd - curStart;
237748
+ curStart = span.start;
237749
+ curEnd = span.end;
237750
+ } else if (span.end > curEnd) curEnd = span.end;
237751
+ }
237752
+ activeMs += curEnd - curStart;
237753
+ return {
237754
+ firstStartedAt: new Date(firstMs).toISOString(),
237755
+ lastEndedAt: new Date(lastMs).toISOString(),
237756
+ inProgress,
237757
+ wallClockMs: lastMs - firstMs,
237758
+ runtimeMs,
237759
+ activeMs,
237760
+ countedRuns,
237761
+ unstartedRuns
237762
+ };
237515
237763
  }
237516
- var UNKNOWN_AGENT_NAME, UNREGISTERED_RUNTIME_NAME, UNSPECIFIED_PROJECT_NAME, UNLINKED_ROW_TITLE, UNTITLED_CONVERSATION, BUCKET_KEYS, AmountAcc, FlagAcc, Cell, collator, ModelCells;
237764
+ function buildChatSessionUsage(sessionId, input) {
237765
+ const facts = computeFacts(input);
237766
+ const lists = attributed(facts, input.relations);
237767
+ const attr = sessionAttribution(input.relations, sessionId, /* @__PURE__ */ new Map());
237768
+ const rootId = attr.kind === "conversation" ? attr.rootSessionId : sessionId;
237769
+ const session = input.relations.sessions.get(rootId);
237770
+ const group = lists.conversations.find((g2) => g2.sessionId === rootId) ?? {
237771
+ sessionId: rootId,
237772
+ title: session?.title || UNTITLED_CONVERSATION,
237773
+ projectId: session?.projectId ?? null,
237774
+ createdAt: session?.createdAt ?? null,
237775
+ amount: ZERO_USAGE_AMOUNT,
237776
+ flags: { inProgress: false, usagePending: false, costUnknownRuns: 0 },
237777
+ rows: []
237778
+ };
237779
+ return {
237780
+ companyId: input.companyId,
237781
+ sessionId,
237782
+ window: input.resolved.window,
237783
+ generatedAt: input.now.toISOString(),
237784
+ group,
237785
+ duration: deriveTreeDuration(lists.treeViews.get(rootId) ?? [], input.now)
237786
+ };
237787
+ }
237788
+ var UNKNOWN_AGENT_NAME, UNREGISTERED_RUNTIME_NAME, UNSPECIFIED_PROJECT_NAME, UNLINKED_ROW_TITLE, UNTITLED_CONVERSATION, BUCKET_KEYS, AmountAcc, FlagAcc, Cell, collator, ModelCells, ZERO_DURATION;
237517
237789
  var init_statistics = __esm({
237518
237790
  "../server/src/domains/usage/statistics.ts"() {
237519
237791
  "use strict";
@@ -237521,6 +237793,7 @@ var init_statistics = __esm({
237521
237793
  init_uncategorized_project();
237522
237794
  init_model_pricing();
237523
237795
  init_accounting();
237796
+ init_statistics_model();
237524
237797
  UNKNOWN_AGENT_NAME = "\u672A\u77E5 Agent";
237525
237798
  UNREGISTERED_RUNTIME_NAME = "\u672A\u767B\u8BB0\u8FD0\u884C\u65F6";
237526
237799
  UNSPECIFIED_PROJECT_NAME = UNCATEGORIZED_PROJECT_NAME;
@@ -237639,6 +237912,16 @@ var init_statistics = __esm({
237639
237912
  return [...this.cells].map(([model, c]) => ({ model, runCount: c.runCount, amount: c.amount.toAmount(), flags: c.flags.toFlags() })).filter((r) => r.runCount > 0 || r.amount.tokens.total > 0 || r.amount.costUsdMicros > 0 || r.amount.costKnown !== "full").sort((a, b2) => b2.amount.tokens.total - a.amount.tokens.total || b2.runCount - a.runCount || (a.model === null ? 1 : b2.model === null ? -1 : collator.compare(a.model, b2.model)));
237640
237913
  }
237641
237914
  };
237915
+ ZERO_DURATION = {
237916
+ firstStartedAt: null,
237917
+ lastEndedAt: null,
237918
+ inProgress: false,
237919
+ wallClockMs: null,
237920
+ runtimeMs: 0,
237921
+ activeMs: 0,
237922
+ countedRuns: 0,
237923
+ unstartedRuns: 0
237924
+ };
237642
237925
  }
237643
237926
  });
237644
237927
 
@@ -237894,6 +238177,22 @@ function createUsageReadService(deps) {
237894
238177
  logRequest({ companyId, op: "breakdown", range: r.query.range, dimension, runs: r.runs.length, companyScoped: r.target.companyScoped, startedMs });
237895
238178
  return body2;
237896
238179
  },
238180
+ async conversationUsage(companyId, sessionId, raw) {
238181
+ const startedMs = Date.now();
238182
+ const loads = requestLoads(companyId);
238183
+ const r = await readLedger(companyId, { ...raw, range: raw.range ?? "all" }, loads);
238184
+ const relations = await loadRelations(companyId, loads);
238185
+ const body2 = buildChatSessionUsage(sessionId, {
238186
+ companyId,
238187
+ resolved: r.resolved,
238188
+ runs: r.runs,
238189
+ coverage: r.coverage,
238190
+ now: r.now,
238191
+ relations
238192
+ });
238193
+ logRequest({ companyId, op: "conversation", range: r.query.range, runs: r.runs.length, companyScoped: r.target.companyScoped, startedMs });
238194
+ return body2;
238195
+ },
237897
238196
  async runtimeLedger(companyId, runtimeInstanceId, raw) {
237898
238197
  const startedMs = Date.now();
237899
238198
  const loads = requestLoads(companyId);
@@ -238871,6 +239170,7 @@ function collabDomain(opts) {
238871
239170
  backfillInboxUnread(waitingItems, markers);
238872
239171
  }
238873
239172
  const items = mergeInbox(kernelItems, [...chatItems, ...waitingItems]);
239173
+ if (myReadMarkers) applyReadAllWatermark(items, markers);
238874
239174
  return { status: 200, body: { items: myReadMarkers ? withEscalationAttention(items, req.auth.companyId ?? opts.defaultCompanyId ?? "", markers, Date.now(), opts.escalationReminders) : items } };
238875
239175
  });
238876
239176
  router.post("/api/inbox/escalations/:id/acknowledge", async (req) => {
@@ -239140,6 +239440,28 @@ function collabDomain(opts) {
239140
239440
  await marks.markRead(me, scope2, (/* @__PURE__ */ new Date()).toISOString());
239141
239441
  return { status: 200, body: { ok: true } };
239142
239442
  });
239443
+ router.post("/api/inbox/read-all", async (req) => {
239444
+ const me = req.auth.actor;
239445
+ if (!isHumanActor(me)) {
239446
+ return { status: 403, body: { error: { code: "human_only", message: "\u53EA\u6709\u4EBA\u7C7B\u8EAB\u4EFD\u53EF\u4EE5\u6807\u8BB0\u5DF2\u8BFB" } } };
239447
+ }
239448
+ const marks = opts.resolveReadMarkers ? await opts.resolveReadMarkers(req.auth.companyId).catch(() => void 0) : opts.readMarkers;
239449
+ let ledger;
239450
+ if (opts.resolveNotificationLedger) {
239451
+ try {
239452
+ ledger = await opts.resolveNotificationLedger(req.auth.companyId);
239453
+ } catch (err) {
239454
+ console.warn(`[notifications] \u65E0\u53EF\u4FE1\u516C\u53F8\u4E0A\u4E0B\u6587\uFF0C\u4E0D\u6807\u5DF2\u8BFB\uFF08\u4E0D\u56DE\u843D\u9ED8\u8BA4\u516C\u53F8\uFF09: ${String(err)}`);
239455
+ }
239456
+ }
239457
+ if (!marks && !ledger) {
239458
+ return { status: 503, body: { error: { code: "unavailable", message: "\u5DF2\u8BFB\u6C34\u4F4D\u4E0E\u901A\u77E5\u53F0\u8D26\u90FD\u4E0D\u53EF\u7528" } } };
239459
+ }
239460
+ const readAt = (/* @__PURE__ */ new Date()).toISOString();
239461
+ const notifications = ledger ? await ledger.markAllRead(me, readAt) : 0;
239462
+ if (marks) await marks.markRead(me, INBOX_READ_ALL_SCOPE, readAt);
239463
+ return { status: 200, body: { ok: true, readAt, notifications } };
239464
+ });
239143
239465
  router.get("/api/audit", async (req) => {
239144
239466
  const { engineStore, oplog, kernel } = await resolveCtx(req.auth.companyId);
239145
239467
  const q2 = req.query;
@@ -244209,8 +244531,14 @@ var init_service7 = __esm({
244209
244531
  listRunLinks(runId) {
244210
244532
  return this.store.listRunLinks(runId);
244211
244533
  }
244212
- /** run 详情聚合(§4.3):run + links + toolCalls + children + 派生 stats。 */
244213
- async getRunDetail(id) {
244534
+ /**
244535
+ * run 详情聚合(§4.3):run + links + toolCalls + children + 派生 stats。
244536
+ *
244537
+ * `scopeFilters` 由调用方给(HTTP 读面传租户闸门,内部调用不给):子 run 是用
244538
+ * `listRuns({ rootRunId })` 捞的,共享 legacy 账本上这一步必须带同一条归属谓词,
244539
+ * 否则详情页的「子运行」会成为列表谓词之外的第二个出口。
244540
+ */
244541
+ async getRunDetail(id, scopeFilters = (f2) => f2) {
244214
244542
  const run = await this.store.getRun(id);
244215
244543
  if (!run) return null;
244216
244544
  const [links, toolCalls, events] = await Promise.all([
@@ -244218,7 +244546,7 @@ var init_service7 = __esm({
244218
244546
  this.store.listToolCalls(id),
244219
244547
  this.store.listEvents(id, {})
244220
244548
  ]);
244221
- const sameRoot = await this.store.listRuns({ rootRunId: run.rootRunId, limit: 1e3 });
244549
+ const sameRoot = await this.store.listRuns(scopeFilters({ rootRunId: run.rootRunId, limit: 1e3 }));
244222
244550
  const children = sameRoot.items.filter((r) => r.parentRunId === id).map((r) => this.withDisplayCost(r));
244223
244551
  const stats = {
244224
244552
  eventCount: events.length,
@@ -244229,9 +244557,14 @@ var init_service7 = __esm({
244229
244557
  };
244230
244558
  return { run: this.withDisplayCost(run), links, toolCalls, children, stats };
244231
244559
  }
244232
- /** 聚合统计(§4.1 /api/traces/summary)。v1 全量扫描,量大后改投影表。 */
244233
- async summary() {
244234
- const { items } = await this.store.listRuns({ limit: 1e3 });
244560
+ /**
244561
+ * 聚合统计(§4.1 /api/traces/summary)。v1 全量扫描,量大后改投影表。
244562
+ *
244563
+ * `filter` 由调用方给:这几个计数是 `listRuns` 扫出来的,共享 legacy 账本上必须带同一条
244564
+ * 归属谓词——轨迹页页头的「共 N 条运行」就是它,不带谓词就是把别家的运行数报给本公司。
244565
+ */
244566
+ async summary(filter = {}) {
244567
+ const { items } = await this.store.listRuns({ ...filter, limit: 1e3 });
244235
244568
  const byStatus = /* @__PURE__ */ new Map();
244236
244569
  const byKind = /* @__PURE__ */ new Map();
244237
244570
  for (const r of items) {
@@ -244499,12 +244832,33 @@ var init_map2 = __esm({
244499
244832
  }
244500
244833
  });
244501
244834
 
244835
+ // ../server/src/domains/trace/read-scope.ts
244836
+ function scopedTraceFilters(filter, scope2) {
244837
+ return scope2.companyScoped ? filter : { ...filter, legacyScope: scope2.legacyScope };
244838
+ }
244839
+ function traceReadAllows(scope2) {
244840
+ return scope2.companyScoped ? () => true : legacyScopeMatcher(scope2.legacyScope);
244841
+ }
244842
+ var COMPANY_SCOPED_TRACE_READ, DENY_ALL_TRACE_READ;
244843
+ var init_read_scope = __esm({
244844
+ "../server/src/domains/trace/read-scope.ts"() {
244845
+ "use strict";
244846
+ init_src();
244847
+ COMPANY_SCOPED_TRACE_READ = { companyScoped: true };
244848
+ DENY_ALL_TRACE_READ = {
244849
+ companyScoped: false,
244850
+ legacyScope: { artifactIds: [], chatSessionIds: [], runtimeInstanceIds: [] }
244851
+ };
244852
+ }
244853
+ });
244854
+
244502
244855
  // ../server/src/domains/trace/routes.ts
244503
244856
  function parseFilters(q2) {
244504
244857
  const f2 = {};
244505
244858
  const actorId = q2.get("actorId");
244506
244859
  const artifactId = q2.get("artifactId");
244507
244860
  const projectId2 = q2.get("projectId");
244861
+ const chatSessionId = q2.get("chatSessionId");
244508
244862
  const runtimeKind = q2.get("runtimeKind");
244509
244863
  const status = q2.get("status");
244510
244864
  const rootRunId = q2.get("rootRunId");
@@ -244515,6 +244869,7 @@ function parseFilters(q2) {
244515
244869
  if (actorId) f2.actorId = actorId;
244516
244870
  if (artifactId) f2.artifactId = artifactId;
244517
244871
  if (projectId2) f2.projectId = projectId2;
244872
+ if (chatSessionId) f2.chatSessionId = chatSessionId;
244518
244873
  if (runtimeKind) f2.runtimeKind = runtimeKind;
244519
244874
  if (status) f2.status = status;
244520
244875
  if (rootRunId) f2.rootRunId = rootRunId;
@@ -244527,36 +244882,53 @@ function parseFilters(q2) {
244527
244882
  }
244528
244883
  function traceDomain(opts) {
244529
244884
  const svc = async (req) => opts.resolveService ? await opts.resolveService(req.auth?.companyId) : opts.service;
244885
+ const scopeOf = async (req) => opts.resolveReadScope ? await opts.resolveReadScope(req.auth?.companyId) : COMPANY_SCOPED_TRACE_READ;
244886
+ const requireRun = async (req) => {
244887
+ const id = req.params.id;
244888
+ const service = await svc(req);
244889
+ const run = await service.getRun(id);
244890
+ if (!run || !traceReadAllows(await scopeOf(req))(run)) {
244891
+ throw new ApiError(404, "NOT_FOUND", `\u6CA1\u6709\u8FD9\u4E2A run\uFF1A${id}`);
244892
+ }
244893
+ return { service, id };
244894
+ };
244530
244895
  return (router) => {
244531
244896
  router.get("/api/traces/runs", async (req) => {
244532
- return { status: 200, body: await (await svc(req)).listRuns(parseFilters(req.query)) };
244897
+ const filter = scopedTraceFilters(parseFilters(req.query), await scopeOf(req));
244898
+ return { status: 200, body: await (await svc(req)).listRuns(filter) };
244533
244899
  });
244534
244900
  router.get("/api/traces/summary", async (req) => {
244535
- return { status: 200, body: await (await svc(req)).summary() };
244901
+ return { status: 200, body: await (await svc(req)).summary(scopedTraceFilters({}, await scopeOf(req))) };
244536
244902
  });
244537
244903
  router.get("/api/traces/runs/:id", async (req) => {
244538
- const detail = await (await svc(req)).getRunDetail(req.params.id);
244539
- if (!detail) throw new ApiError(404, "NOT_FOUND", `\u6CA1\u6709\u8FD9\u4E2A run\uFF1A${req.params.id}`);
244904
+ const scope2 = await scopeOf(req);
244905
+ const detail = await (await svc(req)).getRunDetail(req.params.id, (f2) => scopedTraceFilters(f2, scope2));
244906
+ if (!detail || !traceReadAllows(scope2)(detail.run)) {
244907
+ throw new ApiError(404, "NOT_FOUND", `\u6CA1\u6709\u8FD9\u4E2A run\uFF1A${req.params.id}`);
244908
+ }
244540
244909
  return { status: 200, body: detail };
244541
244910
  });
244542
244911
  router.get("/api/traces/runs/:id/events", async (req) => {
244912
+ const { service, id } = await requireRun(req);
244543
244913
  const afterSeq = req.query.get("afterSeq") ?? req.query.get("cursor");
244544
244914
  const limit = req.query.get("limit");
244545
244915
  return {
244546
244916
  status: 200,
244547
- body: await (await svc(req)).listEvents(req.params.id, {
244917
+ body: await service.listEvents(id, {
244548
244918
  ...afterSeq !== null && Number.isFinite(Number(afterSeq)) ? { afterSeq: Number(afterSeq) } : {},
244549
244919
  ...limit !== null && Number.isFinite(Number(limit)) ? { limit: Number(limit) } : {}
244550
244920
  })
244551
244921
  };
244552
244922
  });
244553
244923
  router.get("/api/traces/runs/:id/tool-calls", async (req) => {
244554
- return { status: 200, body: { items: await (await svc(req)).listToolCalls(req.params.id) } };
244924
+ const { service, id } = await requireRun(req);
244925
+ return { status: 200, body: { items: await service.listToolCalls(id) } };
244555
244926
  });
244556
244927
  router.get("/api/traces/runs/:id/bundle", async (req) => {
244557
244928
  if (!opts.readBundleFile) return { status: 404, body: { error: "bundle not available" } };
244558
244929
  const rel = req.query.get("path");
244559
244930
  if (!rel) return { status: 400, body: { error: "path query required" } };
244931
+ await requireRun(req);
244560
244932
  const content3 = opts.readBundleFile(req.params.id, rel);
244561
244933
  if (content3 == null) return { status: 404, body: { error: "not found" } };
244562
244934
  return { status: 200, body: { path: rel, content: content3 } };
@@ -244568,7 +244940,9 @@ function traceDomain(opts) {
244568
244940
  const runtimeKinds = q2.get("runtimeKind")?.split(",").filter(Boolean);
244569
244941
  const effectiveModels = q2.get("model")?.split(",").filter(Boolean);
244570
244942
  const granularity = q2.get("granularity") ?? "hour";
244943
+ const scope2 = await scopeOf(req);
244571
244944
  const items = await (await svc(req)).queryUsageStats({
244945
+ ...scope2.companyScoped ? {} : { legacyScope: scope2.legacyScope },
244572
244946
  ...actorIds?.length ? { actorIds } : {},
244573
244947
  ...runtimeInstanceIds?.length ? { runtimeInstanceIds } : {},
244574
244948
  ...runtimeKinds?.length ? { runtimeKinds } : {},
@@ -244693,6 +245067,7 @@ var init_routes9 = __esm({
244693
245067
  init_src();
244694
245068
  init_router();
244695
245069
  init_map2();
245070
+ init_read_scope();
244696
245071
  }
244697
245072
  });
244698
245073
 
@@ -244768,6 +245143,7 @@ function createTraceDomain(opts) {
244768
245143
  register: traceDomain({
244769
245144
  service,
244770
245145
  ...resolveService ? { resolveService } : {},
245146
+ ...opts.resolveReadScope ? { resolveReadScope: opts.resolveReadScope } : {},
244771
245147
  readBundleFile: opts.readBundleFile
244772
245148
  })
244773
245149
  };
@@ -244779,6 +245155,7 @@ var init_trace2 = __esm({
244779
245155
  init_routes9();
244780
245156
  init_service7();
244781
245157
  init_routes9();
245158
+ init_read_scope();
244782
245159
  init_sink();
244783
245160
  init_chat_parts();
244784
245161
  init_trace_health();
@@ -246463,9 +246840,12 @@ function createChatSessionsDomain(opts) {
246463
246840
  }
246464
246841
  return manager ? manager : null;
246465
246842
  }
246466
- function ensureSessionReadable(session, actor, kernel) {
246843
+ function ownsSessionOrOperator(session, auth) {
246844
+ return session.humanActorId === auth.actor || isOperatorRequest(auth);
246845
+ }
246846
+ function ensureSessionReadable(session, auth, kernel) {
246467
246847
  if (!session) throw new ApiError(404, "NOT_FOUND", "session not found");
246468
- if (session.humanActorId === actor) return session;
246848
+ if (ownsSessionOrOperator(session, auth)) return session;
246469
246849
  if (session.workspace) {
246470
246850
  const coordinator = coordinatorOfWorkspace(kernel, session.workspace);
246471
246851
  if (coordinator && session.aiActorId === coordinator) return session;
@@ -246630,7 +247010,7 @@ function createChatSessionsDomain(opts) {
246630
247010
  router.get("/api/chat-sessions/:id", async (req) => {
246631
247011
  const store2 = await storeFor(req);
246632
247012
  const [raw, kernel] = await Promise.all([store2.getSession(req.params.id), kernelFor(req)]);
246633
- const session = ensureSessionReadable(raw, req.auth.actor, kernel);
247013
+ const session = ensureSessionReadable(raw, req.auth, kernel);
246634
247014
  const [window3, curRuntimeId] = await Promise.all([
246635
247015
  listRecentTurns(store2, session.id, parseTurns(req.query?.get("turns"))),
246636
247016
  currentRuntimeId(req.auth.companyId, session.aiActorId)
@@ -246771,7 +247151,7 @@ function createChatSessionsDomain(opts) {
246771
247151
  if (!items) throw new ApiError(404, "NOT_FOUND", "\u672C\u5B9E\u4F8B\u672A\u63A5 chat_items \u5FEB\u7167\u8BFB\u53E3");
246772
247152
  const store2 = await storeFor(req);
246773
247153
  const [session, kernel] = await Promise.all([store2.getSession(req.params.id), kernelFor(req)]);
246774
- ensureSessionReadable(session, req.auth.actor, kernel);
247154
+ ensureSessionReadable(session, req.auth, kernel);
246775
247155
  const chatSessionId = req.params.id;
246776
247156
  const sinceVersion = Math.max(0, Number(req.query?.get("sinceVersion") ?? "0") || 0);
246777
247157
  const beforeOrdRaw = Number(req.query?.get("beforeOrd") ?? "0") || 0;
@@ -246942,7 +247322,7 @@ function createChatSessionsDomain(opts) {
246942
247322
  router.get("/api/chat-sessions/:id/delegations", async (req) => {
246943
247323
  const store2 = await storeFor(req);
246944
247324
  const session = await store2.getSession(req.params.id);
246945
- if (!session || session.humanActorId !== req.auth.actor) throw new ApiError(404, "NOT_FOUND", "session not found");
247325
+ if (!session || !ownsSessionOrOperator(session, req.auth)) throw new ApiError(404, "NOT_FOUND", "session not found");
246946
247326
  const wiring = opts.delegations;
246947
247327
  if (!wiring || !wiring.enabled()) return { status: 200, body: { items: [] } };
246948
247328
  const companyId = session.companyId ?? req.auth.companyId ?? void 0;
@@ -247008,7 +247388,7 @@ function createChatSessionsDomain(opts) {
247008
247388
  router.get("/api/chat-sessions/:id/work-orders", async (req) => {
247009
247389
  const store2 = await storeFor(req);
247010
247390
  const session = await store2.getSession(req.params.id);
247011
- if (!session || session.humanActorId !== req.auth.actor) throw new ApiError(404, "NOT_FOUND", "session not found");
247391
+ if (!session || !ownsSessionOrOperator(session, req.auth)) throw new ApiError(404, "NOT_FOUND", "session not found");
247012
247392
  const workOrderIds = await store2.listWorkOrdersForSession(req.params.id);
247013
247393
  const wkKernel = await kernelFor(req);
247014
247394
  if (!wkKernel || workOrderIds.length === 0) return { status: 200, body: { items: [] } };
@@ -247031,6 +247411,29 @@ function createChatSessionsDomain(opts) {
247031
247411
  });
247032
247412
  return { status: 200, body: { items } };
247033
247413
  });
247414
+ router.get("/api/chat-sessions/:id/usage", async (req) => {
247415
+ const store2 = await storeFor(req);
247416
+ const session = await store2.getSession(req.params.id);
247417
+ if (!session || !ownsSessionOrOperator(session, req.auth)) throw new ApiError(404, "NOT_FOUND", "session not found");
247418
+ const companyId = req.auth.companyId ?? session.companyId ?? opts.defaultCompanyId;
247419
+ if (!opts.usageRead || !companyId) {
247420
+ return { status: 503, body: { error: { code: "usage_ledger_unavailable", message: "\u4F7F\u7528\u4E0E\u7EDF\u8BA1\u6570\u636E\u6E90\u672A\u63A5\u5165" } } };
247421
+ }
247422
+ try {
247423
+ return {
247424
+ status: 200,
247425
+ body: await opts.usageRead.conversationUsage(companyId, session.id, {
247426
+ range: req.query.get("range") ?? void 0,
247427
+ asOf: req.query.get("asOf") ?? void 0,
247428
+ timeZone: req.query.get("timeZone") ?? void 0
247429
+ })
247430
+ };
247431
+ } catch (err) {
247432
+ const mapped = usageErrorResponse(err);
247433
+ if (mapped) return mapped;
247434
+ throw err;
247435
+ }
247436
+ });
247034
247437
  router.get("/api/chat-broadcasts/pending", async (req) => {
247035
247438
  if (!opts.broadcastOps) throw new ApiError(404, "NOT_FOUND", "\u672C\u5B9E\u4F8B\u672A\u542F\u7528\u5DE5\u5355\u7EC8\u6001\u64AD\u62A5");
247036
247439
  const store2 = await storeFor(req);
@@ -247487,6 +247890,8 @@ var init_chat_sessions2 = __esm({
247487
247890
  init_persistent_files();
247488
247891
  init_files_view();
247489
247892
  init_router();
247893
+ init_operator();
247894
+ init_usage_read();
247490
247895
  init_collab();
247491
247896
  init_gap_thread();
247492
247897
  init_escalation_opening();
@@ -257454,6 +257859,18 @@ function companyIdFromMetadata(metadata) {
257454
257859
  const cid = metadata.companyId;
257455
257860
  return typeof cid === "string" && cid.trim() ? cid : void 0;
257456
257861
  }
257862
+ function legacyScopeSql(scope2, args, prefix = "") {
257863
+ const parts = [];
257864
+ args.push(scope2.artifactIds);
257865
+ parts.push(`${prefix}artifact_id = ANY($${args.length}::text[])`);
257866
+ args.push(scope2.chatSessionIds);
257867
+ parts.push(`${prefix}chat_session_id = ANY($${args.length}::text[])`);
257868
+ if (scope2.runtimeInstanceIds?.length) {
257869
+ args.push(scope2.runtimeInstanceIds);
257870
+ parts.push(`${prefix}runtime_instance_id = ANY($${args.length}::text[])`);
257871
+ }
257872
+ return `(${parts.join(" OR ")})`;
257873
+ }
257457
257874
  function normalizeUsagePoint2(input, now) {
257458
257875
  let seq;
257459
257876
  if (input.kind === "checkpoint") {
@@ -258245,16 +258662,23 @@ var init_postgres_trace = __esm({
258245
258662
  if (filter.actorId) push2((n) => `r.actor_id = $${n}`, filter.actorId);
258246
258663
  if (filter.artifactId) push2((n) => `r.artifact_id = $${n}`, filter.artifactId);
258247
258664
  if (filter.projectId) push2((n) => `r.project_id = $${n}`, filter.projectId);
258665
+ if (filter.chatSessionId) push2((n) => `r.chat_session_id = $${n}`, filter.chatSessionId);
258248
258666
  if (filter.runtimeKind) push2((n) => `r.runtime_kind = $${n}`, filter.runtimeKind);
258249
258667
  if (filter.status) push2((n) => `r.status = $${n}`, filter.status);
258250
258668
  if (filter.rootRunId) push2((n) => `r.root_run_id = $${n}`, filter.rootRunId);
258669
+ if (filter.legacyScope) {
258670
+ if (legacyScopeIsEmpty(filter.legacyScope)) return { items: [], nextCursor: null };
258671
+ conds.push(legacyScopeSql(filter.legacyScope, args, "r."));
258672
+ }
258251
258673
  if (filter.from) push2((n) => `r.started_at >= $${n}`, filter.from);
258252
258674
  if (filter.to) push2((n) => `r.started_at <= $${n}`, filter.to);
258253
258675
  if (filter.excludeAnalysis) conds.push(`(r.metadata->>'analyzedRunId') IS NULL`);
258254
258676
  if (filter.cursor) {
258677
+ const cursorArgs = [filter.cursor];
258678
+ const cursorScope = filter.legacyScope ? ` AND ${legacyScopeSql(filter.legacyScope, cursorArgs)}` : "";
258255
258679
  const c = await this.pool.query(
258256
- `SELECT started_at, id FROM ${this.s}.agent_runs WHERE id = $1`,
258257
- [filter.cursor]
258680
+ `SELECT started_at, id FROM ${this.s}.agent_runs WHERE id = $1${cursorScope}`,
258681
+ cursorArgs
258258
258682
  );
258259
258683
  if (c.rows[0]) {
258260
258684
  args.push(c.rows[0].started_at);
@@ -258357,7 +258781,7 @@ var init_postgres_trace = __esm({
258357
258781
  */
258358
258782
  async listRunFailures(workOrderId) {
258359
258783
  const r = await this.pool.query(
258360
- `SELECT node_id, error_message, COALESCE(started_at, created_at) AS at
258784
+ `SELECT node_id, actor_id, error_message, COALESCE(started_at, created_at) AS at
258361
258785
  FROM ${this.s}.agent_runs
258362
258786
  WHERE work_order_id = $1 AND node_id IS NOT NULL
258363
258787
  AND error_message IS NOT NULL AND error_message <> ''
@@ -258366,6 +258790,7 @@ var init_postgres_trace = __esm({
258366
258790
  );
258367
258791
  return r.rows.map((row) => ({
258368
258792
  nodeId: row.node_id,
258793
+ actorId: row.actor_id ?? null,
258369
258794
  startedAt: new Date(row.at).toISOString(),
258370
258795
  message: row.error_message
258371
258796
  }));
@@ -259387,6 +259812,7 @@ var init_postgres_trace = __esm({
259387
259812
  }
259388
259813
  /** 按维度查聚合用量。granularity=day 查 daily 表,否则 hourly。 */
259389
259814
  async queryUsageStats(filter) {
259815
+ if (filter.legacyScope) return this.queryUsageStatsScoped(filter, filter.legacyScope);
259390
259816
  const gran = filter.granularity ?? "hour";
259391
259817
  const table = gran === "day" ? "agent_usage_daily" : "agent_usage_hourly";
259392
259818
  const timeCol = gran === "day" ? "day" : "hour";
@@ -259427,6 +259853,67 @@ var init_postgres_trace = __esm({
259427
259853
  costUsdMicros: Number(row.cost_usd_micros)
259428
259854
  }));
259429
259855
  }
259856
+ /**
259857
+ * legacy 共享账本上的按维度用量:**绕开聚合表,按租户谓词从 `agent_runs` 现算**。
259858
+ *
259859
+ * 为什么不能用 `agent_usage_hourly/daily`:那两张表的仲裁键里没有 artifact / chat_session,
259860
+ * 表达不了 {@link LegacyTenantScope};而且 legacy 落点写入时 `companyIdForWrite` 拿不到
259861
+ * `billingCompanyId`、run.metadata 又不带 companyId,所有未切公司的行都落在同一个
259862
+ * `company_id='default'` 桶里——读它就是所有未切公司互相看见(2026-09-22 泄漏的 usage 面)。
259863
+ *
259864
+ * 口径与 {@link backfillUsageAggregates} 完全一致(只算 `usage` 是对象的 run、按 `started_at`
259865
+ * 落桶、同一组 GROUP BY 键),所以 company-primary 公司读聚合表、legacy 公司现算,两条路
259866
+ * 对同一批 run 给出同一组数字。
259867
+ */
259868
+ async queryUsageStatsScoped(filter, scope2) {
259869
+ if (legacyScopeIsEmpty(scope2)) return [];
259870
+ const gran = filter.granularity ?? "hour";
259871
+ const args = [];
259872
+ const conds = [`jsonb_typeof(usage) = 'object'`, legacyScopeSql(scope2, args)];
259873
+ const push2 = (sql, val) => {
259874
+ args.push(val);
259875
+ conds.push(sql.replace("$$", `$${args.length}`));
259876
+ };
259877
+ if (filter.actorIds?.length) push2(`actor_id = ANY($$)`, filter.actorIds);
259878
+ if (filter.runtimeInstanceIds?.length) push2(`runtime_instance_id = ANY($$)`, filter.runtimeInstanceIds);
259879
+ if (filter.runtimeKinds?.length) push2(`runtime_kind = ANY($$)`, filter.runtimeKinds);
259880
+ if (filter.effectiveModels?.length) push2(`effective_model = ANY($$)`, filter.effectiveModels);
259881
+ const bucket2 = gran === "day" ? `date_trunc('day', started_at::timestamptz)` : `date_trunc('hour', started_at::timestamptz)`;
259882
+ if (filter.from) {
259883
+ push2(`${bucket2} >= $$`, gran === "day" ? this.truncDayISO(new Date(filter.from)) : this.truncHourISO(new Date(filter.from)));
259884
+ }
259885
+ if (filter.to) push2(`${bucket2} <= $$`, filter.to);
259886
+ const r = await this.pool.query(
259887
+ `SELECT actor_id, runtime_instance_id, runtime_kind, effective_model,
259888
+ ${gran === "day" ? `${bucket2}::date::text` : bucket2} AS bucket,
259889
+ COUNT(*)::bigint AS run_count,
259890
+ COALESCE(SUM(COALESCE((usage->>'inputTokens')::bigint, 0)), 0) AS input_tokens,
259891
+ COALESCE(SUM(COALESCE((usage->>'outputTokens')::bigint, 0)), 0) AS output_tokens,
259892
+ COALESCE(SUM(COALESCE((usage->>'totalTokens')::bigint, 0)), 0) AS total_tokens,
259893
+ COALESCE(SUM(COALESCE((usage->>'cacheReadTokens')::bigint, 0)), 0) AS cache_read_tokens,
259894
+ COALESCE(SUM(COALESCE((usage->>'cacheCreationTokens')::bigint, 0)), 0) AS cache_creation_tokens,
259895
+ COALESCE(SUM(COALESCE((usage->>'costUsdMicros')::bigint, 0)), 0) AS cost_usd_micros
259896
+ FROM ${this.s}.agent_runs
259897
+ WHERE ${conds.join(" AND ")}
259898
+ GROUP BY actor_id, runtime_instance_id, runtime_kind, effective_model, ${bucket2}
259899
+ ORDER BY ${bucket2} DESC, runtime_instance_id, actor_id, effective_model`,
259900
+ args
259901
+ );
259902
+ return r.rows.map((row) => ({
259903
+ actorId: row.actor_id,
259904
+ runtimeInstanceId: row.runtime_instance_id ?? null,
259905
+ runtimeKind: row.runtime_kind,
259906
+ effectiveModel: row.effective_model ?? null,
259907
+ bucket: this.formatUsageBucket(row.bucket, gran),
259908
+ runCount: Number(row.run_count),
259909
+ inputTokens: Number(row.input_tokens),
259910
+ outputTokens: Number(row.output_tokens),
259911
+ totalTokens: Number(row.total_tokens),
259912
+ cacheReadTokens: Number(row.cache_read_tokens),
259913
+ cacheCreationTokens: Number(row.cache_creation_tokens),
259914
+ costUsdMicros: Number(row.cost_usd_micros)
259915
+ }));
259916
+ }
259430
259917
  /** hour → 整点 ISO;day → YYYY-MM-DD(优先吃 SQL ::text,Date 兜底用本地日历日)。 */
259431
259918
  formatUsageBucket(raw, gran) {
259432
259919
  if (gran === "day") {
@@ -272317,6 +272804,7 @@ __export(src_exports2, {
272317
272804
  CHAT_TURN_ORPHAN_GRACE_MS: () => CHAT_TURN_ORPHAN_GRACE_MS,
272318
272805
  CLEAR_SESSION_RUNTIME_HINT: () => CLEAR_SESSION_RUNTIME_HINT,
272319
272806
  COMPANY_CROSS_TENANT_PREFIXES: () => COMPANY_CROSS_TENANT_PREFIXES,
272807
+ COMPANY_SCOPED_TRACE_READ: () => COMPANY_SCOPED_TRACE_READ,
272320
272808
  CONSOLE_ROUTE_TABLES: () => CONSOLE_ROUTE_TABLES,
272321
272809
  CONVERSATIONAL_ESCALATION_TOOLS: () => CONVERSATIONAL_ESCALATION_TOOLS,
272322
272810
  CONVERSATIONAL_ETHOS: () => CONVERSATIONAL_ETHOS,
@@ -272345,6 +272833,7 @@ __export(src_exports2, {
272345
272833
  DEFAULT_SIMPLE_QUERY_MAX_CHARS: () => DEFAULT_SIMPLE_QUERY_MAX_CHARS,
272346
272834
  DEFAULT_TERMINAL_MAX_AGE_MS: () => DEFAULT_TERMINAL_MAX_AGE_MS,
272347
272835
  DELIVERY_REPAIR_BATCH: () => DELIVERY_REPAIR_BATCH,
272836
+ DENY_ALL_TRACE_READ: () => DENY_ALL_TRACE_READ,
272348
272837
  DISPATCH_DROP_BACKOFF_MS: () => DISPATCH_DROP_BACKOFF_MS,
272349
272838
  DISPATCH_DROP_GRACE_MS: () => DISPATCH_DROP_GRACE_MS,
272350
272839
  DISPATCH_DROP_MAX_PER_TICK: () => DISPATCH_DROP_MAX_PER_TICK,
@@ -272432,6 +272921,7 @@ __export(src_exports2, {
272432
272921
  NdjsonOplogStore: () => NdjsonOplogStore,
272433
272922
  NodeHealthTracker: () => NodeHealthTracker,
272434
272923
  NotificationLedgerWorker: () => NotificationLedgerWorker,
272924
+ OPERATOR_SCOPE: () => OPERATOR_SCOPE,
272435
272925
  ORPHANED_TURN_ASSISTANT_NOTICE: () => ORPHANED_TURN_ASSISTANT_NOTICE,
272436
272926
  OUTPUT_COPY_LIMITS: () => OUTPUT_COPY_LIMITS,
272437
272927
  PARENT_CONVERSATION_PATH: () => PARENT_CONVERSATION_PATH,
@@ -272668,6 +273158,7 @@ __export(src_exports2, {
272668
273158
  isKnowledgeQueryPlanV2: () => isKnowledgeQueryPlanV2,
272669
273159
  isLocationFailureCode: () => isLocationFailureCode,
272670
273160
  isNodeHealthy: () => isNodeHealthy,
273161
+ isOperatorRequest: () => isOperatorRequest,
272671
273162
  isRunTerminalStatus: () => isRunTerminalStatus,
272672
273163
  isRuntimeDrifted: () => isRuntimeDrifted,
272673
273164
  isRuntimeRunReader: () => isRuntimeRunReader,
@@ -272830,6 +273321,7 @@ __export(src_exports2, {
272830
273321
  scanFrontmatterEntries: () => scanFrontmatterEntries,
272831
273322
  scanSessionOutputs: () => scanSessionOutputs,
272832
273323
  scanZeroOutputBreaker: () => scanZeroOutputBreaker,
273324
+ scopedTraceFilters: () => scopedTraceFilters,
272833
273325
  scoreKnowledgeChunk: () => scoreKnowledgeChunk,
272834
273326
  sealKnowledgeSnapshot: () => sealKnowledgeSnapshot,
272835
273327
  seedRoleCatalog: () => seedRoleCatalog,
@@ -272872,6 +273364,7 @@ __export(src_exports2, {
272872
273364
  toYamlDoubleQuoted: () => toYamlDoubleQuoted,
272873
273365
  touchKey: () => touchKey,
272874
273366
  traceDomain: () => traceDomain,
273367
+ traceReadAllows: () => traceReadAllows,
272875
273368
  trajectoryEventToRunEvent: () => trajectoryEventToRunEvent,
272876
273369
  trustedDelegationInvocationId: () => trustedDelegationInvocationId,
272877
273370
  trustedKnowledgeInvocationId: () => trustedKnowledgeInvocationId,
@@ -272895,6 +273388,7 @@ var init_src11 = __esm({
272895
273388
  "use strict";
272896
273389
  init_server3();
272897
273390
  init_tokens();
273391
+ init_operator();
272898
273392
  init_session_dataplane_token();
272899
273393
  init_dev_store();
272900
273394
  init_control_plane_file_store();
@@ -276566,7 +277060,7 @@ async function startServe(opts) {
276566
277060
  const issuer = createTokenIssuer({ secretFile: path28.join(opts.dir, "session-token-secret") });
276567
277061
  const apiActivity = createApiActivityLedger();
276568
277062
  const operatorActor = `actor:human:${os8.userInfo().username}`;
276569
- const operatorToken = issuer.issue(operatorActor);
277063
+ const operatorToken = issuer.issue(operatorActor, { scopes: [OPERATOR_SCOPE] });
276570
277064
  fs34.writeFileSync(path28.join(opts.dir, "operator-token"), operatorToken, { mode: 384 });
276571
277065
  let nodeTokens = createNodeTokenStore(path28.join(opts.dir, "node-tokens.json"));
276572
277066
  const enrollTokens = createEnrollTokenStore(30 * 60 * 1e3, path28.join(opts.dir, "enroll-tokens.json"));
@@ -277050,6 +277544,8 @@ async function startServe(opts) {
277050
277544
  const trace = createTraceDomain({
277051
277545
  store: traceStore,
277052
277546
  ...pgDsn ? { resolveStore: (cid) => traceStoreFor(cid) } : {},
277547
+ // 轨迹读面的租户闸门。PG 部署必给——不给就是未切 `company-primary` 的公司在共享账本上互相可见。
277548
+ resolveReadScope: (cid) => traceReadScopeFor(cid),
277053
277549
  readBundleFile: (id, rel) => trajReader.readBundleFile(id, rel)
277054
277550
  });
277055
277551
  const traceHealth = createTraceHealth();
@@ -278068,6 +278564,33 @@ async function startServe(opts) {
278068
278564
  const chatTurnsFor = async (companyId) => chatStoreRouter ? (await chatStoreRouter.open(companyId, "chatTurns")).chatTurns ?? chatTurnStore : chatTurnStore;
278069
278565
  const chatItemsFor = async (companyId) => chatStoreRouter ? (await chatStoreRouter.open(companyId, "chatItems")).chatItems : void 0;
278070
278566
  const chatStoreFor = async (companyId) => chatStoreRouter ? (await chatStoreRouter.open(companyId)).chatSessions : chatSessionStore;
278567
+ const traceReadScopeFor = async (companyId) => {
278568
+ if (!pgPool) return COMPANY_SCOPED_TRACE_READ;
278569
+ if (!companyId) return DENY_ALL_TRACE_READ;
278570
+ try {
278571
+ const store = await traceStoreFor(companyId);
278572
+ if (store instanceof CutoverAwareTraceStore && (await store.resolveUsageReadTarget()).companyScoped) {
278573
+ return COMPANY_SCOPED_TRACE_READ;
278574
+ }
278575
+ const [model, sessions, runtimes] = await Promise.all([
278576
+ getEngine(companyId).then((e) => e.kernel.model),
278577
+ chatStoreFor(companyId).then((s2) => s2.listUsageSessionMeta ? s2.listUsageSessionMeta() : []),
278578
+ // 与 usageRead.sources.listRuntimes 同一条读:按公司过滤,归属未定(company_id IS NULL)的存量行不算本公司。
278579
+ nodeStore.listRuntimes(void 0, { companyId })
278580
+ ]);
278581
+ return {
278582
+ companyScoped: false,
278583
+ legacyScope: {
278584
+ artifactIds: [...model.artifacts.keys()],
278585
+ chatSessionIds: sessions.map((s2) => s2.id),
278586
+ runtimeInstanceIds: runtimes.map((r) => r.id)
278587
+ }
278588
+ };
278589
+ } catch (err) {
278590
+ console.warn(`[trace] \u89E3\u6790 ${companyId} \u7684\u8F68\u8FF9\u8BFB\u95F8\u95E8\u5931\u8D25\uFF0C\u672C\u6B21\u6309\u300C\u65E0\u53EF\u89C1\u8BB0\u5F55\u300D\u5904\u7406\uFF1A${String(err)}`);
278591
+ return DENY_ALL_TRACE_READ;
278592
+ }
278593
+ };
278071
278594
  const artifactStateFor = async (companyId) => {
278072
278595
  if (!chatStoreRouter) return artifactStateStore;
278073
278596
  const stores = await chatStoreRouter.open(companyId, "work-order-files");
@@ -278969,7 +279492,11 @@ async function startServe(opts) {
278969
279492
  resolveChatItems: (companyId) => chatItemsFor(companyId),
278970
279493
  liveChat,
278971
279494
  // ADR-0587:列表行派生 `unread`,与 collab 域收件箱读同一份按公司的水位(无 PG 时闭包自己回落单实例)。
278972
- resolveReadMarkers: readMarkerStoreFor
279495
+ resolveReadMarkers: readMarkerStoreFor,
279496
+ // ADR 0609 D4:`GET /api/chat-sessions/:id/usage` 的取数口——**与 collab 域是同一个实例**,
279497
+ // 按会话取数与组织统计因此永远同一份账本、同一套归属,不会出现两条路各算各的。
279498
+ usageRead,
279499
+ defaultCompanyId
278973
279500
  })] : []
278974
279501
  ],
278975
279502
  workorderDrafts,
@@ -279516,7 +280043,9 @@ async function startServe(opts) {
279516
280043
  ...c.dispatchId !== void 0 ? { dispatchId: c.dispatchId } : {},
279517
280044
  // ADR-0078 D3:写入围栏的钥匙
279518
280045
  ...c.artifactId !== void 0 ? { artifactId: c.artifactId } : {},
279519
- ...c.sessionId !== void 0 ? { sessionId: c.sessionId } : {}
280046
+ ...c.sessionId !== void 0 ? { sessionId: c.sessionId } : {},
280047
+ // ADR 0609 D1:签名 scopes 透给域路由,`isOperatorRequest` 据此认出部署级 operator-token。
280048
+ ...c.scopes !== void 0 ? { scopes: c.scopes } : {}
279520
280049
  } : null;
279521
280050
  },
279522
280051
  /**
@@ -287843,7 +288372,7 @@ async function waitForCommandAuthorization(base, headers, draftId, fallback) {
287843
288372
  }
287844
288373
  }
287845
288374
  }
287846
- var CLI_VERSION = "2.2.67" ? "2.2.67" : "dev";
288375
+ var CLI_VERSION = "2.2.69" ? "2.2.69" : "dev";
287847
288376
  function apiHeaders(token, company) {
287848
288377
  const headers = {
287849
288378
  authorization: `Bearer ${token}`,
@@ -291649,7 +292178,7 @@ function shimScript() {
291649
292178
  }
291650
292179
 
291651
292180
  // src/index.ts
291652
- var PKG_VERSION = true ? "2.2.67" : "dev";
292181
+ var PKG_VERSION = true ? "2.2.69" : "dev";
291653
292182
  var LOCAL_BIN = localBin();
291654
292183
  var NPM_PREFIX = npmPrefix();
291655
292184
  var INSTANCE = DEFAULT_INSTANCE;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "oasis_test_v2",
3
- "version": "2.2.67",
3
+ "version": "2.2.69",
4
4
  "description": "Oasis node daemon + CLI — background daemon, auto-start, full server CLI",
5
5
  "bin": {
6
6
  "oasis": "./dist/index.js"
@@ -26,6 +26,6 @@
26
26
  "node": ">=20"
27
27
  },
28
28
  "oasisRelease": {
29
- "sourceHead": "4e649f6aa7bc6633070aefaf241972ac1c42f269"
29
+ "sourceHead": "ac031d8ccbeea2dca6ecc80ad012b61de5ca2888"
30
30
  }
31
31
  }