oasis_test_v2 2.2.104 → 2.2.105
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 +176 -26
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -1118,12 +1118,12 @@ function sanitizeRuntimeQuotas(value2, now = Date.now()) {
|
|
|
1118
1118
|
}
|
|
1119
1119
|
return result;
|
|
1120
1120
|
}
|
|
1121
|
-
var
|
|
1121
|
+
var RUNTIME_QUOTA_REUSE_MS, RUNTIME_QUOTA_STALE_MS, RUNTIME_QUOTA_REFRESH_MIN_GAP_MS, statuses;
|
|
1122
1122
|
var init_runtime_quota = __esm({
|
|
1123
1123
|
"../contract/src/runtime-quota.ts"() {
|
|
1124
1124
|
"use strict";
|
|
1125
|
-
|
|
1126
|
-
RUNTIME_QUOTA_STALE_MS =
|
|
1125
|
+
RUNTIME_QUOTA_REUSE_MS = 10 * 6e4;
|
|
1126
|
+
RUNTIME_QUOTA_STALE_MS = 60 * 6e4;
|
|
1127
1127
|
RUNTIME_QUOTA_REFRESH_MIN_GAP_MS = 3e4;
|
|
1128
1128
|
statuses = /* @__PURE__ */ new Set(["ok", "unavailable", "unauthenticated", "unsupported", "error", "rate_limited"]);
|
|
1129
1129
|
}
|
|
@@ -142037,6 +142037,21 @@ var init_store_postgres = __esm({
|
|
|
142037
142037
|
createdAt: iso(r.createdAt)
|
|
142038
142038
|
}));
|
|
142039
142039
|
}
|
|
142040
|
+
async listLiveNodeAssignees(workorderId) {
|
|
142041
|
+
const res = await this.pool.query(
|
|
142042
|
+
`SELECT n.assignee_actor_id AS "assigneeActorId",
|
|
142043
|
+
n.continuation_budget AS "continuationBudget"
|
|
142044
|
+
FROM ${quoteIdent(this.schema)}.workflow_nodes n
|
|
142045
|
+
JOIN ${quoteIdent(this.schema)}.workorders w ON w.id = n.workorder_id
|
|
142046
|
+
WHERE n.workorder_id = $1 AND w.lifecycle = 'active'
|
|
142047
|
+
AND n.cancelled_at IS NULL AND n.assignee_actor_id IS NOT NULL`,
|
|
142048
|
+
[workorderId]
|
|
142049
|
+
);
|
|
142050
|
+
return res.rows.map((r) => ({
|
|
142051
|
+
assigneeActorId: r.assigneeActorId,
|
|
142052
|
+
continuationBudget: r.continuationBudget ?? null
|
|
142053
|
+
}));
|
|
142054
|
+
}
|
|
142040
142055
|
/* ── intervention 草案卡(见 store.ts 的接口注释)。与上面两口钟同类:直连 pool 的窄读写,
|
|
142041
142056
|
标识符位一律经 quoteIdent——带连字符的公司 schema 裸拼会 syntax error。 ── */
|
|
142042
142057
|
async listInterventionDrafts() {
|
|
@@ -144386,7 +144401,7 @@ function emptySnapshotPlaceholder(record10) {
|
|
|
144386
144401
|
reviews: []
|
|
144387
144402
|
};
|
|
144388
144403
|
}
|
|
144389
|
-
var SYSTEM_ACTOR, NOOP_IO, SYSTEM_CLOCK, HandlerRegistry, DEFAULT_STUCK_APPLIED_RECOVERY_MS, SETTLE_FOREIGN_PROGRESS_FALLBACK_MS, EVENT_RETRY_DELAY_MS, EngineBus, ApplyFailure, sleep3;
|
|
144404
|
+
var SYSTEM_ACTOR, POLICY_MISSING, SCAN_POLICY_ATTEMPTS, NOOP_IO, SYSTEM_CLOCK, HandlerRegistry, DEFAULT_STUCK_APPLIED_RECOVERY_MS, SETTLE_FOREIGN_PROGRESS_FALLBACK_MS, EVENT_RETRY_DELAY_MS, EngineBus, ApplyFailure, sleep3;
|
|
144390
144405
|
var init_bus = __esm({
|
|
144391
144406
|
"../engine/src/bus.ts"() {
|
|
144392
144407
|
"use strict";
|
|
@@ -144400,6 +144415,8 @@ var init_bus = __esm({
|
|
|
144400
144415
|
init_protocol_recovery();
|
|
144401
144416
|
init_continuation();
|
|
144402
144417
|
SYSTEM_ACTOR = "actor:system:engine";
|
|
144418
|
+
POLICY_MISSING = Symbol("continuation-policy-missing");
|
|
144419
|
+
SCAN_POLICY_ATTEMPTS = 3;
|
|
144403
144420
|
NOOP_IO = {
|
|
144404
144421
|
async dispatchWork() {
|
|
144405
144422
|
},
|
|
@@ -145086,6 +145103,38 @@ var init_bus = __esm({
|
|
|
145086
145103
|
* ★ 只在调度器(run)内调用,其他代码不得调。
|
|
145087
145104
|
*/
|
|
145088
145105
|
async scan(workorderId) {
|
|
145106
|
+
for (let attempt = 1; ; attempt++) {
|
|
145107
|
+
const policies = await this.prefetchContinuationPolicies(workorderId);
|
|
145108
|
+
const result = await this.scanOnce(workorderId, policies);
|
|
145109
|
+
if (result !== POLICY_MISSING) return result;
|
|
145110
|
+
if (attempt >= SCAN_POLICY_ATTEMPTS) {
|
|
145111
|
+
console.warn(`[bus] scan ${workorderId}: \u8FDE\u7EED ${attempt} \u6B21\u9501\u5185\u51FA\u73B0\u672A\u9884\u8BFB\u8BBE\u7F6E\u7684\u8D1F\u8D23\u4EBA\uFF0C\u672C\u8F6E\u4E0D\u5224\u65AD`);
|
|
145112
|
+
return { emitted: 0, terminated: false };
|
|
145113
|
+
}
|
|
145114
|
+
}
|
|
145115
|
+
}
|
|
145116
|
+
/**
|
|
145117
|
+
* 事务外预读员工分批执行设置(v2 连接池耗尽,工单 fa17f92a)。
|
|
145118
|
+
*
|
|
145119
|
+
* 读设置要经 `io.continuationPolicy` 向**同一个池**借连接;放在 scan 持锁事务里读,事务那条连接
|
|
145120
|
+
* 一直空等,多家公司同时全扫就全卡在「拿着一条、等第二条」上,直到借连接超时,网页请求一起 503。
|
|
145121
|
+
* 所以先不持任何连接读完、放回池子,再进事务。设置的时效与原来相同——原来那次读本就走另一条
|
|
145122
|
+
* 连接、不在事务快照里。
|
|
145123
|
+
*
|
|
145124
|
+
* 预读范围是锁内跳过条件的超集:额度已冻结(待审/已拒)的节点锁内也不读,这里同样跳过;
|
|
145125
|
+
* 额度是否用完要看 works,窄读拿不到,统统预读。锁内判断仍以锁内读到的状态为准。
|
|
145126
|
+
*/
|
|
145127
|
+
async prefetchContinuationPolicies(workorderId) {
|
|
145128
|
+
const policies = /* @__PURE__ */ new Map();
|
|
145129
|
+
if (!this.io.continuationPolicy) return policies;
|
|
145130
|
+
for (const node2 of await this.store.listLiveNodeAssignees(workorderId)) {
|
|
145131
|
+
const decision = node2.continuationBudget?.request?.decision;
|
|
145132
|
+
if (decision === "pending" || decision === "denied" || policies.has(node2.assigneeActorId)) continue;
|
|
145133
|
+
policies.set(node2.assigneeActorId, await this.io.continuationPolicy(node2.assigneeActorId));
|
|
145134
|
+
}
|
|
145135
|
+
return policies;
|
|
145136
|
+
}
|
|
145137
|
+
async scanOnce(workorderId, prefetched) {
|
|
145089
145138
|
let wrote = false;
|
|
145090
145139
|
let settledAt = null;
|
|
145091
145140
|
const result = await this.store.transaction(async (tx) => {
|
|
@@ -145102,7 +145151,11 @@ var init_bus = __esm({
|
|
|
145102
145151
|
const budget = node2.continuationBudget;
|
|
145103
145152
|
if (budget && (!continuationExhausted(state, node2) || budget.request?.decision === "pending" || budget.request?.decision === "denied")) continue;
|
|
145104
145153
|
if (node2.assigneeActorId && !continuationPolicies.has(node2.assigneeActorId)) {
|
|
145105
|
-
|
|
145154
|
+
if (!prefetched.has(node2.assigneeActorId)) {
|
|
145155
|
+
settledAt = null;
|
|
145156
|
+
return POLICY_MISSING;
|
|
145157
|
+
}
|
|
145158
|
+
const policy = prefetched.get(node2.assigneeActorId) ?? null;
|
|
145106
145159
|
if (policy && (!Number.isSafeInteger(policy.maxRounds) || policy.maxRounds < 1 || policy.maxRounds > 100)) {
|
|
145107
145160
|
throw new Error(`invalid continuation round limit for ${node2.assigneeActorId}`);
|
|
145108
145161
|
}
|
|
@@ -145131,6 +145184,7 @@ var init_bus = __esm({
|
|
|
145131
145184
|
wrote = state.mutations.length > 0 || allEvents.length > 0;
|
|
145132
145185
|
return { emitted: allEvents.length, terminated };
|
|
145133
145186
|
});
|
|
145187
|
+
if (result === POLICY_MISSING) return result;
|
|
145134
145188
|
if (settledAt !== null) {
|
|
145135
145189
|
const prev = this.scanProgress.get(workorderId);
|
|
145136
145190
|
this.scanProgress.set(workorderId, {
|
|
@@ -213246,6 +213300,11 @@ async function startOasisServer(opts) {
|
|
|
213246
213300
|
return;
|
|
213247
213301
|
}
|
|
213248
213302
|
const url = new URL(req.url ?? "/", "http://localhost");
|
|
213303
|
+
if (url.pathname === "/healthz" && url.searchParams.get("deep") === "1") {
|
|
213304
|
+
const db = opts.deepHealth ? await opts.deepHealth() : { ok: true, db: "none" };
|
|
213305
|
+
res.writeHead(db.ok ? 200 : 503, { "content-type": "application/json" }).end(JSON.stringify({ ok: db.ok, ...getBuildInfo(), db }));
|
|
213306
|
+
return;
|
|
213307
|
+
}
|
|
213249
213308
|
if (url.pathname === "/healthz") {
|
|
213250
213309
|
res.writeHead(200, { "content-type": "application/json" }).end(JSON.stringify({ ok: true, ...getBuildInfo() }));
|
|
213251
213310
|
return;
|
|
@@ -246611,9 +246670,15 @@ function faultAfterFailure(previous3, input, now) {
|
|
|
246611
246670
|
}
|
|
246612
246671
|
return { ...base, nextProbeAt: new Date(now + probeIntervalMs(attempts)).toISOString() };
|
|
246613
246672
|
}
|
|
246614
|
-
function quotaClearsFault(fault,
|
|
246673
|
+
function quotaClearsFault(fault, quota, now = Date.now()) {
|
|
246615
246674
|
if (RUNTIME_FAULT_RECOVERY[fault.reason] !== "readonly-probe") return false;
|
|
246616
|
-
|
|
246675
|
+
if (quota?.status !== "ok") return false;
|
|
246676
|
+
const exhaustedUntilFuture = quota.windows.some((w2) => {
|
|
246677
|
+
if (w2.usedPercent < 100 || !w2.resetsAt) return false;
|
|
246678
|
+
const resetsAt = Date.parse(w2.resetsAt);
|
|
246679
|
+
return Number.isFinite(resetsAt) && resetsAt > now;
|
|
246680
|
+
});
|
|
246681
|
+
return exhaustedUntilFuture || fault.awaitingManual;
|
|
246617
246682
|
}
|
|
246618
246683
|
function describeFault(fault, now) {
|
|
246619
246684
|
const head = `${fault.nodeId} \xB7 ${fault.runtimeKind} \u6545\u969C\uFF08${fault.reason}${fault.detail ? `\uFF1A${fault.detail}` : ""}\uFF09`;
|
|
@@ -247645,7 +247710,7 @@ function nodesDomain(deps) {
|
|
|
247645
247710
|
void (async () => {
|
|
247646
247711
|
for (const q2 of quotas) {
|
|
247647
247712
|
const existing = faultByTarget.get(faultKey(daemonId, q2.kind));
|
|
247648
|
-
if (!existing || !quotaClearsFault(existing, q2
|
|
247713
|
+
if (!existing || !quotaClearsFault(existing, q2)) continue;
|
|
247649
247714
|
await nodeStore.clearRuntimeFault(daemonId, q2.kind);
|
|
247650
247715
|
faultByTarget.delete(faultKey(daemonId, q2.kind));
|
|
247651
247716
|
console.log(`[nodes] \u53EA\u8BFB\u91C7\u96C6\u56DE ok\uFF0C\u81EA\u52A8\u89E3\u9664\u6545\u969C\uFF1A${daemonId} \xB7 ${q2.kind}\uFF08\u539F\u56E0 ${existing.reason}\uFF09`);
|
|
@@ -248405,13 +248470,20 @@ function atCapacity(node2) {
|
|
|
248405
248470
|
if (limit === null || limit === void 0) return false;
|
|
248406
248471
|
return node2.activeRunCount >= limit;
|
|
248407
248472
|
}
|
|
248408
|
-
function quotaDerateFactor(quota) {
|
|
248409
|
-
if (!quota || quota.status !== "ok"
|
|
248410
|
-
const
|
|
248473
|
+
function quotaDerateFactor(quota, now = Date.now()) {
|
|
248474
|
+
if (!quota || quota.status !== "ok") return { factor: 1 };
|
|
248475
|
+
const windows = quota.windows.filter((w2) => windowStillCurrent(w2, now));
|
|
248476
|
+
if (windows.length === 0) return { factor: 1 };
|
|
248477
|
+
const worst = Math.max(...windows.map((w2) => w2.usedPercent));
|
|
248411
248478
|
if (worst <= RUNTIME_QUOTA_DERATE_THRESHOLD_PERCENT) return { factor: 1, worstUsedPercent: worst };
|
|
248412
248479
|
const remaining = Math.max(0, 100 - worst);
|
|
248413
248480
|
return { factor: remaining / (100 - RUNTIME_QUOTA_DERATE_THRESHOLD_PERCENT), worstUsedPercent: worst };
|
|
248414
248481
|
}
|
|
248482
|
+
function windowStillCurrent(window3, now) {
|
|
248483
|
+
if (!window3.resetsAt) return true;
|
|
248484
|
+
const resetsAt = Date.parse(window3.resetsAt);
|
|
248485
|
+
return !Number.isFinite(resetsAt) || resetsAt > now;
|
|
248486
|
+
}
|
|
248415
248487
|
function faultFromQuota(target, quota, now) {
|
|
248416
248488
|
if (!quota) return null;
|
|
248417
248489
|
const base = { nodeId: target.nodeId, runtimeKind: target.runtimeKind, markedAt: quota.checkedAt, attempts: 0 };
|
|
@@ -248419,7 +248491,7 @@ function faultFromQuota(target, quota, now) {
|
|
|
248419
248491
|
return { ...base, reason: "unauthenticated", awaitingManual: true, detail: "\u8FD0\u884C\u65F6\u767B\u5F55\u5DF2\u5931\u6548\uFF0C\u9700\u8981\u5728\u8BE5\u673A\u5668\u4E0A\u91CD\u65B0\u767B\u5F55" };
|
|
248420
248492
|
}
|
|
248421
248493
|
if (quota.status !== "ok") return null;
|
|
248422
|
-
const exhausted = quota.windows.filter((w2) => w2.usedPercent >= 100);
|
|
248494
|
+
const exhausted = quota.windows.filter((w2) => w2.usedPercent >= 100 && windowStillCurrent(w2, now));
|
|
248423
248495
|
if (exhausted.length === 0) return null;
|
|
248424
248496
|
const resets = exhausted.map((w2) => w2.resetsAt).filter((v2) => Boolean(v2)).sort();
|
|
248425
248497
|
const latest = resets.at(-1);
|
|
@@ -248503,7 +248575,7 @@ function allocateRuntime(input, now = Date.now()) {
|
|
|
248503
248575
|
const rank = (candidate) => {
|
|
248504
248576
|
const node2 = input.nodes.get(candidate.nodeId);
|
|
248505
248577
|
const idleness = idlenessOf(node2);
|
|
248506
|
-
const { factor, worstUsedPercent } = quotaDerateFactor(input.quotas?.get(runtimeTargetKey(candidate)));
|
|
248578
|
+
const { factor, worstUsedPercent } = quotaDerateFactor(input.quotas?.get(runtimeTargetKey(candidate)), now);
|
|
248507
248579
|
return {
|
|
248508
248580
|
candidate,
|
|
248509
248581
|
nodeName: node2.displayName?.trim() || candidate.nodeId,
|
|
@@ -248748,6 +248820,7 @@ function createRuntimeAllocator(ports) {
|
|
|
248748
248820
|
}, now);
|
|
248749
248821
|
await ports.saveFault(fault);
|
|
248750
248822
|
log3(`[runtime-alloc] ${describeFault(fault, now)}`);
|
|
248823
|
+
if (reason === "quota-exhausted" && !previous3) ports.onQuotaExhausted?.(target);
|
|
248751
248824
|
return { chained, fault };
|
|
248752
248825
|
} catch (err) {
|
|
248753
248826
|
log3(`[runtime-alloc] \u6545\u969C\u843D\u5E93\u5931\u8D25\uFF08\u6362\u5019\u9009\u7167\u5E38\uFF09${target.nodeId}\xB7${target.runtimeKind}\uFF1A${String(err)}`);
|
|
@@ -263845,6 +263918,32 @@ function createPgPool(dsn, opts) {
|
|
|
263845
263918
|
connectionTimeoutMillis: opts?.connectionTimeoutMillis ?? 5e3
|
|
263846
263919
|
});
|
|
263847
263920
|
}
|
|
263921
|
+
async function probePgPool(pool, timeoutMs = 2e3) {
|
|
263922
|
+
const startedAt = Date.now();
|
|
263923
|
+
const stats = () => ({ max: pool.options.max, total: pool.totalCount, idle: pool.idleCount, waiting: pool.waitingCount });
|
|
263924
|
+
const attempt = (async () => {
|
|
263925
|
+
const client = await pool.connect();
|
|
263926
|
+
try {
|
|
263927
|
+
await client.query("SELECT 1");
|
|
263928
|
+
} finally {
|
|
263929
|
+
client.release();
|
|
263930
|
+
}
|
|
263931
|
+
})();
|
|
263932
|
+
let timer;
|
|
263933
|
+
const timeout = new Promise((_2, reject) => {
|
|
263934
|
+
timer = setTimeout(() => reject(new Error(`\u501F\u8FDE\u63A5\u8D85\u8FC7 ${timeoutMs}ms`)), timeoutMs);
|
|
263935
|
+
});
|
|
263936
|
+
try {
|
|
263937
|
+
await Promise.race([attempt, timeout]);
|
|
263938
|
+
return { ok: true, ms: Date.now() - startedAt, pool: stats() };
|
|
263939
|
+
} catch (err) {
|
|
263940
|
+
attempt.catch(() => {
|
|
263941
|
+
});
|
|
263942
|
+
return { ok: false, ms: Date.now() - startedAt, error: err instanceof Error ? err.message : String(err), pool: stats() };
|
|
263943
|
+
} finally {
|
|
263944
|
+
clearTimeout(timer);
|
|
263945
|
+
}
|
|
263946
|
+
}
|
|
263848
263947
|
async function dropSchemaIfExists(dsn, schema) {
|
|
263849
263948
|
const pool = createPgPool(dsn, { max: 1 });
|
|
263850
263949
|
try {
|
|
@@ -279789,6 +279888,7 @@ __export(src_exports, {
|
|
|
279789
279888
|
payloadChecksum: () => payloadChecksum,
|
|
279790
279889
|
pinChatSessionsToAgentDefault: () => pinChatSessionsToAgentDefault,
|
|
279791
279890
|
planSessionOrd: () => planSessionOrd,
|
|
279891
|
+
probePgPool: () => probePgPool,
|
|
279792
279892
|
provisionCompanyDataPlaneSchema: () => provisionCompanyDataPlaneSchema,
|
|
279793
279893
|
provisionNewCompanyDataPlane: () => provisionNewCompanyDataPlane,
|
|
279794
279894
|
quoteIdent: () => quoteIdent2,
|
|
@@ -287849,6 +287949,13 @@ async function startServe(opts) {
|
|
|
287849
287949
|
clearFault: async (t) => {
|
|
287850
287950
|
await nodeStore.clearRuntimeFault(t.nodeId, t.runtimeKind);
|
|
287851
287951
|
},
|
|
287952
|
+
/* ADR 0633:撞限流 → 叫那台机器现去问一遍额度。只发给自报能重采的节点;结果帧没人等,
|
|
287953
|
+
serve 那边按不认识的 requestId 丢掉,新数据照旧走 runtime_quotas 落库;用满了才解除落库故障、改按重置时刻挡。 */
|
|
287954
|
+
onQuotaExhausted: (t) => {
|
|
287955
|
+
const daemon = hub?.connectedDaemons().find((d) => d.daemonId === t.nodeId);
|
|
287956
|
+
if (!daemon?.meta.supportsQuotaRefresh) return;
|
|
287957
|
+
hub?.dispatch(t.nodeId, { type: "refresh_quotas", requestId: (0, import_node_crypto91.randomUUID)() });
|
|
287958
|
+
},
|
|
287852
287959
|
actorName: async (actorId) => (await (await actors.resolveCtx(companyId)).service.getActor(actorId).catch(() => null))?.name ?? null
|
|
287853
287960
|
});
|
|
287854
287961
|
runtimeAllocators.set(companyId, allocator);
|
|
@@ -288067,6 +288174,7 @@ async function startServe(opts) {
|
|
|
288067
288174
|
blobs: engine.blobs,
|
|
288068
288175
|
// 自动化 webhook 公开入口(ADR 0156 D4):token 即凭证,归属从触发器行反推。
|
|
288069
288176
|
automationWebhook: (token2, rawBody, headers) => automations.webhook.handle(token2, rawBody, headers),
|
|
288177
|
+
...pgPool ? { deepHealth: () => probePgPool(pgPool) } : {},
|
|
288070
288178
|
...engine.assets ? { assets: engine.assets } : {},
|
|
288071
288179
|
// ★ 新引擎 store:blob GC 从 work_artifacts 扫根(事实源=实体表,oplog 已退役)。
|
|
288072
288180
|
engineStore: engine.kernel.getStore?.(),
|
|
@@ -293857,49 +293965,71 @@ function fileQuotaCache(dir, env = process.env) {
|
|
|
293857
293965
|
(0, import_node_fs25.renameSync)(tmp, target);
|
|
293858
293966
|
} catch {
|
|
293859
293967
|
}
|
|
293968
|
+
},
|
|
293969
|
+
claim(kind, ttlMs) {
|
|
293970
|
+
const lock = `${file(kind)}.lock`;
|
|
293971
|
+
for (let attempt = 0; attempt < 2; attempt++) {
|
|
293972
|
+
try {
|
|
293973
|
+
(0, import_node_fs25.mkdirSync)(dir, { recursive: true, mode: 448 });
|
|
293974
|
+
(0, import_node_fs25.writeFileSync)(lock, String(process.pid), { flag: "wx", mode: 384 });
|
|
293975
|
+
return true;
|
|
293976
|
+
} catch (err) {
|
|
293977
|
+
if (err.code !== "EEXIST") return true;
|
|
293978
|
+
try {
|
|
293979
|
+
if (Date.now() - (0, import_node_fs25.statSync)(lock).mtimeMs < ttlMs) return false;
|
|
293980
|
+
(0, import_node_fs25.unlinkSync)(lock);
|
|
293981
|
+
} catch {
|
|
293982
|
+
}
|
|
293983
|
+
}
|
|
293984
|
+
}
|
|
293985
|
+
return false;
|
|
293986
|
+
},
|
|
293987
|
+
release(kind) {
|
|
293988
|
+
try {
|
|
293989
|
+
(0, import_node_fs25.unlinkSync)(`${file(kind)}.lock`);
|
|
293990
|
+
} catch {
|
|
293991
|
+
}
|
|
293860
293992
|
}
|
|
293861
293993
|
};
|
|
293862
293994
|
}
|
|
293863
293995
|
|
|
293864
293996
|
// ../cli/src/daemon/quota/monitor.ts
|
|
293997
|
+
var CLAIM_TTL_MS = 3e4;
|
|
293998
|
+
var PEER_POLL_MS = 500;
|
|
293865
293999
|
var read2 = (runtime, signal) => runtime.kind === "codex" ? readCodexQuota(runtime.binary ?? "codex", signal) : readClaudeQuota(runtime.binary ?? "claude", signal, { version: runtime.version });
|
|
293866
294000
|
var RuntimeQuotaMonitor = class _RuntimeQuotaMonitor {
|
|
293867
|
-
constructor(runtimes, send, reader = read2,
|
|
294001
|
+
constructor(runtimes, send, reader = read2, reuseMs = RUNTIME_QUOTA_REUSE_MS, minGapMs = RUNTIME_QUOTA_REFRESH_MIN_GAP_MS, shared = null) {
|
|
293868
294002
|
this.runtimes = runtimes;
|
|
293869
294003
|
this.send = send;
|
|
293870
294004
|
this.reader = reader;
|
|
293871
|
-
this.
|
|
294005
|
+
this.reuseMs = reuseMs;
|
|
293872
294006
|
this.minGapMs = minGapMs;
|
|
293873
294007
|
this.shared = shared;
|
|
293874
294008
|
}
|
|
293875
294009
|
cache = [];
|
|
293876
294010
|
collectedAt;
|
|
293877
294011
|
pending;
|
|
293878
|
-
timer;
|
|
293879
294012
|
abort = new AbortController();
|
|
293880
294013
|
stopped = false;
|
|
293881
294014
|
/** daemon 用的那一份:同机同账号的其它 daemon 刚采过就直接用它的结果(见 shared-cache.ts)。 */
|
|
293882
294015
|
static forNode(runtimes, send, sharedDir) {
|
|
293883
|
-
return new _RuntimeQuotaMonitor(runtimes, send, read2,
|
|
294016
|
+
return new _RuntimeQuotaMonitor(runtimes, send, read2, RUNTIME_QUOTA_REUSE_MS, RUNTIME_QUOTA_REFRESH_MIN_GAP_MS, sharedDir ? fileQuotaCache(sharedDir) : null);
|
|
293884
294017
|
}
|
|
293885
294018
|
connected() {
|
|
293886
294019
|
if (this.stopped) return;
|
|
293887
294020
|
if (this.cache.length) this.send(this.cache);
|
|
293888
|
-
if (!this.timer) this.timer = setInterval(() => {
|
|
293889
|
-
void this.refresh();
|
|
293890
|
-
}, this.intervalMs);
|
|
293891
294021
|
void this.refresh();
|
|
293892
294022
|
}
|
|
293893
294023
|
stop() {
|
|
293894
294024
|
this.stopped = true;
|
|
293895
|
-
if (this.timer) clearInterval(this.timer);
|
|
293896
294025
|
this.abort.abort();
|
|
293897
294026
|
}
|
|
294027
|
+
/** 连接时那一次:复用期内采过(自己或同账号的别的 daemon)就不再问上游。 */
|
|
293898
294028
|
refresh() {
|
|
293899
294029
|
if (this.stopped) return Promise.resolve();
|
|
293900
294030
|
if (this.pending) return this.pending;
|
|
293901
|
-
if (this.collectedAt !== void 0 && Date.now() - this.collectedAt < this.
|
|
293902
|
-
this.pending = this.collect(this.
|
|
294031
|
+
if (this.collectedAt !== void 0 && Date.now() - this.collectedAt < this.reuseMs) return Promise.resolve();
|
|
294032
|
+
this.pending = this.collect(this.reuseMs).finally(() => {
|
|
293903
294033
|
this.pending = void 0;
|
|
293904
294034
|
});
|
|
293905
294035
|
return this.pending;
|
|
@@ -293907,7 +294037,7 @@ var RuntimeQuotaMonitor = class _RuntimeQuotaMonitor {
|
|
|
293907
294037
|
/**
|
|
293908
294038
|
* 人点了页面上的「刷新」(服务端下发 `refresh_quotas`,ADR 0620):**现在就去问上游**。
|
|
293909
294039
|
*
|
|
293910
|
-
* 与 {@link refresh} 的唯一区别就是**不看那
|
|
294040
|
+
* 与 {@link refresh} 的唯一区别就是**不看那 10 分钟复用期**——那条是连接时防重复的闸,
|
|
293911
294041
|
* 对一个正盯着页面等结果的人没有任何意义。换上的是一条窄得多的闸:
|
|
293912
294042
|
* {@link RUNTIME_QUOTA_REFRESH_MIN_GAP_MS}(30 秒)。连点被它挡下,且挡下时**如实说**
|
|
293913
294043
|
* 「刚在 N 秒前采过」,不假装采了一次(发起人方申的定则:会静默降级的动作必须回报走了哪条路)。
|
|
@@ -293939,6 +294069,16 @@ var RuntimeQuotaMonitor = class _RuntimeQuotaMonitor {
|
|
|
293939
294069
|
for (const r of this.runtimes) if (r.kind === "codex" || r.kind === "claude-code") kinds.push(r.kind);
|
|
293940
294070
|
return kinds;
|
|
293941
294071
|
}
|
|
294072
|
+
/** 等持锁的那个 daemon 把结果写进共享缓存;等不到(它挂了 / 采失败不外传)就回 undefined,自己去问。 */
|
|
294073
|
+
async awaitPeer(kind) {
|
|
294074
|
+
const since = Date.now();
|
|
294075
|
+
while (!this.stopped && Date.now() - since < CLAIM_TTL_MS) {
|
|
294076
|
+
await new Promise((r) => setTimeout(r, PEER_POLL_MS));
|
|
294077
|
+
const peer = this.shared?.get(kind);
|
|
294078
|
+
if (peer && Date.parse(peer.checkedAt) >= since - PEER_POLL_MS) return peer;
|
|
294079
|
+
}
|
|
294080
|
+
return void 0;
|
|
294081
|
+
}
|
|
293942
294082
|
/** `maxAgeMs`:同机其它 daemon 采到的结果在这个时长内就直接用,不再去问上游。 */
|
|
293943
294083
|
async collect(maxAgeMs) {
|
|
293944
294084
|
const result = [];
|
|
@@ -293956,6 +294096,15 @@ var RuntimeQuotaMonitor = class _RuntimeQuotaMonitor {
|
|
|
293956
294096
|
result.push({ ...peer, kind });
|
|
293957
294097
|
continue;
|
|
293958
294098
|
}
|
|
294099
|
+
const held = this.shared?.claim ? this.shared.claim(kind, CLAIM_TTL_MS) : false;
|
|
294100
|
+
if (this.shared?.claim && !held) {
|
|
294101
|
+
const theirs = await this.awaitPeer(kind);
|
|
294102
|
+
if (this.stopped) return;
|
|
294103
|
+
if (theirs) {
|
|
294104
|
+
result.push({ ...theirs, kind });
|
|
294105
|
+
continue;
|
|
294106
|
+
}
|
|
294107
|
+
}
|
|
293959
294108
|
let quota;
|
|
293960
294109
|
try {
|
|
293961
294110
|
quota = await this.reader(runtime, this.abort.signal);
|
|
@@ -293963,6 +294112,7 @@ var RuntimeQuotaMonitor = class _RuntimeQuotaMonitor {
|
|
|
293963
294112
|
quota = { status: "error", windows: [], checkedAt: (/* @__PURE__ */ new Date()).toISOString() };
|
|
293964
294113
|
}
|
|
293965
294114
|
if (quota.status === "ok" || quota.status === "rate_limited") this.shared?.put(kind, quota);
|
|
294115
|
+
if (held) this.shared?.release?.(kind);
|
|
293966
294116
|
result.push({ ...quota, kind });
|
|
293967
294117
|
}
|
|
293968
294118
|
if (this.stopped) return;
|
|
@@ -297914,7 +298064,7 @@ async function waitForCommandAuthorization(base, headers, draftId, fallback) {
|
|
|
297914
298064
|
}
|
|
297915
298065
|
}
|
|
297916
298066
|
}
|
|
297917
|
-
var CLI_VERSION = "2.2.
|
|
298067
|
+
var CLI_VERSION = "2.2.105" ? "2.2.105" : "dev";
|
|
297918
298068
|
function apiHeaders(token2, company) {
|
|
297919
298069
|
const headers = {
|
|
297920
298070
|
authorization: `Bearer ${token2}`,
|
|
@@ -301950,7 +302100,7 @@ function shimScript() {
|
|
|
301950
302100
|
}
|
|
301951
302101
|
|
|
301952
302102
|
// src/index.ts
|
|
301953
|
-
var PKG_VERSION = true ? "2.2.
|
|
302103
|
+
var PKG_VERSION = true ? "2.2.105" : "dev";
|
|
301954
302104
|
var LOCAL_BIN = localBin();
|
|
301955
302105
|
var NPM_PREFIX = npmPrefix();
|
|
301956
302106
|
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.105",
|
|
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": "962f486e9d39e2c8299be8337837771b2c87fdf0"
|
|
30
30
|
}
|
|
31
31
|
}
|