oasis_test 0.1.86 → 0.1.89

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 +994 -276
  2. package/package.json +2 -2
package/dist/index.js CHANGED
@@ -2488,6 +2488,24 @@ var init_registry = __esm({
2488
2488
  }
2489
2489
  });
2490
2490
 
2491
+ // ../contract/src/credential-ref.ts
2492
+ function makeVarRef(key) {
2493
+ return `${VAR_REF_PREFIX}${key}`;
2494
+ }
2495
+ function parseVarRef(s2) {
2496
+ if (!s2.startsWith(VAR_REF_PREFIX)) return null;
2497
+ const key = s2.slice(VAR_REF_PREFIX.length);
2498
+ if (key.length === 0 || /[\s/]/.test(key)) return null;
2499
+ return key;
2500
+ }
2501
+ var VAR_REF_PREFIX;
2502
+ var init_credential_ref = __esm({
2503
+ "../contract/src/credential-ref.ts"() {
2504
+ "use strict";
2505
+ VAR_REF_PREFIX = "oasis://var/";
2506
+ }
2507
+ });
2508
+
2491
2509
  // ../contract/src/views.ts
2492
2510
  var init_views = __esm({
2493
2511
  "../contract/src/views.ts"() {
@@ -2565,6 +2583,7 @@ var init_src = __esm({
2565
2583
  init_runtime();
2566
2584
  init_daemon_protocol();
2567
2585
  init_registry();
2586
+ init_credential_ref();
2568
2587
  init_views();
2569
2588
  init_telemetry();
2570
2589
  init_trace();
@@ -6887,7 +6906,7 @@ var init_dispatcher = __esm({
6887
6906
  this.assertSpawnAttemptCurrent(jobKey, attempt);
6888
6907
  const tokenFor = this.opts.tokenFor ?? ((a, _ctx) => `token:${a}`);
6889
6908
  const actorToken = tokenFor(dispatchSpec.actor, credentialCtx);
6890
- const provisioned = this.opts.provision ? await this.opts.provision(dispatchSpec.actor) : void 0;
6909
+ const provisioned = this.opts.provision ? await this.opts.provision(dispatchSpec.actor, spec.artifactId) : void 0;
6891
6910
  this.assertSpawnAttemptCurrent(jobKey, attempt);
6892
6911
  const jobEnv = {
6893
6912
  ...provisioned?.env ?? {},
@@ -23533,8 +23552,8 @@ var require_axios = __commonJS({
23533
23552
  axios.toFormData = toFormData;
23534
23553
  axios.AxiosError = AxiosError$1;
23535
23554
  axios.Cancel = axios.CanceledError;
23536
- axios.all = function all(promises) {
23537
- return Promise.all(promises);
23555
+ axios.all = function all(promises2) {
23556
+ return Promise.all(promises2);
23538
23557
  };
23539
23558
  axios.spread = spread;
23540
23559
  axios.isAxiosError = isAxiosError;
@@ -124780,8 +124799,8 @@ function getElementAtPath(obj, path30) {
124780
124799
  }
124781
124800
  function promiseAllObject(promisesObj) {
124782
124801
  const keys = Object.keys(promisesObj);
124783
- const promises = keys.map((key) => promisesObj[key]);
124784
- return Promise.all(promises).then((results) => {
124802
+ const promises2 = keys.map((key) => promisesObj[key]);
124803
+ return Promise.all(promises2).then((results) => {
124785
124804
  const resolvedObj = {};
124786
124805
  for (let i = 0; i < keys.length; i++) {
124787
124806
  resolvedObj[keys[i]] = results[i];
@@ -140760,8 +140779,8 @@ var init_install = __esm({
140760
140779
  function shSingleQuote(v2) {
140761
140780
  return `'${v2.replace(/'/g, `'\\''`)}'`;
140762
140781
  }
140763
- async function writeSessionCredsFile(slug6, exports2) {
140764
- const dir = await (0, import_promises.mkdtemp)((0, import_node_path5.join)((0, import_node_os3.tmpdir)(), `oasis-${slug6}-`));
140782
+ async function writeSessionCredsFile(slug6, exports2, reuseDir) {
140783
+ const dir = reuseDir ?? await (0, import_promises.mkdtemp)((0, import_node_path5.join)((0, import_node_os3.tmpdir)(), `oasis-${slug6}-`));
140765
140784
  const file = (0, import_node_path5.join)(dir, "creds.env");
140766
140785
  const body = Object.entries(exports2).map(([k2, v2]) => `export ${k2}=${shSingleQuote(v2)}`).join("\n");
140767
140786
  await (0, import_promises.writeFile)(file, body + "\n", { mode: 384 });
@@ -140787,7 +140806,7 @@ async function refreshCredentialsIfNeeded(connector, credentials, opts = {}) {
140787
140806
  return { status: "unsupported", detail: `${connector.config.slug}: \u51ED\u636E\u4E0D\u8FC7\u671F\uFF0C\u65E0\u9700\u7EED\u671F` };
140788
140807
  }
140789
140808
  const nowMs = opts.nowMs ?? Date.now();
140790
- const skewMs = opts.skewMs ?? CREDENTIAL_REFRESH_SKEW_MS;
140809
+ const skewMs = opts.skewMs ?? connector.minRemainingLifetimeMs ?? CREDENTIAL_REFRESH_SKEW_MS;
140791
140810
  const expiresAt = readEpochMs(credentials, connector.expiresAtCredentialKey);
140792
140811
  if (!isExpiringSoon(expiresAt, nowMs, skewMs)) {
140793
140812
  return { status: "fresh" };
@@ -141072,6 +141091,8 @@ var init_feishu = __esm({
141072
141091
  "FEISHU_REFRESH_TOKEN_EXPIRES_AT",
141073
141092
  "FEISHU_USER_OPEN_ID"
141074
141093
  ];
141094
+ /** 宿主机登录态隔离,理由同 github(见接口注释):由通用装配层无条件设。 */
141095
+ hostCredentialIsolationEnvKeys = ["LARKSUITE_CLI_CONFIG_DIR"];
141075
141096
  /** inject() 写进 sessionEnv 的全部键——通用 cleanup() 按它逐个删。 */
141076
141097
  injectedEnvKeys = [
141077
141098
  "LARK_BIN",
@@ -141140,7 +141161,7 @@ var init_feishu = __esm({
141140
141161
  LARKSUITE_CLI_APP_ID: appId,
141141
141162
  LARKSUITE_CLI_APP_SECRET: appSecret,
141142
141163
  ...userAccessToken ? { LARKSUITE_CLI_USER_ACCESS_TOKEN: userAccessToken } : {}
141143
- });
141164
+ }, sessionEnv.get("FEISHU_RUN_TMPDIR"));
141144
141165
  const configDir = (0, import_node_path6.join)(creds.dir, "lark-config");
141145
141166
  await (0, import_promises3.mkdir)(configDir, { recursive: true, mode: 448 });
141146
141167
  sessionEnv.set("LARK_BIN", await this.resolveAbsoluteLarkBin());
@@ -141611,7 +141632,8 @@ async function fetchAppBotIdentity(slug6, opts = {}, deps = {}) {
141611
141632
  const body = await resp.json().catch(() => ({}));
141612
141633
  const id = body["id"];
141613
141634
  if (id === void 0 || id === null) return null;
141614
- return { login, email: `${String(id)}+${login}@users.noreply.github.com` };
141635
+ const actualLogin = typeof body["login"] === "string" && body["login"] ? body["login"] : login;
141636
+ return { login: actualLogin, email: `${String(id)}+${actualLogin}@users.noreply.github.com` };
141615
141637
  } catch {
141616
141638
  return null;
141617
141639
  }
@@ -141632,10 +141654,17 @@ async function exchangeManifestCode(code, deps = {}) {
141632
141654
  const appId = body["id"] === void 0 ? "" : String(body["id"]);
141633
141655
  const privateKeyPem = typeof body["pem"] === "string" ? body["pem"] : "";
141634
141656
  if (!appId || !privateKeyPem) throw new Error("github app: id/pem missing in conversion response");
141657
+ const owner = body["owner"];
141658
+ const rawPerms = body["permissions"];
141659
+ const permissions = {};
141660
+ for (const [k2, v2] of Object.entries(rawPerms ?? {})) if (typeof v2 === "string") permissions[k2] = v2;
141635
141661
  return {
141636
141662
  appId,
141637
141663
  privateKeyPem,
141638
141664
  slug: typeof body["slug"] === "string" ? body["slug"] : "",
141665
+ ownerLogin: typeof owner?.["login"] === "string" ? owner["login"] : "",
141666
+ ownerType: typeof owner?.["type"] === "string" ? owner["type"] : "",
141667
+ permissions,
141639
141668
  ...typeof body["webhook_secret"] === "string" ? { webhookSecret: body["webhook_secret"] } : {}
141640
141669
  };
141641
141670
  }
@@ -141681,7 +141710,8 @@ var init_github = __esm({
141681
141710
  { key: "app_installation_id", description: "App \u5728\u8BE5\u7EC4\u7EC7/\u8D26\u53F7\u4E0B\u7684\u5B89\u88C5 ID", secret: false, required: false },
141682
141711
  { key: "app_token_expires_at", description: "installation token \u7684\u7EDD\u5BF9\u8FC7\u671F\u65F6\u523B\uFF08epoch ms\uFF09", secret: false, required: false },
141683
141712
  { key: "app_slug", description: "App \u7684 slug\u2014\u2014bot \u8EAB\u4EFD\u663E\u793A\u4E3A <slug>[bot]", secret: false, required: false },
141684
- { key: "app_bot_email", description: "bot \u8D26\u53F7\u7684 noreply \u90AE\u7BB1\uFF08commit \u7F72\u540D\u9760\u5B83\u94FE\u63A5\u5230 bot\uFF09", secret: false, required: false }
141713
+ { key: "app_bot_email", description: "bot \u8D26\u53F7\u7684 noreply \u90AE\u7BB1\uFF08commit \u7F72\u540D\u9760\u5B83\u94FE\u63A5\u5230 bot\uFF09", secret: false, required: false },
141714
+ { key: "app_bot_name", description: "bot \u8D26\u53F7\u7684 login\uFF08commit \u7F72\u540D\u7528\u5B83\uFF0C\u4E0E PR \u4F5C\u8005\u9010\u5B57\u4E00\u81F4\uFF09", secret: false, required: false }
141685
141715
  ]
141686
141716
  };
141687
141717
  /** wrapper 在 agent PATH 上冒名的命令——必须与真实 CLI 同名,否则遮蔽不生效。 */
@@ -141700,7 +141730,9 @@ var init_github = __esm({
141700
141730
  { key: "app_slug", from: "GITHUB_APP_SLUG", required: false },
141701
141731
  // bot 的 noreply 邮箱:commit 署名靠**邮箱**链接到账号(name 只是自由文本)。
141702
141732
  // 公开信息,非密钥。
141703
- { key: "app_bot_email", from: "GITHUB_APP_BOT_EMAIL", required: false }
141733
+ { key: "app_bot_email", from: "GITHUB_APP_BOT_EMAIL", required: false },
141734
+ // bot 的 login:commit 署名的 name 用它,才能和 PR 作者显示成同一个身份。
141735
+ { key: "app_bot_name", from: "GITHUB_APP_BOT_NAME", required: false }
141704
141736
  ];
141705
141737
  /**
141706
141738
  * 续期材料——**只在 server 侧读**。私钥留在这边铸 token,节点只拿铸出来的那一小时。
@@ -141716,6 +141748,18 @@ var init_github = __esm({
141716
141748
  { key: "access_token", from: "GITHUB_APP_TOKEN", required: false },
141717
141749
  { key: "app_token_expires_at", from: "GITHUB_APP_TOKEN_EXPIRES_AT", required: false }
141718
141750
  ];
141751
+ /**
141752
+ * installation token 满寿命就是 1 小时;声明成全寿命 ⇒ **每次注入都铸新的**。
141753
+ *
141754
+ * 代价可接受:铸 token 是纯换取(私钥 + installation_id 推出来的),
141755
+ * **旧 token 不会被新的作废**(GitHub 要显式 `DELETE /installation/token` 才失效),
141756
+ * 所以多铸一次既不打断在跑的会话、也不消耗任何一次性材料。
141757
+ *
141758
+ * 换来的是:一场会话开头拿到的 token 一定是满 60 分钟,而不是「上一场剩下的 6 分钟」。
141759
+ * ⚠ 仍挡不住跑超过 60 分钟的会话——会话硬顶是 120 分钟(`SESSION_WALL_CLOCK_MS`),
141760
+ * 那半截要靠会话内续期,本字段管不到。
141761
+ */
141762
+ minRemainingLifetimeMs = 60 * 6e4;
141719
141763
  /** 过期判定只认这个键——必须与注入时消费的 token 同源(契约测试盯着)。 */
141720
141764
  expiresAtCredentialKey = "app_token_expires_at";
141721
141765
  // 私钥不会过期,故不声明 refreshMaterialExpiresAtCredentialKey,也无需 clearOnExpired。
@@ -141725,6 +141769,12 @@ var init_github = __esm({
141725
141769
  "GITHUB_APP_TOKEN",
141726
141770
  "GITHUB_APP_PRIVATE_KEY"
141727
141771
  ];
141772
+ /**
141773
+ * 宿主机登录态隔离:`gh` 只认 env 里的 GH_TOKEN 或 `~/.config/gh/hosts.yml`。
141774
+ * 这个键**由通用装配层无条件设**(哪怕本连接器没接通),否则关掉连接器时
141775
+ * agent 会静默用成宿主机那个真人账号——见接口里的详细说明。
141776
+ */
141777
+ hostCredentialIsolationEnvKeys = ["GH_CONFIG_DIR"];
141728
141778
  /** inject() 写进 sessionEnv 的全部键——通用 cleanup() 按它逐个删。 */
141729
141779
  injectedEnvKeys = [
141730
141780
  "GH_BIN",
@@ -141815,7 +141865,7 @@ var init_github = __esm({
141815
141865
  const creds = await writeSessionCredsFile(this.config.slug, {
141816
141866
  GH_TOKEN: token,
141817
141867
  GITHUB_TOKEN: token
141818
- });
141868
+ }, sessionEnv.get("GH_RUN_TMPDIR"));
141819
141869
  const configDir = (0, import_node_path7.join)(creds.dir, "gh-config");
141820
141870
  await (0, import_promises4.mkdir)(configDir, { recursive: true, mode: 448 });
141821
141871
  sessionEnv.set("GH_BIN", await this.resolveRealBin("gh"));
@@ -141825,9 +141875,11 @@ var init_github = __esm({
141825
141875
  const appSlug = credentials["app_slug"];
141826
141876
  if (appSlug) sessionEnv.set("GH_APP_SLUG", appSlug);
141827
141877
  const botEmail = credentials["app_bot_email"] ?? "";
141828
- sessionEnv.set("GIT_AUTHOR_NAME", name);
141878
+ const botName = credentials["app_bot_name"] ?? "";
141879
+ const signName = botName || name;
141880
+ sessionEnv.set("GIT_AUTHOR_NAME", signName);
141829
141881
  sessionEnv.set("GIT_AUTHOR_EMAIL", botEmail);
141830
- sessionEnv.set("GIT_COMMITTER_NAME", name);
141882
+ sessionEnv.set("GIT_COMMITTER_NAME", signName);
141831
141883
  sessionEnv.set("GIT_COMMITTER_EMAIL", botEmail);
141832
141884
  sessionEnv.set("EMAIL", botEmail);
141833
141885
  sessionEnv.set("GIT_CONFIG_COUNT", "1");
@@ -141866,7 +141918,27 @@ function callbackHtml(ok, message) {
141866
141918
  setTimeout(function(){window.close()},${ok ? 800 : 4e3})</script>
141867
141919
  </body>`;
141868
141920
  }
141869
- var GITHUB_APP_DEFAULT_PERMISSIONS, GITHUB_APP_PERMISSION_LABELS, PENDING_TTL_MS, PendingAppCreations;
141921
+ function checkAppOwnership(conv, want) {
141922
+ if (want.ownerKind === "org" && want.ownerLogin) {
141923
+ const got = conv.ownerLogin;
141924
+ if (!got) {
141925
+ return { message: "GitHub \u6CA1\u8FD4\u56DE App \u7684\u5F52\u5C5E\u8D26\u53F7\uFF0C\u65E0\u6CD5\u786E\u8BA4\u5EFA\u5BF9\u4E86\u5730\u65B9\u2014\u2014\u8BF7\u5220\u6389\u8FD9\u4E2A App \u91CD\u8BD5\u3002" };
141926
+ }
141927
+ if (got.toLowerCase() !== want.ownerLogin.toLowerCase()) {
141928
+ return {
141929
+ message: `\u4F60\u9009\u7684\u662F\u7EC4\u7EC7 ${want.ownerLogin}\uFF0C\u4F46\u8FD9\u4E2A App \u5EFA\u5728\u4E86 ${got}\uFF08${conv.ownerType || "\u672A\u77E5\u7C7B\u578B"}\uFF09\u540D\u4E0B\u3002\u79C1\u6709 App \u53EA\u80FD\u88C5\u5728\u62E5\u6709\u5B83\u7684\u8D26\u53F7\u4E0A\uFF0C\u6240\u4EE5\u5B83\u6C38\u8FDC\u8BBF\u95EE\u4E0D\u5230 ${want.ownerLogin} \u7684\u4ED3\u5E93\u2014\u2014\u8865\u88C5\u4E5F\u6CA1\u7528\u3002\u8BF7\u53BB GitHub \u5220\u6389 ${got} \u540D\u4E0B\u8FD9\u4E2A App\uFF0C\u7136\u540E\u91CD\u65B0\u8FDE\u63A5\uFF0C\u5728\u521B\u5EFA\u9875\u786E\u8BA4\u5730\u5740\u662F github.com/organizations/${want.ownerLogin}/settings/apps/new\u3002`
141930
+ };
141931
+ }
141932
+ }
141933
+ const missing = REQUIRED_WRITE_PERMISSIONS.filter((k2) => conv.permissions[k2] !== "write");
141934
+ if (missing.length > 0) {
141935
+ return {
141936
+ message: `\u8FD9\u4E2A App \u7F3A\u5C11\u5FC5\u8981\u7684\u5199\u6743\u9650\uFF1A${missing.join(" / ")}\uFF08\u9700\u8981 write\uFF09\u3002agent \u5C06\u65E0\u6CD5\u63A8\u9001\u5206\u652F\u6216\u5F00 PR\u3002\u8BF7\u5728 App \u8BBE\u7F6E\u9875\u8865\u4E0A\u6743\u9650\u540E\u91CD\u65B0\u8FDE\u63A5\u3002`
141937
+ };
141938
+ }
141939
+ return null;
141940
+ }
141941
+ var GITHUB_APP_DEFAULT_PERMISSIONS, GITHUB_APP_PERMISSION_LABELS, PENDING_TTL_MS, PendingAppCreations, REQUIRED_WRITE_PERMISSIONS;
141870
141942
  var init_app_manifest = __esm({
141871
141943
  "../connectors/src/github/app-manifest.ts"() {
141872
141944
  "use strict";
@@ -141909,6 +141981,7 @@ var init_app_manifest = __esm({
141909
141981
  return this.map.size;
141910
141982
  }
141911
141983
  };
141984
+ REQUIRED_WRITE_PERMISSIONS = ["contents", "pull_requests"];
141912
141985
  }
141913
141986
  });
141914
141987
 
@@ -141965,19 +142038,27 @@ function collectCredentials(connector, ctx) {
141965
142038
  }
141966
142039
  return creds;
141967
142040
  }
142041
+ function collectAllConnectorCredentials(ctx) {
142042
+ return createAllConnectors().flatMap((connector) => {
142043
+ const credentials = collectCredentials(connector, ctx);
142044
+ return credentials ? [{ slug: connector.config.slug, credentials }] : [];
142045
+ });
142046
+ }
141968
142047
  async function buildConnectorProvision(ctx) {
141969
142048
  const envOverrides = {};
141970
142049
  const wrapperPaths = [];
141971
142050
  const requiredTools = /* @__PURE__ */ new Set();
141972
- const connectorCreds = [];
141973
142051
  const cleanups = [];
141974
142052
  const sensitiveVars = /* @__PURE__ */ new Set();
142053
+ const isolationKeys = /* @__PURE__ */ new Set();
142054
+ const connectorCreds = collectAllConnectorCredentials(ctx);
142055
+ const credentialsBySlug = new Map(connectorCreds.map((c) => [c.slug, c.credentials]));
141975
142056
  for (const connector of createAllConnectors()) {
141976
142057
  for (const v2 of connector.sensitiveVars) sensitiveVars.add(v2);
141977
- const credentials = collectCredentials(connector, ctx);
141978
- if (credentials === null) continue;
142058
+ for (const k2 of connector.hostCredentialIsolationEnvKeys ?? []) isolationKeys.add(k2);
142059
+ const credentials = credentialsBySlug.get(connector.config.slug);
142060
+ if (credentials === void 0) continue;
141979
142061
  try {
141980
- connectorCreds.push({ slug: connector.config.slug, credentials });
141981
142062
  const sessionEnv = /* @__PURE__ */ new Map();
141982
142063
  await connector.inject(credentials, sessionEnv);
141983
142064
  for (const [k2, v2] of sessionEnv) envOverrides[k2] = v2;
@@ -141993,6 +142074,19 @@ async function buildConnectorProvision(ctx) {
141993
142074
  log("[provision]", `${connector.config.slug}: \u88C5\u914D\u5931\u8D25\uFF08\u8BE5\u8FDE\u63A5\u5668\u672C\u8F6E\u4E0D\u53EF\u7528\uFF09: ${String(err)}`);
141994
142075
  }
141995
142076
  }
142077
+ const isolationRoot = await (0, import_promises5.mkdtemp)((0, import_node_path8.join)((0, import_node_os4.tmpdir)(), "oasis-isolate-"));
142078
+ let isolationUsed = false;
142079
+ for (const key of isolationKeys) {
142080
+ if (envOverrides[key]) continue;
142081
+ const dir = (0, import_node_path8.join)(isolationRoot, key.toLowerCase());
142082
+ await (0, import_promises5.mkdir)(dir, { recursive: true, mode: 448 });
142083
+ envOverrides[key] = dir;
142084
+ isolationUsed = true;
142085
+ }
142086
+ if (!isolationUsed) await (0, import_promises5.rm)(isolationRoot, { recursive: true, force: true });
142087
+ else cleanups.push(async () => {
142088
+ await (0, import_promises5.rm)(isolationRoot, { recursive: true, force: true });
142089
+ });
141996
142090
  const cleanup = async () => {
141997
142091
  for (const fn of cleanups) await fn().catch(() => {
141998
142092
  });
@@ -142006,23 +142100,28 @@ async function buildConnectorProvision(ctx) {
142006
142100
  cleanup
142007
142101
  };
142008
142102
  }
142103
+ var import_promises5, import_node_os4, import_node_path8;
142009
142104
  var init_provision = __esm({
142010
142105
  "../connectors/src/provision.ts"() {
142011
142106
  "use strict";
142012
142107
  init_base();
142013
142108
  init_registry2();
142014
142109
  init_logger();
142110
+ import_promises5 = require("node:fs/promises");
142111
+ import_node_os4 = require("node:os");
142112
+ import_node_path8 = require("node:path");
142015
142113
  }
142016
142114
  });
142017
142115
 
142018
142116
  // ../connectors/src/prepare-job.ts
142019
- async function prepareConnectorsForJob(job) {
142117
+ async function prepareConnectorsForJob(job, deps) {
142020
142118
  const creds = job.connectorCreds ?? [];
142021
142119
  const remoteWrappers = job.wrapperPaths ?? [];
142022
142120
  const localWrappers = [];
142023
142121
  const cleanups = [];
142024
142122
  const envOverrides = {};
142025
142123
  const statuses = [];
142124
+ const injected = /* @__PURE__ */ new Map();
142026
142125
  for (const { slug: slug6, credentials } of creds) {
142027
142126
  const connector = createConnectorBySlug(slug6);
142028
142127
  if (!connector) {
@@ -142037,6 +142136,7 @@ async function prepareConnectorsForJob(job) {
142037
142136
  await connector.inject(credentials, sessionEnv);
142038
142137
  for (const [k2, v2] of sessionEnv) envOverrides[k2] = v2;
142039
142138
  cleanups.push(() => connector.cleanup(sessionEnv));
142139
+ injected.set(slug6, { connector, sessionEnv });
142040
142140
  status.credsReady = true;
142041
142141
  } catch (err) {
142042
142142
  status.error = String(err);
@@ -142070,13 +142170,62 @@ async function prepareConnectorsForJob(job) {
142070
142170
  env: { ...job.env ?? {}, ...envOverrides, PATH: pathWithTools },
142071
142171
  wrapperPaths: [...keptLocal, ...keptRemote]
142072
142172
  };
142173
+ if (injected.size > 0 && (job.limits?.wallClockMs ?? 0) > SESSION_CREDENTIAL_RENEW_INTERVAL_MS) {
142174
+ const fetchCredentials = deps?.fetchCredentials ?? fetchCredentialsFromServer(job);
142175
+ const setTimer = deps?.setTimer ?? defaultSetTimer;
142176
+ const timer = setTimer(() => {
142177
+ void renewInjectedCredentials(injected, fetchCredentials);
142178
+ }, SESSION_CREDENTIAL_RENEW_INTERVAL_MS);
142179
+ cleanups.push(async () => {
142180
+ timer.stop();
142181
+ });
142182
+ log("[node-connectors]", `\u4F1A\u8BDD\u4E2D\u9014\u7EED\u671F\u5DF2\u542F\u52A8\uFF08\u6BCF ${SESSION_CREDENTIAL_RENEW_INTERVAL_MS / 6e4} \u5206\u949F\uFF0C${injected.size} \u4E2A\u8FDE\u63A5\u5668\uFF09`);
142183
+ }
142073
142184
  const cleanup = async () => {
142074
142185
  for (const fn of cleanups) await fn().catch(() => {
142075
142186
  });
142076
142187
  };
142077
142188
  return { job: preparedJob, cleanup, statuses };
142078
142189
  }
142079
- var import_node_fs5;
142190
+ async function renewInjectedCredentials(injected, fetchCredentials) {
142191
+ const outcome = { renewed: [], failed: [] };
142192
+ let fresh;
142193
+ try {
142194
+ fresh = await fetchCredentials();
142195
+ } catch (err) {
142196
+ for (const slug6 of injected.keys()) outcome.failed.push({ slug: slug6, error: String(err) });
142197
+ log("[node-connectors]", `\u7EED\u671F\uFF1A\u5411 server \u53D6\u51ED\u636E\u5931\u8D25\uFF0C\u672C\u8F6E\u6CBF\u7528\u65E7\u51ED\u636E: ${String(err)}`);
142198
+ return outcome;
142199
+ }
142200
+ for (const { slug: slug6, credentials } of fresh) {
142201
+ const target = injected.get(slug6);
142202
+ if (!target) continue;
142203
+ try {
142204
+ await target.connector.inject(credentials, target.sessionEnv);
142205
+ outcome.renewed.push(slug6);
142206
+ } catch (err) {
142207
+ outcome.failed.push({ slug: slug6, error: String(err) });
142208
+ log("[node-connectors]", `\u26A0 \u7EED\u671F\uFF1A${slug6} \u5C31\u5730\u91CD\u6CE8\u5931\u8D25\uFF0C\u6CBF\u7528\u65E7\u51ED\u636E: ${String(err)}`);
142209
+ }
142210
+ }
142211
+ if (outcome.renewed.length > 0) log("[node-connectors]", `\u7EED\u671F\uFF1A\u5DF2\u5C31\u5730\u6362\u65B0 ${outcome.renewed.join(", ")}`);
142212
+ return outcome;
142213
+ }
142214
+ function defaultSetTimer(fn, ms) {
142215
+ const timer = setInterval(fn, ms);
142216
+ timer.unref?.();
142217
+ return { stop: () => clearInterval(timer) };
142218
+ }
142219
+ function fetchCredentialsFromServer(job) {
142220
+ return async () => {
142221
+ const url = new URL("/api/connector-credentials", job.server?.url ?? "").toString();
142222
+ const res = await fetch(url, { headers: { authorization: `Bearer ${job.actorToken}` } });
142223
+ if (!res.ok) throw new Error(`HTTP ${res.status}`);
142224
+ const body = await res.json();
142225
+ return body.connectorCreds ?? [];
142226
+ };
142227
+ }
142228
+ var import_node_fs5, SESSION_CREDENTIAL_RENEW_INTERVAL_MS;
142080
142229
  var init_prepare_job = __esm({
142081
142230
  "../connectors/src/prepare-job.ts"() {
142082
142231
  "use strict";
@@ -142084,6 +142233,7 @@ var init_prepare_job = __esm({
142084
142233
  init_base();
142085
142234
  init_registry2();
142086
142235
  init_logger();
142236
+ SESSION_CREDENTIAL_RENEW_INTERVAL_MS = 30 * 6e4;
142087
142237
  }
142088
142238
  });
142089
142239
 
@@ -142422,8 +142572,8 @@ function materializeFiles(dir, files) {
142422
142572
  if (!files) return;
142423
142573
  for (const [rel, content] of Object.entries(files)) {
142424
142574
  try {
142425
- const file = import_node_path8.default.join(dir, rel);
142426
- import_node_fs6.default.mkdirSync(import_node_path8.default.dirname(file), { recursive: true });
142575
+ const file = import_node_path9.default.join(dir, rel);
142576
+ import_node_fs6.default.mkdirSync(import_node_path9.default.dirname(file), { recursive: true });
142427
142577
  import_node_fs6.default.writeFileSync(file, content);
142428
142578
  } catch {
142429
142579
  }
@@ -142439,7 +142589,7 @@ function resolveLegacyWorkRoot(workRoot) {
142439
142589
  if (workRoot) return workRoot;
142440
142590
  const env = process.env["OASIS_WORK_ROOT"];
142441
142591
  if (env) return env;
142442
- return import_node_os4.default.tmpdir();
142592
+ return import_node_os5.default.tmpdir();
142443
142593
  }
142444
142594
  function resolveWorkRoots(workRoot) {
142445
142595
  return { workRoot: resolveWorkRoot(workRoot), legacyRoot: resolveLegacyWorkRoot(workRoot) };
@@ -142450,7 +142600,7 @@ function slug5(workdirKey) {
142450
142600
  return `${safe}-${h}`;
142451
142601
  }
142452
142602
  function sessionDirFor(workRoot, runtimeKind, workdirKey) {
142453
- return import_node_path8.default.join(resolveWorkRoot(workRoot), "sessions", runtimeKind, slug5(workdirKey));
142603
+ return import_node_path9.default.join(resolveWorkRoot(workRoot), "sessions", runtimeKind, slug5(workdirKey));
142454
142604
  }
142455
142605
  function sessionDirKind(runtimeKind) {
142456
142606
  return runtimeKind === "claude-code" ? "claude" : runtimeKind;
@@ -142463,20 +142613,20 @@ function resolveSessionDirWithLegacy(args) {
142463
142613
  return import_node_fs6.default.existsSync(legacy) ? legacy : fresh;
142464
142614
  }
142465
142615
  function oneShotDirFor(workRoot, runtimeKind) {
142466
- const base = import_node_path8.default.join(resolveWorkRoot(workRoot), "tmp");
142616
+ const base = import_node_path9.default.join(resolveWorkRoot(workRoot), "tmp");
142467
142617
  import_node_fs6.default.mkdirSync(base, { recursive: true });
142468
- return import_node_fs6.default.mkdtempSync(import_node_path8.default.join(base, `${runtimeKind}-`));
142618
+ return import_node_fs6.default.mkdtempSync(import_node_path9.default.join(base, `${runtimeKind}-`));
142469
142619
  }
142470
142620
  function writeMeta(dir, meta) {
142471
142621
  try {
142472
- import_node_fs6.default.writeFileSync(import_node_path8.default.join(dir, META_FILE), `${JSON.stringify(meta, null, 2)}
142622
+ import_node_fs6.default.writeFileSync(import_node_path9.default.join(dir, META_FILE), `${JSON.stringify(meta, null, 2)}
142473
142623
  `);
142474
142624
  } catch {
142475
142625
  }
142476
142626
  }
142477
142627
  function readMeta(dir) {
142478
142628
  try {
142479
- const raw = JSON.parse(import_node_fs6.default.readFileSync(import_node_path8.default.join(dir, META_FILE), "utf8"));
142629
+ const raw = JSON.parse(import_node_fs6.default.readFileSync(import_node_path9.default.join(dir, META_FILE), "utf8"));
142480
142630
  if (!raw || typeof raw !== "object") return null;
142481
142631
  const m2 = raw;
142482
142632
  return typeof m2.kind === "string" && typeof m2.createdAt === "string" ? m2 : null;
@@ -142505,14 +142655,14 @@ function lockWorkdir(dir, pid, holder, workdirKey) {
142505
142655
  ...workdirKey !== void 0 ? { workdirKey } : {}
142506
142656
  };
142507
142657
  try {
142508
- import_node_fs6.default.writeFileSync(import_node_path8.default.join(dir, LOCK_FILE), `${JSON.stringify(lock)}
142658
+ import_node_fs6.default.writeFileSync(import_node_path9.default.join(dir, LOCK_FILE), `${JSON.stringify(lock)}
142509
142659
  `);
142510
142660
  } catch {
142511
142661
  }
142512
142662
  }
142513
142663
  function readLock(dir) {
142514
142664
  try {
142515
- const raw = JSON.parse(import_node_fs6.default.readFileSync(import_node_path8.default.join(dir, LOCK_FILE), "utf8"));
142665
+ const raw = JSON.parse(import_node_fs6.default.readFileSync(import_node_path9.default.join(dir, LOCK_FILE), "utf8"));
142516
142666
  if (!raw || typeof raw !== "object") return null;
142517
142667
  const l = raw;
142518
142668
  return Number.isInteger(l.pid) && l.pid > 0 ? l : null;
@@ -142522,14 +142672,14 @@ function readLock(dir) {
142522
142672
  }
142523
142673
  function unlockWorkdir(dir) {
142524
142674
  try {
142525
- import_node_fs6.default.unlinkSync(import_node_path8.default.join(dir, LOCK_FILE));
142675
+ import_node_fs6.default.unlinkSync(import_node_path9.default.join(dir, LOCK_FILE));
142526
142676
  } catch {
142527
142677
  }
142528
142678
  }
142529
142679
  function isWorkdirLive(dir) {
142530
142680
  let raw;
142531
142681
  try {
142532
- raw = import_node_fs6.default.readFileSync(import_node_path8.default.join(dir, LOCK_FILE), "utf8");
142682
+ raw = import_node_fs6.default.readFileSync(import_node_path9.default.join(dir, LOCK_FILE), "utf8");
142533
142683
  } catch {
142534
142684
  return false;
142535
142685
  }
@@ -142546,7 +142696,7 @@ function clearWorkdir(dir) {
142546
142696
  );
142547
142697
  }
142548
142698
  for (const entry of import_node_fs6.default.readdirSync(dir)) {
142549
- import_node_fs6.default.rmSync(import_node_path8.default.join(dir, entry), { recursive: true, force: true });
142699
+ import_node_fs6.default.rmSync(import_node_path9.default.join(dir, entry), { recursive: true, force: true });
142550
142700
  }
142551
142701
  }
142552
142702
  function prepareWorkdir(args) {
@@ -142576,19 +142726,19 @@ function prepareWorkdir(args) {
142576
142726
  return dir;
142577
142727
  }
142578
142728
  function legacyChatSessionDir(workRoot, rtId) {
142579
- return import_node_path8.default.join(resolveLegacyWorkRoot(workRoot), "oasis-chat-sessions", rtId.replace(/[^a-zA-Z0-9_-]+/g, "_"));
142729
+ return import_node_path9.default.join(resolveLegacyWorkRoot(workRoot), "oasis-chat-sessions", rtId.replace(/[^a-zA-Z0-9_-]+/g, "_"));
142580
142730
  }
142581
- var import_node_crypto9, import_node_fs6, import_node_os4, import_node_path8, META_FILE, LOCK_FILE, NEW_WORK_ROOT, LEGACY_ROOTS, LEGACY_ONESHOT_PREFIX;
142731
+ var import_node_crypto9, import_node_fs6, import_node_os5, import_node_path9, META_FILE, LOCK_FILE, NEW_WORK_ROOT, LEGACY_ROOTS, LEGACY_ONESHOT_PREFIX;
142582
142732
  var init_session_paths = __esm({
142583
142733
  "../adapters/src/_core/session-paths.ts"() {
142584
142734
  "use strict";
142585
142735
  import_node_crypto9 = require("node:crypto");
142586
142736
  import_node_fs6 = __toESM(require("node:fs"), 1);
142587
- import_node_os4 = __toESM(require("node:os"), 1);
142588
- import_node_path8 = __toESM(require("node:path"), 1);
142737
+ import_node_os5 = __toESM(require("node:os"), 1);
142738
+ import_node_path9 = __toESM(require("node:path"), 1);
142589
142739
  META_FILE = ".oasis-meta.json";
142590
142740
  LOCK_FILE = ".oasis-lock";
142591
- NEW_WORK_ROOT = () => import_node_path8.default.join(import_node_os4.default.homedir(), ".oasis", "work");
142741
+ NEW_WORK_ROOT = () => import_node_path9.default.join(import_node_os5.default.homedir(), ".oasis", "work");
142592
142742
  LEGACY_ROOTS = ["oasis-chat-sessions", "oasis-transcripts"];
142593
142743
  LEGACY_ONESHOT_PREFIX = "oasis-session-";
142594
142744
  }
@@ -143810,7 +143960,7 @@ async function discoverACPModels(bin, provider, args = ["acp"]) {
143810
143960
  clientCapabilities: {}
143811
143961
  });
143812
143962
  try {
143813
- tmpDir = (0, import_node_fs7.mkdtempSync)((0, import_node_path9.join)((0, import_node_os5.tmpdir)(), `oasis-acp-${provider}-`));
143963
+ tmpDir = (0, import_node_fs7.mkdtempSync)((0, import_node_path10.join)((0, import_node_os6.tmpdir)(), `oasis-acp-${provider}-`));
143814
143964
  } catch {
143815
143965
  return fail();
143816
143966
  }
@@ -143866,14 +144016,14 @@ function parseACPSessionNewModels(result) {
143866
144016
  async function discoverOpenclawModels(bin) {
143867
144017
  throw new Error("discoverOpenclawModels: TODO");
143868
144018
  }
143869
- var import_node_child_process9, import_node_fs7, import_node_os5, import_node_path9, modelCache, CACHE_TTL_MS;
144019
+ var import_node_child_process9, import_node_fs7, import_node_os6, import_node_path10, modelCache, CACHE_TTL_MS;
143870
144020
  var init_models = __esm({
143871
144021
  "../adapters/src/_core/models.ts"() {
143872
144022
  "use strict";
143873
144023
  import_node_child_process9 = require("node:child_process");
143874
144024
  import_node_fs7 = require("node:fs");
143875
- import_node_os5 = require("node:os");
143876
- import_node_path9 = require("node:path");
144025
+ import_node_os6 = require("node:os");
144026
+ import_node_path10 = require("node:path");
143877
144027
  modelCache = /* @__PURE__ */ new Map();
143878
144028
  CACHE_TTL_MS = 6e4;
143879
144029
  }
@@ -146440,7 +146590,7 @@ function dirSizeBytes(dir) {
146440
146590
  return;
146441
146591
  }
146442
146592
  for (const e of entries) {
146443
- const p2 = import_node_path10.default.join(d, e.name);
146593
+ const p2 = import_node_path11.default.join(d, e.name);
146444
146594
  if (e.isSymbolicLink()) continue;
146445
146595
  if (e.isDirectory()) {
146446
146596
  walk(p2);
@@ -146457,40 +146607,40 @@ function dirSizeBytes(dir) {
146457
146607
  }
146458
146608
  function listWorkdirs(workRoot, legacyRoot) {
146459
146609
  const out = [];
146460
- const sessions = import_node_path10.default.join(workRoot, "sessions");
146610
+ const sessions = import_node_path11.default.join(workRoot, "sessions");
146461
146611
  try {
146462
146612
  for (const kind of import_node_fs8.default.readdirSync(sessions)) {
146463
- const kindDir = import_node_path10.default.join(sessions, kind);
146613
+ const kindDir = import_node_path11.default.join(sessions, kind);
146464
146614
  try {
146465
- for (const slug6 of import_node_fs8.default.readdirSync(kindDir)) out.push(import_node_path10.default.join(kindDir, slug6));
146615
+ for (const slug6 of import_node_fs8.default.readdirSync(kindDir)) out.push(import_node_path11.default.join(kindDir, slug6));
146466
146616
  } catch {
146467
146617
  }
146468
146618
  }
146469
146619
  } catch {
146470
146620
  }
146471
- const tmp = import_node_path10.default.join(workRoot, "tmp");
146621
+ const tmp = import_node_path11.default.join(workRoot, "tmp");
146472
146622
  try {
146473
- for (const d of import_node_fs8.default.readdirSync(tmp)) out.push(import_node_path10.default.join(tmp, d));
146623
+ for (const d of import_node_fs8.default.readdirSync(tmp)) out.push(import_node_path11.default.join(tmp, d));
146474
146624
  } catch {
146475
146625
  }
146476
146626
  for (const legacy of LEGACY_ROOTS) {
146477
- const root = import_node_path10.default.join(legacyRoot, legacy);
146627
+ const root = import_node_path11.default.join(legacyRoot, legacy);
146478
146628
  try {
146479
- for (const d of import_node_fs8.default.readdirSync(root)) out.push(import_node_path10.default.join(root, d));
146629
+ for (const d of import_node_fs8.default.readdirSync(root)) out.push(import_node_path11.default.join(root, d));
146480
146630
  } catch {
146481
146631
  }
146482
146632
  }
146483
146633
  try {
146484
146634
  for (const d of import_node_fs8.default.readdirSync(legacyRoot)) {
146485
- if (d.startsWith(LEGACY_ONESHOT_PREFIX)) out.push(import_node_path10.default.join(legacyRoot, d));
146635
+ if (d.startsWith(LEGACY_ONESHOT_PREFIX)) out.push(import_node_path11.default.join(legacyRoot, d));
146486
146636
  }
146487
146637
  } catch {
146488
146638
  }
146489
146639
  return [...new Set(out)];
146490
146640
  }
146491
146641
  function kindFromPath(workRoot, dir) {
146492
- const rel = import_node_path10.default.relative(workRoot, dir);
146493
- const parts = rel.split(import_node_path10.default.sep);
146642
+ const rel = import_node_path11.default.relative(workRoot, dir);
146643
+ const parts = rel.split(import_node_path11.default.sep);
146494
146644
  if (parts[0] === "sessions" && parts[1]) return parts[1];
146495
146645
  if (parts[0] === "tmp" && parts[1]) return parts[1].replace(/-[^-]*$/, "");
146496
146646
  return void 0;
@@ -146532,7 +146682,7 @@ async function runGcSweep(deps) {
146532
146682
  let deleted = 0;
146533
146683
  for (const d of decisions) {
146534
146684
  if (d.action !== "delete") continue;
146535
- if (import_node_fs8.default.existsSync(import_node_path10.default.join(d.dir, LOCK_FILE)) && isWorkdirLive(d.dir)) {
146685
+ if (import_node_fs8.default.existsSync(import_node_path11.default.join(d.dir, LOCK_FILE)) && isWorkdirLive(d.dir)) {
146536
146686
  log3(`[gc] \u8DF3\u8FC7 ${d.dir}\uFF1A\u6267\u884C\u524D\u590D\u67E5\u53D1\u73B0\u5DF2\u88AB\u5360\u7528`);
146537
146687
  continue;
146538
146688
  }
@@ -146583,12 +146733,12 @@ function startGcLoop(deps) {
146583
146733
  clearInterval(timer);
146584
146734
  };
146585
146735
  }
146586
- var import_node_fs8, import_node_path10;
146736
+ var import_node_fs8, import_node_path11;
146587
146737
  var init_gc_loop = __esm({
146588
146738
  "../adapters/src/_core/gc-loop.ts"() {
146589
146739
  "use strict";
146590
146740
  import_node_fs8 = __toESM(require("node:fs"), 1);
146591
- import_node_path10 = __toESM(require("node:path"), 1);
146741
+ import_node_path11 = __toESM(require("node:path"), 1);
146592
146742
  init_session_paths();
146593
146743
  init_workdir_gc();
146594
146744
  }
@@ -146624,14 +146774,14 @@ var init_src4 = __esm({
146624
146774
  });
146625
146775
 
146626
146776
  // ../connectors/src/connector-adapter.ts
146627
- var import_promises5, import_node_fs9, import_node_os6, import_node_path11, ConnectorAwareAdapter;
146777
+ var import_promises6, import_node_fs9, import_node_os7, import_node_path12, ConnectorAwareAdapter;
146628
146778
  var init_connector_adapter = __esm({
146629
146779
  "../connectors/src/connector-adapter.ts"() {
146630
146780
  "use strict";
146631
- import_promises5 = require("node:fs/promises");
146781
+ import_promises6 = require("node:fs/promises");
146632
146782
  import_node_fs9 = require("node:fs");
146633
- import_node_os6 = require("node:os");
146634
- import_node_path11 = require("node:path");
146783
+ import_node_os7 = require("node:os");
146784
+ import_node_path12 = require("node:path");
146635
146785
  init_src4();
146636
146786
  init_base();
146637
146787
  init_prepare_job();
@@ -146654,11 +146804,11 @@ var init_connector_adapter = __esm({
146654
146804
  const wrapperPaths = [...prepared.job.wrapperPaths ?? [], ...sentinelWrappers];
146655
146805
  let extraEnv = { ...strippedEnv };
146656
146806
  if (wrapperPaths.length > 0) {
146657
- const wrapperBinDir = await (0, import_promises5.mkdtemp)((0, import_node_path11.join)((0, import_node_os6.tmpdir)(), "oasis-wrappers-"));
146658
- await (0, import_promises5.mkdir)(wrapperBinDir, { recursive: true });
146807
+ const wrapperBinDir = await (0, import_promises6.mkdtemp)((0, import_node_path12.join)((0, import_node_os7.tmpdir)(), "oasis-wrappers-"));
146808
+ await (0, import_promises6.mkdir)(wrapperBinDir, { recursive: true });
146659
146809
  for (const wp of wrapperPaths) {
146660
- const linkName = (0, import_node_path11.basename)(wp).replace(/\.sh$/, "");
146661
- await (0, import_promises5.symlink)(wp, (0, import_node_path11.join)(wrapperBinDir, linkName));
146810
+ const linkName = (0, import_node_path12.basename)(wp).replace(/\.sh$/, "");
146811
+ await (0, import_promises6.symlink)(wp, (0, import_node_path12.join)(wrapperBinDir, linkName));
146662
146812
  }
146663
146813
  extraEnv = { ...extraEnv, PATH: [wrapperBinDir, extraEnv["PATH"] ?? ""].filter(Boolean).join(":") };
146664
146814
  log("[adapter]", ` connectors injected +${Date.now() - t0}ms wrappers=${wrapperPaths.length}`);
@@ -149445,12 +149595,12 @@ async function startOasisServer(opts) {
149445
149595
  const actorCtx = await opts.resolveActorContext(body.actorId).catch(() => null);
149446
149596
  if (actorCtx) {
149447
149597
  const { mkdtempSync: mkdtempSync5, mkdirSync: mkdirSync21, writeFileSync: writeFileSync13 } = await import("node:fs");
149448
- const { join: join30, dirname: dirname24 } = await import("node:path");
149449
- const { tmpdir: tmpdir8 } = await import("node:os");
149450
- const dir = mkdtempSync5(join30(tmpdir8(), "oasis-chat-"));
149598
+ const { join: join31, dirname: dirname24 } = await import("node:path");
149599
+ const { tmpdir: tmpdir9 } = await import("node:os");
149600
+ const dir = mkdtempSync5(join31(tmpdir9(), "oasis-chat-"));
149451
149601
  if (actorCtx.config?.prompt) {
149452
149602
  for (const [rel, content] of Object.entries(splitIdentityFiles2(actorCtx.config.prompt))) {
149453
- const file = join30(dir, rel);
149603
+ const file = join31(dir, rel);
149454
149604
  mkdirSync21(dirname24(file), { recursive: true });
149455
149605
  writeFileSync13(file, content);
149456
149606
  }
@@ -149462,12 +149612,12 @@ async function startOasisServer(opts) {
149462
149612
  const s2 = byId.get(id);
149463
149613
  return s2 ? `- **${s2.name}** (\`${s2.id}\`): ${s2.description}` : `- \`${id}\`\uFF08\u672A\u5728\u6280\u80FD\u5E93\u4E2D\uFF0C\u53EF\u80FD\u5DF2\u5378\u8F7D\uFF09`;
149464
149614
  });
149465
- writeFileSync13(join30(dir, "SKILLS.md"), ["# \u53EF\u7528\u6280\u80FD", "", "\u4EE5\u4E0B\u6280\u80FD\u5DF2\u4E3A\u4F60\u542F\u7528\uFF0C\u53EF\u5728\u672C\u6B21\u4F1A\u8BDD\u4E2D\u76F4\u63A5\u4F7F\u7528\uFF1A", "", ...lines].join("\n"));
149615
+ writeFileSync13(join31(dir, "SKILLS.md"), ["# \u53EF\u7528\u6280\u80FD", "", "\u4EE5\u4E0B\u6280\u80FD\u5DF2\u4E3A\u4F60\u542F\u7528\uFF0C\u53EF\u5728\u672C\u6B21\u4F1A\u8BDD\u4E2D\u76F4\u63A5\u4F7F\u7528\uFF1A", "", ...lines].join("\n"));
149466
149616
  }
149467
149617
  if (opts.materializeSkills) {
149468
149618
  const skillFiles = await opts.materializeSkills(body.actorId, "claude").catch(() => ({}));
149469
149619
  for (const [rel, content] of Object.entries(skillFiles)) {
149470
- const file = join30(dir, rel);
149620
+ const file = join31(dir, rel);
149471
149621
  mkdirSync21(dirname24(file), { recursive: true });
149472
149622
  writeFileSync13(file, content);
149473
149623
  }
@@ -149481,7 +149631,7 @@ async function startOasisServer(opts) {
149481
149631
  const modeNote = c.mode === "oauth" ? "OAuth \xB7 \u51ED\u8BC1\u7531\u5E73\u53F0\u7BA1\u7406\uFF0C\u901A\u8FC7\u5BF9\u5E94 CLI wrapper \u8C03\u7528" : "\u76F4\u63A5\u5199\u5165 \xB7 \u51ED\u8BC1\u5DF2\u6CE8\u5165\u73AF\u5883\u53D8\u91CF";
149482
149632
  return `- **${c.name}** (\`${c.id}\`): ${statusNote} \xB7 ${modeNote}`;
149483
149633
  });
149484
- writeFileSync13(join30(dir, "CONNECTORS.md"), ["# \u53EF\u7528\u8FDE\u63A5\u5668", "", "\u4EE5\u4E0B\u8FDE\u63A5\u5668\u5DF2\u4E3A\u672C\u6B21\u4F1A\u8BDD\u914D\u7F6E\uFF0C\u51ED\u8BC1\u5DF2\u901A\u8FC7\u73AF\u5883\u53D8\u91CF\u6216 CLI wrapper \u6CE8\u5165\uFF0C\u65E0\u9700\u624B\u52A8\u914D\u7F6E\uFF1A", "", ...lines].join("\n"));
149634
+ writeFileSync13(join31(dir, "CONNECTORS.md"), ["# \u53EF\u7528\u8FDE\u63A5\u5668", "", "\u4EE5\u4E0B\u8FDE\u63A5\u5668\u5DF2\u4E3A\u672C\u6B21\u4F1A\u8BDD\u914D\u7F6E\uFF0C\u51ED\u8BC1\u5DF2\u901A\u8FC7\u73AF\u5883\u53D8\u91CF\u6216 CLI wrapper \u6CE8\u5165\uFF0C\u65E0\u9700\u624B\u52A8\u914D\u7F6E\uFF1A", "", ...lines].join("\n"));
149485
149635
  }
149486
149636
  spawnCwd = dir;
149487
149637
  }
@@ -149722,7 +149872,14 @@ async function startOasisServer(opts) {
149722
149872
  baseUrl: base,
149723
149873
  redirectPath: "/api/connectors/github/app/manifest-callback"
149724
149874
  });
149725
- githubAppPending.put(state, { ...actorId ? { actorId } : {}, employeeSlug: nameSlug, createdAtMs: Date.now() });
149875
+ githubAppPending.put(state, {
149876
+ ...actorId ? { actorId } : {},
149877
+ employeeSlug: nameSlug,
149878
+ createdAtMs: Date.now(),
149879
+ // 存下这次选的归属——回调时要拿它跟 GitHub 返回的 owner 比对。
149880
+ wantOwnerKind: kind,
149881
+ ...kind === "org" && owner?.login ? { wantOwnerLogin: owner.login } : {}
149882
+ });
149726
149883
  res.writeHead(200, { "content-type": "application/json" }).end(JSON.stringify({
149727
149884
  state,
149728
149885
  formAction: manifestFormAction(kind === "org" ? { kind: "org", login: owner.login } : { kind: "user" }, state),
@@ -149785,6 +149942,14 @@ async function startOasisServer(opts) {
149785
149942
  }
149786
149943
  try {
149787
149944
  const conv = await exchangeManifestCode(code);
149945
+ const problem = checkAppOwnership(conv, {
149946
+ ...pending.wantOwnerLogin ? { ownerLogin: pending.wantOwnerLogin } : {},
149947
+ ...pending.wantOwnerKind ? { ownerKind: pending.wantOwnerKind } : {}
149948
+ });
149949
+ if (problem) {
149950
+ fail(problem.message);
149951
+ return;
149952
+ }
149788
149953
  const svc = opts.actors?.service;
149789
149954
  if (!svc) throw new Error("actors service unavailable");
149790
149955
  const scope = pending.actorId ? { scope: "personal", actorId: pending.actorId } : { scope: "global" };
@@ -149852,7 +150017,10 @@ async function startOasisServer(opts) {
149852
150017
  }
149853
150018
  if (pending.slug) {
149854
150019
  const bot = await fetchAppBotIdentity(pending.slug, { token: minted.token });
149855
- if (bot) await put("GITHUB_APP_BOT_EMAIL", bot.email, false);
150020
+ if (bot) {
150021
+ await put("GITHUB_APP_BOT_EMAIL", bot.email, false);
150022
+ await put("GITHUB_APP_BOT_NAME", bot.login, false);
150023
+ }
149856
150024
  }
149857
150025
  pending.done = true;
149858
150026
  delete pending.privateKeyPem;
@@ -150437,9 +150605,11 @@ var init_memory_registry_store = __esm({
150437
150605
  // key: ${runtimeId}::${modelId}
150438
150606
  connectors = /* @__PURE__ */ new Map();
150439
150607
  variables = /* @__PURE__ */ new Map();
150440
- // key = key::actorId
150441
- _varKey(key, actorId) {
150442
- return `${key}::${actorId ?? ""}`;
150608
+ // key = key::actorId::projectId
150609
+ // 唯一键覆盖三种作用域(ADR 0125:无 environment 维):personal=(key,actorId)、project=(key,projectId)、global=(key)。
150610
+ // 三段拼接后天然互不碰撞(personal 有 actorId projectId,project 反之,global 全空)。
150611
+ _varKey(key, actorId, projectId) {
150612
+ return `${key}::${actorId ?? ""}::${projectId ?? ""}`;
150443
150613
  }
150444
150614
  skillCatalog = /* @__PURE__ */ new Map();
150445
150615
  installedSkills = /* @__PURE__ */ new Map();
@@ -150572,7 +150742,7 @@ var init_memory_registry_store = __esm({
150572
150742
  this.skillFiles.delete(skillId);
150573
150743
  }
150574
150744
  async putVariable(v2) {
150575
- this.variables.set(this._varKey(v2.key, v2.actorId), { ...v2 });
150745
+ this.variables.set(this._varKey(v2.key, v2.actorId, v2.projectId), { ...v2 });
150576
150746
  }
150577
150747
  async patchVariableValue(key, actorId, valueEncrypted, updatedAt) {
150578
150748
  const mk = this._varKey(key, actorId);
@@ -150587,8 +150757,11 @@ var init_memory_registry_store = __esm({
150587
150757
  if (actorId !== void 0) return all.filter((v2) => v2.scope === "global" || v2.actorId === actorId).map((v2) => ({ ...v2 }));
150588
150758
  return all.map((v2) => ({ ...v2 }));
150589
150759
  }
150590
- async deleteVariable(key, actorId) {
150591
- this.variables.delete(this._varKey(key, actorId));
150760
+ async resolveProjectVariables(projectId) {
150761
+ return [...this.variables.values()].filter((v2) => v2.scope === "project" && v2.projectId === projectId).map((v2) => ({ ...v2 }));
150762
+ }
150763
+ async deleteVariable(key, actorId, scoping) {
150764
+ this.variables.delete(this._varKey(key, actorId, scoping?.projectId));
150592
150765
  }
150593
150766
  };
150594
150767
  }
@@ -150887,6 +151060,7 @@ var init_memory_trace_store = __esm({
150887
151060
  startedAt: run.startedAt,
150888
151061
  effectiveModel: run.effectiveModel,
150889
151062
  runtimeKind: run.runtimeKind,
151063
+ runtimeInstanceId: run.runtimeInstanceId ?? null,
150890
151064
  usage: structuredClone(run.usage)
150891
151065
  }));
150892
151066
  }
@@ -154711,6 +154885,10 @@ async function sweepSilentSessions(deps) {
154711
154885
  continue;
154712
154886
  }
154713
154887
  if (!live.lastEventAt) continue;
154888
+ if (deps.isTraceHealthy && !deps.isTraceHealthy(s2.sessionId)) {
154889
+ deps.log?.(`[liveness] \u8DF3\u8FC7 ${s2.artifactId}\uFF1Atrace \u5199\u5165\u4E0D\u5065\u5EB7\uFF08\u89C2\u6D4B\u964D\u7EA7\uFF09\uFF0C\u4FE1\u606F\u4E0D\u8DB3\u4E0D\u5224\u9759\u9ED8 \u2192 \u4EA4 wallClock \u515C\u5E95`);
154890
+ continue;
154891
+ }
154714
154892
  const runtime = { hasInFlight: true, lastSessionEventAt: live.lastEventAt, openToolCall: live.openToolCall };
154715
154893
  if (!isRuntimeSilent(runtime, deps.now, deps.silentThresholdMs)) continue;
154716
154894
  const ok = await deps.kill(s2.jobKey).catch(() => false);
@@ -155410,6 +155588,73 @@ var init_recovery_plan = __esm({
155410
155588
  }
155411
155589
  });
155412
155590
 
155591
+ // ../server/src/domains/trace/resilient-appender.ts
155592
+ var ResilientTraceAppender;
155593
+ var init_resilient_appender = __esm({
155594
+ "../server/src/domains/trace/resilient-appender.ts"() {
155595
+ "use strict";
155596
+ ResilientTraceAppender = class {
155597
+ constructor(opts) {
155598
+ this.opts = opts;
155599
+ this.seq = opts.startSeq ?? 0;
155600
+ }
155601
+ seq;
155602
+ failing = false;
155603
+ /** 当前已成功落库的最大 seq。 */
155604
+ get currentSeq() {
155605
+ return this.seq;
155606
+ }
155607
+ /** 恢复路径:把 seq 对齐到库里已有的最大值(不改健康态)。 */
155608
+ resetSeq(seq) {
155609
+ this.seq = seq;
155610
+ }
155611
+ onSuccess() {
155612
+ this.opts.health?.markHealthy(this.opts.runId);
155613
+ if (this.failing) {
155614
+ this.failing = false;
155615
+ this.opts.onRecover?.();
155616
+ }
155617
+ }
155618
+ onFailure(err) {
155619
+ this.opts.health?.markUnhealthy(this.opts.runId);
155620
+ if (!this.failing) {
155621
+ this.failing = true;
155622
+ this.opts.onError?.(err);
155623
+ }
155624
+ }
155625
+ /**
155626
+ * 追加一批事件(seq 自动连续分配);**成功才推进 seq**。返回是否落库。
155627
+ * 幂等吸收(storage 层)保证:若失败其实是"提交成功但连接抖断",下次同 seq 重投会被吸收、不重复。
155628
+ */
155629
+ async append(events) {
155630
+ if (events.length === 0) return true;
155631
+ const base = this.seq;
155632
+ const withSeq = events.map((e, i) => ({ ...e, seq: base + i + 1 }));
155633
+ try {
155634
+ await this.opts.store.appendEvents(this.opts.runId, withSeq);
155635
+ this.seq = base + events.length;
155636
+ this.onSuccess();
155637
+ return true;
155638
+ } catch (err) {
155639
+ this.onFailure(err);
155640
+ return false;
155641
+ }
155642
+ }
155643
+ /** 更新 run(心跳 lastProgressAt / 终态 / metadata 等);失败不熄灭事件通道。 */
155644
+ async update(patch) {
155645
+ try {
155646
+ await this.opts.store.updateRun(this.opts.runId, patch);
155647
+ this.onSuccess();
155648
+ return true;
155649
+ } catch (err) {
155650
+ this.onFailure(err);
155651
+ return false;
155652
+ }
155653
+ }
155654
+ };
155655
+ }
155656
+ });
155657
+
155413
155658
  // ../server/src/domains/trace/sink.ts
155414
155659
  function deriveMessage(payload) {
155415
155660
  if (payload && typeof payload === "object") {
@@ -155419,11 +155664,10 @@ function deriveMessage(payload) {
155419
155664
  }
155420
155665
  return void 0;
155421
155666
  }
155422
- function trajectoryEventToRunEvent(e, seq) {
155667
+ function trajectoryEventToRunEvent(e) {
155423
155668
  const map = KIND_MAP[e.kind] ?? { eventType: "progress", stream: "runtime" };
155424
155669
  const message = deriveMessage(e.payload);
155425
155670
  return {
155426
- seq,
155427
155671
  eventType: map.eventType,
155428
155672
  stream: map.stream,
155429
155673
  ...message !== void 0 ? { message } : {},
@@ -155502,6 +155746,7 @@ var KIND_MAP, MODEL_TEXT_LIMIT, TOOL_PREVIEW_LIMIT, PROGRESS_TOUCH_THROTTLE_MS,
155502
155746
  var init_sink = __esm({
155503
155747
  "../server/src/domains/trace/sink.ts"() {
155504
155748
  "use strict";
155749
+ init_resilient_appender();
155505
155750
  KIND_MAP = {
155506
155751
  message: { eventType: "model.output.completed", stream: "model" },
155507
155752
  thought: { eventType: "model.output.delta", stream: "model" },
@@ -155517,12 +155762,23 @@ var init_sink = __esm({
155517
155762
  store;
155518
155763
  runtimeKind;
155519
155764
  onError;
155765
+ health;
155520
155766
  sessions = /* @__PURE__ */ new Map();
155521
155767
  constructor(opts) {
155522
155768
  this.store = opts.store;
155523
155769
  this.runtimeKind = opts.runtimeKind ?? "custom";
155524
155770
  this.onError = opts.onError ?? (() => {
155525
155771
  });
155772
+ this.health = opts.health;
155773
+ }
155774
+ /** 为一个 run 造事件通道 appender(seq 两道防线 + 健康度上报都收在这里)。 */
155775
+ makeAppender(runId) {
155776
+ return new ResilientTraceAppender({
155777
+ runId,
155778
+ store: this.store,
155779
+ ...this.health ? { health: this.health } : {},
155780
+ onError: (err) => this.onError(err)
155781
+ });
155526
155782
  }
155527
155783
  runtimeKindFor(session) {
155528
155784
  if (this.runtimeKind !== "auto") return this.runtimeKind;
@@ -155544,7 +155800,7 @@ var init_sink = __esm({
155544
155800
  begin(session, _bundleFiles) {
155545
155801
  const st = {
155546
155802
  runId: session.runId,
155547
- seq: 0,
155803
+ appender: this.makeAppender(session.runId),
155548
155804
  chain: Promise.resolve(),
155549
155805
  openTools: /* @__PURE__ */ new Map(),
155550
155806
  // runtimeSessionId:运行时自己的会话号(= 会话工作目录名,也是 --resume 的键)。落账后
@@ -155578,8 +155834,7 @@ var init_sink = __esm({
155578
155834
  relation: "triggered_by",
155579
155835
  createdAt: session.startedAt
155580
155836
  });
155581
- await this.store.appendEvents(st.runId, [{
155582
- seq: ++st.seq,
155837
+ await st.appender.append([{
155583
155838
  eventType: "run.started",
155584
155839
  stream: "runtime",
155585
155840
  message: `${session.action} ${session.artifactId}`
@@ -155589,7 +155844,7 @@ var init_sink = __esm({
155589
155844
  recover(session) {
155590
155845
  const st = {
155591
155846
  runId: session.runId,
155592
- seq: 0,
155847
+ appender: this.makeAppender(session.runId),
155593
155848
  chain: Promise.resolve(),
155594
155849
  openTools: /* @__PURE__ */ new Map(),
155595
155850
  baseMetadata: { bundleManifest: session.bundleManifest, recovered: true },
@@ -155616,11 +155871,10 @@ var init_sink = __esm({
155616
155871
  st.baseMetadata = { ...asMetadata(existing.metadata) ?? st.baseMetadata, recovered: true };
155617
155872
  }
155618
155873
  const events = await this.store.listEvents(session.runId, {});
155619
- st.seq = events.reduce((max, event) => Math.max(max, event.seq), 0);
155874
+ st.appender.resetSeq(events.reduce((max, event) => Math.max(max, event.seq), 0));
155620
155875
  const recoveredAt = session.recoveredAt ?? (/* @__PURE__ */ new Date()).toISOString();
155621
155876
  const interruptedAt = session.interruptedAt ?? recoveredAt;
155622
- await this.store.appendEvents(st.runId, [{
155623
- seq: ++st.seq,
155877
+ await st.appender.append([{
155624
155878
  eventType: "progress",
155625
155879
  stream: "system",
155626
155880
  level: "warn",
@@ -155645,11 +155899,11 @@ var init_sink = __esm({
155645
155899
  const ms = Date.parse(event.ts);
155646
155900
  if (!Number.isNaN(ms) && ms - (st.lastProgressTouchMs ?? 0) >= PROGRESS_TOUCH_THROTTLE_MS) {
155647
155901
  st.lastProgressTouchMs = ms;
155648
- await this.store.updateRun(st.runId, { lastProgressAt: event.ts });
155902
+ await st.appender.update({ lastProgressAt: event.ts });
155649
155903
  }
155650
155904
  return;
155651
155905
  }
155652
- await this.store.appendEvents(st.runId, [trajectoryEventToRunEvent(event, ++st.seq)]);
155906
+ await st.appender.append([trajectoryEventToRunEvent(event)]);
155653
155907
  await this.materializeToolCall(st, event);
155654
155908
  });
155655
155909
  }
@@ -155700,8 +155954,7 @@ var init_sink = __esm({
155700
155954
  const captured = this.sessions.get(sessionId);
155701
155955
  this.enqueue(sessionId, async (st) => {
155702
155956
  for (const ref2 of update.opRefs) {
155703
- await this.store.appendEvents(st.runId, [{
155704
- seq: ++st.seq,
155957
+ await st.appender.append([{
155705
155958
  eventType: "oplog.appended",
155706
155959
  stream: "oasis",
155707
155960
  message: ref2.kind,
@@ -155715,8 +155968,7 @@ var init_sink = __esm({
155715
155968
  createdAt: update.exit.at
155716
155969
  });
155717
155970
  }
155718
- await this.store.appendEvents(st.runId, [{
155719
- seq: ++st.seq,
155971
+ await st.appender.append([{
155720
155972
  eventType: "run.finished",
155721
155973
  stream: "runtime",
155722
155974
  message: `exit code=${update.exit.code ?? "killed"} ops=${update.opRefs.length}`
@@ -155750,6 +156002,7 @@ var init_sink = __esm({
155750
156002
  });
155751
156003
  if (captured) captured.chain = captured.chain.finally(() => {
155752
156004
  this.sessions.delete(sessionId);
156005
+ this.health?.forget(captured.runId);
155753
156006
  });
155754
156007
  }
155755
156008
  };
@@ -155811,27 +156064,26 @@ function wireRecoveredChatTurn(deps) {
155811
156064
  live.emit(rest);
155812
156065
  }
155813
156066
  deps.register();
155814
- let traceSeq = 0;
155815
- let traceEnabled = true;
156067
+ const appender = new ResilientTraceAppender({
156068
+ runId: plan.runId,
156069
+ store: trace,
156070
+ ...deps.traceHealth ? { health: deps.traceHealth } : {},
156071
+ onError: (err) => log3(`[chat-recovery] run ${plan.runId} trace \u5199\u5931\u8D25\uFF08\u7EE7\u7EED\u3001\u4E0D\u7184\u706D\uFF09: ${String(err)}`)
156072
+ });
155816
156073
  let traceChain = trace.listEvents(plan.runId, {}).then((events) => {
155817
- for (const e of events) if (e.seq > traceSeq) traceSeq = e.seq;
156074
+ appender.resetSeq(events.reduce((n, e) => Math.max(n, e.seq), 0));
155818
156075
  }).catch((err) => {
155819
- traceEnabled = false;
155820
- log3(`[chat-recovery] run ${plan.runId} \u8BFB\u4E8B\u4EF6\u5931\u8D25\uFF0C\u672C\u8F6E trace \u7EED\u8D26\u5173\u95ED: ${String(err)}`);
156076
+ deps.traceHealth?.markUnhealthy(plan.runId);
156077
+ log3(`[chat-recovery] run ${plan.runId} \u8BFB\u4E8B\u4EF6\u5931\u8D25: ${String(err)}`);
155821
156078
  });
155822
156079
  const enqueueTrace = (step) => {
155823
- if (!traceEnabled) return;
155824
- traceChain = traceChain.then(async () => {
155825
- if (traceEnabled) await step();
155826
- }).catch((err) => {
155827
- traceEnabled = false;
155828
- log3(`[chat-recovery] run ${plan.runId} trace \u7EED\u8D26\u5931\u8D25: ${String(err)}`);
156080
+ traceChain = traceChain.then(step).catch((err) => {
156081
+ log3(`[chat-recovery] run ${plan.runId} trace \u7EED\u8D26\u5F02\u5E38: ${String(err)}`);
155829
156082
  });
155830
156083
  };
155831
156084
  const recoveredAt = (/* @__PURE__ */ new Date()).toISOString();
155832
156085
  enqueueTrace(async () => {
155833
- await trace.appendEvents(plan.runId, [{
155834
- seq: ++traceSeq,
156086
+ await appender.append([{
155835
156087
  eventType: "progress",
155836
156088
  stream: "system",
155837
156089
  level: "warn",
@@ -155855,11 +156107,11 @@ function wireRecoveredChatTurn(deps) {
155855
156107
  const ms = Date.parse(event.ts);
155856
156108
  if (!Number.isNaN(ms) && ms - lastProgressTouchMs >= PROGRESS_TOUCH_THROTTLE_MS2) {
155857
156109
  lastProgressTouchMs = ms;
155858
- enqueueTrace(() => trace.updateRun(plan.runId, { lastProgressAt: event.ts }).then(() => void 0));
156110
+ enqueueTrace(() => appender.update({ lastProgressAt: event.ts }).then(() => void 0));
155859
156111
  }
155860
156112
  return;
155861
156113
  }
155862
- enqueueTrace(() => trace.appendEvents(plan.runId, [trajectoryEventToRunEvent(event, ++traceSeq)]).then(() => void 0));
156114
+ enqueueTrace(() => appender.append([trajectoryEventToRunEvent(event)]).then(() => void 0));
155863
156115
  if (event.kind === "message" && sawTextOutput) return;
155864
156116
  const part = chatPartFromTrajectoryEvent(event);
155865
156117
  if (part) {
@@ -155940,14 +156192,13 @@ function wireRecoveredChatTurn(deps) {
155940
156192
  live.finish(failed ? "error" : "done");
155941
156193
  deps.unregister();
155942
156194
  enqueueTrace(async () => {
155943
- await trace.appendEvents(plan.runId, [{
155944
- seq: ++traceSeq,
156195
+ await appender.append([{
155945
156196
  eventType: "run.finished",
155946
156197
  stream: "runtime",
155947
156198
  message: `exit code=${info.code ?? "killed"}`,
155948
156199
  startedAt: finishedAt
155949
156200
  }]);
155950
- await trace.updateRun(plan.runId, runTerminalPatch(info, plan.startedAt, finishedAt));
156201
+ await appender.update(runTerminalPatch(info, plan.startedAt, finishedAt));
155951
156202
  if (info.usage && trace.updateUsageAggregates) {
155952
156203
  const run = await trace.getRun(plan.runId);
155953
156204
  if (run) void trace.updateUsageAggregates(run).catch(() => void 0);
@@ -156044,17 +156295,17 @@ async function reconcileChatExitFrame(deps) {
156044
156295
  }).catch(() => void 0);
156045
156296
  try {
156046
156297
  const events = await deps.trace.listEvents(row.runId, {});
156047
- let traceSeq = events.reduce((n, e) => Math.max(n, e.seq), 0);
156048
- await deps.trace.appendEvents(row.runId, [
156049
- ...stash.frames.filter((frame) => frame.type === "event").map((frame) => frame.event).filter((event) => event.kind !== "system").map((event) => trajectoryEventToRunEvent(event, ++traceSeq)),
156298
+ const baseSeq = events.reduce((n, e) => Math.max(n, e.seq), 0);
156299
+ const seqless = [
156300
+ ...stash.frames.filter((frame) => frame.type === "event").map((frame) => frame.event).filter((event) => event.kind !== "system").map((event) => trajectoryEventToRunEvent(event)),
156050
156301
  {
156051
- seq: ++traceSeq,
156052
156302
  eventType: "run.finished",
156053
156303
  stream: "runtime",
156054
156304
  message: `exit code=${info.code ?? "killed"}\uFF08serve \u505C\u673A\u7A97\u53E3\u5185\u7ED3\u675F\uFF0C\u8282\u70B9 outbox \u91CD\u53D1\u5BF9\u8D26\uFF09`,
156055
156305
  startedAt: finishedAt
156056
156306
  }
156057
- ]);
156307
+ ];
156308
+ await deps.trace.appendEvents(row.runId, seqless.map((e, i) => ({ ...e, seq: baseSeq + i + 1 })));
156058
156309
  await deps.trace.updateRun(row.runId, runTerminalPatch(info, run.startedAt, finishedAt));
156059
156310
  if (info.usage && deps.trace.updateUsageAggregates) {
156060
156311
  const fresh = await deps.trace.getRun(row.runId);
@@ -156075,6 +156326,7 @@ var init_chat_recovery = __esm({
156075
156326
  import_node_crypto20 = require("node:crypto");
156076
156327
  init_chat_parts();
156077
156328
  init_sink();
156329
+ init_resilient_appender();
156078
156330
  asObj3 = (v2) => v2 && typeof v2 === "object" && !Array.isArray(v2) ? v2 : void 0;
156079
156331
  partsOf = (parts) => Array.isArray(parts) ? parts.filter((p2) => p2 && typeof p2 === "object" && typeof p2.type === "string") : [];
156080
156332
  maxPartSeq = (parts) => parts.reduce((n, p2) => Math.max(n, typeof p2.seq === "number" ? p2.seq : 0), 0);
@@ -156668,6 +156920,24 @@ var init_skill_fetcher = __esm({
156668
156920
  });
156669
156921
 
156670
156922
  // ../server/src/domains/actors/service.ts
156923
+ function applyRefDelivery(env, entries, warn = (m2) => console.warn(m2)) {
156924
+ for (const e of entries) {
156925
+ if (!(e.key in env)) continue;
156926
+ const mode = e.deliveryMode;
156927
+ switch (mode) {
156928
+ case "env":
156929
+ break;
156930
+ // 非敏感:明文保持不动
156931
+ case "ref":
156932
+ env[e.key] = makeVarRef(e.key);
156933
+ break;
156934
+ default:
156935
+ delete env[e.key];
156936
+ warn(`[credential] \u672A\u77E5 deliveryMode "${mode}"\uFF08key=${e.key}\uFF09\u2014\u2014\u5DF2\u4ECE env \u5254\u9664\uFF0C\u4FDD\u5B88\u5904\u7406\u4E0D\u6CE8\u5165\u660E\u6587`);
156937
+ break;
156938
+ }
156939
+ }
156940
+ }
156671
156941
  function computeEffectiveEntries(config2, connections, connectors) {
156672
156942
  const globalConnected = new Set(connectors.filter((c) => c.status === "connected").map((c) => c.id));
156673
156943
  const record6 = new Map(connections.map((c) => [c.connectorId, c.enabled]));
@@ -156699,6 +156969,7 @@ var init_service3 = __esm({
156699
156969
  init_skill_fetcher();
156700
156970
  init_identity();
156701
156971
  init_src5();
156972
+ init_src();
156702
156973
  REDACTED_MARKER = "[REDACTED]";
156703
156974
  ActorsService = class {
156704
156975
  constructor(opts) {
@@ -157352,8 +157623,11 @@ ${input.description}
157352
157623
  key: v2.key,
157353
157624
  scope: v2.scope,
157354
157625
  ...v2.actorId !== void 0 ? { actorId: v2.actorId } : {},
157626
+ ...v2.projectId !== void 0 ? { projectId: v2.projectId } : {},
157355
157627
  ...v2.connectorId !== void 0 ? { connectorId: v2.connectorId } : {},
157356
157628
  ...v2.overrides !== void 0 ? { overrides: v2.overrides } : {},
157629
+ // 缺省 "env"(存量行为不变,敏感项需显式选 "ref",ADR §4.4 / 待确认 3)
157630
+ deliveryMode: v2.deliveryMode ?? "env",
157357
157631
  valueEncrypted: v2.encrypted === false ? `plain:${v2.value}` : this.encrypt(v2.value),
157358
157632
  updatedAt: this.now()
157359
157633
  });
@@ -157389,8 +157663,11 @@ ${input.description}
157389
157663
  key: r.key,
157390
157664
  scope: r.scope,
157391
157665
  ...r.actorId !== void 0 ? { actorId: r.actorId } : {},
157666
+ ...r.projectId !== void 0 ? { projectId: r.projectId } : {},
157392
157667
  ...r.connectorId !== void 0 ? { connectorId: r.connectorId } : {},
157393
157668
  ...r.overrides !== void 0 ? { overrides: r.overrides } : {},
157669
+ deliveryMode: r.deliveryMode ?? "env",
157670
+ // 缺省视为 env(ADR §4.4;存量行读侧统一回填)
157394
157671
  encrypted: !isPlain,
157395
157672
  maskedValue: isPlain ? r.valueEncrypted.slice(6) : mask(this.decrypt(r.valueEncrypted)),
157396
157673
  updatedAt: r.updatedAt
@@ -157414,8 +157691,13 @@ ${input.description}
157414
157691
  }
157415
157692
  return this.revealVariable(key, void 0);
157416
157693
  }
157417
- deleteVariable(key, actorId) {
157418
- return this.opts.store.deleteVariable(key, actorId);
157694
+ deleteVariable(key, actorId, scoping) {
157695
+ return this.opts.store.deleteVariable(key, actorId, scoping);
157696
+ }
157697
+ /** 该 key 是否在**任一**作用域存在(reveal 判权失败时区分 not_found / no_permission,仅 server 侧审计用;§4.5.2)。 */
157698
+ async hasVariableKey(key) {
157699
+ const rows = await this.opts.store.listVariables();
157700
+ return rows.some((r) => r.key === key);
157419
157701
  }
157420
157702
  async resolveActorEnv(actorId) {
157421
157703
  const cfg = await this.opts.store.latestConfig(actorId);
@@ -157445,6 +157727,64 @@ ${input.description}
157445
157727
  }
157446
157728
  return env;
157447
157729
  }
157730
+ /** 密文 → 明文(与 resolveActorEnv 内联的 decode 同规则:plain: 前缀直读,否则 AES 解密)。 */
157731
+ decodeCiphertext(ct) {
157732
+ return ct.startsWith("plain:") ? ct.slice(6) : this.decrypt(ct);
157733
+ }
157734
+ /**
157735
+ * ADR 凭据保险库 §4.3 / ADR 0125:按「项目」解析 project 变量为明文条目(含 deliveryMode)。
157736
+ * store 未实现 resolveProjectVariables(旧实现)时返回空——调用方跳过 project 层。
157737
+ */
157738
+ async resolveProjectVariables(projectId) {
157739
+ if (!this.opts.store.resolveProjectVariables) return [];
157740
+ const rows = await this.opts.store.resolveProjectVariables(projectId);
157741
+ return rows.map((r) => ({ key: r.key, value: this.decodeCiphertext(r.valueEncrypted), deliveryMode: r.deliveryMode ?? "env" }));
157742
+ }
157743
+ /**
157744
+ * ADR 凭据保险库 §4.3 / ADR 0125:派单/reveal 用的**合并后**变量条目(明文 + deliveryMode)。
157745
+ * 优先级低→高:global < project < personal(同 key 时高层覆盖低层,personal 最优先)。
157746
+ * 连接器门禁与 resolveActorEnv 同源(未启用的连接器变量不注入)。
157747
+ * - `opts.projectId` 给出时才叠加 project 层;缺省 = 只有 personal+global(存量行为)。
157748
+ */
157749
+ async resolveActorProvisionEntries(actorId, opts) {
157750
+ const cfg = await this.opts.store.latestConfig(actorId);
157751
+ const enabledConnectors = await this.effectiveEnabledConnectors(actorId, cfg);
157752
+ if (opts?.refreshConnectors !== false) {
157753
+ await refreshEnabledConnectorCredentials(
157754
+ this,
157755
+ createAllConnectors(),
157756
+ actorId,
157757
+ (slug6) => enabledConnectors.has(slug6)
157758
+ );
157759
+ }
157760
+ const rows = await this.opts.store.listVariables(actorId);
157761
+ const projectRows = opts?.projectId && this.opts.store.resolveProjectVariables ? await this.opts.store.resolveProjectVariables(opts.projectId) : [];
157762
+ const merged = /* @__PURE__ */ new Map();
157763
+ const gated = (row) => !row.connectorId || enabledConnectors.has(row.connectorId);
157764
+ const put = (row) => {
157765
+ if (!gated(row)) return;
157766
+ merged.set(row.key, { key: row.key, value: this.decodeCiphertext(row.valueEncrypted), deliveryMode: row.deliveryMode ?? "env" });
157767
+ };
157768
+ for (const row of rows) {
157769
+ if (row.scope === "global" && !row.actorId) put(row);
157770
+ }
157771
+ for (const row of projectRows) {
157772
+ if (row.scope === "project") put(row);
157773
+ }
157774
+ for (const row of rows) {
157775
+ if (row.scope === "personal" && (!row.actorId || row.actorId === actorId)) put(row);
157776
+ }
157777
+ return [...merged.values()];
157778
+ }
157779
+ /**
157780
+ * ADR 凭据保险库 §4.5.1:reveal 的判权即「能否被解析」——该 key 通过 §4.3 合并顺序对本 caller
157781
+ * 能得出值即可读,返回明文;否则返回 null(缺 key / 无权,统一由路由映射成 403,不区分)。
157782
+ */
157783
+ async resolveRevealValue(actorId, key, opts) {
157784
+ const entries = await this.resolveActorProvisionEntries(actorId, { ...opts, refreshConnectors: false });
157785
+ const hit = entries.find((e) => e.key === key);
157786
+ return hit ? { value: hit.value } : null;
157787
+ }
157448
157788
  encrypt(plain) {
157449
157789
  const iv = (0, import_node_crypto22.randomBytes)(12);
157450
157790
  const cipher = (0, import_node_crypto22.createCipheriv)("aes-256-gcm", this.opts.variableKey, iv);
@@ -158453,7 +158793,7 @@ var init_skill_materializer = __esm({
158453
158793
 
158454
158794
  // ../server/src/governance/connector-skills.ts
158455
158795
  function connectorSkillsDir(dataDir, connectorId) {
158456
- return (0, import_node_path12.join)(dataDir, "connector-skills", connectorId);
158796
+ return (0, import_node_path13.join)(dataDir, "connector-skills", connectorId);
158457
158797
  }
158458
158798
  function parseConnectorSkillId(id) {
158459
158799
  if (!id.startsWith("connector:")) return null;
@@ -158463,17 +158803,17 @@ function parseConnectorSkillId(id) {
158463
158803
  return { connectorId: rest.slice(0, i), slug: rest.slice(i + 1) };
158464
158804
  }
158465
158805
  async function collect(root, dir, out) {
158466
- for (const e of await (0, import_promises6.readdir)(dir, { withFileTypes: true })) {
158467
- const abs = (0, import_node_path12.join)(dir, e.name);
158806
+ for (const e of await (0, import_promises7.readdir)(dir, { withFileTypes: true })) {
158807
+ const abs = (0, import_node_path13.join)(dir, e.name);
158468
158808
  if (e.isDirectory()) await collect(root, abs, out);
158469
- else if (e.isFile()) out[(0, import_node_path12.relative)(root, abs).split(/[\\/]/).join("/")] = await (0, import_promises6.readFile)(abs, "utf8");
158809
+ else if (e.isFile()) out[(0, import_node_path13.relative)(root, abs).split(/[\\/]/).join("/")] = await (0, import_promises7.readFile)(abs, "utf8");
158470
158810
  }
158471
158811
  }
158472
158812
  async function readLocalPack(dataDir, connectorId) {
158473
158813
  const root = connectorSkillsDir(dataDir, connectorId);
158474
158814
  let slugs;
158475
158815
  try {
158476
- slugs = (await (0, import_promises6.readdir)(root, { withFileTypes: true })).filter((d) => d.isDirectory()).map((d) => d.name);
158816
+ slugs = (await (0, import_promises7.readdir)(root, { withFileTypes: true })).filter((d) => d.isDirectory()).map((d) => d.name);
158477
158817
  } catch {
158478
158818
  return [];
158479
158819
  }
@@ -158481,7 +158821,7 @@ async function readLocalPack(dataDir, connectorId) {
158481
158821
  for (const slug6 of slugs) {
158482
158822
  const files = {};
158483
158823
  try {
158484
- await collect((0, import_node_path12.join)(root, slug6), (0, import_node_path12.join)(root, slug6), files);
158824
+ await collect((0, import_node_path13.join)(root, slug6), (0, import_node_path13.join)(root, slug6), files);
158485
158825
  } catch {
158486
158826
  continue;
158487
158827
  }
@@ -158497,17 +158837,17 @@ async function readLocalPack(dataDir, connectorId) {
158497
158837
  async function writePack(dataDir, connectorId, pack) {
158498
158838
  const root = connectorSkillsDir(dataDir, connectorId);
158499
158839
  const tmp = `${root}.tmp`;
158500
- await (0, import_promises6.rm)(tmp, { recursive: true, force: true });
158840
+ await (0, import_promises7.rm)(tmp, { recursive: true, force: true });
158501
158841
  for (const s2 of pack) {
158502
158842
  for (const [rel, content] of Object.entries(s2.files)) {
158503
- const abs = (0, import_node_path12.join)(tmp, s2.slug, rel);
158504
- await (0, import_promises6.mkdir)((0, import_node_path12.dirname)(abs), { recursive: true });
158505
- await (0, import_promises6.writeFile)(abs, content, "utf8");
158843
+ const abs = (0, import_node_path13.join)(tmp, s2.slug, rel);
158844
+ await (0, import_promises7.mkdir)((0, import_node_path13.dirname)(abs), { recursive: true });
158845
+ await (0, import_promises7.writeFile)(abs, content, "utf8");
158506
158846
  }
158507
158847
  }
158508
- await (0, import_promises6.rm)(root, { recursive: true, force: true });
158509
- await (0, import_promises6.mkdir)((0, import_node_path12.dirname)(root), { recursive: true });
158510
- await (0, import_promises6.rename)(tmp, root);
158848
+ await (0, import_promises7.rm)(root, { recursive: true, force: true });
158849
+ await (0, import_promises7.mkdir)((0, import_node_path13.dirname)(root), { recursive: true });
158850
+ await (0, import_promises7.rename)(tmp, root);
158511
158851
  }
158512
158852
  function packMatches(local, remote) {
158513
158853
  if (local.length !== remote.length) return false;
@@ -158534,18 +158874,21 @@ async function syncConnectorSkills(args) {
158534
158874
  log3(`[connector-skills] ${args.connectorId}\uFF1A\u5DF2\u4ECE\u5382\u5546\u540C\u6B65 ${fresh.length} \u4E2A\u6280\u80FD`);
158535
158875
  return { skills: fresh, outcome: "fetched" };
158536
158876
  }
158537
- var import_promises6, import_node_path12, toId;
158877
+ var import_promises7, import_node_path13, toId;
158538
158878
  var init_connector_skills = __esm({
158539
158879
  "../server/src/governance/connector-skills.ts"() {
158540
158880
  "use strict";
158541
- import_promises6 = require("node:fs/promises");
158542
- import_node_path12 = require("node:path");
158881
+ import_promises7 = require("node:fs/promises");
158882
+ import_node_path13 = require("node:path");
158543
158883
  init_src5();
158544
158884
  toId = (connectorId, slug6) => `connector:${connectorId}/${slug6}`;
158545
158885
  }
158546
158886
  });
158547
158887
 
158548
158888
  // ../server/src/domains/actors/routes.ts
158889
+ function isKnownRevealSource(source) {
158890
+ return CREDENTIAL_REVEAL_SOURCES.has(source) || source.startsWith("agent-skill:");
158891
+ }
158549
158892
  function positionsFromPayload(body) {
158550
158893
  if (!body) return void 0;
158551
158894
  if (Object.prototype.hasOwnProperty.call(body, "roles")) {
@@ -158578,6 +158921,8 @@ function actorApiRecord(actor) {
158578
158921
  }
158579
158922
  function actorsDomain(opts) {
158580
158923
  const { resolveCtx } = opts;
158924
+ const revealLimits = opts.credentialRevealLimits ?? { perSession: 200, perSessionPerKey: 20 };
158925
+ const revealLimiter = new RevealRateLimiter(revealLimits.perSession, revealLimits.perSessionPerKey);
158581
158926
  return (router) => {
158582
158927
  router.get("/api/actors", async (req) => {
158583
158928
  const { service } = await resolveCtx(req.auth.companyId);
@@ -158817,11 +159162,23 @@ function actorsDomain(opts) {
158817
159162
  const { service } = await resolveCtx(req.auth.companyId);
158818
159163
  const b2 = req.body;
158819
159164
  if (!b2?.key) throw new ApiError(400, "BAD_REQUEST", "\u7F3A\u5C11 key");
159165
+ const scope = b2.scope ?? "global";
159166
+ if (scope === "project" && !b2.projectId) {
159167
+ throw new ApiError(400, "BAD_REQUEST", "scope=project \u5FC5\u987B\u63D0\u4F9B projectId");
159168
+ }
159169
+ if (scope === "project" && opts.projectExists && !await opts.projectExists(b2.projectId)) {
159170
+ throw new ApiError(400, "BAD_REQUEST", `\u9879\u76EE\u4E0D\u5B58\u5728\uFF1A${b2.projectId}\uFF08scope=project \u53D8\u91CF\u7684 projectId \u5FC5\u987B\u662F\u771F\u5B9E\u9879\u76EE\uFF09`);
159171
+ }
159172
+ if (b2.deliveryMode !== void 0 && b2.deliveryMode !== "env" && b2.deliveryMode !== "ref") {
159173
+ throw new ApiError(400, "BAD_REQUEST", "deliveryMode \u53EA\u80FD\u662F env \u6216 ref");
159174
+ }
158820
159175
  if (b2.value === void 0 || b2.value === "") return { status: 200, body: { ok: true } };
158821
159176
  await service.putVariable({
158822
159177
  key: b2.key,
158823
159178
  value: b2.value,
158824
- scope: b2.scope ?? "global",
159179
+ scope,
159180
+ ...scope === "project" ? { projectId: b2.projectId } : {},
159181
+ ...b2.deliveryMode !== void 0 ? { deliveryMode: b2.deliveryMode } : {},
158825
159182
  ...b2.connectorId !== void 0 ? { connectorId: b2.connectorId } : {},
158826
159183
  ...b2.overrides !== void 0 ? { overrides: b2.overrides } : {},
158827
159184
  ...b2.encrypted !== void 0 ? { encrypted: b2.encrypted } : {}
@@ -158830,7 +159187,12 @@ function actorsDomain(opts) {
158830
159187
  });
158831
159188
  router.delete("/api/variables/:key", async (req) => {
158832
159189
  const { service } = await resolveCtx(req.auth.companyId);
158833
- await service.deleteVariable(req.params.key);
159190
+ const projectId = req.query.get("projectId") ?? void 0;
159191
+ await service.deleteVariable(
159192
+ req.params.key,
159193
+ void 0,
159194
+ projectId ? { projectId } : void 0
159195
+ );
158834
159196
  return { status: 200, body: { ok: true } };
158835
159197
  });
158836
159198
  router.get("/api/actors/:id/variables", async (req) => {
@@ -158842,6 +159204,9 @@ function actorsDomain(opts) {
158842
159204
  const actorId = req.params.id;
158843
159205
  const b2 = req.body;
158844
159206
  if (!b2?.key) throw new ApiError(400, "BAD_REQUEST", "\u7F3A\u5C11 key");
159207
+ if (b2.deliveryMode !== void 0 && b2.deliveryMode !== "env" && b2.deliveryMode !== "ref") {
159208
+ throw new ApiError(400, "BAD_REQUEST", "deliveryMode \u53EA\u80FD\u662F env \u6216 ref");
159209
+ }
158845
159210
  if (b2.value === void 0 || b2.value === "") return { status: 200, body: { ok: true } };
158846
159211
  if (b2.connectorId === void 0) {
158847
159212
  const existing = await service.listVariables(actorId);
@@ -158859,6 +159224,7 @@ function actorsDomain(opts) {
158859
159224
  value: b2.value,
158860
159225
  actorId,
158861
159226
  scope: "personal",
159227
+ ...b2.deliveryMode !== void 0 ? { deliveryMode: b2.deliveryMode } : {},
158862
159228
  ...b2.connectorId !== void 0 ? { connectorId: b2.connectorId } : {},
158863
159229
  ...b2.overrides !== void 0 ? { overrides: b2.overrides } : {},
158864
159230
  ...b2.encrypted !== void 0 ? { encrypted: b2.encrypted } : {}
@@ -158870,6 +159236,51 @@ function actorsDomain(opts) {
158870
159236
  await service.deleteVariable(req.params.key, req.params.id);
158871
159237
  return { status: 200, body: { ok: true } };
158872
159238
  });
159239
+ router.post("/api/variables/reveal", async (req) => {
159240
+ const { service } = await resolveCtx(req.auth.companyId);
159241
+ const b2 = req.body;
159242
+ const rawKey = typeof b2?.key === "string" ? b2.key.trim() : "";
159243
+ if (!rawKey) throw new ApiError(400, "BAD_REQUEST", "\u7F3A\u5C11 key");
159244
+ const key = parseVarRef(rawKey) ?? rawKey;
159245
+ const source = typeof b2?.source === "string" && b2.source.trim() || "agent-cli";
159246
+ if (!isKnownRevealSource(source)) throw new ApiError(400, "BAD_REQUEST", `\u672A\u8BC6\u522B\u7684 source\u300C${source}\u300D\uFF08\xA74.5.4\uFF1A\u53EA\u63A5\u53D7\u8BC6\u522B\u503C\uFF09`);
159247
+ const actor = req.auth.actor;
159248
+ const artifactId = req.auth.dispatch?.artifactId;
159249
+ const sessionId = req.auth.dispatch?.sessionId;
159250
+ const now = Date.now();
159251
+ const timestamp = new Date(now).toISOString();
159252
+ const sessionKey = sessionId ?? `${actor}::${artifactId ?? "no-artifact"}`;
159253
+ const dispatchFields = { ...sessionId ? { sessionId } : {}, ...artifactId ? { artifactId } : {} };
159254
+ const throttled = revealLimiter.check(sessionKey, key, now);
159255
+ if (throttled) {
159256
+ await opts.credentialAudit?.({ kind: "credential_reveal_throttled", actor, variableKey: key, source, timestamp, scope: throttled, ...dispatchFields });
159257
+ return { status: 429, body: { code: "throttled", key, scope: throttled } };
159258
+ }
159259
+ const scope = artifactId && opts.resolveDispatchScope ? await opts.resolveDispatchScope(artifactId) : {};
159260
+ const resolved = await service.resolveRevealValue(actor, key, scope);
159261
+ if (!resolved) {
159262
+ const reason = await service.hasVariableKey(key) ? "no_permission" : "not_found";
159263
+ await opts.credentialAudit?.({ kind: "credential_reveal_denied", actor, variableKey: key, source, timestamp, reason, ...dispatchFields });
159264
+ return { status: 403, body: { code: "denied", key } };
159265
+ }
159266
+ await opts.credentialAudit?.({ kind: "credential_reveal", actor, variableKey: key, source, timestamp, ...dispatchFields });
159267
+ return { status: 200, body: { value: resolved.value } };
159268
+ });
159269
+ router.get("/api/variables/reveal-audit", async (req) => {
159270
+ if (!opts.readCredentialAudit) return { status: 501, body: { error: "credential audit reader \u672A\u914D\u7F6E" } };
159271
+ const actor = req.query.get("actor") ?? void 0;
159272
+ const key = req.query.get("key") ?? void 0;
159273
+ const kind = req.query.get("kind") ?? void 0;
159274
+ const limitRaw = req.query.get("limit");
159275
+ const limit = limitRaw ? Math.min(Math.max(parseInt(limitRaw, 10) || 50, 1), 500) : 50;
159276
+ const items = await opts.readCredentialAudit({
159277
+ ...actor ? { actor } : {},
159278
+ ...key ? { key } : {},
159279
+ ...kind ? { kind } : {},
159280
+ limit
159281
+ });
159282
+ return { status: 200, body: { items } };
159283
+ });
158873
159284
  router.get("/api/skills/market", async (req) => {
158874
159285
  const { service } = await resolveCtx(req.auth.companyId);
158875
159286
  const [catalog, installed] = await Promise.all([
@@ -159028,6 +159439,18 @@ function actorsDomain(opts) {
159028
159439
  const results = await opts.refreshConnectorSkills();
159029
159440
  return { status: 200, body: { results } };
159030
159441
  });
159442
+ router.get("/api/connector-credentials", async (req) => {
159443
+ const { service } = await resolveCtx(req.auth.companyId);
159444
+ const actorId = req.auth.actor;
159445
+ const entries = await service.resolveActorProvisionEntries(actorId);
159446
+ const vars = {};
159447
+ for (const e of entries) vars[e.key] = e.value;
159448
+ const connectorCreds = collectAllConnectorCredentials({
159449
+ vars,
159450
+ actorName: vars["GIT_AUTHOR_NAME"] ?? actorId.split(":").pop() ?? "oasis-agent"
159451
+ });
159452
+ return { status: 200, body: { connectorCreds } };
159453
+ });
159031
159454
  router.get("/api/skill-cache/manifest", async (req) => {
159032
159455
  const { service } = await resolveCtx(req.auth.companyId);
159033
159456
  const builtins = opts.getBuiltinSkills?.() ?? [];
@@ -159306,12 +159729,15 @@ function actorsDomain(opts) {
159306
159729
  });
159307
159730
  };
159308
159731
  }
159732
+ var CREDENTIAL_REVEAL_SOURCES, RevealRateLimiter;
159309
159733
  var init_routes = __esm({
159310
159734
  "../server/src/domains/actors/routes.ts"() {
159311
159735
  "use strict";
159312
159736
  init_src();
159313
159737
  init_src2();
159314
159738
  init_router();
159739
+ init_src();
159740
+ init_src5();
159315
159741
  init_memory();
159316
159742
  init_skill_fetcher();
159317
159743
  init_stats();
@@ -159320,6 +159746,44 @@ var init_routes = __esm({
159320
159746
  init_image_upload();
159321
159747
  init_skill_materializer();
159322
159748
  init_connector_skills();
159749
+ CREDENTIAL_REVEAL_SOURCES = /* @__PURE__ */ new Set(["agent-cli", "smoke-test", "oasis-internal"]);
159750
+ RevealRateLimiter = class {
159751
+ constructor(perSession, perSessionPerKey, ttlMs = 2 * 60 * 6e4) {
159752
+ this.perSession = perSession;
159753
+ this.perSessionPerKey = perSessionPerKey;
159754
+ this.ttlMs = ttlMs;
159755
+ }
159756
+ sessionCounts = /* @__PURE__ */ new Map();
159757
+ sessionKeyCounts = /* @__PURE__ */ new Map();
159758
+ /** 记一次尝试;返回命中的限流档(若超限)或 null(放行)。 */
159759
+ check(sessionKey, varKey, now) {
159760
+ this.prune(now);
159761
+ const sk = `${sessionKey}::${varKey}`;
159762
+ const s2 = this.sessionCounts.get(sessionKey) ?? { count: 0, last: now };
159763
+ const k2 = this.sessionKeyCounts.get(sk) ?? { count: 0, last: now };
159764
+ if (k2.count >= this.perSessionPerKey) {
159765
+ k2.last = now;
159766
+ this.sessionKeyCounts.set(sk, k2);
159767
+ return "session_key";
159768
+ }
159769
+ if (s2.count >= this.perSession) {
159770
+ s2.last = now;
159771
+ this.sessionCounts.set(sessionKey, s2);
159772
+ return "session";
159773
+ }
159774
+ s2.count += 1;
159775
+ s2.last = now;
159776
+ this.sessionCounts.set(sessionKey, s2);
159777
+ k2.count += 1;
159778
+ k2.last = now;
159779
+ this.sessionKeyCounts.set(sk, k2);
159780
+ return null;
159781
+ }
159782
+ prune(now) {
159783
+ for (const [key, v2] of this.sessionCounts) if (now - v2.last > this.ttlMs) this.sessionCounts.delete(key);
159784
+ for (const [key, v2] of this.sessionKeyCounts) if (now - v2.last > this.ttlMs) this.sessionKeyCounts.delete(key);
159785
+ }
159786
+ };
159323
159787
  }
159324
159788
  });
159325
159789
 
@@ -159418,7 +159882,7 @@ function createActorsDomain(opts) {
159418
159882
  return {
159419
159883
  service: defaultCtx.service,
159420
159884
  resolveCtx,
159421
- register: actorsDomain({ resolveCtx, ...opts.trace ? { trace: opts.trace } : {}, ...opts.listBuiltinSkills ? { listBuiltinSkills: opts.listBuiltinSkills } : {}, ...opts.getBuiltinSkills ? { getBuiltinSkills: opts.getBuiltinSkills } : {}, ...opts.getConnectorSkills ? { getConnectorSkills: opts.getConnectorSkills } : {}, ...opts.refreshConnectorSkills ? { refreshConnectorSkills: opts.refreshConnectorSkills } : {}, ...opts.memory ? { memory: opts.memory } : {} })
159885
+ register: actorsDomain({ resolveCtx, ...opts.trace ? { trace: opts.trace } : {}, ...opts.listBuiltinSkills ? { listBuiltinSkills: opts.listBuiltinSkills } : {}, ...opts.getBuiltinSkills ? { getBuiltinSkills: opts.getBuiltinSkills } : {}, ...opts.getConnectorSkills ? { getConnectorSkills: opts.getConnectorSkills } : {}, ...opts.refreshConnectorSkills ? { refreshConnectorSkills: opts.refreshConnectorSkills } : {}, ...opts.memory ? { memory: opts.memory } : {}, ...opts.resolveDispatchScope ? { resolveDispatchScope: opts.resolveDispatchScope } : {}, ...opts.projectExists ? { projectExists: opts.projectExists } : {}, ...opts.credentialAudit ? { credentialAudit: opts.credentialAudit } : {}, ...opts.readCredentialAudit ? { readCredentialAudit: opts.readCredentialAudit } : {}, ...opts.credentialRevealLimits ? { credentialRevealLimits: opts.credentialRevealLimits } : {} })
159422
159886
  };
159423
159887
  }
159424
159888
  var import_node_crypto24;
@@ -161847,10 +162311,10 @@ function nodesDomain(deps) {
161847
162311
  }
161848
162312
  }
161849
162313
  try {
161850
- const here = (0, import_node_path13.dirname)((0, import_node_url4.fileURLToPath)(__esm_import_meta_url));
162314
+ const here = (0, import_node_path14.dirname)((0, import_node_url4.fileURLToPath)(__esm_import_meta_url));
161851
162315
  for (const rel of ["../../../../node-daemon/package.json", "../../../../../node-daemon/package.json"]) {
161852
162316
  try {
161853
- const raw = (0, import_node_fs11.readFileSync)((0, import_node_path13.resolve)(here, rel), "utf8");
162317
+ const raw = (0, import_node_fs11.readFileSync)((0, import_node_path14.resolve)(here, rel), "utf8");
161854
162318
  const pkg = JSON.parse(raw);
161855
162319
  if (pkg.version) return { version: pkg.version, source: "monorepo" };
161856
162320
  } catch {
@@ -162162,14 +162626,14 @@ function nodesDomain(deps) {
162162
162626
  });
162163
162627
  };
162164
162628
  }
162165
- var import_node_crypto26, import_node_fs11, import_node_url4, import_node_path13;
162629
+ var import_node_crypto26, import_node_fs11, import_node_url4, import_node_path14;
162166
162630
  var init_routes5 = __esm({
162167
162631
  "../server/src/domains/nodes/routes.ts"() {
162168
162632
  "use strict";
162169
162633
  import_node_crypto26 = require("node:crypto");
162170
162634
  import_node_fs11 = require("node:fs");
162171
162635
  import_node_url4 = require("node:url");
162172
- import_node_path13 = require("node:path");
162636
+ import_node_path14 = require("node:path");
162173
162637
  init_src4();
162174
162638
  init_connect_script();
162175
162639
  init_node_health();
@@ -163324,6 +163788,9 @@ function buildOrganizationUsageSummary(input) {
163324
163788
  const runtimeRunCounts = /* @__PURE__ */ new Map();
163325
163789
  const runtimeModels = /* @__PURE__ */ new Map();
163326
163790
  const runtimeEmployees = /* @__PURE__ */ new Map();
163791
+ const runtimeInstanceKind = /* @__PURE__ */ new Map();
163792
+ const runtimeHostnames = input.runtimeHostnames ?? /* @__PURE__ */ new Map();
163793
+ const runtimeNodeNames = input.runtimeNodeNames ?? /* @__PURE__ */ new Map();
163327
163794
  for (const run of input.runs) {
163328
163795
  const usage = usageOf(run);
163329
163796
  if (!usage) {
@@ -163379,18 +163846,19 @@ function buildOrganizationUsageSummary(input) {
163379
163846
  empWoMap.set(rowId, empWoUsage);
163380
163847
  }
163381
163848
  addUsage(empWoUsage.usage, usage);
163382
- const kind = run.runtimeKind;
163383
- let rtUsage = runtimeTotals.get(kind);
163849
+ const rtKey = run.runtimeInstanceId ?? `__unregistered__:${run.runtimeKind}`;
163850
+ let rtUsage = runtimeTotals.get(rtKey);
163384
163851
  if (!rtUsage) {
163385
163852
  rtUsage = emptyUsage();
163386
- runtimeTotals.set(kind, rtUsage);
163853
+ runtimeTotals.set(rtKey, rtUsage);
163854
+ runtimeInstanceKind.set(rtKey, run.runtimeKind);
163387
163855
  }
163388
163856
  addUsage(rtUsage, usage);
163389
- runtimeRunCounts.set(kind, (runtimeRunCounts.get(kind) ?? 0) + 1);
163390
- let modelMap = runtimeModels.get(kind);
163857
+ runtimeRunCounts.set(rtKey, (runtimeRunCounts.get(rtKey) ?? 0) + 1);
163858
+ let modelMap = runtimeModels.get(rtKey);
163391
163859
  if (!modelMap) {
163392
163860
  modelMap = /* @__PURE__ */ new Map();
163393
- runtimeModels.set(kind, modelMap);
163861
+ runtimeModels.set(rtKey, modelMap);
163394
163862
  }
163395
163863
  let modelUsage = modelMap.get(run.effectiveModel);
163396
163864
  if (!modelUsage) {
@@ -163398,10 +163866,10 @@ function buildOrganizationUsageSummary(input) {
163398
163866
  modelMap.set(run.effectiveModel, modelUsage);
163399
163867
  }
163400
163868
  addUsage(modelUsage, usage);
163401
- let rtEmpMap = runtimeEmployees.get(kind);
163869
+ let rtEmpMap = runtimeEmployees.get(rtKey);
163402
163870
  if (!rtEmpMap) {
163403
163871
  rtEmpMap = /* @__PURE__ */ new Map();
163404
- runtimeEmployees.set(kind, rtEmpMap);
163872
+ runtimeEmployees.set(rtKey, rtEmpMap);
163405
163873
  }
163406
163874
  let rtEmpUsage = rtEmpMap.get(run.actorId);
163407
163875
  if (!rtEmpUsage) {
@@ -163453,9 +163921,33 @@ function buildOrganizationUsageSummary(input) {
163453
163921
  (e) => e.share,
163454
163922
  (e, s2) => ({ ...e, share: s2 })
163455
163923
  );
163924
+ function parseNodeId(rtKey) {
163925
+ const parts = rtKey.split(":");
163926
+ if (parts.length >= 3 && parts[0] === "runtime") return parts[1];
163927
+ return "";
163928
+ }
163929
+ function resolveDisplayName(rtKey, kind) {
163930
+ const nodeId = parseNodeId(rtKey);
163931
+ if (nodeId) {
163932
+ const name = runtimeNodeNames.get(nodeId);
163933
+ if (name) return name;
163934
+ }
163935
+ const h = runtimeHostnames.get(rtKey);
163936
+ if (h) return h;
163937
+ if (nodeId) return nodeId;
163938
+ return kind;
163939
+ }
163940
+ function resolveHostname(rtKey, kind) {
163941
+ const h = runtimeHostnames.get(rtKey);
163942
+ if (h) return h;
163943
+ const nodeId = parseNodeId(rtKey);
163944
+ if (nodeId) return nodeId;
163945
+ return kind;
163946
+ }
163456
163947
  const runtimeRows = normalizeShares(
163457
- [...runtimeTotals].map(([runtimeKind, usage]) => {
163458
- const modelMap = runtimeModels.get(runtimeKind) ?? /* @__PURE__ */ new Map();
163948
+ [...runtimeTotals].map(([rtKey, usage]) => {
163949
+ const kind = runtimeInstanceKind.get(rtKey) ?? "unknown";
163950
+ const modelMap = runtimeModels.get(rtKey) ?? /* @__PURE__ */ new Map();
163459
163951
  const models = normalizeShares(
163460
163952
  [...modelMap].map(([model, modelUsage]) => ({
163461
163953
  model,
@@ -163465,7 +163957,7 @@ function buildOrganizationUsageSummary(input) {
163465
163957
  (m2) => m2.share,
163466
163958
  (m2, s2) => ({ ...m2, share: s2 })
163467
163959
  );
163468
- const empMap = runtimeEmployees.get(runtimeKind);
163960
+ const empMap = runtimeEmployees.get(rtKey);
163469
163961
  const employees = normalizeShares(
163470
163962
  empMap ? [...empMap].map(([actorId, empUsage]) => ({
163471
163963
  actorId,
@@ -163476,10 +163968,14 @@ function buildOrganizationUsageSummary(input) {
163476
163968
  (e, s2) => ({ ...e, share: s2 })
163477
163969
  );
163478
163970
  return {
163479
- runtimeKind,
163971
+ runtimeInstanceId: rtKey,
163972
+ nodeId: parseNodeId(rtKey),
163973
+ displayName: resolveDisplayName(rtKey, kind),
163974
+ hostname: resolveHostname(rtKey, kind),
163975
+ runtimeKind: kind,
163480
163976
  usage,
163481
163977
  share: total.tokens > 0 ? usage.tokens / total.tokens : 0,
163482
- runCount: runtimeRunCounts.get(runtimeKind) ?? 0,
163978
+ runCount: runtimeRunCounts.get(rtKey) ?? 0,
163483
163979
  models,
163484
163980
  employees
163485
163981
  };
@@ -163512,12 +164008,12 @@ function nameKey(name) {
163512
164008
  function cleanName(name) {
163513
164009
  return name.trim().replace(/\s+/g, " ");
163514
164010
  }
163515
- var import_node_fs12, import_node_path14, import_node_crypto27, SEP, keyOf, prefixOf, MemoryWorkorderTagStore, FileWorkorderTagStore;
164011
+ var import_node_fs12, import_node_path15, import_node_crypto27, SEP, keyOf, prefixOf, MemoryWorkorderTagStore, FileWorkorderTagStore;
163516
164012
  var init_tags = __esm({
163517
164013
  "../server/src/domains/collab/tags.ts"() {
163518
164014
  "use strict";
163519
164015
  import_node_fs12 = __toESM(require("node:fs"), 1);
163520
- import_node_path14 = __toESM(require("node:path"), 1);
164016
+ import_node_path15 = __toESM(require("node:path"), 1);
163521
164017
  import_node_crypto27 = __toESM(require("node:crypto"), 1);
163522
164018
  SEP = "::";
163523
164019
  keyOf = (companyId, id) => `${companyId}${SEP}${id}`;
@@ -163603,7 +164099,7 @@ var init_tags = __esm({
163603
164099
  }
163604
164100
  persist() {
163605
164101
  try {
163606
- import_node_fs12.default.mkdirSync(import_node_path14.default.dirname(this.file), { recursive: true });
164102
+ import_node_fs12.default.mkdirSync(import_node_path15.default.dirname(this.file), { recursive: true });
163607
164103
  const tmp = `${this.file}.tmp`;
163608
164104
  import_node_fs12.default.writeFileSync(tmp, JSON.stringify(Object.fromEntries(this.map), null, 2));
163609
164105
  import_node_fs12.default.renameSync(tmp, this.file);
@@ -163744,10 +164240,22 @@ function collabDomain(opts) {
163744
164240
  return { status: 400, body: { error: { code: "bad_request", message: "timeZone \u65E0\u6548" } } };
163745
164241
  }
163746
164242
  const { kernel } = await resolveCtx(req.auth.companyId);
163747
- const runs = await opts.trace.listUsageRuns({ from, to });
164243
+ const [runs, runtimeRecords, nodeRecords] = await Promise.all([
164244
+ opts.trace.listUsageRuns({ from, to }),
164245
+ opts.listRuntimes ? opts.listRuntimes().catch(() => []) : Promise.resolve([]),
164246
+ opts.listNodes ? opts.listNodes().catch(() => []) : Promise.resolve([])
164247
+ ]);
164248
+ const runtimeHostnames = /* @__PURE__ */ new Map();
164249
+ for (const rt of runtimeRecords) {
164250
+ if (rt.id && rt.hostname) runtimeHostnames.set(rt.id, rt.hostname);
164251
+ }
164252
+ const runtimeNodeNames = /* @__PURE__ */ new Map();
164253
+ for (const n of nodeRecords) {
164254
+ if (n.id && n.name) runtimeNodeNames.set(n.id, n.name);
164255
+ }
163748
164256
  return {
163749
164257
  status: 200,
163750
- body: buildOrganizationUsageSummary({ model: kernel.model, runs, bucketKind: bucket, timeZone })
164258
+ body: buildOrganizationUsageSummary({ model: kernel.model, runs, bucketKind: bucket, timeZone, runtimeHostnames, runtimeNodeNames })
163751
164259
  };
163752
164260
  });
163753
164261
  router.get("/api/workorders", async (req) => {
@@ -164547,10 +165055,10 @@ function tokenType(p2) {
164547
165055
  function parseOtlpMetricsUsage(body) {
164548
165056
  const req = body;
164549
165057
  const out = [];
164550
- for (const rm4 of req.resourceMetrics ?? []) {
164551
- const runId = attrString(rm4.resource?.attributes, "oasis.run_id");
165058
+ for (const rm5 of req.resourceMetrics ?? []) {
165059
+ const runId = attrString(rm5.resource?.attributes, "oasis.run_id");
164552
165060
  if (!runId) continue;
164553
- const actorId = attrString(rm4.resource?.attributes, "oasis.actor_id");
165061
+ const actorId = attrString(rm5.resource?.attributes, "oasis.actor_id");
164554
165062
  let input = 0;
164555
165063
  let output = 0;
164556
165064
  let cacheRead = 0;
@@ -164558,7 +165066,7 @@ function parseOtlpMetricsUsage(body) {
164558
165066
  let costUsd = 0;
164559
165067
  let sawToken = false;
164560
165068
  let sawCost = false;
164561
- for (const sm of rm4.scopeMetrics ?? []) {
165069
+ for (const sm of rm5.scopeMetrics ?? []) {
164562
165070
  for (const m2 of sm.metrics ?? []) {
164563
165071
  const name = (m2.name ?? "").toLowerCase();
164564
165072
  const points = m2.sum?.dataPoints ?? m2.gauge?.dataPoints ?? [];
@@ -164934,6 +165442,30 @@ var init_routes6 = __esm({
164934
165442
  }
164935
165443
  });
164936
165444
 
165445
+ // ../server/src/domains/trace/trace-health.ts
165446
+ function createTraceHealth() {
165447
+ const unhealthy = /* @__PURE__ */ new Set();
165448
+ return {
165449
+ markUnhealthy(runId) {
165450
+ unhealthy.add(runId);
165451
+ },
165452
+ markHealthy(runId) {
165453
+ unhealthy.delete(runId);
165454
+ },
165455
+ isHealthy(runId) {
165456
+ return !unhealthy.has(runId);
165457
+ },
165458
+ forget(runId) {
165459
+ unhealthy.delete(runId);
165460
+ }
165461
+ };
165462
+ }
165463
+ var init_trace_health = __esm({
165464
+ "../server/src/domains/trace/trace-health.ts"() {
165465
+ "use strict";
165466
+ }
165467
+ });
165468
+
164937
165469
  // ../server/src/domains/trace/index.ts
164938
165470
  function createTraceDomain(opts) {
164939
165471
  const service = new TraceService({ store: opts.store });
@@ -164948,6 +165480,8 @@ var init_trace2 = __esm({
164948
165480
  init_routes6();
164949
165481
  init_sink();
164950
165482
  init_chat_parts();
165483
+ init_trace_health();
165484
+ init_resilient_appender();
164951
165485
  }
164952
165486
  });
164953
165487
 
@@ -167019,12 +167553,12 @@ function normalize(patch) {
167019
167553
  ...reviewRoles !== void 0 ? { reviewRoles: [...new Set(reviewRoles.map((r) => r.trim()).filter(Boolean))] } : {}
167020
167554
  };
167021
167555
  }
167022
- var import_node_fs13, import_node_path15, SEP2, keyOf2, prefixOf2, MemoryPlaybookOverrideStore, FilePlaybookOverrideStore;
167556
+ var import_node_fs13, import_node_path16, SEP2, keyOf2, prefixOf2, MemoryPlaybookOverrideStore, FilePlaybookOverrideStore;
167023
167557
  var init_overrides = __esm({
167024
167558
  "../server/src/domains/playbooks/overrides.ts"() {
167025
167559
  "use strict";
167026
167560
  import_node_fs13 = __toESM(require("node:fs"), 1);
167027
- import_node_path15 = __toESM(require("node:path"), 1);
167561
+ import_node_path16 = __toESM(require("node:path"), 1);
167028
167562
  SEP2 = "::";
167029
167563
  keyOf2 = (companyId, ref2, nodeKey) => `${companyId}${SEP2}${ref2}${SEP2}${nodeKey}`;
167030
167564
  prefixOf2 = (companyId, ref2) => `${companyId}${SEP2}${ref2}${SEP2}`;
@@ -167063,7 +167597,7 @@ var init_overrides = __esm({
167063
167597
  }
167064
167598
  persist() {
167065
167599
  try {
167066
- import_node_fs13.default.mkdirSync(import_node_path15.default.dirname(this.file), { recursive: true });
167600
+ import_node_fs13.default.mkdirSync(import_node_path16.default.dirname(this.file), { recursive: true });
167067
167601
  const tmp = `${this.file}.tmp`;
167068
167602
  import_node_fs13.default.writeFileSync(tmp, JSON.stringify(Object.fromEntries(this.map), null, 2));
167069
167603
  import_node_fs13.default.renameSync(tmp, this.file);
@@ -167502,12 +168036,12 @@ var init_daemon_adapter = __esm({
167502
168036
  });
167503
168037
 
167504
168038
  // ../server/src/side-map.ts
167505
- var import_node_fs14, import_node_path16, FileSideMap;
168039
+ var import_node_fs14, import_node_path17, FileSideMap;
167506
168040
  var init_side_map = __esm({
167507
168041
  "../server/src/side-map.ts"() {
167508
168042
  "use strict";
167509
168043
  import_node_fs14 = __toESM(require("node:fs"), 1);
167510
- import_node_path16 = __toESM(require("node:path"), 1);
168044
+ import_node_path17 = __toESM(require("node:path"), 1);
167511
168045
  FileSideMap = class {
167512
168046
  constructor(file) {
167513
168047
  this.file = file;
@@ -167534,7 +168068,7 @@ var init_side_map = __esm({
167534
168068
  }
167535
168069
  persist() {
167536
168070
  try {
167537
- import_node_fs14.default.mkdirSync(import_node_path16.default.dirname(this.file), { recursive: true });
168071
+ import_node_fs14.default.mkdirSync(import_node_path17.default.dirname(this.file), { recursive: true });
167538
168072
  const tmp = `${this.file}.tmp`;
167539
168073
  import_node_fs14.default.writeFileSync(tmp, JSON.stringify(Object.fromEntries(this.map), null, 2));
167540
168074
  import_node_fs14.default.renameSync(tmp, this.file);
@@ -168990,26 +169524,26 @@ ${ctx.nodeFault}
168990
169524
 
168991
169525
  // ../server/src/governance/builtin-skills.ts
168992
169526
  async function collectDir(root, dir, out) {
168993
- const entries = await (0, import_promises7.readdir)(dir, { withFileTypes: true });
169527
+ const entries = await (0, import_promises8.readdir)(dir, { withFileTypes: true });
168994
169528
  for (const e of entries) {
168995
- const abs = (0, import_node_path17.join)(dir, e.name);
169529
+ const abs = (0, import_node_path18.join)(dir, e.name);
168996
169530
  if (e.isDirectory()) {
168997
169531
  await collectDir(root, abs, out);
168998
169532
  } else if (e.isFile()) {
168999
- const info = await (0, import_promises7.stat)(abs);
169533
+ const info = await (0, import_promises8.stat)(abs);
169000
169534
  if (info.size > MAX_FILE_BYTES3) {
169001
169535
  console.warn(`[builtin-skills] skip oversized file (${info.size}B): ${abs}`);
169002
169536
  continue;
169003
169537
  }
169004
- const rel = (0, import_node_path17.relative)(root, abs).split(/[\\/]/).join("/");
169005
- out[rel] = await (0, import_promises7.readFile)(abs, "utf8");
169538
+ const rel = (0, import_node_path18.relative)(root, abs).split(/[\\/]/).join("/");
169539
+ out[rel] = await (0, import_promises8.readFile)(abs, "utf8");
169006
169540
  }
169007
169541
  }
169008
169542
  }
169009
169543
  async function loadBuiltinSkills(skillsDir) {
169010
169544
  let dirents;
169011
169545
  try {
169012
- dirents = await (0, import_promises7.readdir)(skillsDir, { withFileTypes: true });
169546
+ dirents = await (0, import_promises8.readdir)(skillsDir, { withFileTypes: true });
169013
169547
  } catch {
169014
169548
  return [];
169015
169549
  }
@@ -169017,7 +169551,7 @@ async function loadBuiltinSkills(skillsDir) {
169017
169551
  for (const d of dirents) {
169018
169552
  if (!d.isDirectory()) continue;
169019
169553
  const slug6 = d.name;
169020
- const skillDir = (0, import_node_path17.join)(skillsDir, slug6);
169554
+ const skillDir = (0, import_node_path18.join)(skillsDir, slug6);
169021
169555
  const files = {};
169022
169556
  try {
169023
169557
  await collectDir(skillDir, skillDir, files);
@@ -169045,12 +169579,12 @@ function materializeBuiltinSkills(builtins, skillsDirPrefix) {
169045
169579
  }
169046
169580
  return out;
169047
169581
  }
169048
- var import_promises7, import_node_path17, MAX_FILE_BYTES3;
169582
+ var import_promises8, import_node_path18, MAX_FILE_BYTES3;
169049
169583
  var init_builtin_skills = __esm({
169050
169584
  "../server/src/governance/builtin-skills.ts"() {
169051
169585
  "use strict";
169052
- import_promises7 = require("node:fs/promises");
169053
- import_node_path17 = require("node:path");
169586
+ import_promises8 = require("node:fs/promises");
169587
+ import_node_path18 = require("node:path");
169054
169588
  init_skill_fetcher();
169055
169589
  init_skill_materializer();
169056
169590
  MAX_FILE_BYTES3 = 1 << 20;
@@ -174542,6 +175076,21 @@ var init_postgres = __esm({
174542
175076
  });
174543
175077
 
174544
175078
  // ../storage/src/postgres-registry.ts
175079
+ function rowToVariable(row) {
175080
+ return {
175081
+ key: row.key,
175082
+ scope: row.scope === "connector" ? "global" : row.scope,
175083
+ ...row.actor_id !== null && row.actor_id !== void 0 ? { actorId: row.actor_id } : {},
175084
+ ...row.project_id !== null && row.project_id !== void 0 ? { projectId: row.project_id } : {},
175085
+ ...row.connector_id !== null && row.connector_id !== void 0 ? { connectorId: row.connector_id } : {},
175086
+ ...row.overrides !== null && row.overrides !== void 0 ? { overrides: row.overrides } : {},
175087
+ // 保留库里存的原始 delivery_mode(含未来 broker/wrapper 档,annotation d7456d5e Req3)——
175088
+ // **不塌成 env**,否则未知档位读回来变明文;仅 null/空回填 "env"(存量行)。派单侧按未知档保守处理。
175089
+ deliveryMode: typeof row.delivery_mode === "string" && row.delivery_mode ? row.delivery_mode : "env",
175090
+ valueEncrypted: row.value_encrypted,
175091
+ updatedAt: new Date(row.updated_at).toISOString()
175092
+ };
175093
+ }
174545
175094
  var ident4, PostgresRegistryStore, rowToActor, rowToConfig;
174546
175095
  var init_postgres_registry = __esm({
174547
175096
  "../storage/src/postgres-registry.ts"() {
@@ -174673,9 +175222,21 @@ var init_postgres_registry = __esm({
174673
175222
  value_encrypted text NOT NULL,
174674
175223
  updated_at timestamptz NOT NULL
174675
175224
  )`);
174676
- await pool.query(`CREATE UNIQUE INDEX IF NOT EXISTS "${s2}_variables_key_actor_uq" ON "${s2}".variables (key, COALESCE(actor_id, ''))`);
174677
175225
  await pool.query(`ALTER TABLE IF EXISTS "${s2}".variables ADD COLUMN IF NOT EXISTS overrides text`);
174678
175226
  await pool.query(`ALTER TABLE IF EXISTS "${s2}".variables ADD COLUMN IF NOT EXISTS actor_id text`);
175227
+ await pool.query(`ALTER TABLE IF EXISTS "${s2}".variables ADD COLUMN IF NOT EXISTS project_id text`);
175228
+ await pool.query(`ALTER TABLE IF EXISTS "${s2}".variables ADD COLUMN IF NOT EXISTS environment text`);
175229
+ await pool.query(`ALTER TABLE IF EXISTS "${s2}".variables ADD COLUMN IF NOT EXISTS delivery_mode text`);
175230
+ await pool.query(`DROP INDEX IF EXISTS "${s2}"."${s2}_variables_key_actor_uq"`);
175231
+ await pool.query(`DROP INDEX IF EXISTS "${s2}"."${s2}_variables_scope_uq"`);
175232
+ await pool.query(`
175233
+ DELETE FROM "${s2}".variables v
175234
+ WHERE v.ctid NOT IN (
175235
+ SELECT DISTINCT ON (key, COALESCE(actor_id, ''), COALESCE(project_id, '')) ctid
175236
+ FROM "${s2}".variables
175237
+ ORDER BY key, COALESCE(actor_id, ''), COALESCE(project_id, ''), updated_at DESC, ctid DESC
175238
+ )`);
175239
+ await pool.query(`CREATE UNIQUE INDEX IF NOT EXISTS "${s2}_variables_scope_uq_v2" ON "${s2}".variables (key, COALESCE(actor_id, ''), COALESCE(project_id, ''))`);
174679
175240
  await pool.query(`
174680
175241
  CREATE TABLE IF NOT EXISTS "${s2}".runtime_configs (
174681
175242
  runtime_id text PRIMARY KEY,
@@ -175119,27 +175680,36 @@ var init_postgres_registry = __esm({
175119
175680
  }
175120
175681
  /* ---------- 变量 ---------- */
175121
175682
  async putVariable(v2) {
175122
- const aid = v2.actorId ?? null;
175123
175683
  await this.pool.query(
175124
- `INSERT INTO ${this.s}.variables (key, scope, actor_id, connector_id, overrides, value_encrypted, updated_at)
175125
- VALUES ($1,$2,$3,$4,$5,$6,$7)
175126
- ON CONFLICT (key, COALESCE(actor_id, '')) DO UPDATE
175127
- SET scope=$2, actor_id=$3, connector_id=$4, overrides=$5, value_encrypted=$6, updated_at=$7`,
175128
- [v2.key, v2.scope, aid, v2.connectorId ?? null, v2.overrides ?? null, v2.valueEncrypted, v2.updatedAt]
175684
+ `INSERT INTO ${this.s}.variables (key, scope, actor_id, project_id, connector_id, overrides, delivery_mode, value_encrypted, updated_at)
175685
+ VALUES ($1,$2,$3,$4,$5,$6,$7,$8,$9)
175686
+ ON CONFLICT (key, COALESCE(actor_id, ''), COALESCE(project_id, '')) DO UPDATE
175687
+ SET scope=$2, actor_id=$3, project_id=$4, connector_id=$5, overrides=$6, delivery_mode=$7, value_encrypted=$8, updated_at=$9`,
175688
+ [
175689
+ v2.key,
175690
+ v2.scope,
175691
+ v2.actorId ?? null,
175692
+ v2.projectId ?? null,
175693
+ v2.connectorId ?? null,
175694
+ v2.overrides ?? null,
175695
+ v2.deliveryMode ?? null,
175696
+ v2.valueEncrypted,
175697
+ v2.updatedAt
175698
+ ]
175129
175699
  );
175130
175700
  }
175131
175701
  async patchVariableValue(key, actorId, valueEncrypted, updatedAt) {
175132
175702
  const aid = actorId ?? null;
175133
175703
  await this.pool.query(
175134
175704
  `UPDATE ${this.s}.variables SET value_encrypted=$3, updated_at=$4
175135
- WHERE key=$1 AND COALESCE(actor_id,'')=COALESCE($2::text,'')`,
175705
+ WHERE key=$1 AND COALESCE(actor_id,'')=COALESCE($2::text,'') AND COALESCE(project_id,'')=''`,
175136
175706
  [key, aid, valueEncrypted, updatedAt]
175137
175707
  );
175138
175708
  }
175139
175709
  async getVariableCiphertext(key, actorId) {
175140
175710
  const aid = actorId ?? null;
175141
175711
  const r = await this.pool.query(
175142
- `SELECT value_encrypted FROM ${this.s}.variables WHERE key=$1 AND COALESCE(actor_id,'')=COALESCE($2::text,'')`,
175712
+ `SELECT value_encrypted FROM ${this.s}.variables WHERE key=$1 AND COALESCE(actor_id,'')=COALESCE($2::text,'') AND COALESCE(project_id,'')=''`,
175143
175713
  [key, aid]
175144
175714
  );
175145
175715
  return r.rows[0]?.value_encrypted ?? null;
@@ -175154,21 +175724,21 @@ var init_postgres_registry = __esm({
175154
175724
  } else {
175155
175725
  r = await this.pool.query(`SELECT * FROM ${this.s}.variables ORDER BY key`);
175156
175726
  }
175157
- return r.rows.map((row) => ({
175158
- key: row.key,
175159
- scope: row.scope === "connector" ? "global" : row.scope,
175160
- ...row.actor_id !== null && row.actor_id !== void 0 ? { actorId: row.actor_id } : {},
175161
- ...row.connector_id !== null ? { connectorId: row.connector_id } : {},
175162
- ...row.overrides !== null ? { overrides: row.overrides } : {},
175163
- valueEncrypted: row.value_encrypted,
175164
- updatedAt: new Date(row.updated_at).toISOString()
175165
- }));
175727
+ return r.rows.map((row) => rowToVariable(row));
175166
175728
  }
175167
- async deleteVariable(key, actorId) {
175168
- const aid = actorId ?? null;
175729
+ async resolveProjectVariables(projectId) {
175730
+ const r = await this.pool.query(
175731
+ `SELECT * FROM ${this.s}.variables WHERE scope='project' AND project_id=$1 ORDER BY key`,
175732
+ [projectId]
175733
+ );
175734
+ return r.rows.map((row) => rowToVariable(row));
175735
+ }
175736
+ async deleteVariable(key, actorId, scoping) {
175169
175737
  await this.pool.query(
175170
- `DELETE FROM ${this.s}.variables WHERE key=$1 AND COALESCE(actor_id,'')=COALESCE($2::text,'')`,
175171
- [key, aid]
175738
+ `DELETE FROM ${this.s}.variables
175739
+ WHERE key=$1 AND COALESCE(actor_id,'')=COALESCE($2::text,'')
175740
+ AND COALESCE(project_id,'')=COALESCE($3::text,'')`,
175741
+ [key, actorId ?? null, scoping?.projectId ?? null]
175172
175742
  );
175173
175743
  }
175174
175744
  };
@@ -175987,6 +176557,7 @@ var init_postgres_projects = __esm({
175987
176557
  work_order_id text PRIMARY KEY,
175988
176558
  project_id text NOT NULL
175989
176559
  )`);
176560
+ await pool.query(`ALTER TABLE IF EXISTS "${s2}".workspace_bindings ADD COLUMN IF NOT EXISTS environment text`);
175990
176561
  await pool.query(`
175991
176562
  CREATE TABLE IF NOT EXISTS "${s2}".workspace_dispatch_hold (
175992
176563
  work_order_id text PRIMARY KEY
@@ -176856,6 +177427,38 @@ var init_postgres_control_plane = __esm({
176856
177427
  }
176857
177428
  });
176858
177429
 
177430
+ // ../storage/src/pg-sanitize.ts
177431
+ function hasPgUnstorable(s2) {
177432
+ return new RegExp(PG_UNSTORABLE_SOURCE).test(s2);
177433
+ }
177434
+ function scrubPgString(v2) {
177435
+ return typeof v2 === "string" ? v2.replace(new RegExp(PG_UNSTORABLE_SOURCE, "g"), REPLACEMENT) : v2;
177436
+ }
177437
+ function scrubPgJson(v2) {
177438
+ const re = new RegExp(PG_UNSTORABLE_SOURCE, "g");
177439
+ const walk = (x2) => {
177440
+ if (typeof x2 === "string") return x2.replace(re, REPLACEMENT);
177441
+ if (Array.isArray(x2)) return x2.map(walk);
177442
+ if (x2 && typeof x2 === "object") {
177443
+ const out = {};
177444
+ for (const [k2, val] of Object.entries(x2)) {
177445
+ out[k2.replace(re, REPLACEMENT)] = walk(val);
177446
+ }
177447
+ return out;
177448
+ }
177449
+ return x2;
177450
+ };
177451
+ return walk(v2);
177452
+ }
177453
+ var REPLACEMENT, PG_UNSTORABLE_SOURCE;
177454
+ var init_pg_sanitize = __esm({
177455
+ "../storage/src/pg-sanitize.ts"() {
177456
+ "use strict";
177457
+ REPLACEMENT = "\uFFFD";
177458
+ PG_UNSTORABLE_SOURCE = "\\u0000|[\\uD800-\\uDBFF](?![\\uDC00-\\uDFFF])|(?<![\\uD800-\\uDBFF])[\\uDC00-\\uDFFF]";
177459
+ }
177460
+ });
177461
+
176859
177462
  // ../storage/src/postgres-trace.ts
176860
177463
  var ident9, PostgresTraceStore, rowToRun, rowToEvent2, rowToToolCall, rowToArtifact2;
176861
177464
  var init_postgres_trace = __esm({
@@ -176863,6 +177466,7 @@ var init_postgres_trace = __esm({
176863
177466
  "use strict";
176864
177467
  init_esm2();
176865
177468
  init_src();
177469
+ init_pg_sanitize();
176866
177470
  ident9 = (s2) => {
176867
177471
  if (!/^[a-z_][a-z0-9_]*$/.test(s2)) throw new Error(`invalid schema name: ${s2}`);
176868
177472
  return s2;
@@ -177123,12 +177727,12 @@ var init_postgres_trace = __esm({
177123
177727
  run.exitCode,
177124
177728
  run.exitReason,
177125
177729
  run.errorCode,
177126
- run.errorMessage,
177730
+ scrubPgString(run.errorMessage),
177127
177731
  run.usage === null ? null : JSON.stringify(run.usage),
177128
177732
  run.effectiveModel,
177129
177733
  run.modelSource,
177130
177734
  run.transcriptRef,
177131
- JSON.stringify(run.metadata),
177735
+ JSON.stringify(scrubPgJson(run.metadata)),
177132
177736
  run.createdAt,
177133
177737
  run.updatedAt
177134
177738
  ]
@@ -177171,7 +177775,7 @@ var init_postgres_trace = __esm({
177171
177775
  for (const [key, spec] of Object.entries(_PostgresTraceStore.RUN_COLS)) {
177172
177776
  if (!(key in patch)) continue;
177173
177777
  const v2 = patch[key];
177174
- args.push(spec.json ? v2 == null ? null : JSON.stringify(v2) : v2 ?? null);
177778
+ args.push(spec.json ? v2 == null ? null : JSON.stringify(scrubPgJson(v2)) : scrubPgString(v2 ?? null));
177175
177779
  sets.push(spec.json ? `${spec.col}=$${args.length}::jsonb` : `${spec.col}=$${args.length}`);
177176
177780
  }
177177
177781
  args.push(this.now());
@@ -177239,7 +177843,7 @@ var init_postgres_trace = __esm({
177239
177843
  }
177240
177844
  async listUsageRuns(filter) {
177241
177845
  const r = await this.pool.query(
177242
- `SELECT actor_id, artifact_id, started_at, effective_model, runtime_kind, usage
177846
+ `SELECT actor_id, artifact_id, started_at, effective_model, runtime_kind, runtime_instance_id, usage
177243
177847
  FROM ${this.s}.agent_runs
177244
177848
  WHERE jsonb_typeof(usage) = 'object' AND started_at >= $1 AND started_at <= $2
177245
177849
  ORDER BY started_at`,
@@ -177251,6 +177855,7 @@ var init_postgres_trace = __esm({
177251
177855
  startedAt: row.started_at,
177252
177856
  effectiveModel: row.effective_model ?? null,
177253
177857
  runtimeKind: row.runtime_kind ?? "shell",
177858
+ runtimeInstanceId: row.runtime_instance_id ?? null,
177254
177859
  usage: row.usage
177255
177860
  }));
177256
177861
  }
@@ -177309,8 +177914,8 @@ var init_postgres_trace = __esm({
177309
177914
  stream,
177310
177915
  level,
177311
177916
  inp.color ?? null,
177312
- inp.message ?? null,
177313
- JSON.stringify(payload),
177917
+ scrubPgString(inp.message ?? null),
177918
+ JSON.stringify(scrubPgJson(payload)),
177314
177919
  inp.blobRef ?? null,
177315
177920
  inp.spanId ?? null,
177316
177921
  inp.parentSpanId ?? null,
@@ -177385,11 +177990,11 @@ var init_postgres_trace = __esm({
177385
177990
  c.durationMs ?? null,
177386
177991
  c.inputRef ?? null,
177387
177992
  c.outputRef ?? null,
177388
- c.inputPreview ?? null,
177389
- c.outputPreview ?? null,
177993
+ scrubPgString(c.inputPreview ?? null),
177994
+ scrubPgString(c.outputPreview ?? null),
177390
177995
  c.errorCode ?? null,
177391
- c.errorMessage ?? null,
177392
- JSON.stringify(c.metadata ?? {})
177996
+ scrubPgString(c.errorMessage ?? null),
177997
+ JSON.stringify(scrubPgJson(c.metadata ?? {}))
177393
177998
  ]
177394
177999
  );
177395
178000
  }
@@ -177434,12 +178039,12 @@ var init_postgres_trace = __esm({
177434
178039
  a.id,
177435
178040
  a.runId,
177436
178041
  a.kind,
177437
- a.name,
178042
+ scrubPgString(a.name),
177438
178043
  a.contentType ?? null,
177439
178044
  a.size ?? null,
177440
178045
  a.blobRef,
177441
178046
  a.createdAt,
177442
- JSON.stringify(a.metadata ?? {})
178047
+ JSON.stringify(scrubPgJson(a.metadata ?? {}))
177443
178048
  ]
177444
178049
  );
177445
178050
  }
@@ -179151,7 +179756,10 @@ __export(src_exports, {
179151
179756
  PostgresTraceStore: () => PostgresTraceStore,
179152
179757
  PostgresTypeRegistryStore: () => PostgresTypeRegistryStore,
179153
179758
  backfillTypeRegistryFromFile: () => backfillTypeRegistryFromFile,
179154
- createPgPool: () => createPgPool
179759
+ createPgPool: () => createPgPool,
179760
+ hasPgUnstorable: () => hasPgUnstorable,
179761
+ scrubPgJson: () => scrubPgJson,
179762
+ scrubPgString: () => scrubPgString
179155
179763
  });
179156
179764
  var init_src8 = __esm({
179157
179765
  "../storage/src/index.ts"() {
@@ -179164,6 +179772,7 @@ var init_src8 = __esm({
179164
179772
  init_pool();
179165
179773
  init_postgres_control_plane();
179166
179774
  init_postgres_trace();
179775
+ init_pg_sanitize();
179167
179776
  init_postgres_automations();
179168
179777
  init_postgres_chat_sessions();
179169
179778
  init_postgres_nodes();
@@ -179563,15 +180172,18 @@ function splitIdentityFiles3(prompt) {
179563
180172
  }
179564
180173
  return out;
179565
180174
  }
179566
- async function buildActorProvision(service, actorId) {
179567
- const raw = await service.resolveActorEnv(actorId);
179568
- const env = { ...raw };
180175
+ async function buildActorProvision(service, actorId, scope) {
180176
+ const entries = await service.resolveActorProvisionEntries(actorId, scope);
180177
+ const raw = {};
180178
+ for (const e of entries) raw[e.key] = e.value;
179569
180179
  const provision = await buildConnectorProvision({
179570
180180
  vars: raw,
179571
180181
  actorName: raw["GIT_AUTHOR_NAME"] ?? actorId.split(":").pop() ?? "oasis-agent"
179572
180182
  });
180183
+ const env = { ...raw };
179573
180184
  for (const k2 of provision.sensitiveVars) delete env[k2];
179574
180185
  for (const [k2, v2] of Object.entries(provision.envOverrides)) env[k2] = v2;
180186
+ applyRefDelivery(env, entries);
179575
180187
  return {
179576
180188
  env,
179577
180189
  wrapperPaths: [oasisWrapperScript(), ...provision.wrapperPaths],
@@ -179797,6 +180409,45 @@ async function startServe(opts) {
179797
180409
  });
179798
180410
  for (const l of registryListeners) l({ kind: record6.kind, actor: record6.actor, target: record6.target, timestamp: record6.timestamp });
179799
180411
  };
180412
+ const credentialAudit = (record6) => {
180413
+ fs29.appendFile(registryAuditFile, JSON.stringify(record6) + "\n", () => {
180414
+ });
180415
+ for (const l of registryListeners) l({ kind: record6.kind, actor: record6.actor, target: record6.variableKey, timestamp: record6.timestamp });
180416
+ };
180417
+ const readCredentialAudit = async (filter) => {
180418
+ let raw;
180419
+ try {
180420
+ raw = await fs29.promises.readFile(registryAuditFile, "utf8");
180421
+ } catch {
180422
+ return [];
180423
+ }
180424
+ const out = [];
180425
+ for (const line of raw.split("\n")) {
180426
+ if (!line.trim()) continue;
180427
+ let rec;
180428
+ try {
180429
+ rec = JSON.parse(line);
180430
+ } catch {
180431
+ continue;
180432
+ }
180433
+ if (typeof rec.kind !== "string" || !rec.kind.startsWith("credential_reveal")) continue;
180434
+ if (filter.actor && rec.actor !== filter.actor) continue;
180435
+ if (filter.key && rec.variableKey !== filter.key) continue;
180436
+ if (filter.kind && rec.kind !== filter.kind) continue;
180437
+ out.push(rec);
180438
+ }
180439
+ out.reverse();
180440
+ return typeof filter.limit === "number" ? out.slice(0, filter.limit) : out;
180441
+ };
180442
+ const resolveDispatchScope = async (artifactId) => {
180443
+ const ws = kernel.model.artifacts.get(artifactId)?.workspace;
180444
+ if (!ws) return {};
180445
+ const binding = await artifactStateStore.getWorkspaceBinding(ws);
180446
+ if (!binding) return {};
180447
+ return {
180448
+ ...binding.projectId ? { projectId: binding.projectId } : {}
180449
+ };
180450
+ };
179800
180451
  await syncRegistryRolesToKernel(registryStore, kernel);
179801
180452
  let builtinSkills = [];
179802
180453
  let connectorSkills = [];
@@ -179813,6 +180464,13 @@ async function startServe(opts) {
179813
180464
  blobs: assets,
179814
180465
  trace: () => traceStoreForActors,
179815
180466
  audit: registryAudit,
180467
+ // ADR 凭据保险库 §4.5/§4.7 / ADR 0125:reveal 判权的项目解析 + 凭据读取审计读写。
180468
+ resolveDispatchScope,
180469
+ credentialAudit,
180470
+ readCredentialAudit,
180471
+ // ADR 0125:写 scope=project 变量时校验 projectId 真实存在(getProject 直查,含临时项目)。
180472
+ // projectStateStore 在下方 ~行 1023 才赋值——本箭头只在请求时跑,那时已就绪(同 resolveDispatchScope 惰性模式)。
180473
+ projectExists: async (id) => await projectStateStore.getProject(id) !== null,
179816
180474
  // CO-302 数据面隔离(actors 域试点):按当前公司取其引擎,用各自 registry 服务该域请求。
179817
180475
  // engineRouter 在下方(行 ~411)以 const 声明;此箭头只在请求时调用,那时已初始化,闭包引用合法
179818
180476
  // (TDZ 只在构造时访问才报错,这里不访问)。默认公司命中现有单实例(registry===registryStore),
@@ -179943,7 +180601,13 @@ async function startServe(opts) {
179943
180601
  traceStoreForActors = traceStore;
179944
180602
  const trajReader = new FsTrajectorySink(opts.dir);
179945
180603
  const trace = createTraceDomain({ store: traceStore, readBundleFile: (id, rel) => trajReader.readBundleFile(id, rel) });
179946
- const traceStoreSink = new TraceStoreSink({ store: traceStore, runtimeKind: "auto" });
180604
+ const traceHealth = createTraceHealth();
180605
+ const traceStoreSink = new TraceStoreSink({
180606
+ store: traceStore,
180607
+ runtimeKind: "auto",
180608
+ health: traceHealth,
180609
+ onError: (err) => console.warn(`[trace-sink] \u5199\u89C2\u6D4B\u6D41\u5931\u8D25\uFF08\u7EE7\u7EED\u3001\u4E0D\u5F71\u54CD\u6D3E\u53D1\uFF09: ${String(err)}`)
180610
+ });
179947
180611
  const automationStore = pgPool ? await PostgresAutomationStore.open(pgPool, pgSchema) : new MemoryAutomationStore();
179948
180612
  console.log(`[serve] \u81EA\u52A8\u5316\u4F53\u7CFB\uFF1A${pgPool ? `Postgres schema=${pgSchema}` : "\u5185\u5B58 dev store"}`);
179949
180613
  const automations = createAutomationsDomain({ store: automationStore, kernel });
@@ -180404,7 +181068,11 @@ async function startServe(opts) {
180404
181068
  trace: trace.service,
180405
181069
  registry: registryStore,
180406
181070
  artifacts: artifactStateStore,
181071
+ // 组织汇总用——从 node_runtimes 取 hostname,解析运行时实例标签(kind @ hostname)
181072
+ listRuntimes: (nodeId) => nodeStore.listRuntimes(nodeId),
180407
181073
  dispatchJournal: () => journalRing.slice(-200),
181074
+ // 组织汇总用——从 nodes 取 name(用户友好名),优先于 hostname 作为展示标签
181075
+ listNodes: () => nodeStore.listNodes(),
180408
181076
  // 诊断 92ac8f18-83e5fde6 §④ D-2:按**请求公司**取其 dispatcher 的 queued/backoff 信号。
180409
181077
  // CO-302 每家公司各有一套 dispatcher(dispatchers 以 companyId 为键)——固定读默认公司会让
180410
181078
  // 非默认公司请求拿到错的/空的信号(code-review 打回)。该公司未起 dispatcher(老部署无 --dispatch、
@@ -180544,10 +181212,14 @@ async function startServe(opts) {
180544
181212
  const traceRunId = `chat-run:${(0, import_node_crypto37.randomUUID)()}`;
180545
181213
  const artifactId = `artifact:chat:${(0, import_node_crypto37.randomUUID)()}`;
180546
181214
  const traceStartedAt = (/* @__PURE__ */ new Date()).toISOString();
180547
- let traceSeq = 0;
180548
181215
  let lastProgressTouchMs = 0;
180549
181216
  const PROGRESS_TOUCH_THROTTLE_MS2 = 2e4;
180550
- let traceEnabled = true;
181217
+ const traceAppender = new ResilientTraceAppender({
181218
+ runId: traceRunId,
181219
+ store: traceStore,
181220
+ health: traceHealth,
181221
+ onError: (err) => console.warn(`[trace] chat run ${traceRunId} \u5199\u5931\u8D25\uFF08\u7EE7\u7EED\u3001\u4E0D\u7184\u706D\uFF09: ${String(err)}`)
181222
+ });
180551
181223
  const analyzedRunId = chatSessionId ? (await chatSessionStore.getSession(chatSessionId).catch(() => null))?.analyzedRunId ?? void 0 : void 0;
180552
181224
  let traceChain = traceStore.createRun({
180553
181225
  id: traceRunId,
@@ -180568,16 +181240,14 @@ async function startServe(opts) {
180568
181240
  ...analyzedRunId ? { analyzedRunId } : {}
180569
181241
  }
180570
181242
  }).then(async () => {
180571
- await traceStore.appendEvents(traceRunId, [
181243
+ await traceAppender.append([
180572
181244
  {
180573
- seq: ++traceSeq,
180574
181245
  eventType: "run.started",
180575
181246
  stream: "runtime",
180576
181247
  message: `chat ${actorId}`,
180577
181248
  startedAt: traceStartedAt
180578
181249
  },
180579
181250
  {
180580
- seq: ++traceSeq,
180581
181251
  eventType: "input.message",
180582
181252
  stream: "model",
180583
181253
  actorId,
@@ -180587,16 +181257,12 @@ async function startServe(opts) {
180587
181257
  }
180588
181258
  ]);
180589
181259
  }).catch((err) => {
180590
- traceEnabled = false;
180591
- console.warn(`[trace] chat run create failed: ${String(err)}`);
181260
+ traceHealth.markUnhealthy(traceRunId);
181261
+ console.warn(`[trace] chat run create failed (continuing): ${String(err)}`);
180592
181262
  });
180593
181263
  const enqueueTrace = (step) => {
180594
- if (!traceEnabled) return;
180595
- traceChain = traceChain.then(async () => {
180596
- if (traceEnabled) await step();
180597
- }).catch((err) => {
180598
- traceEnabled = false;
180599
- console.warn(`[trace] chat run ${traceRunId} failed: ${String(err)}`);
181264
+ traceChain = traceChain.then(step).catch((err) => {
181265
+ console.warn(`[trace] chat run ${traceRunId} \u7EED\u8D26\u5F02\u5E38: ${String(err)}`);
180600
181266
  });
180601
181267
  };
180602
181268
  const provision = await buildActorProvision(actorService, actorId);
@@ -180743,7 +181409,7 @@ async function startServe(opts) {
180743
181409
  const run = await traceStore.getRun(traceRunId);
180744
181410
  if (!run) return;
180745
181411
  const base = run.metadata && typeof run.metadata === "object" && !Array.isArray(run.metadata) ? run.metadata : {};
180746
- await traceStore.updateRun(traceRunId, {
181412
+ await traceAppender.update({
180747
181413
  metadata: { ...base, dispatchId: handle.id },
180748
181414
  // 恢复路径拿不到 dispatchChat 闭包里的 fallback 解析结果,开跑时先写进账本;退出帧若上报
180749
181415
  // 更精确 model 仍会覆盖为 runtime 来源。
@@ -180765,23 +181431,23 @@ async function startServe(opts) {
180765
181431
  const ms = Date.parse(event.ts);
180766
181432
  if (!Number.isNaN(ms) && ms - lastProgressTouchMs >= PROGRESS_TOUCH_THROTTLE_MS2) {
180767
181433
  lastProgressTouchMs = ms;
180768
- enqueueTrace(() => traceStore.updateRun(traceRunId, { lastProgressAt: event.ts }).then(() => void 0));
181434
+ enqueueTrace(() => traceAppender.update({ lastProgressAt: event.ts }).then(() => void 0));
180769
181435
  }
180770
181436
  return;
180771
181437
  }
180772
- enqueueTrace(() => traceStore.appendEvents(traceRunId, [trajectoryEventToRunEvent(event, ++traceSeq)]).then(() => void 0));
181438
+ enqueueTrace(() => traceAppender.append([trajectoryEventToRunEvent(event)]).then(() => void 0));
180773
181439
  });
180774
181440
  let capturedNativeSessionId;
180775
181441
  const done = new Promise((resolve8, reject) => {
180776
181442
  handle.onExit((info) => {
180777
181443
  chatLiveSessions.delete(chatJobKey);
181444
+ traceHealth.forget(traceRunId);
180778
181445
  void provision.cleanup();
180779
181446
  const endedAt = (/* @__PURE__ */ new Date()).toISOString();
180780
181447
  const durationMs = Date.parse(endedAt) - Date.parse(traceStartedAt);
180781
181448
  if (info.runtimeSessionId) capturedNativeSessionId = info.runtimeSessionId;
180782
181449
  enqueueTrace(async () => {
180783
- await traceStore.appendEvents(traceRunId, [{
180784
- seq: ++traceSeq,
181450
+ await traceAppender.append([{
180785
181451
  eventType: "run.finished",
180786
181452
  stream: "runtime",
180787
181453
  message: `exit code=${info.code ?? "killed"}`,
@@ -180790,7 +181456,7 @@ async function startServe(opts) {
180790
181456
  const usage = info.usage ?? null;
180791
181457
  const effectiveModel = info.model ?? chatModel ?? null;
180792
181458
  const modelSource = info.model ? "runtime" : chatModelSource;
180793
- await traceStore.updateRun(traceRunId, {
181459
+ await traceAppender.update({
180794
181460
  status: info.reason === "timeout" ? "timeout" : info.reason === "cancelled" ? "cancelled" : info.code === 0 ? "succeeded" : "failed",
180795
181461
  endedAt,
180796
181462
  durationMs,
@@ -180895,6 +181561,7 @@ async function startServe(opts) {
180895
181561
  unregister: () => {
180896
181562
  chatLiveSessions.delete(jobKey);
180897
181563
  },
181564
+ traceHealth,
180898
181565
  log: (m2) => console.log(m2)
180899
181566
  });
180900
181567
  console.log(`[chat-recovery] \u91CD\u6302 chat \u8F6E\uFF1Asession=${plan.chatSessionId} run=${plan.runId} node=${daemonId}`);
@@ -181115,7 +181782,7 @@ async function startServe(opts) {
181115
181782
  console.warn(`[dispatch] artifact ${artifactId} owner ${actor} \u5DF2\u505C\u7528\uFF0C\u6309 ownerRole=${ownerRole} \u6539\u6D3E\u7ED9 ${replacement.id}${rejected.length ? `\uFF08\u8DF3\u8FC7\u4E0D\u5065\u5EB7\u7684\uFF1A${rejected.join("\u3001")}\uFF09` : ""}`);
181116
181783
  return { actor: replacement.id, originalActor: actor, routeReason: "disabled-owner-fallback" };
181117
181784
  },
181118
- provision: async (actorId) => buildActorProvision((await runActors()).service, actorId),
181785
+ provision: async (actorId, artifactId) => buildActorProvision((await runActors()).service, actorId, artifactId ? await resolveDispatchScope(artifactId) : void 0),
181119
181786
  resolveActorContext: async (actorId) => buildActorContext((await runActors()).service, actorId),
181120
181787
  // 收尾评审的角色分工段(reviewer 此前彼此不知道对方存在 → 越界 / 重复劳动)。
181121
181788
  // 职责取自**岗位目录**(配置面),不在代码里硬编码任何岗位——加角色自动就在。
@@ -181738,7 +182405,7 @@ ${nodeFault}` : "");
181738
182405
  resolveManager: (id) => resolveManagerBinding(registryStore, id),
181739
182406
  schemaTypes: schema.map((d) => d.name),
181740
182407
  schemaTypeDescriptions: Object.fromEntries(schema.filter((d) => d.description).map((d) => [d.name, d.description])),
181741
- provision: (actorId) => buildActorProvision(actors.service, actorId),
182408
+ provision: async (actorId, artifactId) => buildActorProvision(actors.service, actorId, artifactId ? await resolveDispatchScope(artifactId) : void 0),
181742
182409
  // 阻塞诊断(proposal D1/D3):派发器运行时信号(queued/fused/backoff/hasInFlight)注入诊断,
181743
182410
  // 让协调者把"资源 / 运行时"病因和"图病"分开——资源排队的节点不会被误唤去砍流程。
181744
182411
  runtimeSignals: (id) => {
@@ -181778,6 +182445,9 @@ ${nodeFault}` : "");
181778
182445
  if (c) return Promise.resolve(c.kill()).then(() => true).catch(() => false);
181779
182446
  return dispatcherByJob.get(jobKey)?.killSession(jobKey) ?? Promise.resolve(false);
181780
182447
  },
182448
+ // trace 写不进去的 run:冻结的事件钟是"观测降级"不是"真静默",按"信息不足→不判"跳过、交 wallClock
182449
+ // 兜底(brief §限制1 / 诊断 §6)。真卡死的 run trace 健康、isTraceHealthy=true,照常被杀。
182450
+ isTraceHealthy: (sessionId) => traceHealth.isHealthy(sessionId),
181781
182451
  now: Date.now(),
181782
182452
  silentThresholdMs,
181783
182453
  log: (m2) => console.log(m2)
@@ -181940,7 +182610,7 @@ var SessionManager = class {
181940
182610
 
181941
182611
  // ../cli/src/daemon/ws-client.ts
181942
182612
  init_wrapper();
181943
- var import_node_os7 = require("node:os");
182613
+ var import_node_os8 = require("node:os");
181944
182614
  var import_node_crypto38 = require("node:crypto");
181945
182615
  init_src5();
181946
182616
 
@@ -181996,7 +182666,7 @@ function detectRuntimes() {
181996
182666
 
181997
182667
  // ../cli/src/daemon/workdir-handler.ts
181998
182668
  var import_node_fs16 = __toESM(require("node:fs"), 1);
181999
- var import_node_path18 = __toESM(require("node:path"), 1);
182669
+ var import_node_path19 = __toESM(require("node:path"), 1);
182000
182670
  init_src4();
182001
182671
  init_src();
182002
182672
  var WORKDIR_READ_MAX_BYTES2 = 2 * 1024 * 1024;
@@ -182015,10 +182685,10 @@ function isSensitiveSegment(segment) {
182015
182685
  }
182016
182686
  function relPathIsSensitive(rel) {
182017
182687
  if (!rel) return false;
182018
- return rel.split(import_node_path18.default.sep).some((seg) => seg.length > 0 && isSensitiveSegment(seg));
182688
+ return rel.split(import_node_path19.default.sep).some((seg) => seg.length > 0 && isSensitiveSegment(seg));
182019
182689
  }
182020
182690
  function withinBase(p2, base) {
182021
- return p2 === base || p2.startsWith(base + import_node_path18.default.sep);
182691
+ return p2 === base || p2.startsWith(base + import_node_path19.default.sep);
182022
182692
  }
182023
182693
  function normalizeRel(raw) {
182024
182694
  const trimmed = (raw ?? "").trim();
@@ -182035,7 +182705,7 @@ async function trustedCanonicalContainer(req, logicalBase, dirKind) {
182035
182705
  } catch {
182036
182706
  return null;
182037
182707
  }
182038
- return isLegacy ? import_node_path18.default.join(trustedRootReal, "oasis-chat-sessions") : import_node_path18.default.join(trustedRootReal, "sessions", dirKind);
182708
+ return isLegacy ? import_node_path19.default.join(trustedRootReal, "oasis-chat-sessions") : import_node_path19.default.join(trustedRootReal, "sessions", dirKind);
182039
182709
  }
182040
182710
  async function resolveWithinWorkdir(req) {
182041
182711
  const dirKind = sessionDirKind(req.runtimeKind);
@@ -182053,11 +182723,11 @@ async function resolveWithinWorkdir(req) {
182053
182723
  } catch {
182054
182724
  return { ok: false, code: "NOT_FOUND" };
182055
182725
  }
182056
- if (import_node_path18.default.dirname(base) !== canonicalContainer) return { ok: false, code: "PATH_ESCAPE" };
182726
+ if (import_node_path19.default.dirname(base) !== canonicalContainer) return { ok: false, code: "PATH_ESCAPE" };
182057
182727
  const rel = normalizeRel(req.path);
182058
- const requested = import_node_path18.default.resolve(base, rel);
182728
+ const requested = import_node_path19.default.resolve(base, rel);
182059
182729
  if (!withinBase(requested, base)) return { ok: false, code: "PATH_ESCAPE" };
182060
- const cleanRel = base === requested ? "" : import_node_path18.default.relative(base, requested);
182730
+ const cleanRel = base === requested ? "" : import_node_path19.default.relative(base, requested);
182061
182731
  if (relPathIsSensitive(cleanRel)) return { ok: false, code: "SENSITIVE" };
182062
182732
  let real;
182063
182733
  try {
@@ -182067,7 +182737,7 @@ async function resolveWithinWorkdir(req) {
182067
182737
  return { ok: false, code: "PATH_ESCAPE" };
182068
182738
  }
182069
182739
  if (!withinBase(real, base)) return { ok: false, code: "PATH_ESCAPE" };
182070
- const realRel = base === real ? "" : import_node_path18.default.relative(base, real);
182740
+ const realRel = base === real ? "" : import_node_path19.default.relative(base, real);
182071
182741
  if (relPathIsSensitive(realRel)) return { ok: false, code: "SENSITIVE" };
182072
182742
  return { ok: true, base, real };
182073
182743
  }
@@ -182103,7 +182773,7 @@ async function handleWorkdirList(req) {
182103
182773
  const slice = truncated ? visible.slice(0, WORKDIR_LIST_MAX_ENTRIES) : visible;
182104
182774
  const entries = [];
182105
182775
  for (const d of slice) {
182106
- const abs = import_node_path18.default.join(anchor, d.name);
182776
+ const abs = import_node_path19.default.join(anchor, d.name);
182107
182777
  try {
182108
182778
  const st = await import_node_fs16.default.promises.lstat(abs);
182109
182779
  if (st.isSymbolicLink()) continue;
@@ -182135,7 +182805,7 @@ async function verifyOpenedFd(fh, base, fallback) {
182135
182805
  const fdReal = await fdCanonicalPath(fh);
182136
182806
  if (fdReal === null) return { anchor: fallback };
182137
182807
  if (!withinBase(fdReal, base)) return { error: { ok: false, code: "PATH_ESCAPE" } };
182138
- const fdRel = base === fdReal ? "" : import_node_path18.default.relative(base, fdReal);
182808
+ const fdRel = base === fdReal ? "" : import_node_path19.default.relative(base, fdReal);
182139
182809
  if (relPathIsSensitive(fdRel)) return { error: { ok: false, code: "SENSITIVE" } };
182140
182810
  return { anchor: `/proc/self/fd/${fh.fd}` };
182141
182811
  }
@@ -182224,7 +182894,7 @@ function looksBinary(bytes) {
182224
182894
  function contentTypeFor(absPath, bytes) {
182225
182895
  const sniffed = sniffContentType(bytes);
182226
182896
  if (sniffed) return sniffed;
182227
- const ext = import_node_path18.default.extname(absPath).toLowerCase();
182897
+ const ext = import_node_path19.default.extname(absPath).toLowerCase();
182228
182898
  if (EXT_CONTENT_TYPE[ext]) return EXT_CONTENT_TYPE[ext];
182229
182899
  return looksBinary(bytes) ? "application/octet-stream" : "text/plain; charset=utf-8";
182230
182900
  }
@@ -182567,7 +183237,7 @@ var DaemonWsClient = class {
182567
183237
  buildMeta() {
182568
183238
  const activeSessions = this.sessions.activeSessions();
182569
183239
  return {
182570
- hostname: (0, import_node_os7.hostname)(),
183240
+ hostname: (0, import_node_os8.hostname)(),
182571
183241
  adapters: this.adapters,
182572
183242
  ...this.reportRuntimes ? { runtimes: this.runtimes } : {},
182573
183243
  nodeVersion: process.version,
@@ -182597,18 +183267,18 @@ var RuntimeRouterAdapter = class {
182597
183267
 
182598
183268
  // ../cli/src/daemon/reap-claude-projects.ts
182599
183269
  var import_node_fs17 = __toESM(require("node:fs"), 1);
182600
- var import_node_os8 = __toESM(require("node:os"), 1);
182601
- var import_node_path19 = __toESM(require("node:path"), 1);
183270
+ var import_node_os9 = __toESM(require("node:os"), 1);
183271
+ var import_node_path20 = __toESM(require("node:path"), 1);
182602
183272
  function claudeProjectSlug(cwd) {
182603
183273
  return cwd.replace(/[^a-zA-Z0-9]/g, "-");
182604
183274
  }
182605
183275
  function claudeProjectsRoot() {
182606
- const configDir = process.env["CLAUDE_CONFIG_DIR"] || import_node_path19.default.join(import_node_os8.default.homedir(), ".claude");
182607
- return import_node_path19.default.join(configDir, "projects");
183276
+ const configDir = process.env["CLAUDE_CONFIG_DIR"] || import_node_path20.default.join(import_node_os9.default.homedir(), ".claude");
183277
+ return import_node_path20.default.join(configDir, "projects");
182608
183278
  }
182609
183279
  function reapClaudeProjects(workdir, runtimeKind) {
182610
183280
  if (runtimeKind !== "claude" && runtimeKind !== "claude-code") return;
182611
- const dir = import_node_path19.default.join(claudeProjectsRoot(), claudeProjectSlug(workdir));
183281
+ const dir = import_node_path20.default.join(claudeProjectsRoot(), claudeProjectSlug(workdir));
182612
183282
  if (!import_node_fs17.default.existsSync(dir)) return;
182613
183283
  try {
182614
183284
  import_node_fs17.default.rmSync(dir, { recursive: true, force: true });
@@ -182618,7 +183288,7 @@ function reapClaudeProjects(workdir, runtimeKind) {
182618
183288
 
182619
183289
  // ../cli/src/node.ts
182620
183290
  init_src4();
182621
- var import_node_os9 = __toESM(require("node:os"), 1);
183291
+ var import_node_os10 = __toESM(require("node:os"), 1);
182622
183292
  var RESILIENCE = {
182623
183293
  codex: { idleTimeoutMs: 6e5 }
182624
183294
  };
@@ -182682,7 +183352,7 @@ async function startNode(opts) {
182682
183352
  console.log(`[oasis node] ${opts.nodeId} \u2192 ${opts.serverUrl}`);
182683
183353
  client.start();
182684
183354
  const { workRoot, legacyRoot } = resolveWorkRoots(opts.workRoot);
182685
- const workRootIsEphemeral = workRoot.startsWith(import_node_os9.default.tmpdir());
183355
+ const workRootIsEphemeral = workRoot.startsWith(import_node_os10.default.tmpdir());
182686
183356
  if (opts.gcEnabled === false && !workRootIsEphemeral) {
182687
183357
  console.warn(`[oasis node] \u26A0 \u56DE\u6536\u5668\u5DF2\u5173\u95ED\uFF0C\u800C\u5DE5\u4F5C\u533A\u5728\u6301\u4E45\u76D8\uFF08${workRoot}\uFF09\u2014\u2014\u6CA1\u6709\u4EFB\u4F55\u5783\u573E\u56DE\u6536\uFF0C\u5B9E\u6D4B\u4E24\u5468\u4F1A\u6DA8 12.4GB\u3002\u4EC5\u4F9B\u6392\u969C\uFF0C\u522B\u957F\u671F\u8FD9\u4E48\u8DD1\u3002`);
182688
183358
  }
@@ -182717,7 +183387,7 @@ async function startNode(opts) {
182717
183387
  var import_node_child_process16 = require("node:child_process");
182718
183388
  var import_node_fs18 = require("node:fs");
182719
183389
  var import_node_crypto39 = require("node:crypto");
182720
- var import_node_os10 = require("node:os");
183390
+ var import_node_os11 = require("node:os");
182721
183391
  function linuxMachineId() {
182722
183392
  for (const p2 of ["/etc/machine-id", "/var/lib/dbus/machine-id"]) {
182723
183393
  try {
@@ -182757,13 +183427,13 @@ function windowsMachineId() {
182757
183427
  }
182758
183428
  function fallbackFingerprint() {
182759
183429
  const macs = [];
182760
- const ifaces = (0, import_node_os10.networkInterfaces)();
183430
+ const ifaces = (0, import_node_os11.networkInterfaces)();
182761
183431
  for (const name of Object.keys(ifaces).sort()) {
182762
183432
  for (const ni of ifaces[name] ?? []) {
182763
183433
  if (!ni.internal && ni.mac && ni.mac !== "00:00:00:00:00:00") macs.push(ni.mac);
182764
183434
  }
182765
183435
  }
182766
- return `fallback:${(0, import_node_os10.hostname)()}:${macs.sort()[0] ?? "no-mac"}`;
183436
+ return `fallback:${(0, import_node_os11.hostname)()}:${macs.sort()[0] ?? "no-mac"}`;
182767
183437
  }
182768
183438
  function machineFingerprint() {
182769
183439
  const byOs = process.platform === "darwin" ? macMachineId() : process.platform === "win32" ? windowsMachineId() : linuxMachineId();
@@ -182773,7 +183443,7 @@ var defaultSources = {
182773
183443
  machineFingerprint,
182774
183444
  osUser: () => {
182775
183445
  try {
182776
- return (0, import_node_os10.userInfo)().username;
183446
+ return (0, import_node_os11.userInfo)().username;
182777
183447
  } catch {
182778
183448
  return process.env["USER"] ?? process.env["USERNAME"] ?? "unknown";
182779
183449
  }
@@ -182957,7 +183627,7 @@ var COMMAND_DECLS = {
182957
183627
  // —— 版本机(§5) ——
182958
183628
  spawn: {
182959
183629
  usage: "oasis spawn <artifactId> --type <t> [--title <\u77ED\u540D>] [--brief <\u89C4\u683C>] [--input <\u4E0A\u6E38id>[,<id>\u2026]] [--owner <actorId>] [--workspace ws] [--persistence temp|persistent] [--parts-file <json>] [--<\u5B57\u6BB5> <\u503C>] [--set-json <json>]",
182960
- description: "\u521B\u5EFA\u65B0\u4EA7\u7269\u8282\u70B9\uFF08\xA75 \u7248\u672C\u673A\uFF09\u3002--<\u5B57\u6BB5> <\u503C> \u662F\u8BE5\u7C7B\u578B\u52A8\u6001\u58F0\u660E\u7684\u81EA\u5B9A\u4E49\u5B57\u6BB5\uFF08\u7528 oasis artifact-types show <type> \u67E5\u770B\uFF09\u3002",
183630
+ description: "\u521B\u5EFA\u65B0\u4EA7\u7269\u8282\u70B9\uFF08\xA75 \u7248\u672C\u673A\uFF09\u3002--<\u5B57\u6BB5> <\u503C> \u662F\u8BE5\u7C7B\u578B\u52A8\u6001\u58F0\u660E\u7684\u81EA\u5B9A\u4E49\u5B57\u6BB5\uFF08\u7528 oasis artifact-types show <type|\u8BE5\u4EA7\u7269 id> \u67E5\u770B\uFF09\u3002",
182961
183631
  positional: [
182962
183632
  { name: "<artifactId>", required: true, desc: "\u4EA7\u7269 id" }
182963
183633
  ],
@@ -183510,9 +184180,9 @@ var COMMAND_DECLS = {
183510
184180
  },
183511
184181
  // —— 类型管理 ——
183512
184182
  "artifact-types": {
183513
- usage: "oasis artifact-types [list | search <q> | show <type> | add ... | edit ... | deprecate ... | delete ... | add-field ... | edit-field ... | drop-field ...]",
183514
- description: "\u7BA1\u7406 artifact \u7C7B\u578B\u6CE8\u518C\u8868\u3002\u5B50\u547D\u4EE4\uFF1Alist\uFF08\u5217\u51FA\u5168\u90E8\uFF09\u3001search\uFF08\u641C\u7D22\uFF09\u3001show\uFF08\u770B\u8BE6\u60C5\u542B\u81EA\u5B9A\u4E49\u5B57\u6BB5\u8868\uFF09\u3001add/edit/deprecate/delete\uFF08\u7C7B\u578B CRUD\uFF09\u3001add-field/edit-field/drop-field\uFF08\u5B57\u6BB5\u7BA1\u7406\uFF09\u3002",
183515
- examples: ["oasis artifact-types list", "oasis artifact-types show dev"]
184183
+ usage: "oasis artifact-types [list | search <q> | show <type|artifactId> | add ... | edit ... | deprecate ... | delete ... | add-field ... | edit-field ... | drop-field ...]",
184184
+ description: "\u7BA1\u7406 artifact \u7C7B\u578B\u6CE8\u518C\u8868\u3002\u5B50\u547D\u4EE4\uFF1Alist\uFF08\u5217\u51FA\u5168\u90E8\uFF09\u3001search\uFF08\u641C\u7D22\uFF09\u3001show\uFF08\u770B\u8BE6\u60C5\u542B\u81EA\u5B9A\u4E49\u5B57\u6BB5\u8868\uFF1B**\u76F4\u63A5\u5582\u4EA7\u7269 id \u4E5F\u884C**\uFF0C\u81EA\u52A8\u89E3\u6790\u6210\u5B83\u7684\u7C7B\u578B\uFF09\u3001add/edit/deprecate/delete\uFF08\u7C7B\u578B CRUD\uFF09\u3001add-field/edit-field/drop-field\uFF08\u5B57\u6BB5\u7BA1\u7406\uFF09\u3002",
184185
+ examples: ["oasis artifact-types list", "oasis artifact-types show dev", "oasis artifact-types show artifact:brief:abc-123"]
183516
184186
  },
183517
184187
  // —— 工单 ——
183518
184188
  "create-workorder": {
@@ -183724,7 +184394,7 @@ var COMMAND_DECLS = {
183724
184394
  },
183725
184395
  edit: {
183726
184396
  usage: "oasis edit <id> [--title \u2026] [--brief \u2026] [--<\u5B57\u6BB5> <\u503C>] [--set-json <json>] [--clear <\u5B57\u6BB5,\u2026>]",
183727
- description: "\u4FEE\u6539\u4EA7\u7269\u5C5E\u6027\uFF08\xA75 \u6682\u5B58\u6001\u2014\u2014OASIS_STAGE=1 \u4E0B\u751F\u6548\uFF09\u3002--<\u5B57\u6BB5> <\u503C> \u662F\u8BE5\u7C7B\u578B\u52A8\u6001\u58F0\u660E\u7684\u81EA\u5B9A\u4E49\u5B57\u6BB5\uFF08\u7528 oasis show <id> / artifact-types show <type> \u67E5\u770B\uFF09\u3002set-json / clear \u7684\u6392\u9664\u4E0E\u4E13\u9879\u5904\u7406\u89C1 fieldsFromFlags\u3002",
184397
+ description: "\u4FEE\u6539\u4EA7\u7269\u5C5E\u6027\uFF08\xA75 \u6682\u5B58\u6001\u2014\u2014OASIS_STAGE=1 \u4E0B\u751F\u6548\uFF09\u3002--<\u5B57\u6BB5> <\u503C> \u662F\u8BE5\u7C7B\u578B\u52A8\u6001\u58F0\u660E\u7684\u81EA\u5B9A\u4E49\u5B57\u6BB5\uFF08\u7528 oasis artifact-types show <\u8BE5\u4EA7\u7269 id> \u67E5\u770B\uFF09\u3002set-json / clear \u7684\u6392\u9664\u4E0E\u4E13\u9879\u5904\u7406\u89C1 fieldsFromFlags\u3002",
183728
184398
  positional: [
183729
184399
  { name: "<id>", required: true, desc: "\u4EA7\u7269 id" }
183730
184400
  ],
@@ -183777,6 +184447,19 @@ var COMMAND_DECLS = {
183777
184447
  description: "\u4EA4\u4ED8\u7269\u6587\u6863\u4F53\u7CFB\u64CD\u4F5C\uFF08artifact revision \u5B50\u547D\u4EE4\u96C6\uFF09\u3002",
183778
184448
  subCommands: ["artifact revision register", "artifact revision approve", "artifact revision reject"],
183779
184449
  examples: ['oasis artifact revision register --project oasis --artifact-id artifact:dev:abc --type dev --title "v1.0" --version 1 --content-ref ref-001']
184450
+ },
184451
+ var: {
184452
+ usage: "oasis var <reveal> ...",
184453
+ description: "\u51ED\u636E\u4FDD\u9669\u5E93\uFF1A\u8BFB\u53D6\u654F\u611F\u53D8\u91CF\u660E\u6587\uFF08ADR \u51ED\u636E\u4FDD\u9669\u5E93 \xA74.5\uFF09\u3002",
184454
+ subCommands: ["var reveal"],
184455
+ examples: ["oasis var reveal DEV_SPA_LOGIN_PASSWORD"]
184456
+ },
184457
+ "var reveal": {
184458
+ usage: "oasis var reveal <key> [--source <tag>]",
184459
+ description: "\u8BFB\u53D6\u654F\u611F\u53D8\u91CF\u7684\u660E\u6587\u3002env \u91CC\u82E5\u89C1 oasis://var/X\uFF08\u5F15\u7528\u975E\u660E\u6587\uFF09\u2192 \u7528\u672C\u547D\u4EE4\u53D6\u660E\u6587\u3002\u7F3A key/\u65E0\u6743\u8FD4\u56DE denied\uFF1B\u77ED\u65F6\u8BFB\u53D6\u8FC7\u591A\u4F1A\u9650\u6D41\u3002\u660E\u6587\u53EA\u8F93\u51FA\u5230 stdout\u3002",
184460
+ positional: [{ name: "key", required: true, desc: "\u53D8\u91CF key\uFF08\u53EF\u4F20\u88F8 key \u6216 oasis://var/<key> \u5F15\u7528\uFF0C\u4E8C\u8005\u7B49\u4EF7\uFF09" }],
184461
+ flags: [{ name: "source", desc: "\u8C03\u7528\u6765\u6E90\u6807\u8BB0\uFF08\u5BA1\u8BA1\u7528\uFF0C\u5982 smoke-test\uFF1B\u7F3A\u7701 agent-cli\uFF09", own: true }],
184462
+ examples: ["oasis var reveal DEV_SPA_LOGIN_PASSWORD", "oasis var reveal oasis://var/GLM_API_KEY --source smoke-test"]
183780
184463
  }
183781
184464
  };
183782
184465
  function formatCommandHelp(decl) {
@@ -183858,7 +184541,7 @@ var USAGE = `oasis \u2014\u2014 artifact-centric \u534F\u4F5C\u5185\u6838 CLI\uF
183858
184541
  \u7248\u672C\u673A\uFF08\xA75\uFF09
183859
184542
  spawn <artifactId> --type <t> [--title <\u77ED\u540D>] [--brief <\u89C4\u683C>] [--input <\u4E0A\u6E38id>[,<id>\u2026]] [--owner <actorId>] [--workspace ws] [--persistence temp|persistent] [--parts-file <json>] [--<\u5B57\u6BB5> <\u503C>] [--set-json <json>]
183860
184543
  # --parts-file\uFF1Aconvergent \u5E76\u884C\u5206\u89E3\u7684\u90E8\u4EF6\u6E05\u5355 JSON [{name, spec?, deps?}]\uFF08Stage 3\uFF0C\u7CFB\u7EDF\u6309\u90E8\u4EF6\u6D3E N \u8DEF\uFF09
183861
- # --<\u5B57\u6BB5> <\u503C>\uFF1A\u8BE5\u7C7B\u578B\u3010\u52A8\u6001\u58F0\u660E\u3011\u7684\u81EA\u5B9A\u4E49\u5B57\u6BB5\uFF08\u4E0D\u662F\u56FA\u5B9A\u6E05\u5355\uFF09\u2014\u2014\u7528 "oasis artifact-types show <type>" \u770B\u53EF\u586B\u5B57\u6BB5\uFF1Bstring[]/\u6279\u91CF\u7528 --set-json\uFF1B\u7F3A\u5FC5\u586B\u4F1A\u62A5\u9519\u5217\u51FA
184544
+ # --<\u5B57\u6BB5> <\u503C>\uFF1A\u8BE5\u7C7B\u578B\u3010\u52A8\u6001\u58F0\u660E\u3011\u7684\u81EA\u5B9A\u4E49\u5B57\u6BB5\uFF08\u4E0D\u662F\u56FA\u5B9A\u6E05\u5355\uFF09\u2014\u2014\u7528 "oasis artifact-types show <type|\u8BE5\u4EA7\u7269 id>" \u770B\u53EF\u586B\u5B57\u6BB5\uFF1Bstring[]/\u6279\u91CF\u7528 --set-json\uFF1B\u7F3A\u5FC5\u586B\u4F1A\u62A5\u9519\u5217\u51FA
183862
184545
  propose <artifactId> [--file <path> | --from-dir <path> | --git-commit <sha>] --reason <text> [--base <revisionId|none>] [--part <name>] [--rebase-of <revisionId>] [--checkpoint --continue]
183863
184546
  # --file \u5355\u6587\u4EF6\uFF1B--from-dir \u9012\u5F52\u6574\u76EE\u5F55\u4E3A\u591A\u6587\u4EF6 revision\uFF08\u88C5\u7BB1\u5355\uFF0C\u8DE8\u7248\u672C\u53BB\u91CD\uFF0CF1\uFF09\u3002
183864
184547
  # \u4EE3\u7801\u8282\u70B9\u53EF\u3010\u7701\u7565\u5168\u90E8\u5185\u5BB9\u53C2\u6570\u3011\uFF0C\u4EC5 --reason\uFF1A\u5148\u628A\u6539\u52A8 push \u5230\u5DE5\u5355 feat \u5206\u652F\uFF0C\u7CFB\u7EDF\u81EA\u52A8\u4ECE git \u6536\u96C6\uFF08\u672C\u4F53\u5728 git\uFF09\u3002
@@ -183914,7 +184597,7 @@ var USAGE = `oasis \u2014\u2014 artifact-centric \u534F\u4F5C\u5185\u6838 CLI\uF
183914
184597
  log <artifactId> # \u8BE5\u4EA7\u7269\u7684**\u64CD\u4F5C\u65E5\u5FD7**\uFF1A\u5DF2\u53D1\u751F\u7684\u5185\u6838\u4E8B\u5B9E\uFF08spawn/propose/merge/conclude\u2026\uFF09\uFF0C\u4ECE oplog \u8BFB
183915
184598
  session-log <runId|artifactId> [--n 20] # \u67D0 agent **\u4F1A\u8BDD\u7684\u8FD0\u884C\u65F6\u8F93\u51FA**\uFF08\u65C1\u8D26\uFF0C\u975E\u5185\u6838\u4E8B\u5B9E\uFF09\uFF1A\u6700\u65B0 N \u6761\u4E8B\u4EF6 + "\u8DDD\u4E0A\u6B21\u4E8B\u4EF6 X \u5206\u949F"\uFF08>10min\u2248\u5361\uFF09+ \u5728\u98DE tool_use\u2014\u2014\u5224"\u60F3 vs \u5361"\u3002\u6CE8\u610F\u2260 log\uFF1Alog \u770B\u4EA7\u7269\u7ECF\u5386\u4E86\u4EC0\u4E48\uFF0Csession-log \u770B agent \u6B64\u523B\u5728\u8BF4/\u5E72\u4EC0\u4E48
183916
184599
  artifact-types [list | search <q>] # \u770B/\u68C0\u7D22\u5DF2\u6CE8\u518C\u7C7B\u578B\uFF08\u7528\u9014/owner/\u72B6\u6001\uFF09\u2014\u2014\u7ED9\u4EBA\u548C agent \u9009 type
183917
- artifact-types show <type> # \u770B\u5355\u7C7B\u578B\u8BE6\u60C5\uFF1A\u542B\u3010\u81EA\u5B9A\u4E49\u5B57\u6BB5\u8868\u3011(\u540D/\u7C7B\u578B/\u5FC5\u586B/\u8BF4\u660E)\u2014\u2014spawn/edit \u524D\u67E5\u80FD\u586B\u5565
184600
+ artifact-types show <type|artifactId> # \u770B\u5355\u7C7B\u578B\u8BE6\u60C5\uFF1A\u542B\u3010\u81EA\u5B9A\u4E49\u5B57\u6BB5\u8868\u3011(\u540D/\u7C7B\u578B/\u5FC5\u586B/\u8BF4\u660E)\u2014\u2014spawn/edit \u524D\u67E5\u80FD\u586B\u5565\u3002**\u76F4\u63A5\u5582\u4EA7\u7269 id \u4E5F\u884C**\uFF0C\u81EA\u52A8\u89E3\u6790\u6210\u5B83\u7684\u7C7B\u578B
183918
184601
  artifact-types add <name> (--content-type <t> --owner-role <r> | --file <json>) [--description \u2026] # \u65B0\u589E\uFF08--file \u6536\u5B8C\u6574 ArtifactTypeDef\uFF1Atriggers/reviews/grounding/budget/acl/mirror\u2026\uFF09
183919
184602
  artifact-types edit <name> [--file <json>] [--description \u2026] [--owner-role \u2026] [--content-type \u2026] # \u6539\u7C7B\u578B\u914D\u7F6E\uFF08name \u4E0D\u53EF\u6539\uFF1BcontentType \u6709\u5B58\u91CF\u5373\u62E6\uFF1B--file \u6536\u5B8C\u6574 patch\u3002\u6539\u5B57\u6BB5\u7528 *-field\uFF09
183920
184603
  artifact-types add-field <type> <\u5B57\u6BB5> --type <string|string[]|number|boolean> [--required true] [--description \u2026] # \u7ED9\u7C7B\u578B\u3010\u58F0\u660E\u3011\u4E00\u4E2A\u81EA\u5B9A\u4E49\u5B57\u6BB5\uFF08\u6539 schema\u3001\u5F71\u54CD\u8BE5 type \u6240\u6709\u8282\u70B9\u7684\u672A\u6765\u5199\u5165\uFF09
@@ -184571,7 +185254,7 @@ async function runCli(argv, println = console.log) {
184571
185254
  const fields = fieldsFromFlags(ownFlagsFromDecl("edit"));
184572
185255
  return {
184573
185256
  action: "edit",
184574
- artifactId: needPos(positional, 0, "oasis edit <id> [--title \u2026] [--brief \u2026] [--<\u5B57\u6BB5> <\u503C>] [--set-json <json>] [--clear <\u5B57\u6BB5,\u2026>]\uFF08--<\u5B57\u6BB5> \u662F\u8BE5\u7C7B\u578B\u52A8\u6001\u58F0\u660E\u7684\uFF0C\u7528 oasis show <id> / artifact-types show <type> \u770B\u53EF\u586B\u5B57\u6BB5\uFF09"),
185257
+ artifactId: needPos(positional, 0, "oasis edit <id> [--title \u2026] [--brief \u2026] [--<\u5B57\u6BB5> <\u503C>] [--set-json <json>] [--clear <\u5B57\u6BB5,\u2026>]\uFF08--<\u5B57\u6BB5> \u662F\u8BE5\u7C7B\u578B\u52A8\u6001\u58F0\u660E\u7684\uFF0C\u7528 oasis artifact-types show <\u8BE5\u4EA7\u7269 id> \u770B\u53EF\u586B\u5B57\u6BB5\uFF09"),
184575
185258
  ...flags.get("title") !== void 0 ? { title: flags.get("title") } : {},
184576
185259
  ...flags.get("brief") !== void 0 ? { description: flags.get("brief") } : {},
184577
185260
  ...fields !== void 0 ? { fields } : {}
@@ -185442,6 +186125,33 @@ ${res.warning}`);
185442
186125
  println(`\u5DF2\u5220\u9664 ${memId}`);
185443
186126
  break;
185444
186127
  }
186128
+ case "var": {
186129
+ const sub = positional[0];
186130
+ if (sub === "reveal") {
186131
+ const rawKey = needPos(positional, 1, "oasis var reveal <key> [--source <tag>]");
186132
+ const key = parseVarRef(rawKey) ?? rawKey;
186133
+ const source = flags.get("source");
186134
+ try {
186135
+ const out = await api.request("POST", "/api/variables/reveal", {
186136
+ key,
186137
+ ...source ? { source } : {}
186138
+ });
186139
+ println(out.value);
186140
+ } catch (e) {
186141
+ if (e instanceof ApiRequestError && e.status === 403) {
186142
+ throw new Error(`\u51ED\u636E\u4E0D\u53EF\u8BFB\uFF1A${key}\uFF08403 denied\uFF09\u2014\u2014\u8BE5 key \u4E0D\u5B58\u5728\uFF0C\u6216\u672C\u4F1A\u8BDD\u65E0\u6743\u8BFB\u53D6\uFF08ADR \xA74.5\uFF09\u3002\u522B\u5FAA\u73AF\u91CD\u8BD5\uFF1B\u7F3A\u51ED\u636E\u8BF7\u4E0A\u62A5\u8FD0\u7EF4\u3002`);
186143
+ }
186144
+ if (e instanceof ApiRequestError && e.status === 429) {
186145
+ const scope = e.body?.scope ?? "session";
186146
+ throw new Error(`reveal \u89E6\u53D1\u9650\u6D41\uFF1A${key}\uFF08429 throttled\uFF0C\u6863=${scope}\uFF09\u2014\u2014\u77ED\u65F6\u5185\u8BFB\u53D6\u8FC7\u591A\u3002\u522B\u518D\u5FAA\u73AF reveal \u540C\u4E00 key\u3002`);
186147
+ }
186148
+ throw e;
186149
+ }
186150
+ break;
186151
+ }
186152
+ throw new Error(`\u672A\u77E5\u5B50\u547D\u4EE4\uFF1Aoasis var ${sub ?? ""}
186153
+ \u53EF\u7528\uFF1Aoasis var reveal <key> [--source <tag>]`);
186154
+ }
185445
186155
  case "pin": {
185446
186156
  const { message } = await api.cmd("pin", {
185447
186157
  artifactId: needPos(positional, 0, "oasis pin <artifactId> --to u --rev r"),
@@ -185989,13 +186699,21 @@ ${res.warning}`);
185989
186699
  break;
185990
186700
  }
185991
186701
  if (sub === "show") {
185992
- const name = needPos(positional, 1, "oasis artifact-types show <type>");
186702
+ const ref2 = needPos(positional, 1, "oasis artifact-types show <type|artifactId>");
186703
+ let name = ref2;
186704
+ let resolvedFrom;
186705
+ if (ref2.startsWith("artifact:")) {
186706
+ const v2 = await api.view("show", ref2);
186707
+ name = v2.artifact.type;
186708
+ resolvedFrom = ref2;
186709
+ }
185993
186710
  const { items } = await api.request("GET", "/api/artifact-types");
185994
186711
  const t = items.find((x2) => x2.name === name);
185995
186712
  if (!t) {
185996
186713
  println(`\uFF08\u65E0\u6B64\u7C7B\u578B: ${name}\uFF09`);
185997
186714
  break;
185998
186715
  }
186716
+ if (resolvedFrom) println(`${resolvedFrom} \u7684\u7C7B\u578B\u662F ${name}\uFF1A`);
185999
186717
  println(`${t.name}${t.status === "deprecated" ? " [\u5DF2\u5F03\u7528]" : ""} (${t.contentType}, owner=${t.ownerRole})${t.description ? ` \u2014\u2014 ${t.description}` : ""}`);
186000
186718
  const fds = t.fieldDefs ?? [];
186001
186719
  println(fds.length === 0 ? " \u81EA\u5B9A\u4E49\u5B57\u6BB5\uFF1A\u65E0" : " \u81EA\u5B9A\u4E49\u5B57\u6BB5\uFF1A");
@@ -186133,7 +186851,7 @@ function syncRuntimeAssets(candidateRoots, binDir) {
186133
186851
  }
186134
186852
 
186135
186853
  // src/index.ts
186136
- var PKG_VERSION = true ? "0.1.86" : "dev";
186854
+ var PKG_VERSION = true ? "0.1.89" : "dev";
186137
186855
  var OASIS_DIR = path29.join(os9.homedir(), ".oasis");
186138
186856
  var CONFIG_FILE = path29.join(OASIS_DIR, "node-config.json");
186139
186857
  var PID_FILE = path29.join(OASIS_DIR, "node.pid");