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.
- package/dist/index.js +573 -44
- 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
|
|
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)
|
|
127001
|
-
|
|
127002
|
-
|
|
127003
|
-
|
|
127004
|
-
|
|
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
|
-
|
|
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
|
|
234050
|
-
const
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
/**
|
|
244213
|
-
|
|
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
|
-
/**
|
|
244233
|
-
|
|
244234
|
-
|
|
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
|
-
|
|
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
|
|
244539
|
-
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
-
|
|
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.
|
|
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.
|
|
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.
|
|
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": "
|
|
29
|
+
"sourceHead": "ac031d8ccbeea2dca6ecc80ad012b61de5ca2888"
|
|
30
30
|
}
|
|
31
31
|
}
|