oasis_test_v2 2.2.113 → 2.2.114

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 +333 -18
  2. package/package.json +2 -2
package/dist/index.js CHANGED
@@ -20479,7 +20479,8 @@ var init_feishu = __esm({
20479
20479
  "FEISHU_REFRESH_TOKEN_EXPIRES_AT",
20480
20480
  FEISHU_USER_OPEN_ID_VAR,
20481
20481
  FEISHU_AUTHORIZED_BY_VAR,
20482
- FEISHU_USER_AUTH_INVALID_VAR
20482
+ FEISHU_USER_AUTH_INVALID_VAR,
20483
+ "FEISHU_AUTH_PENDING_APPROVAL"
20483
20484
  ];
20484
20485
  /** 宿主机登录态隔离,理由同 github(见接口注释):由通用装配层无条件设。 */
20485
20486
  hostCredentialIsolationEnvKeys = ["LARKSUITE_CLI_CONFIG_DIR"];
@@ -20671,7 +20672,8 @@ async function pollFeishuDeviceToken(input) {
20671
20672
  const data = await resp.json();
20672
20673
  const errStr = data["error"];
20673
20674
  if (errStr === "authorization_pending" || errStr === "slow_down") return { status: "pending" };
20674
- if (!resp.ok || errStr) throw new Error(String(data["error_description"] ?? errStr ?? "login failed"));
20675
+ if (errStr) throw new FeishuDeviceFlowError(errStr, String(data["error_description"] ?? errStr));
20676
+ if (!resp.ok) throw new Error(String(data["error_description"] ?? "login failed"));
20675
20677
  if (!data["access_token"]) throw new Error("no access_token in response");
20676
20678
  return { status: "ok", data };
20677
20679
  }
@@ -20720,7 +20722,7 @@ function feishuUserAuthInvalidText(input) {
20720
20722
  ${input.link}` : `${head}
20721
20723
  \u8BF7\u5230 Oasis \u8BE5 Agent \u7684\u300C\u8FDE\u63A5\u5668\u300D\u9875\u91CD\u65B0\u6388\u6743\u98DE\u4E66\u3002`;
20722
20724
  }
20723
- var FEISHU_DEVICE_AUTHORIZATION_URL, FEISHU_USER_INFO_URL, FEISHU_SEND_MESSAGE_URL, FEISHU_BATCH_GET_ID_URL;
20725
+ var FEISHU_DEVICE_AUTHORIZATION_URL, FEISHU_USER_INFO_URL, FEISHU_SEND_MESSAGE_URL, FEISHU_BATCH_GET_ID_URL, FeishuDeviceFlowError;
20724
20726
  var init_user_auth = __esm({
20725
20727
  "../connectors/src/feishu/user-auth.ts"() {
20726
20728
  "use strict";
@@ -20729,6 +20731,78 @@ var init_user_auth = __esm({
20729
20731
  FEISHU_USER_INFO_URL = "https://open.feishu.cn/open-apis/authen/v1/user_info";
20730
20732
  FEISHU_SEND_MESSAGE_URL = "https://open.feishu.cn/open-apis/im/v1/messages";
20731
20733
  FEISHU_BATCH_GET_ID_URL = "https://open.feishu.cn/open-apis/contact/v3/users/batch_get_id";
20734
+ FeishuDeviceFlowError = class extends Error {
20735
+ constructor(code2, message) {
20736
+ super(message);
20737
+ this.code = code2;
20738
+ this.name = "FeishuDeviceFlowError";
20739
+ }
20740
+ };
20741
+ }
20742
+ });
20743
+
20744
+ // ../connectors/src/feishu/pending-approval.ts
20745
+ function feishuPendingApprovalScopes(input) {
20746
+ const granted = input.granted ? new Set(input.granted) : null;
20747
+ const missing = input.requested.filter((s2) => s2 !== "offline_access" && !granted?.has(s2));
20748
+ if (!input.appScopes) return missing.filter((s2) => !AUTO_APPROVED2.has(s2));
20749
+ const byName = userScopeIndex(input.appScopes);
20750
+ return missing.filter((s2) => byName.has(s2) && !isGranted(byName.get(s2)));
20751
+ }
20752
+ function feishuPendingApprovalSettled(pending, appScopes) {
20753
+ const byName = userScopeIndex(appScopes);
20754
+ return pending.pendingScopes.every((s2) => isGranted(byName.get(s2)));
20755
+ }
20756
+ function feishuPendingApprovalModules(pending) {
20757
+ const waiting = new Set(pending.pendingScopes);
20758
+ return pending.modules.filter((m2) => (FEISHU_DOMAIN_SCOPES[m2]?.scopes ?? []).some((s2) => waiting.has(s2)));
20759
+ }
20760
+ async function readFeishuPendingApproval(store, actorId) {
20761
+ const raw = await store.revealVariable(FEISHU_AUTH_PENDING_APPROVAL_VAR, actorId).catch(() => null);
20762
+ if (!raw) return null;
20763
+ try {
20764
+ const p2 = JSON.parse(raw);
20765
+ return typeof p2?.at === "string" && typeof p2.appId === "string" && Array.isArray(p2.modules) && Array.isArray(p2.pendingScopes) ? p2 : null;
20766
+ } catch {
20767
+ return null;
20768
+ }
20769
+ }
20770
+ async function writeFeishuPendingApproval(store, actorId, pending) {
20771
+ await store.putVariable({
20772
+ key: FEISHU_AUTH_PENDING_APPROVAL_VAR,
20773
+ value: JSON.stringify(pending),
20774
+ ...actorId ? { scope: "personal", actorId } : { scope: "global" },
20775
+ connectorId: "feishu",
20776
+ encrypted: false
20777
+ });
20778
+ }
20779
+ async function clearFeishuPendingApproval(store, actorId) {
20780
+ await store.deleteVariable(FEISHU_AUTH_PENDING_APPROVAL_VAR, actorId);
20781
+ }
20782
+ function feishuApprovalGrantedText(input) {
20783
+ const head = `\u7BA1\u7406\u5458\u5DF2\u6279\u51C6\u300C${input.agentName}\u300D\u7533\u8BF7\u7684\u98DE\u4E66\u6743\u9650\uFF08${moduleNames(input.modules)}\uFF09\u3002\u8FD8\u5DEE\u4F60\u70B9\u4E00\u6B21\u6388\u6743\uFF0C\u8FD9\u90E8\u5206\u529F\u80FD\u624D\u80FD\u4EE5\u4F60\u7684\u8EAB\u4EFD\u4F7F\u7528\u3002`;
20784
+ return input.link ? `${head}
20785
+ \u70B9\u8FD9\u4E2A\u94FE\u63A5\u8865\u4E0A\u6743\u9650\uFF08\u9700\u7528\u6536\u5230\u8FD9\u6761\u79C1\u4FE1\u7684\u98DE\u4E66\u8D26\u53F7\u767B\u5F55\uFF0C7 \u5929\u5185\u6709\u6548\uFF09\uFF1A
20786
+ ${input.link}` : `${head}
20787
+ \u8BF7\u5230 Oasis \u8BE5 Agent \u7684\u300C\u8FDE\u63A5\u5668\u300D\u9875\u91CD\u65B0\u8FDE\u63A5\u98DE\u4E66\u3002`;
20788
+ }
20789
+ function feishuPendingApprovalNotice(modules) {
20790
+ return `${moduleNames(modules)}\u542B\u9700\u98DE\u4E66\u7BA1\u7406\u5458\u5BA1\u6279\u7684\u6743\u9650\uFF0C\u5DF2\u63D0\u4EA4\u5BA1\u6279\u3002\u6279\u51C6\u540E\u4F1A\u5728\u98DE\u4E66\u79C1\u4FE1\u901A\u77E5\u4F60\uFF0C\u70B9\u4E00\u4E0B\u5373\u53EF\u8865\u4E0A\u8FD9\u90E8\u5206\u6743\u9650\uFF1B\u5DF2\u6709\u7684\u98DE\u4E66\u80FD\u529B\u4E0D\u53D7\u5F71\u54CD\u3002`;
20791
+ }
20792
+ var FEISHU_AUTH_PENDING_APPROVAL_VAR, FEISHU_PENDING_APPROVAL_WATCH_MS, FEISHU_APPROVED_SUPPLEMENT_KEEP_MS, AUTO_APPROVED2, userScopeIndex, isGranted, moduleNames;
20793
+ var init_pending_approval = __esm({
20794
+ "../connectors/src/feishu/pending-approval.ts"() {
20795
+ "use strict";
20796
+ init_scopes();
20797
+ FEISHU_AUTH_PENDING_APPROVAL_VAR = "FEISHU_AUTH_PENDING_APPROVAL";
20798
+ FEISHU_PENDING_APPROVAL_WATCH_MS = 30 * 24 * 36e5;
20799
+ FEISHU_APPROVED_SUPPLEMENT_KEEP_MS = 7 * 24 * 36e5;
20800
+ AUTO_APPROVED2 = new Set(FEISHU_BASIC_SCOPES);
20801
+ userScopeIndex = (appScopes) => new Map(
20802
+ appScopes.filter((s2) => s2.scope_name && (!s2.scope_type || s2.scope_type === "user")).map((s2) => [s2.scope_name, s2])
20803
+ );
20804
+ isGranted = (s2) => !!s2 && (s2.grant_status === void 0 || s2.grant_status === 1);
20805
+ moduleNames = (modules) => modules.map((m2) => FEISHU_MODULE_LABELS[m2] ?? m2).join("\u3001") || "\u6240\u7533\u8BF7\u7684";
20732
20806
  }
20733
20807
  });
20734
20808
 
@@ -24653,6 +24727,7 @@ var init_src3 = __esm({
24653
24727
  init_host_isolation();
24654
24728
  init_feishu();
24655
24729
  init_user_auth();
24730
+ init_pending_approval();
24656
24731
  init_scopes();
24657
24732
  init_oauth();
24658
24733
  init_scheduled_refresh();
@@ -24674,6 +24749,14 @@ var init_src3 = __esm({
24674
24749
  });
24675
24750
 
24676
24751
  // ../server/src/feishu-reauth.ts
24752
+ async function supplementSelection(store, actorId) {
24753
+ const pending = await readFeishuPendingApproval(store, actorId);
24754
+ if (!pending) return null;
24755
+ const appId = await store.revealResolvedVariable("FEISHU_APP_ID", actorId).catch(() => null);
24756
+ if (appId !== pending.appId) return null;
24757
+ const sel = normalizeFeishuAuthSelection({ modules: pending.modules, sendAsUser: pending.sendAsUser });
24758
+ return sel ? { sel, sendAsUser: pending.sendAsUserRequested, pending } : null;
24759
+ }
24677
24760
  async function reauthSelection(store, actorId) {
24678
24761
  const raw = await store.revealVariable(FEISHU_AUTH_SELECTION_VAR, actorId).catch(() => null);
24679
24762
  if (raw) {
@@ -24714,17 +24797,31 @@ function createFeishuReauth(deps) {
24714
24797
  };
24715
24798
  }
24716
24799
  await withConnectorCredentialLock("feishu", claims.actorId, async () => {
24800
+ const pending2 = claims.purpose === "supplement" ? await readFeishuPendingApproval(store, claims.actorId) : null;
24717
24801
  await persistFeishuUserAuthorization(store, claims.actorId, data, now(), {
24718
24802
  openId: who.openId,
24719
- ...who.name ? { userName: who.name } : {}
24803
+ ...who.name ? { userName: who.name } : {},
24804
+ ...pending2?.applicant ? { authorizedBy: pending2.applicant } : {}
24720
24805
  });
24806
+ const record10 = feishuAuthSelectionRecord(sel, sendAsUserRequested, data);
24721
24807
  await store.putVariable({
24722
24808
  key: FEISHU_AUTH_SELECTION_VAR,
24723
- value: JSON.stringify(feishuAuthSelectionRecord(sel, sendAsUserRequested, data)),
24809
+ value: JSON.stringify(record10),
24724
24810
  ...claims.actorId ? { scope: "personal", actorId: claims.actorId } : { scope: "global" },
24725
24811
  connectorId: "feishu",
24726
24812
  encrypted: false
24727
24813
  });
24814
+ if (pending2) {
24815
+ const granted = record10.grantedScopes ? new Set(record10.grantedScopes) : null;
24816
+ const still = granted ? pending2.pendingScopes.filter((x2) => !granted.has(x2)) : [];
24817
+ if (still.length === 0) await clearFeishuPendingApproval(store, claims.actorId);
24818
+ else {
24819
+ const next = { ...pending2, pendingScopes: still };
24820
+ delete next.approvedAt;
24821
+ delete next.notice;
24822
+ await writeFeishuPendingApproval(store, claims.actorId, next);
24823
+ }
24824
+ }
24728
24825
  });
24729
24826
  await deps.onRestored?.(claims, { openId: who.openId, ...who.name ? { userName: who.name } : {} }).catch((e) => warn(`onRestored: ${String(e)}`));
24730
24827
  return { state: "restored", ...who.name ? { userName: who.name } : {} };
@@ -24756,7 +24853,15 @@ function createFeishuReauth(deps) {
24756
24853
  if (!store) return { ok: false, status: 404, message: "\u627E\u4E0D\u5230\u8FD9\u4E2A\u94FE\u63A5\u6240\u5C5E\u7684\u7EC4\u7EC7\u3002" };
24757
24854
  const app = await appCredentials(store, claims.actorId);
24758
24855
  if (!app) return { ok: false, status: 409, message: "\u8FD9\u540D Agent \u7684\u98DE\u4E66\u5E94\u7528\u51ED\u636E\u5DF2\u4E0D\u5B58\u5728\uFF0C\u65E0\u6CD5\u91CD\u65B0\u6388\u6743\u3002\u8BF7\u5230 Oasis \u8FDE\u63A5\u5668\u9875\u91CD\u65B0\u8FDE\u63A5\u98DE\u4E66\u3002" };
24759
- const { sel, sendAsUser } = await reauthSelection(store, claims.actorId);
24856
+ let picked;
24857
+ if (claims.purpose === "supplement") {
24858
+ const sup = await supplementSelection(store, claims.actorId);
24859
+ if (!sup) return { ok: false, status: 410, message: "\u8FD9\u6B21\u8865\u6388\u6743\u5DF2\u7ECF\u5B8C\u6210\u6216\u5DF2\u5931\u6548\u3002\u8BF7\u5230 Oasis \u8BE5 Agent \u7684\u300C\u8FDE\u63A5\u5668\u300D\u9875\u67E5\u770B\uFF0C\u9700\u8981\u65F6\u91CD\u65B0\u8FDE\u63A5\u98DE\u4E66\u3002" };
24860
+ picked = sup;
24861
+ } else {
24862
+ picked = await reauthSelection(store, claims.actorId);
24863
+ }
24864
+ const { sel, sendAsUser } = picked;
24760
24865
  const auth = await (deps.start ?? startFeishuDeviceAuthorization)({
24761
24866
  appId: app.appId,
24762
24867
  appSecret: app.appSecret,
@@ -24771,7 +24876,7 @@ function createFeishuReauth(deps) {
24771
24876
  const intervalSec = auth.interval ?? DEFAULT_POLL_INTERVAL_SEC;
24772
24877
  void watch(auth.deviceCode, entry, { store, ...app, sel, sendAsUser, intervalSec });
24773
24878
  const agentName = await deps.agentName?.(claims.companyId, claims.actorId).catch(() => void 0) ?? claims.actorId ?? "\u7EC4\u7EC7";
24774
- return { ok: true, verificationUrl: auth.verificationUrl, deviceCode: auth.deviceCode, agentName, intervalSec };
24879
+ return { ok: true, verificationUrl: auth.verificationUrl, deviceCode: auth.deviceCode, agentName, intervalSec, purpose: claims.purpose };
24775
24880
  },
24776
24881
  /** 页面轮询结果。票与 deviceCode 必须是同一次 begin 发出来的。 */
24777
24882
  status(token2, deviceCode) {
@@ -24788,17 +24893,18 @@ function createFeishuReauth(deps) {
24788
24893
  function feishuReauthPageHtml(input) {
24789
24894
  const head = `<!doctype html><html lang="zh-CN"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><title>\u91CD\u65B0\u6388\u6743\u98DE\u4E66</title>
24790
24895
  <style>body{font-family:system-ui,-apple-system,sans-serif;max-width:32rem;margin:3rem auto;padding:0 1.25rem;color:#1f2329;line-height:1.6}a.btn{display:inline-block;background:#1f2329;color:#fff;padding:.6rem 1.2rem;border-radius:.5rem;text-decoration:none}#s{margin-top:1.25rem}</style></head><body>`;
24791
- if (!input.ok) return `${head}<h2>\u65E0\u6CD5\u91CD\u65B0\u6388\u6743</h2><p>${escapeHtml(input.message)}</p></body></html>`;
24792
- const cfg = JSON.stringify({ t: input.token, d: input.deviceCode, i: Math.max(2, input.intervalSec) }).replace(/</g, "\\u003c");
24793
- return `${head}<h2>\u91CD\u65B0\u6388\u6743\u98DE\u4E66\uFF1A${escapeHtml(input.agentName)}</h2>
24794
- <p>\u8BF7\u7528<strong>\u539F\u6388\u6743\u4EBA</strong>\u7684\u98DE\u4E66\u8D26\u53F7\u5B8C\u6210\u6388\u6743\u3002\u6362\u6210\u522B\u7684\u8D26\u53F7\u6388\u6743\u4F1A\u88AB\u62D2\u7EDD\uFF0C\u539F\u6388\u6743\u4FDD\u6301\u4E0D\u53D8\u3002</p>
24896
+ if (!input.ok) return `${head}<h2>\u65E0\u6CD5\u5B8C\u6210\u6388\u6743</h2><p>${escapeHtml(input.message)}</p></body></html>`;
24897
+ const supplement = input.purpose === "supplement";
24898
+ const cfg = JSON.stringify({ t: input.token, d: input.deviceCode, i: Math.max(2, input.intervalSec), s: supplement }).replace(/</g, "\\u003c");
24899
+ return `${head}<h2>${supplement ? "\u8865\u6388\u6743\u98DE\u4E66\uFF08\u7BA1\u7406\u5458\u5DF2\u6279\u51C6\uFF09" : "\u91CD\u65B0\u6388\u6743\u98DE\u4E66"}\uFF1A${escapeHtml(input.agentName)}</h2>
24900
+ <p>\u8BF7\u7528<strong>${supplement ? "\u7533\u8BF7\u4EBA" : "\u539F\u6388\u6743\u4EBA"}</strong>\u7684\u98DE\u4E66\u8D26\u53F7\u5B8C\u6210\u6388\u6743\u3002\u6362\u6210\u522B\u7684\u8D26\u53F7\u6388\u6743\u4F1A\u88AB\u62D2\u7EDD\uFF0C\u539F\u6388\u6743\u4FDD\u6301\u4E0D\u53D8\u3002</p>
24795
24901
  <p><a class="btn" href="${escapeHtml(input.verificationUrl)}" target="_blank" rel="noopener">\u53BB\u98DE\u4E66\u6388\u6743</a></p>
24796
24902
  <p id="s">\u7B49\u5F85\u4F60\u5728\u98DE\u4E66\u70B9\u300C\u6388\u6743\u300D\u2026</p>
24797
24903
  <script>(function(){var c=${cfg};var el=document.getElementById("s");
24798
24904
  function tick(){fetch("reauth/status",{method:"POST",headers:{"content-type":"application/json"},body:JSON.stringify({t:c.t,deviceCode:c.d})})
24799
24905
  .then(function(r){return r.json()}).then(function(b){
24800
24906
  if(b.state==="pending"){setTimeout(tick,c.i*1000);return}
24801
- if(b.state==="restored"){el.textContent="\u5DF2\u6062\u590D\uFF1A"+(b.userName?b.userName+" \u7684":"")+"\u98DE\u4E66\u7528\u6237\u6388\u6743\u91CD\u65B0\u751F\u6548\uFF0C\u53EF\u4EE5\u5173\u6389\u8FD9\u4E2A\u9875\u9762\u4E86\u3002";return}
24907
+ if(b.state==="restored"){el.textContent=(c.s?"\u5DF2\u8865\u4E0A\uFF1A":"\u5DF2\u6062\u590D\uFF1A")+(b.userName?b.userName+" \u7684":"")+(c.s?"\u98DE\u4E66\u7528\u6237\u6388\u6743\u5DF2\u5305\u542B\u7BA1\u7406\u5458\u6279\u51C6\u7684\u6743\u9650":"\u98DE\u4E66\u7528\u6237\u6388\u6743\u91CD\u65B0\u751F\u6548")+"\uFF0C\u53EF\u4EE5\u5173\u6389\u8FD9\u4E2A\u9875\u9762\u4E86\u3002";return}
24802
24908
  el.textContent=b.message||"\u6388\u6743\u6CA1\u6709\u5B8C\u6210\uFF0C\u53EF\u4EE5\u91CD\u65B0\u70B9\u94FE\u63A5\u518D\u8BD5\u3002"}).catch(function(){setTimeout(tick,c.i*1000)})}
24803
24909
  setTimeout(tick,c.i*1000)})();</script></body></html>`;
24804
24910
  }
@@ -212197,6 +212303,42 @@ var init_feishu_module_access = __esm({
212197
212303
  }
212198
212304
  });
212199
212305
 
212306
+ // ../server/src/feishu-pending-approval.ts
212307
+ async function recordFeishuPendingApproval(input) {
212308
+ const requested = scopesForSelection(input.selection, input.sendAsUserRequested);
212309
+ let appScopes = null;
212310
+ try {
212311
+ appScopes = await (input.fetchScopes ?? fetchFeishuAppScopes)(input.appId, input.appSecret);
212312
+ } catch (e) {
212313
+ (input.warn ?? ((m2) => console.warn(`[feishu-oauth] ${m2}`)))(`\u5E94\u7528\u6743\u9650\u5217\u8868\u67E5\u8BE2\u5931\u8D25\uFF0C\u5F85\u5BA1\u6279\u6309\u514D\u5BA1\u96C6\u5408\u4F30\u7B97: ${String(e)}`);
212314
+ }
212315
+ const pendingScopes = feishuPendingApprovalScopes({ requested, granted: input.granted, appScopes });
212316
+ if (pendingScopes.length === 0) {
212317
+ if (input.granted) {
212318
+ await withConnectorCredentialLock("feishu", input.actorId, () => clearFeishuPendingApproval(input.store, input.actorId));
212319
+ }
212320
+ return null;
212321
+ }
212322
+ const record10 = {
212323
+ at: new Date(input.now).toISOString(),
212324
+ appId: input.appId,
212325
+ ...input.applicant ? { applicant: input.applicant } : {},
212326
+ modules: input.selection.modules,
212327
+ sendAsUser: input.selection.sendAsUser,
212328
+ sendAsUserRequested: input.sendAsUserRequested,
212329
+ pendingScopes
212330
+ };
212331
+ await withConnectorCredentialLock("feishu", input.actorId, () => writeFeishuPendingApproval(input.store, input.actorId, record10));
212332
+ const modules = feishuPendingApprovalModules(record10);
212333
+ return { modules, scopes: pendingScopes, notice: feishuPendingApprovalNotice(modules) };
212334
+ }
212335
+ var init_feishu_pending_approval = __esm({
212336
+ "../server/src/feishu-pending-approval.ts"() {
212337
+ "use strict";
212338
+ init_src3();
212339
+ }
212340
+ });
212341
+
212200
212342
  // ../server/src/github-app-inventory.ts
212201
212343
  async function collectOasisGithubApps(svc) {
212202
212344
  const rows = (await svc.listVariables()).filter((r) => r.scope === "global" || r.scope === "personal");
@@ -218498,12 +218640,40 @@ ${composed}`;
218498
218640
  const pollSvc = await connectorActorsService();
218499
218641
  const appSecret = pollSvc ? await pollSvc.revealResolvedVariable("FEISHU_APP_SECRET", actorId) : null;
218500
218642
  if (!appSecret) throw new Error("FEISHU_APP_SECRET not found \u2014 please complete app setup first");
218501
- const polled = await pollFeishuDeviceToken({ appId, appSecret, deviceCode });
218643
+ let polled;
218644
+ try {
218645
+ polled = await pollFeishuDeviceToken({ appId, appSecret, deviceCode });
218646
+ } catch (e) {
218647
+ if (!(e instanceof FeishuDeviceFlowError) || !pollSvc) throw e;
218648
+ const pendingApproval2 = await recordFeishuPendingApproval({
218649
+ store: pollSvc,
218650
+ actorId,
218651
+ appId,
218652
+ appSecret,
218653
+ selection: startedSelection.selection,
218654
+ sendAsUserRequested: startedSelection.sendAsUserRequested,
218655
+ ...isHumanActorId(actor) ? { applicant: actor } : {},
218656
+ now: Date.now()
218657
+ });
218658
+ const expired = e.code === "expired_token" || e.code === "invalid_grant";
218659
+ res.writeHead(200, { "content-type": "application/json" }).end(JSON.stringify(pendingApproval2 ? {
218660
+ ok: false,
218661
+ final: true,
218662
+ pendingApproval: { modules: pendingApproval2.modules, scopes: pendingApproval2.scopes },
218663
+ notice: expired ? `\u6388\u6743\u9875\u5DF2\u8D85\u65F6\u3002\u5982\u679C\u4F60\u5728\u6388\u6743\u9875\u70B9\u4E86\u300C\u7533\u8BF7\u300D\uFF1A${pendingApproval2.notice}\u6CA1\u70B9\u7684\u8BDD\u91CD\u65B0\u53D1\u8D77\u6388\u6743\u5373\u53EF\u3002` : pendingApproval2.notice
218664
+ } : {
218665
+ ok: false,
218666
+ final: true,
218667
+ error: expired ? "\u6388\u6743\u8D85\u65F6\uFF08\u8BBE\u5907\u7801\u5DF2\u8FC7\u671F\uFF09\uFF0C\u8BF7\u91CD\u65B0\u53D1\u8D77\u6388\u6743" : `\u98DE\u4E66\u6CA1\u6709\u5B8C\u6210\u6388\u6743\uFF08${e.message.slice(0, 200)}\uFF09\uFF0C\u53EF\u4EE5\u91CD\u65B0\u53D1\u8D77\u6388\u6743`
218668
+ }));
218669
+ return;
218670
+ }
218502
218671
  if (polled.status === "pending") {
218503
218672
  res.writeHead(200).end(JSON.stringify({ ok: false, error: "authorization_pending" }));
218504
218673
  return;
218505
218674
  }
218506
218675
  const data = polled.data;
218676
+ let pendingApproval = null;
218507
218677
  const svc = await connectorActorsService();
218508
218678
  if (svc) {
218509
218679
  if (actorId) {
@@ -218528,6 +218698,23 @@ ${composed}`;
218528
218698
  connectorId: "feishu",
218529
218699
  encrypted: false
218530
218700
  });
218701
+ if (record10.grantedScopes) {
218702
+ try {
218703
+ pendingApproval = await recordFeishuPendingApproval({
218704
+ store: svc,
218705
+ actorId,
218706
+ appId,
218707
+ appSecret,
218708
+ selection: startedSelection.selection,
218709
+ sendAsUserRequested: startedSelection.sendAsUserRequested,
218710
+ ...isHumanActorId(actor) ? { applicant: actor } : {},
218711
+ granted: record10.grantedScopes,
218712
+ now: Date.now()
218713
+ });
218714
+ } catch (e) {
218715
+ console.error(`[feishu-oauth] \u5F85\u5BA1\u6279\u8BB0\u5F55\u5199\u5165\u5931\u8D25: ${String(e)}`);
218716
+ }
218717
+ }
218531
218718
  if (!who.openId) console.error("[feishu-oauth] user_info \u672A\u53D6\u5230\u6388\u6743\u4EBA open_id\uFF1A\u6388\u6743\u5931\u6548\u65F6\u65E0\u6CD5\u79C1\u4FE1\u672C\u4EBA\u3001\u4E5F\u7B7E\u4E0D\u51FA\u4E00\u952E\u91CD\u65B0\u6388\u6743\u94FE\u63A5");
218532
218719
  try {
218533
218720
  const secret = await svc.revealResolvedVariable("FEISHU_APP_SECRET", actorId);
@@ -218552,7 +218739,12 @@ ${composed}`;
218552
218739
  }
218553
218740
  }
218554
218741
  }
218555
- res.writeHead(200, { "content-type": "application/json" }).end(JSON.stringify({ ok: true, appId, stored: true }));
218742
+ res.writeHead(200, { "content-type": "application/json" }).end(JSON.stringify({
218743
+ ok: true,
218744
+ appId,
218745
+ stored: true,
218746
+ ...pendingApproval ? { pendingApproval: { modules: pendingApproval.modules, scopes: pendingApproval.scopes }, notice: pendingApproval.notice } : {}
218747
+ }));
218556
218748
  } catch (e) {
218557
218749
  res.writeHead(500).end(JSON.stringify({ error: String(e) }));
218558
218750
  }
@@ -218895,6 +219087,7 @@ var init_server3 = __esm({
218895
219087
  init_child_live_turn();
218896
219088
  init_registry_audit_sink();
218897
219089
  init_feishu_module_access();
219090
+ init_feishu_pending_approval();
218898
219091
  init_src3();
218899
219092
  init_github_app_inventory();
218900
219093
  init_github_app_timing();
@@ -287145,6 +287338,122 @@ function createConnectorRenewalRuntime(options) {
287145
287338
  }
287146
287339
  for (const k2 of handledThisSweep) if (JSON.parse(k2)[0] === companyId) handledThisSweep.delete(k2);
287147
287340
  };
287341
+ const applicantOpenId = async (companyId, store, actorId, app, applicant) => {
287342
+ if (!applicant || !isHumanActorId(applicant)) return null;
287343
+ const by = await store.revealVariable(FEISHU_AUTHORIZED_BY_VAR, actorId).catch(() => null);
287344
+ const stored = by === applicant ? await store.revealVariable(FEISHU_USER_OPEN_ID_VAR, actorId).catch(() => null) : null;
287345
+ return stored || recoverOpenId(companyId, app, applicant);
287346
+ };
287347
+ const deliverApprovalNotice = async (companyId, store, actorId, pending, app) => {
287348
+ const prev = pending.notice ?? { inApp: false, dm: "pending", attempts: 0 };
287349
+ const state = { ...prev, attempts: prev.attempts + 1 };
287350
+ const who = `actor=${actorId ?? "-"} \u7B2C ${state.attempts}/${MAX_NOTICE_ATTEMPTS} \u6B21`;
287351
+ const name = await agentName(companyId, actorId);
287352
+ const modules = feishuPendingApprovalModules(pending);
287353
+ if (!state.inApp) {
287354
+ try {
287355
+ const ledger = await options.notifications(companyId);
287356
+ if (!ledger) throw new Error("\u8BE5\u516C\u53F8\u7684\u901A\u77E5\u53F0\u8D26\u4E0D\u53EF\u7528");
287357
+ const recipients = pending.applicant && isHumanActorId(pending.applicant) ? [pending.applicant] : (await recipientsFor(companyId, store, actorId)).recipients;
287358
+ if (recipients.length === 0) throw new Error("\u6CA1\u6709\u6536\u4EF6\u4EBA\uFF08\u6CA1\u8BB0\u7533\u8BF7\u4EBA\u3001\u5458\u5DE5\u4E5F\u6CA1\u6709\u7ED1\u5B9A\u771F\u4EBA\uFF09");
287359
+ const rows = [];
287360
+ for (const recipient of recipients) {
287361
+ rows.push(await ledger.insert({
287362
+ recipient,
287363
+ category: "connector",
287364
+ kind: "feishu-approval-granted",
287365
+ // 每次申请一条(键里带提交时刻);同一次批准补发 / 重复扫描不重发(唯一约束)。
287366
+ sourceKey: `connector:feishu:${actorId ?? "org"}@approved:${pending.at}`,
287367
+ title: `\u7BA1\u7406\u5458\u5DF2\u6279\u51C6\u300C${name}\u300D\u7533\u8BF7\u7684\u98DE\u4E66\u6743\u9650\uFF0C\u8865\u6388\u6743\u540E\u5373\u53EF\u4F7F\u7528`,
287368
+ ...actorId ? { actorId } : {},
287369
+ enteredAt: pending.approvedAt ?? new Date(now()).toISOString()
287370
+ }));
287371
+ }
287372
+ options.onNotifications?.(companyId, rows);
287373
+ state.inApp = true;
287374
+ } catch (err) {
287375
+ options.warn(new Error(`\u98DE\u4E66\u6743\u9650\u6279\u51C6\u7AD9\u5185\u901A\u77E5\u672A\u5199\u5165\uFF08${who}\uFF0C\u4E0B\u4E00\u62CD\u91CD\u8BD5\uFF09: ${String(err)}`));
287376
+ }
287377
+ }
287378
+ if (state.dm === "pending") {
287379
+ try {
287380
+ const openId = await applicantOpenId(companyId, store, actorId, app, pending.applicant);
287381
+ if (!openId) {
287382
+ state.dm = "unavailable";
287383
+ console.warn(`[credential-refresh] \u98DE\u4E66\u6743\u9650\u6279\u51C6\u79C1\u4FE1\u65E0\u4ECE\u53D1\u51FA\uFF08${pending.applicant ? "\u901A\u8BAF\u5F55\u6CA1\u6309\u7533\u8BF7\u4EBA\u7684\u8D26\u53F7\u90AE\u7BB1\u627E\u5230\u4ED6" : "\u6CA1\u8BB0\u7533\u8BF7\u4EBA"}\uFF09actor=${actorId ?? "-"}\uFF1B\u53EA\u6709\u7AD9\u5185\u901A\u77E5\uFF0C\u9700\u5230\u8FDE\u63A5\u5668\u9875\u91CD\u65B0\u8FDE\u63A5`);
287384
+ } else {
287385
+ const token2 = options.links.issue({
287386
+ companyId,
287387
+ ...actorId ? { actorId } : {},
287388
+ connectorId: "feishu",
287389
+ purpose: "supplement",
287390
+ subject: openId
287391
+ });
287392
+ const link = options.apiBase ? feishuReauthUrl(options.apiBase, token2) : void 0;
287393
+ await notifyFeishuUser(store, actorId, feishuApprovalGrantedText({ agentName: name, modules, ...link ? { link } : {} }), openId);
287394
+ state.dm = "sent";
287395
+ }
287396
+ } catch (err) {
287397
+ options.warn(new Error(`\u98DE\u4E66\u6743\u9650\u6279\u51C6\u79C1\u4FE1\u53D1\u9001\u5931\u8D25\uFF08${who}\uFF0C\u4E0B\u4E00\u62CD\u91CD\u8BD5\uFF09: ${String(err)}`));
287398
+ }
287399
+ }
287400
+ if ((!state.inApp || state.dm === "pending") && state.attempts >= MAX_NOTICE_ATTEMPTS) {
287401
+ options.warn(new Error(`\u98DE\u4E66\u6743\u9650\u6279\u51C6\u901A\u77E5\u8865\u53D1 ${state.attempts} \u6B21\u4ECD\u672A\u9001\u5230\uFF0C\u653E\u5F03 actor=${actorId ?? "-"} \u7AD9\u5185=${String(state.inApp)} \u79C1\u4FE1=${state.dm}`));
287402
+ }
287403
+ return state;
287404
+ };
287405
+ const savePendingApproval = async (store, actorId, at2, next) => {
287406
+ await withConnectorCredentialLock("feishu", actorId, async () => {
287407
+ const current = await readFeishuPendingApproval(store, actorId);
287408
+ if (current?.at !== at2) return;
287409
+ if (next) await writeFeishuPendingApproval(store, actorId, next);
287410
+ else await clearFeishuPendingApproval(store, actorId);
287411
+ });
287412
+ };
287413
+ const checkPendingApproval = async (companyId, store, actorId) => {
287414
+ const pending = await readFeishuPendingApproval(store, actorId);
287415
+ if (!pending) return;
287416
+ const t = now();
287417
+ const app = await appCredentials(store, actorId);
287418
+ if (!app || app.appId !== pending.appId) {
287419
+ console.warn(`[credential-refresh] \u98DE\u4E66\u5F85\u5BA1\u6279\u8BB0\u5F55\u4F5C\u5E9F\uFF08\u5E94\u7528\u51ED\u636E${app ? "\u5DF2\u6362\u6210\u522B\u7684\u5E94\u7528" : "\u4E0D\u5B58\u5728"}\uFF09actor=${actorId ?? "-"}`);
287420
+ await savePendingApproval(store, actorId, pending.at, null);
287421
+ return;
287422
+ }
287423
+ let next;
287424
+ if (!pending.approvedAt) {
287425
+ if (t - Date.parse(pending.at) > FEISHU_PENDING_APPROVAL_WATCH_MS) {
287426
+ options.warn(new Error(`\u98DE\u4E66\u5F85\u5BA1\u6279 ${pending.at} \u8D77 30 \u5929\u672A\u68C0\u6D4B\u5230\u6279\u51C6\uFF0C\u4E0D\u518D\u68C0\u6D4B actor=${actorId ?? "-"} \u6743\u9650=${pending.pendingScopes.join(",")}`));
287427
+ await savePendingApproval(store, actorId, pending.at, null);
287428
+ return;
287429
+ }
287430
+ const scopes = await (options.fetchAppScopes ?? fetchFeishuAppScopes)(app.appId, app.appSecret);
287431
+ if (!feishuPendingApprovalSettled(pending, scopes)) return;
287432
+ next = { ...pending, approvedAt: new Date(t).toISOString(), notice: { inApp: false, dm: "pending", attempts: 0 } };
287433
+ console.warn(`[credential-refresh] \u98DE\u4E66\u5F85\u5BA1\u6279\u5DF2\u6279\u51C6 actor=${actorId ?? "-"} \u6743\u9650=${pending.pendingScopes.join(",")}`);
287434
+ } else {
287435
+ if (t - Date.parse(pending.approvedAt) > FEISHU_APPROVED_SUPPLEMENT_KEEP_MS) {
287436
+ await savePendingApproval(store, actorId, pending.at, null);
287437
+ return;
287438
+ }
287439
+ const n = pending.notice;
287440
+ if (!n || n.inApp && n.dm !== "pending" || n.attempts >= MAX_NOTICE_ATTEMPTS) return;
287441
+ next = pending;
287442
+ }
287443
+ await savePendingApproval(store, actorId, pending.at, { ...next, notice: await deliverApprovalNotice(companyId, store, actorId, next, app) });
287444
+ };
287445
+ const checkPendingApprovals = async (companyId, store) => {
287446
+ const svc = await options.actors(companyId);
287447
+ if (!svc) return;
287448
+ const ids2 = [void 0, ...(await svc.listActors({ status: "active" })).map((a) => a.id)];
287449
+ for (const actorId of ids2) {
287450
+ try {
287451
+ await checkPendingApproval(companyId, store, actorId);
287452
+ } catch (err) {
287453
+ options.warn(new Error(`\u98DE\u4E66\u5F85\u5BA1\u6279\u68C0\u6D4B\u5931\u8D25\uFF08\u4E0B\u4E00\u62CD\u91CD\u8BD5\uFF09actor=${actorId ?? "-"}: ${String(err)}`));
287454
+ }
287455
+ }
287456
+ };
287148
287457
  const onFeishuRenewed = async (e, store) => {
287149
287458
  if (await store.revealVariable(FEISHU_USER_OPEN_ID_VAR, e.actorId)) return;
287150
287459
  const accessToken = e.outcome.credentials?.["user_access_token"];
@@ -287183,7 +287492,13 @@ function createConnectorRenewalRuntime(options) {
287183
287492
  onRenewed: async (e, store) => {
287184
287493
  if (e.connector.config.slug === "feishu") await onFeishuRenewed(e, store);
287185
287494
  },
287186
- afterCompany: retryPendingNotices,
287495
+ afterCompany: async (companyId, store) => {
287496
+ try {
287497
+ await retryPendingNotices(companyId, store);
287498
+ } finally {
287499
+ await checkPendingApprovals(companyId, store);
287500
+ }
287501
+ },
287187
287502
  warn: options.warn,
287188
287503
  ...options.now ? { now: options.now } : {}
287189
287504
  });
@@ -287195,7 +287510,7 @@ function createConnectorRenewalRuntime(options) {
287195
287510
  const store = await options.actors(claims.companyId);
287196
287511
  if (!store) return;
287197
287512
  const name = await agentName(claims.companyId, claims.actorId);
287198
- await notifyFeishuUser(store, claims.actorId, `\u300C${name}\u300D\u7684\u98DE\u4E66\u7528\u6237\u6388\u6743\u5DF2\u6062\u590D\u3002`).catch(() => void 0);
287513
+ await notifyFeishuUser(store, claims.actorId, claims.purpose === "supplement" ? `\u300C${name}\u300D\u7684\u98DE\u4E66\u6743\u9650\u5DF2\u8865\u4E0A\uFF0C\u7BA1\u7406\u5458\u6279\u51C6\u7684\u529F\u80FD\u73B0\u5728\u53EF\u4EE5\u4F7F\u7528\u4E86\u3002` : `\u300C${name}\u300D\u7684\u98DE\u4E66\u7528\u6237\u6388\u6743\u5DF2\u6062\u590D\u3002`).catch(() => void 0);
287199
287514
  },
287200
287515
  onRejected: async (claims, who) => {
287201
287516
  const store = await options.actors(claims.companyId);
@@ -301084,7 +301399,7 @@ async function waitForCommandAuthorization(base, headers, draftId, fallback) {
301084
301399
  }
301085
301400
  }
301086
301401
  }
301087
- var CLI_VERSION = "2.2.113" ? "2.2.113" : "dev";
301402
+ var CLI_VERSION = "2.2.114" ? "2.2.114" : "dev";
301088
301403
  function apiHeaders(token2, company) {
301089
301404
  const headers = {
301090
301405
  authorization: `Bearer ${token2}`,
@@ -305255,7 +305570,7 @@ function shimScript() {
305255
305570
  }
305256
305571
 
305257
305572
  // src/index.ts
305258
- var PKG_VERSION = true ? "2.2.113" : "dev";
305573
+ var PKG_VERSION = true ? "2.2.114" : "dev";
305259
305574
  var LOCAL_BIN = localBin();
305260
305575
  var NPM_PREFIX = npmPrefix();
305261
305576
  var INSTANCE = DEFAULT_INSTANCE;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "oasis_test_v2",
3
- "version": "2.2.113",
3
+ "version": "2.2.114",
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": "590c9d80c3286fa10fdb0eb14de96140b1520130"
29
+ "sourceHead": "46e4f9275679ea8eec37f0f1e30b45cc48e3e70c"
30
30
  }
31
31
  }