oasis_test_v2 2.2.103 → 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.
Files changed (2) hide show
  1. package/dist/index.js +176 -26
  2. 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 RUNTIME_QUOTA_POLL_MS, RUNTIME_QUOTA_STALE_MS, RUNTIME_QUOTA_REFRESH_MIN_GAP_MS, statuses;
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
- RUNTIME_QUOTA_POLL_MS = 5 * 6e4;
1126
- RUNTIME_QUOTA_STALE_MS = 2 * RUNTIME_QUOTA_POLL_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
- const policy = await this.io.continuationPolicy(node2.assigneeActorId);
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, quotaStatus) {
246673
+ function quotaClearsFault(fault, quota, now = Date.now()) {
246615
246674
  if (RUNTIME_FAULT_RECOVERY[fault.reason] !== "readonly-probe") return false;
246616
- return quotaStatus === "ok";
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.status)) continue;
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" || quota.windows.length === 0) return { factor: 1 };
248410
- const worst = Math.max(...quota.windows.map((w2) => w2.usedPercent));
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, intervalMs = RUNTIME_QUOTA_POLL_MS, minGapMs = RUNTIME_QUOTA_REFRESH_MIN_GAP_MS, shared = null) {
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.intervalMs = intervalMs;
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, RUNTIME_QUOTA_POLL_MS, RUNTIME_QUOTA_REFRESH_MIN_GAP_MS, sharedDir ? fileQuotaCache(sharedDir) : null);
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.intervalMs) return Promise.resolve();
293902
- this.pending = this.collect(this.intervalMs).finally(() => {
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} 的唯一区别就是**不看那 5 分钟**——那条节流是背景轮询的节奏,
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.103" ? "2.2.103" : "dev";
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.103" : "dev";
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.103",
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": "be78091fa39438c497835a6207b5df1d519afcc9"
29
+ "sourceHead": "962f486e9d39e2c8299be8337837771b2c87fdf0"
30
30
  }
31
31
  }