oasis_test 0.1.87 → 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 +927 -250
  2. package/package.json +1 -1
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());
@@ -141633,10 +141654,17 @@ async function exchangeManifestCode(code, deps = {}) {
141633
141654
  const appId = body["id"] === void 0 ? "" : String(body["id"]);
141634
141655
  const privateKeyPem = typeof body["pem"] === "string" ? body["pem"] : "";
141635
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;
141636
141661
  return {
141637
141662
  appId,
141638
141663
  privateKeyPem,
141639
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,
141640
141668
  ...typeof body["webhook_secret"] === "string" ? { webhookSecret: body["webhook_secret"] } : {}
141641
141669
  };
141642
141670
  }
@@ -141720,6 +141748,18 @@ var init_github = __esm({
141720
141748
  { key: "access_token", from: "GITHUB_APP_TOKEN", required: false },
141721
141749
  { key: "app_token_expires_at", from: "GITHUB_APP_TOKEN_EXPIRES_AT", required: false }
141722
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;
141723
141763
  /** 过期判定只认这个键——必须与注入时消费的 token 同源(契约测试盯着)。 */
141724
141764
  expiresAtCredentialKey = "app_token_expires_at";
141725
141765
  // 私钥不会过期,故不声明 refreshMaterialExpiresAtCredentialKey,也无需 clearOnExpired。
@@ -141729,6 +141769,12 @@ var init_github = __esm({
141729
141769
  "GITHUB_APP_TOKEN",
141730
141770
  "GITHUB_APP_PRIVATE_KEY"
141731
141771
  ];
141772
+ /**
141773
+ * 宿主机登录态隔离:`gh` 只认 env 里的 GH_TOKEN 或 `~/.config/gh/hosts.yml`。
141774
+ * 这个键**由通用装配层无条件设**(哪怕本连接器没接通),否则关掉连接器时
141775
+ * agent 会静默用成宿主机那个真人账号——见接口里的详细说明。
141776
+ */
141777
+ hostCredentialIsolationEnvKeys = ["GH_CONFIG_DIR"];
141732
141778
  /** inject() 写进 sessionEnv 的全部键——通用 cleanup() 按它逐个删。 */
141733
141779
  injectedEnvKeys = [
141734
141780
  "GH_BIN",
@@ -141819,7 +141865,7 @@ var init_github = __esm({
141819
141865
  const creds = await writeSessionCredsFile(this.config.slug, {
141820
141866
  GH_TOKEN: token,
141821
141867
  GITHUB_TOKEN: token
141822
- });
141868
+ }, sessionEnv.get("GH_RUN_TMPDIR"));
141823
141869
  const configDir = (0, import_node_path7.join)(creds.dir, "gh-config");
141824
141870
  await (0, import_promises4.mkdir)(configDir, { recursive: true, mode: 448 });
141825
141871
  sessionEnv.set("GH_BIN", await this.resolveRealBin("gh"));
@@ -141872,7 +141918,27 @@ function callbackHtml(ok, message) {
141872
141918
  setTimeout(function(){window.close()},${ok ? 800 : 4e3})</script>
141873
141919
  </body>`;
141874
141920
  }
141875
- 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;
141876
141942
  var init_app_manifest = __esm({
141877
141943
  "../connectors/src/github/app-manifest.ts"() {
141878
141944
  "use strict";
@@ -141915,6 +141981,7 @@ var init_app_manifest = __esm({
141915
141981
  return this.map.size;
141916
141982
  }
141917
141983
  };
141984
+ REQUIRED_WRITE_PERMISSIONS = ["contents", "pull_requests"];
141918
141985
  }
141919
141986
  });
141920
141987
 
@@ -141971,19 +142038,27 @@ function collectCredentials(connector, ctx) {
141971
142038
  }
141972
142039
  return creds;
141973
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
+ }
141974
142047
  async function buildConnectorProvision(ctx) {
141975
142048
  const envOverrides = {};
141976
142049
  const wrapperPaths = [];
141977
142050
  const requiredTools = /* @__PURE__ */ new Set();
141978
- const connectorCreds = [];
141979
142051
  const cleanups = [];
141980
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]));
141981
142056
  for (const connector of createAllConnectors()) {
141982
142057
  for (const v2 of connector.sensitiveVars) sensitiveVars.add(v2);
141983
- const credentials = collectCredentials(connector, ctx);
141984
- 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;
141985
142061
  try {
141986
- connectorCreds.push({ slug: connector.config.slug, credentials });
141987
142062
  const sessionEnv = /* @__PURE__ */ new Map();
141988
142063
  await connector.inject(credentials, sessionEnv);
141989
142064
  for (const [k2, v2] of sessionEnv) envOverrides[k2] = v2;
@@ -141999,6 +142074,19 @@ async function buildConnectorProvision(ctx) {
141999
142074
  log("[provision]", `${connector.config.slug}: \u88C5\u914D\u5931\u8D25\uFF08\u8BE5\u8FDE\u63A5\u5668\u672C\u8F6E\u4E0D\u53EF\u7528\uFF09: ${String(err)}`);
142000
142075
  }
142001
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
+ });
142002
142090
  const cleanup = async () => {
142003
142091
  for (const fn of cleanups) await fn().catch(() => {
142004
142092
  });
@@ -142012,23 +142100,28 @@ async function buildConnectorProvision(ctx) {
142012
142100
  cleanup
142013
142101
  };
142014
142102
  }
142103
+ var import_promises5, import_node_os4, import_node_path8;
142015
142104
  var init_provision = __esm({
142016
142105
  "../connectors/src/provision.ts"() {
142017
142106
  "use strict";
142018
142107
  init_base();
142019
142108
  init_registry2();
142020
142109
  init_logger();
142110
+ import_promises5 = require("node:fs/promises");
142111
+ import_node_os4 = require("node:os");
142112
+ import_node_path8 = require("node:path");
142021
142113
  }
142022
142114
  });
142023
142115
 
142024
142116
  // ../connectors/src/prepare-job.ts
142025
- async function prepareConnectorsForJob(job) {
142117
+ async function prepareConnectorsForJob(job, deps) {
142026
142118
  const creds = job.connectorCreds ?? [];
142027
142119
  const remoteWrappers = job.wrapperPaths ?? [];
142028
142120
  const localWrappers = [];
142029
142121
  const cleanups = [];
142030
142122
  const envOverrides = {};
142031
142123
  const statuses = [];
142124
+ const injected = /* @__PURE__ */ new Map();
142032
142125
  for (const { slug: slug6, credentials } of creds) {
142033
142126
  const connector = createConnectorBySlug(slug6);
142034
142127
  if (!connector) {
@@ -142043,6 +142136,7 @@ async function prepareConnectorsForJob(job) {
142043
142136
  await connector.inject(credentials, sessionEnv);
142044
142137
  for (const [k2, v2] of sessionEnv) envOverrides[k2] = v2;
142045
142138
  cleanups.push(() => connector.cleanup(sessionEnv));
142139
+ injected.set(slug6, { connector, sessionEnv });
142046
142140
  status.credsReady = true;
142047
142141
  } catch (err) {
142048
142142
  status.error = String(err);
@@ -142076,13 +142170,62 @@ async function prepareConnectorsForJob(job) {
142076
142170
  env: { ...job.env ?? {}, ...envOverrides, PATH: pathWithTools },
142077
142171
  wrapperPaths: [...keptLocal, ...keptRemote]
142078
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
+ }
142079
142184
  const cleanup = async () => {
142080
142185
  for (const fn of cleanups) await fn().catch(() => {
142081
142186
  });
142082
142187
  };
142083
142188
  return { job: preparedJob, cleanup, statuses };
142084
142189
  }
142085
- 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;
142086
142229
  var init_prepare_job = __esm({
142087
142230
  "../connectors/src/prepare-job.ts"() {
142088
142231
  "use strict";
@@ -142090,6 +142233,7 @@ var init_prepare_job = __esm({
142090
142233
  init_base();
142091
142234
  init_registry2();
142092
142235
  init_logger();
142236
+ SESSION_CREDENTIAL_RENEW_INTERVAL_MS = 30 * 6e4;
142093
142237
  }
142094
142238
  });
142095
142239
 
@@ -142428,8 +142572,8 @@ function materializeFiles(dir, files) {
142428
142572
  if (!files) return;
142429
142573
  for (const [rel, content] of Object.entries(files)) {
142430
142574
  try {
142431
- const file = import_node_path8.default.join(dir, rel);
142432
- 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 });
142433
142577
  import_node_fs6.default.writeFileSync(file, content);
142434
142578
  } catch {
142435
142579
  }
@@ -142445,7 +142589,7 @@ function resolveLegacyWorkRoot(workRoot) {
142445
142589
  if (workRoot) return workRoot;
142446
142590
  const env = process.env["OASIS_WORK_ROOT"];
142447
142591
  if (env) return env;
142448
- return import_node_os4.default.tmpdir();
142592
+ return import_node_os5.default.tmpdir();
142449
142593
  }
142450
142594
  function resolveWorkRoots(workRoot) {
142451
142595
  return { workRoot: resolveWorkRoot(workRoot), legacyRoot: resolveLegacyWorkRoot(workRoot) };
@@ -142456,7 +142600,7 @@ function slug5(workdirKey) {
142456
142600
  return `${safe}-${h}`;
142457
142601
  }
142458
142602
  function sessionDirFor(workRoot, runtimeKind, workdirKey) {
142459
- 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));
142460
142604
  }
142461
142605
  function sessionDirKind(runtimeKind) {
142462
142606
  return runtimeKind === "claude-code" ? "claude" : runtimeKind;
@@ -142469,20 +142613,20 @@ function resolveSessionDirWithLegacy(args) {
142469
142613
  return import_node_fs6.default.existsSync(legacy) ? legacy : fresh;
142470
142614
  }
142471
142615
  function oneShotDirFor(workRoot, runtimeKind) {
142472
- const base = import_node_path8.default.join(resolveWorkRoot(workRoot), "tmp");
142616
+ const base = import_node_path9.default.join(resolveWorkRoot(workRoot), "tmp");
142473
142617
  import_node_fs6.default.mkdirSync(base, { recursive: true });
142474
- 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}-`));
142475
142619
  }
142476
142620
  function writeMeta(dir, meta) {
142477
142621
  try {
142478
- 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)}
142479
142623
  `);
142480
142624
  } catch {
142481
142625
  }
142482
142626
  }
142483
142627
  function readMeta(dir) {
142484
142628
  try {
142485
- 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"));
142486
142630
  if (!raw || typeof raw !== "object") return null;
142487
142631
  const m2 = raw;
142488
142632
  return typeof m2.kind === "string" && typeof m2.createdAt === "string" ? m2 : null;
@@ -142511,14 +142655,14 @@ function lockWorkdir(dir, pid, holder, workdirKey) {
142511
142655
  ...workdirKey !== void 0 ? { workdirKey } : {}
142512
142656
  };
142513
142657
  try {
142514
- 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)}
142515
142659
  `);
142516
142660
  } catch {
142517
142661
  }
142518
142662
  }
142519
142663
  function readLock(dir) {
142520
142664
  try {
142521
- 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"));
142522
142666
  if (!raw || typeof raw !== "object") return null;
142523
142667
  const l = raw;
142524
142668
  return Number.isInteger(l.pid) && l.pid > 0 ? l : null;
@@ -142528,14 +142672,14 @@ function readLock(dir) {
142528
142672
  }
142529
142673
  function unlockWorkdir(dir) {
142530
142674
  try {
142531
- 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));
142532
142676
  } catch {
142533
142677
  }
142534
142678
  }
142535
142679
  function isWorkdirLive(dir) {
142536
142680
  let raw;
142537
142681
  try {
142538
- 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");
142539
142683
  } catch {
142540
142684
  return false;
142541
142685
  }
@@ -142552,7 +142696,7 @@ function clearWorkdir(dir) {
142552
142696
  );
142553
142697
  }
142554
142698
  for (const entry of import_node_fs6.default.readdirSync(dir)) {
142555
- 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 });
142556
142700
  }
142557
142701
  }
142558
142702
  function prepareWorkdir(args) {
@@ -142582,19 +142726,19 @@ function prepareWorkdir(args) {
142582
142726
  return dir;
142583
142727
  }
142584
142728
  function legacyChatSessionDir(workRoot, rtId) {
142585
- 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, "_"));
142586
142730
  }
142587
- 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;
142588
142732
  var init_session_paths = __esm({
142589
142733
  "../adapters/src/_core/session-paths.ts"() {
142590
142734
  "use strict";
142591
142735
  import_node_crypto9 = require("node:crypto");
142592
142736
  import_node_fs6 = __toESM(require("node:fs"), 1);
142593
- import_node_os4 = __toESM(require("node:os"), 1);
142594
- 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);
142595
142739
  META_FILE = ".oasis-meta.json";
142596
142740
  LOCK_FILE = ".oasis-lock";
142597
- 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");
142598
142742
  LEGACY_ROOTS = ["oasis-chat-sessions", "oasis-transcripts"];
142599
142743
  LEGACY_ONESHOT_PREFIX = "oasis-session-";
142600
142744
  }
@@ -143816,7 +143960,7 @@ async function discoverACPModels(bin, provider, args = ["acp"]) {
143816
143960
  clientCapabilities: {}
143817
143961
  });
143818
143962
  try {
143819
- 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}-`));
143820
143964
  } catch {
143821
143965
  return fail();
143822
143966
  }
@@ -143872,14 +144016,14 @@ function parseACPSessionNewModels(result) {
143872
144016
  async function discoverOpenclawModels(bin) {
143873
144017
  throw new Error("discoverOpenclawModels: TODO");
143874
144018
  }
143875
- 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;
143876
144020
  var init_models = __esm({
143877
144021
  "../adapters/src/_core/models.ts"() {
143878
144022
  "use strict";
143879
144023
  import_node_child_process9 = require("node:child_process");
143880
144024
  import_node_fs7 = require("node:fs");
143881
- import_node_os5 = require("node:os");
143882
- import_node_path9 = require("node:path");
144025
+ import_node_os6 = require("node:os");
144026
+ import_node_path10 = require("node:path");
143883
144027
  modelCache = /* @__PURE__ */ new Map();
143884
144028
  CACHE_TTL_MS = 6e4;
143885
144029
  }
@@ -146446,7 +146590,7 @@ function dirSizeBytes(dir) {
146446
146590
  return;
146447
146591
  }
146448
146592
  for (const e of entries) {
146449
- const p2 = import_node_path10.default.join(d, e.name);
146593
+ const p2 = import_node_path11.default.join(d, e.name);
146450
146594
  if (e.isSymbolicLink()) continue;
146451
146595
  if (e.isDirectory()) {
146452
146596
  walk(p2);
@@ -146463,40 +146607,40 @@ function dirSizeBytes(dir) {
146463
146607
  }
146464
146608
  function listWorkdirs(workRoot, legacyRoot) {
146465
146609
  const out = [];
146466
- const sessions = import_node_path10.default.join(workRoot, "sessions");
146610
+ const sessions = import_node_path11.default.join(workRoot, "sessions");
146467
146611
  try {
146468
146612
  for (const kind of import_node_fs8.default.readdirSync(sessions)) {
146469
- const kindDir = import_node_path10.default.join(sessions, kind);
146613
+ const kindDir = import_node_path11.default.join(sessions, kind);
146470
146614
  try {
146471
- 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));
146472
146616
  } catch {
146473
146617
  }
146474
146618
  }
146475
146619
  } catch {
146476
146620
  }
146477
- const tmp = import_node_path10.default.join(workRoot, "tmp");
146621
+ const tmp = import_node_path11.default.join(workRoot, "tmp");
146478
146622
  try {
146479
- 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));
146480
146624
  } catch {
146481
146625
  }
146482
146626
  for (const legacy of LEGACY_ROOTS) {
146483
- const root = import_node_path10.default.join(legacyRoot, legacy);
146627
+ const root = import_node_path11.default.join(legacyRoot, legacy);
146484
146628
  try {
146485
- 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));
146486
146630
  } catch {
146487
146631
  }
146488
146632
  }
146489
146633
  try {
146490
146634
  for (const d of import_node_fs8.default.readdirSync(legacyRoot)) {
146491
- 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));
146492
146636
  }
146493
146637
  } catch {
146494
146638
  }
146495
146639
  return [...new Set(out)];
146496
146640
  }
146497
146641
  function kindFromPath(workRoot, dir) {
146498
- const rel = import_node_path10.default.relative(workRoot, dir);
146499
- 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);
146500
146644
  if (parts[0] === "sessions" && parts[1]) return parts[1];
146501
146645
  if (parts[0] === "tmp" && parts[1]) return parts[1].replace(/-[^-]*$/, "");
146502
146646
  return void 0;
@@ -146538,7 +146682,7 @@ async function runGcSweep(deps) {
146538
146682
  let deleted = 0;
146539
146683
  for (const d of decisions) {
146540
146684
  if (d.action !== "delete") continue;
146541
- 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)) {
146542
146686
  log3(`[gc] \u8DF3\u8FC7 ${d.dir}\uFF1A\u6267\u884C\u524D\u590D\u67E5\u53D1\u73B0\u5DF2\u88AB\u5360\u7528`);
146543
146687
  continue;
146544
146688
  }
@@ -146589,12 +146733,12 @@ function startGcLoop(deps) {
146589
146733
  clearInterval(timer);
146590
146734
  };
146591
146735
  }
146592
- var import_node_fs8, import_node_path10;
146736
+ var import_node_fs8, import_node_path11;
146593
146737
  var init_gc_loop = __esm({
146594
146738
  "../adapters/src/_core/gc-loop.ts"() {
146595
146739
  "use strict";
146596
146740
  import_node_fs8 = __toESM(require("node:fs"), 1);
146597
- import_node_path10 = __toESM(require("node:path"), 1);
146741
+ import_node_path11 = __toESM(require("node:path"), 1);
146598
146742
  init_session_paths();
146599
146743
  init_workdir_gc();
146600
146744
  }
@@ -146630,14 +146774,14 @@ var init_src4 = __esm({
146630
146774
  });
146631
146775
 
146632
146776
  // ../connectors/src/connector-adapter.ts
146633
- 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;
146634
146778
  var init_connector_adapter = __esm({
146635
146779
  "../connectors/src/connector-adapter.ts"() {
146636
146780
  "use strict";
146637
- import_promises5 = require("node:fs/promises");
146781
+ import_promises6 = require("node:fs/promises");
146638
146782
  import_node_fs9 = require("node:fs");
146639
- import_node_os6 = require("node:os");
146640
- import_node_path11 = require("node:path");
146783
+ import_node_os7 = require("node:os");
146784
+ import_node_path12 = require("node:path");
146641
146785
  init_src4();
146642
146786
  init_base();
146643
146787
  init_prepare_job();
@@ -146660,11 +146804,11 @@ var init_connector_adapter = __esm({
146660
146804
  const wrapperPaths = [...prepared.job.wrapperPaths ?? [], ...sentinelWrappers];
146661
146805
  let extraEnv = { ...strippedEnv };
146662
146806
  if (wrapperPaths.length > 0) {
146663
- const wrapperBinDir = await (0, import_promises5.mkdtemp)((0, import_node_path11.join)((0, import_node_os6.tmpdir)(), "oasis-wrappers-"));
146664
- 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 });
146665
146809
  for (const wp of wrapperPaths) {
146666
- const linkName = (0, import_node_path11.basename)(wp).replace(/\.sh$/, "");
146667
- 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));
146668
146812
  }
146669
146813
  extraEnv = { ...extraEnv, PATH: [wrapperBinDir, extraEnv["PATH"] ?? ""].filter(Boolean).join(":") };
146670
146814
  log("[adapter]", ` connectors injected +${Date.now() - t0}ms wrappers=${wrapperPaths.length}`);
@@ -149451,12 +149595,12 @@ async function startOasisServer(opts) {
149451
149595
  const actorCtx = await opts.resolveActorContext(body.actorId).catch(() => null);
149452
149596
  if (actorCtx) {
149453
149597
  const { mkdtempSync: mkdtempSync5, mkdirSync: mkdirSync21, writeFileSync: writeFileSync13 } = await import("node:fs");
149454
- const { join: join30, dirname: dirname24 } = await import("node:path");
149455
- const { tmpdir: tmpdir8 } = await import("node:os");
149456
- 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-"));
149457
149601
  if (actorCtx.config?.prompt) {
149458
149602
  for (const [rel, content] of Object.entries(splitIdentityFiles2(actorCtx.config.prompt))) {
149459
- const file = join30(dir, rel);
149603
+ const file = join31(dir, rel);
149460
149604
  mkdirSync21(dirname24(file), { recursive: true });
149461
149605
  writeFileSync13(file, content);
149462
149606
  }
@@ -149468,12 +149612,12 @@ async function startOasisServer(opts) {
149468
149612
  const s2 = byId.get(id);
149469
149613
  return s2 ? `- **${s2.name}** (\`${s2.id}\`): ${s2.description}` : `- \`${id}\`\uFF08\u672A\u5728\u6280\u80FD\u5E93\u4E2D\uFF0C\u53EF\u80FD\u5DF2\u5378\u8F7D\uFF09`;
149470
149614
  });
149471
- 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"));
149472
149616
  }
149473
149617
  if (opts.materializeSkills) {
149474
149618
  const skillFiles = await opts.materializeSkills(body.actorId, "claude").catch(() => ({}));
149475
149619
  for (const [rel, content] of Object.entries(skillFiles)) {
149476
- const file = join30(dir, rel);
149620
+ const file = join31(dir, rel);
149477
149621
  mkdirSync21(dirname24(file), { recursive: true });
149478
149622
  writeFileSync13(file, content);
149479
149623
  }
@@ -149487,7 +149631,7 @@ async function startOasisServer(opts) {
149487
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";
149488
149632
  return `- **${c.name}** (\`${c.id}\`): ${statusNote} \xB7 ${modeNote}`;
149489
149633
  });
149490
- 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"));
149491
149635
  }
149492
149636
  spawnCwd = dir;
149493
149637
  }
@@ -149728,7 +149872,14 @@ async function startOasisServer(opts) {
149728
149872
  baseUrl: base,
149729
149873
  redirectPath: "/api/connectors/github/app/manifest-callback"
149730
149874
  });
149731
- 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
+ });
149732
149883
  res.writeHead(200, { "content-type": "application/json" }).end(JSON.stringify({
149733
149884
  state,
149734
149885
  formAction: manifestFormAction(kind === "org" ? { kind: "org", login: owner.login } : { kind: "user" }, state),
@@ -149791,6 +149942,14 @@ async function startOasisServer(opts) {
149791
149942
  }
149792
149943
  try {
149793
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
+ }
149794
149953
  const svc = opts.actors?.service;
149795
149954
  if (!svc) throw new Error("actors service unavailable");
149796
149955
  const scope = pending.actorId ? { scope: "personal", actorId: pending.actorId } : { scope: "global" };
@@ -150446,9 +150605,11 @@ var init_memory_registry_store = __esm({
150446
150605
  // key: ${runtimeId}::${modelId}
150447
150606
  connectors = /* @__PURE__ */ new Map();
150448
150607
  variables = /* @__PURE__ */ new Map();
150449
- // key = key::actorId
150450
- _varKey(key, actorId) {
150451
- 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 ?? ""}`;
150452
150613
  }
150453
150614
  skillCatalog = /* @__PURE__ */ new Map();
150454
150615
  installedSkills = /* @__PURE__ */ new Map();
@@ -150581,7 +150742,7 @@ var init_memory_registry_store = __esm({
150581
150742
  this.skillFiles.delete(skillId);
150582
150743
  }
150583
150744
  async putVariable(v2) {
150584
- this.variables.set(this._varKey(v2.key, v2.actorId), { ...v2 });
150745
+ this.variables.set(this._varKey(v2.key, v2.actorId, v2.projectId), { ...v2 });
150585
150746
  }
150586
150747
  async patchVariableValue(key, actorId, valueEncrypted, updatedAt) {
150587
150748
  const mk = this._varKey(key, actorId);
@@ -150596,8 +150757,11 @@ var init_memory_registry_store = __esm({
150596
150757
  if (actorId !== void 0) return all.filter((v2) => v2.scope === "global" || v2.actorId === actorId).map((v2) => ({ ...v2 }));
150597
150758
  return all.map((v2) => ({ ...v2 }));
150598
150759
  }
150599
- async deleteVariable(key, actorId) {
150600
- 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));
150601
150765
  }
150602
150766
  };
150603
150767
  }
@@ -154721,6 +154885,10 @@ async function sweepSilentSessions(deps) {
154721
154885
  continue;
154722
154886
  }
154723
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
+ }
154724
154892
  const runtime = { hasInFlight: true, lastSessionEventAt: live.lastEventAt, openToolCall: live.openToolCall };
154725
154893
  if (!isRuntimeSilent(runtime, deps.now, deps.silentThresholdMs)) continue;
154726
154894
  const ok = await deps.kill(s2.jobKey).catch(() => false);
@@ -155420,6 +155588,73 @@ var init_recovery_plan = __esm({
155420
155588
  }
155421
155589
  });
155422
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
+
155423
155658
  // ../server/src/domains/trace/sink.ts
155424
155659
  function deriveMessage(payload) {
155425
155660
  if (payload && typeof payload === "object") {
@@ -155429,11 +155664,10 @@ function deriveMessage(payload) {
155429
155664
  }
155430
155665
  return void 0;
155431
155666
  }
155432
- function trajectoryEventToRunEvent(e, seq) {
155667
+ function trajectoryEventToRunEvent(e) {
155433
155668
  const map = KIND_MAP[e.kind] ?? { eventType: "progress", stream: "runtime" };
155434
155669
  const message = deriveMessage(e.payload);
155435
155670
  return {
155436
- seq,
155437
155671
  eventType: map.eventType,
155438
155672
  stream: map.stream,
155439
155673
  ...message !== void 0 ? { message } : {},
@@ -155512,6 +155746,7 @@ var KIND_MAP, MODEL_TEXT_LIMIT, TOOL_PREVIEW_LIMIT, PROGRESS_TOUCH_THROTTLE_MS,
155512
155746
  var init_sink = __esm({
155513
155747
  "../server/src/domains/trace/sink.ts"() {
155514
155748
  "use strict";
155749
+ init_resilient_appender();
155515
155750
  KIND_MAP = {
155516
155751
  message: { eventType: "model.output.completed", stream: "model" },
155517
155752
  thought: { eventType: "model.output.delta", stream: "model" },
@@ -155527,12 +155762,23 @@ var init_sink = __esm({
155527
155762
  store;
155528
155763
  runtimeKind;
155529
155764
  onError;
155765
+ health;
155530
155766
  sessions = /* @__PURE__ */ new Map();
155531
155767
  constructor(opts) {
155532
155768
  this.store = opts.store;
155533
155769
  this.runtimeKind = opts.runtimeKind ?? "custom";
155534
155770
  this.onError = opts.onError ?? (() => {
155535
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
+ });
155536
155782
  }
155537
155783
  runtimeKindFor(session) {
155538
155784
  if (this.runtimeKind !== "auto") return this.runtimeKind;
@@ -155554,7 +155800,7 @@ var init_sink = __esm({
155554
155800
  begin(session, _bundleFiles) {
155555
155801
  const st = {
155556
155802
  runId: session.runId,
155557
- seq: 0,
155803
+ appender: this.makeAppender(session.runId),
155558
155804
  chain: Promise.resolve(),
155559
155805
  openTools: /* @__PURE__ */ new Map(),
155560
155806
  // runtimeSessionId:运行时自己的会话号(= 会话工作目录名,也是 --resume 的键)。落账后
@@ -155588,8 +155834,7 @@ var init_sink = __esm({
155588
155834
  relation: "triggered_by",
155589
155835
  createdAt: session.startedAt
155590
155836
  });
155591
- await this.store.appendEvents(st.runId, [{
155592
- seq: ++st.seq,
155837
+ await st.appender.append([{
155593
155838
  eventType: "run.started",
155594
155839
  stream: "runtime",
155595
155840
  message: `${session.action} ${session.artifactId}`
@@ -155599,7 +155844,7 @@ var init_sink = __esm({
155599
155844
  recover(session) {
155600
155845
  const st = {
155601
155846
  runId: session.runId,
155602
- seq: 0,
155847
+ appender: this.makeAppender(session.runId),
155603
155848
  chain: Promise.resolve(),
155604
155849
  openTools: /* @__PURE__ */ new Map(),
155605
155850
  baseMetadata: { bundleManifest: session.bundleManifest, recovered: true },
@@ -155626,11 +155871,10 @@ var init_sink = __esm({
155626
155871
  st.baseMetadata = { ...asMetadata(existing.metadata) ?? st.baseMetadata, recovered: true };
155627
155872
  }
155628
155873
  const events = await this.store.listEvents(session.runId, {});
155629
- 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));
155630
155875
  const recoveredAt = session.recoveredAt ?? (/* @__PURE__ */ new Date()).toISOString();
155631
155876
  const interruptedAt = session.interruptedAt ?? recoveredAt;
155632
- await this.store.appendEvents(st.runId, [{
155633
- seq: ++st.seq,
155877
+ await st.appender.append([{
155634
155878
  eventType: "progress",
155635
155879
  stream: "system",
155636
155880
  level: "warn",
@@ -155655,11 +155899,11 @@ var init_sink = __esm({
155655
155899
  const ms = Date.parse(event.ts);
155656
155900
  if (!Number.isNaN(ms) && ms - (st.lastProgressTouchMs ?? 0) >= PROGRESS_TOUCH_THROTTLE_MS) {
155657
155901
  st.lastProgressTouchMs = ms;
155658
- await this.store.updateRun(st.runId, { lastProgressAt: event.ts });
155902
+ await st.appender.update({ lastProgressAt: event.ts });
155659
155903
  }
155660
155904
  return;
155661
155905
  }
155662
- await this.store.appendEvents(st.runId, [trajectoryEventToRunEvent(event, ++st.seq)]);
155906
+ await st.appender.append([trajectoryEventToRunEvent(event)]);
155663
155907
  await this.materializeToolCall(st, event);
155664
155908
  });
155665
155909
  }
@@ -155710,8 +155954,7 @@ var init_sink = __esm({
155710
155954
  const captured = this.sessions.get(sessionId);
155711
155955
  this.enqueue(sessionId, async (st) => {
155712
155956
  for (const ref2 of update.opRefs) {
155713
- await this.store.appendEvents(st.runId, [{
155714
- seq: ++st.seq,
155957
+ await st.appender.append([{
155715
155958
  eventType: "oplog.appended",
155716
155959
  stream: "oasis",
155717
155960
  message: ref2.kind,
@@ -155725,8 +155968,7 @@ var init_sink = __esm({
155725
155968
  createdAt: update.exit.at
155726
155969
  });
155727
155970
  }
155728
- await this.store.appendEvents(st.runId, [{
155729
- seq: ++st.seq,
155971
+ await st.appender.append([{
155730
155972
  eventType: "run.finished",
155731
155973
  stream: "runtime",
155732
155974
  message: `exit code=${update.exit.code ?? "killed"} ops=${update.opRefs.length}`
@@ -155760,6 +156002,7 @@ var init_sink = __esm({
155760
156002
  });
155761
156003
  if (captured) captured.chain = captured.chain.finally(() => {
155762
156004
  this.sessions.delete(sessionId);
156005
+ this.health?.forget(captured.runId);
155763
156006
  });
155764
156007
  }
155765
156008
  };
@@ -155821,27 +156064,26 @@ function wireRecoveredChatTurn(deps) {
155821
156064
  live.emit(rest);
155822
156065
  }
155823
156066
  deps.register();
155824
- let traceSeq = 0;
155825
- 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
+ });
155826
156073
  let traceChain = trace.listEvents(plan.runId, {}).then((events) => {
155827
- 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));
155828
156075
  }).catch((err) => {
155829
- traceEnabled = false;
155830
- 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)}`);
155831
156078
  });
155832
156079
  const enqueueTrace = (step) => {
155833
- if (!traceEnabled) return;
155834
- traceChain = traceChain.then(async () => {
155835
- if (traceEnabled) await step();
155836
- }).catch((err) => {
155837
- traceEnabled = false;
155838
- 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)}`);
155839
156082
  });
155840
156083
  };
155841
156084
  const recoveredAt = (/* @__PURE__ */ new Date()).toISOString();
155842
156085
  enqueueTrace(async () => {
155843
- await trace.appendEvents(plan.runId, [{
155844
- seq: ++traceSeq,
156086
+ await appender.append([{
155845
156087
  eventType: "progress",
155846
156088
  stream: "system",
155847
156089
  level: "warn",
@@ -155865,11 +156107,11 @@ function wireRecoveredChatTurn(deps) {
155865
156107
  const ms = Date.parse(event.ts);
155866
156108
  if (!Number.isNaN(ms) && ms - lastProgressTouchMs >= PROGRESS_TOUCH_THROTTLE_MS2) {
155867
156109
  lastProgressTouchMs = ms;
155868
- enqueueTrace(() => trace.updateRun(plan.runId, { lastProgressAt: event.ts }).then(() => void 0));
156110
+ enqueueTrace(() => appender.update({ lastProgressAt: event.ts }).then(() => void 0));
155869
156111
  }
155870
156112
  return;
155871
156113
  }
155872
- enqueueTrace(() => trace.appendEvents(plan.runId, [trajectoryEventToRunEvent(event, ++traceSeq)]).then(() => void 0));
156114
+ enqueueTrace(() => appender.append([trajectoryEventToRunEvent(event)]).then(() => void 0));
155873
156115
  if (event.kind === "message" && sawTextOutput) return;
155874
156116
  const part = chatPartFromTrajectoryEvent(event);
155875
156117
  if (part) {
@@ -155950,14 +156192,13 @@ function wireRecoveredChatTurn(deps) {
155950
156192
  live.finish(failed ? "error" : "done");
155951
156193
  deps.unregister();
155952
156194
  enqueueTrace(async () => {
155953
- await trace.appendEvents(plan.runId, [{
155954
- seq: ++traceSeq,
156195
+ await appender.append([{
155955
156196
  eventType: "run.finished",
155956
156197
  stream: "runtime",
155957
156198
  message: `exit code=${info.code ?? "killed"}`,
155958
156199
  startedAt: finishedAt
155959
156200
  }]);
155960
- await trace.updateRun(plan.runId, runTerminalPatch(info, plan.startedAt, finishedAt));
156201
+ await appender.update(runTerminalPatch(info, plan.startedAt, finishedAt));
155961
156202
  if (info.usage && trace.updateUsageAggregates) {
155962
156203
  const run = await trace.getRun(plan.runId);
155963
156204
  if (run) void trace.updateUsageAggregates(run).catch(() => void 0);
@@ -156054,17 +156295,17 @@ async function reconcileChatExitFrame(deps) {
156054
156295
  }).catch(() => void 0);
156055
156296
  try {
156056
156297
  const events = await deps.trace.listEvents(row.runId, {});
156057
- let traceSeq = events.reduce((n, e) => Math.max(n, e.seq), 0);
156058
- await deps.trace.appendEvents(row.runId, [
156059
- ...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)),
156060
156301
  {
156061
- seq: ++traceSeq,
156062
156302
  eventType: "run.finished",
156063
156303
  stream: "runtime",
156064
156304
  message: `exit code=${info.code ?? "killed"}\uFF08serve \u505C\u673A\u7A97\u53E3\u5185\u7ED3\u675F\uFF0C\u8282\u70B9 outbox \u91CD\u53D1\u5BF9\u8D26\uFF09`,
156065
156305
  startedAt: finishedAt
156066
156306
  }
156067
- ]);
156307
+ ];
156308
+ await deps.trace.appendEvents(row.runId, seqless.map((e, i) => ({ ...e, seq: baseSeq + i + 1 })));
156068
156309
  await deps.trace.updateRun(row.runId, runTerminalPatch(info, run.startedAt, finishedAt));
156069
156310
  if (info.usage && deps.trace.updateUsageAggregates) {
156070
156311
  const fresh = await deps.trace.getRun(row.runId);
@@ -156085,6 +156326,7 @@ var init_chat_recovery = __esm({
156085
156326
  import_node_crypto20 = require("node:crypto");
156086
156327
  init_chat_parts();
156087
156328
  init_sink();
156329
+ init_resilient_appender();
156088
156330
  asObj3 = (v2) => v2 && typeof v2 === "object" && !Array.isArray(v2) ? v2 : void 0;
156089
156331
  partsOf = (parts) => Array.isArray(parts) ? parts.filter((p2) => p2 && typeof p2 === "object" && typeof p2.type === "string") : [];
156090
156332
  maxPartSeq = (parts) => parts.reduce((n, p2) => Math.max(n, typeof p2.seq === "number" ? p2.seq : 0), 0);
@@ -156678,6 +156920,24 @@ var init_skill_fetcher = __esm({
156678
156920
  });
156679
156921
 
156680
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
+ }
156681
156941
  function computeEffectiveEntries(config2, connections, connectors) {
156682
156942
  const globalConnected = new Set(connectors.filter((c) => c.status === "connected").map((c) => c.id));
156683
156943
  const record6 = new Map(connections.map((c) => [c.connectorId, c.enabled]));
@@ -156709,6 +156969,7 @@ var init_service3 = __esm({
156709
156969
  init_skill_fetcher();
156710
156970
  init_identity();
156711
156971
  init_src5();
156972
+ init_src();
156712
156973
  REDACTED_MARKER = "[REDACTED]";
156713
156974
  ActorsService = class {
156714
156975
  constructor(opts) {
@@ -157362,8 +157623,11 @@ ${input.description}
157362
157623
  key: v2.key,
157363
157624
  scope: v2.scope,
157364
157625
  ...v2.actorId !== void 0 ? { actorId: v2.actorId } : {},
157626
+ ...v2.projectId !== void 0 ? { projectId: v2.projectId } : {},
157365
157627
  ...v2.connectorId !== void 0 ? { connectorId: v2.connectorId } : {},
157366
157628
  ...v2.overrides !== void 0 ? { overrides: v2.overrides } : {},
157629
+ // 缺省 "env"(存量行为不变,敏感项需显式选 "ref",ADR §4.4 / 待确认 3)
157630
+ deliveryMode: v2.deliveryMode ?? "env",
157367
157631
  valueEncrypted: v2.encrypted === false ? `plain:${v2.value}` : this.encrypt(v2.value),
157368
157632
  updatedAt: this.now()
157369
157633
  });
@@ -157399,8 +157663,11 @@ ${input.description}
157399
157663
  key: r.key,
157400
157664
  scope: r.scope,
157401
157665
  ...r.actorId !== void 0 ? { actorId: r.actorId } : {},
157666
+ ...r.projectId !== void 0 ? { projectId: r.projectId } : {},
157402
157667
  ...r.connectorId !== void 0 ? { connectorId: r.connectorId } : {},
157403
157668
  ...r.overrides !== void 0 ? { overrides: r.overrides } : {},
157669
+ deliveryMode: r.deliveryMode ?? "env",
157670
+ // 缺省视为 env(ADR §4.4;存量行读侧统一回填)
157404
157671
  encrypted: !isPlain,
157405
157672
  maskedValue: isPlain ? r.valueEncrypted.slice(6) : mask(this.decrypt(r.valueEncrypted)),
157406
157673
  updatedAt: r.updatedAt
@@ -157424,8 +157691,13 @@ ${input.description}
157424
157691
  }
157425
157692
  return this.revealVariable(key, void 0);
157426
157693
  }
157427
- deleteVariable(key, actorId) {
157428
- 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);
157429
157701
  }
157430
157702
  async resolveActorEnv(actorId) {
157431
157703
  const cfg = await this.opts.store.latestConfig(actorId);
@@ -157455,6 +157727,64 @@ ${input.description}
157455
157727
  }
157456
157728
  return env;
157457
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
+ }
157458
157788
  encrypt(plain) {
157459
157789
  const iv = (0, import_node_crypto22.randomBytes)(12);
157460
157790
  const cipher = (0, import_node_crypto22.createCipheriv)("aes-256-gcm", this.opts.variableKey, iv);
@@ -158463,7 +158793,7 @@ var init_skill_materializer = __esm({
158463
158793
 
158464
158794
  // ../server/src/governance/connector-skills.ts
158465
158795
  function connectorSkillsDir(dataDir, connectorId) {
158466
- return (0, import_node_path12.join)(dataDir, "connector-skills", connectorId);
158796
+ return (0, import_node_path13.join)(dataDir, "connector-skills", connectorId);
158467
158797
  }
158468
158798
  function parseConnectorSkillId(id) {
158469
158799
  if (!id.startsWith("connector:")) return null;
@@ -158473,17 +158803,17 @@ function parseConnectorSkillId(id) {
158473
158803
  return { connectorId: rest.slice(0, i), slug: rest.slice(i + 1) };
158474
158804
  }
158475
158805
  async function collect(root, dir, out) {
158476
- for (const e of await (0, import_promises6.readdir)(dir, { withFileTypes: true })) {
158477
- 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);
158478
158808
  if (e.isDirectory()) await collect(root, abs, out);
158479
- 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");
158480
158810
  }
158481
158811
  }
158482
158812
  async function readLocalPack(dataDir, connectorId) {
158483
158813
  const root = connectorSkillsDir(dataDir, connectorId);
158484
158814
  let slugs;
158485
158815
  try {
158486
- 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);
158487
158817
  } catch {
158488
158818
  return [];
158489
158819
  }
@@ -158491,7 +158821,7 @@ async function readLocalPack(dataDir, connectorId) {
158491
158821
  for (const slug6 of slugs) {
158492
158822
  const files = {};
158493
158823
  try {
158494
- 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);
158495
158825
  } catch {
158496
158826
  continue;
158497
158827
  }
@@ -158507,17 +158837,17 @@ async function readLocalPack(dataDir, connectorId) {
158507
158837
  async function writePack(dataDir, connectorId, pack) {
158508
158838
  const root = connectorSkillsDir(dataDir, connectorId);
158509
158839
  const tmp = `${root}.tmp`;
158510
- await (0, import_promises6.rm)(tmp, { recursive: true, force: true });
158840
+ await (0, import_promises7.rm)(tmp, { recursive: true, force: true });
158511
158841
  for (const s2 of pack) {
158512
158842
  for (const [rel, content] of Object.entries(s2.files)) {
158513
- const abs = (0, import_node_path12.join)(tmp, s2.slug, rel);
158514
- await (0, import_promises6.mkdir)((0, import_node_path12.dirname)(abs), { recursive: true });
158515
- 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");
158516
158846
  }
158517
158847
  }
158518
- await (0, import_promises6.rm)(root, { recursive: true, force: true });
158519
- await (0, import_promises6.mkdir)((0, import_node_path12.dirname)(root), { recursive: true });
158520
- 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);
158521
158851
  }
158522
158852
  function packMatches(local, remote) {
158523
158853
  if (local.length !== remote.length) return false;
@@ -158544,18 +158874,21 @@ async function syncConnectorSkills(args) {
158544
158874
  log3(`[connector-skills] ${args.connectorId}\uFF1A\u5DF2\u4ECE\u5382\u5546\u540C\u6B65 ${fresh.length} \u4E2A\u6280\u80FD`);
158545
158875
  return { skills: fresh, outcome: "fetched" };
158546
158876
  }
158547
- var import_promises6, import_node_path12, toId;
158877
+ var import_promises7, import_node_path13, toId;
158548
158878
  var init_connector_skills = __esm({
158549
158879
  "../server/src/governance/connector-skills.ts"() {
158550
158880
  "use strict";
158551
- import_promises6 = require("node:fs/promises");
158552
- import_node_path12 = require("node:path");
158881
+ import_promises7 = require("node:fs/promises");
158882
+ import_node_path13 = require("node:path");
158553
158883
  init_src5();
158554
158884
  toId = (connectorId, slug6) => `connector:${connectorId}/${slug6}`;
158555
158885
  }
158556
158886
  });
158557
158887
 
158558
158888
  // ../server/src/domains/actors/routes.ts
158889
+ function isKnownRevealSource(source) {
158890
+ return CREDENTIAL_REVEAL_SOURCES.has(source) || source.startsWith("agent-skill:");
158891
+ }
158559
158892
  function positionsFromPayload(body) {
158560
158893
  if (!body) return void 0;
158561
158894
  if (Object.prototype.hasOwnProperty.call(body, "roles")) {
@@ -158588,6 +158921,8 @@ function actorApiRecord(actor) {
158588
158921
  }
158589
158922
  function actorsDomain(opts) {
158590
158923
  const { resolveCtx } = opts;
158924
+ const revealLimits = opts.credentialRevealLimits ?? { perSession: 200, perSessionPerKey: 20 };
158925
+ const revealLimiter = new RevealRateLimiter(revealLimits.perSession, revealLimits.perSessionPerKey);
158591
158926
  return (router) => {
158592
158927
  router.get("/api/actors", async (req) => {
158593
158928
  const { service } = await resolveCtx(req.auth.companyId);
@@ -158827,11 +159162,23 @@ function actorsDomain(opts) {
158827
159162
  const { service } = await resolveCtx(req.auth.companyId);
158828
159163
  const b2 = req.body;
158829
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
+ }
158830
159175
  if (b2.value === void 0 || b2.value === "") return { status: 200, body: { ok: true } };
158831
159176
  await service.putVariable({
158832
159177
  key: b2.key,
158833
159178
  value: b2.value,
158834
- scope: b2.scope ?? "global",
159179
+ scope,
159180
+ ...scope === "project" ? { projectId: b2.projectId } : {},
159181
+ ...b2.deliveryMode !== void 0 ? { deliveryMode: b2.deliveryMode } : {},
158835
159182
  ...b2.connectorId !== void 0 ? { connectorId: b2.connectorId } : {},
158836
159183
  ...b2.overrides !== void 0 ? { overrides: b2.overrides } : {},
158837
159184
  ...b2.encrypted !== void 0 ? { encrypted: b2.encrypted } : {}
@@ -158840,7 +159187,12 @@ function actorsDomain(opts) {
158840
159187
  });
158841
159188
  router.delete("/api/variables/:key", async (req) => {
158842
159189
  const { service } = await resolveCtx(req.auth.companyId);
158843
- 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
+ );
158844
159196
  return { status: 200, body: { ok: true } };
158845
159197
  });
158846
159198
  router.get("/api/actors/:id/variables", async (req) => {
@@ -158852,6 +159204,9 @@ function actorsDomain(opts) {
158852
159204
  const actorId = req.params.id;
158853
159205
  const b2 = req.body;
158854
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
+ }
158855
159210
  if (b2.value === void 0 || b2.value === "") return { status: 200, body: { ok: true } };
158856
159211
  if (b2.connectorId === void 0) {
158857
159212
  const existing = await service.listVariables(actorId);
@@ -158869,6 +159224,7 @@ function actorsDomain(opts) {
158869
159224
  value: b2.value,
158870
159225
  actorId,
158871
159226
  scope: "personal",
159227
+ ...b2.deliveryMode !== void 0 ? { deliveryMode: b2.deliveryMode } : {},
158872
159228
  ...b2.connectorId !== void 0 ? { connectorId: b2.connectorId } : {},
158873
159229
  ...b2.overrides !== void 0 ? { overrides: b2.overrides } : {},
158874
159230
  ...b2.encrypted !== void 0 ? { encrypted: b2.encrypted } : {}
@@ -158880,6 +159236,51 @@ function actorsDomain(opts) {
158880
159236
  await service.deleteVariable(req.params.key, req.params.id);
158881
159237
  return { status: 200, body: { ok: true } };
158882
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
+ });
158883
159284
  router.get("/api/skills/market", async (req) => {
158884
159285
  const { service } = await resolveCtx(req.auth.companyId);
158885
159286
  const [catalog, installed] = await Promise.all([
@@ -159038,6 +159439,18 @@ function actorsDomain(opts) {
159038
159439
  const results = await opts.refreshConnectorSkills();
159039
159440
  return { status: 200, body: { results } };
159040
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
+ });
159041
159454
  router.get("/api/skill-cache/manifest", async (req) => {
159042
159455
  const { service } = await resolveCtx(req.auth.companyId);
159043
159456
  const builtins = opts.getBuiltinSkills?.() ?? [];
@@ -159316,12 +159729,15 @@ function actorsDomain(opts) {
159316
159729
  });
159317
159730
  };
159318
159731
  }
159732
+ var CREDENTIAL_REVEAL_SOURCES, RevealRateLimiter;
159319
159733
  var init_routes = __esm({
159320
159734
  "../server/src/domains/actors/routes.ts"() {
159321
159735
  "use strict";
159322
159736
  init_src();
159323
159737
  init_src2();
159324
159738
  init_router();
159739
+ init_src();
159740
+ init_src5();
159325
159741
  init_memory();
159326
159742
  init_skill_fetcher();
159327
159743
  init_stats();
@@ -159330,6 +159746,44 @@ var init_routes = __esm({
159330
159746
  init_image_upload();
159331
159747
  init_skill_materializer();
159332
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
+ };
159333
159787
  }
159334
159788
  });
159335
159789
 
@@ -159428,7 +159882,7 @@ function createActorsDomain(opts) {
159428
159882
  return {
159429
159883
  service: defaultCtx.service,
159430
159884
  resolveCtx,
159431
- 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 } : {} })
159432
159886
  };
159433
159887
  }
159434
159888
  var import_node_crypto24;
@@ -161857,10 +162311,10 @@ function nodesDomain(deps) {
161857
162311
  }
161858
162312
  }
161859
162313
  try {
161860
- 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));
161861
162315
  for (const rel of ["../../../../node-daemon/package.json", "../../../../../node-daemon/package.json"]) {
161862
162316
  try {
161863
- 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");
161864
162318
  const pkg = JSON.parse(raw);
161865
162319
  if (pkg.version) return { version: pkg.version, source: "monorepo" };
161866
162320
  } catch {
@@ -162172,14 +162626,14 @@ function nodesDomain(deps) {
162172
162626
  });
162173
162627
  };
162174
162628
  }
162175
- 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;
162176
162630
  var init_routes5 = __esm({
162177
162631
  "../server/src/domains/nodes/routes.ts"() {
162178
162632
  "use strict";
162179
162633
  import_node_crypto26 = require("node:crypto");
162180
162634
  import_node_fs11 = require("node:fs");
162181
162635
  import_node_url4 = require("node:url");
162182
- import_node_path13 = require("node:path");
162636
+ import_node_path14 = require("node:path");
162183
162637
  init_src4();
162184
162638
  init_connect_script();
162185
162639
  init_node_health();
@@ -163336,6 +163790,7 @@ function buildOrganizationUsageSummary(input) {
163336
163790
  const runtimeEmployees = /* @__PURE__ */ new Map();
163337
163791
  const runtimeInstanceKind = /* @__PURE__ */ new Map();
163338
163792
  const runtimeHostnames = input.runtimeHostnames ?? /* @__PURE__ */ new Map();
163793
+ const runtimeNodeNames = input.runtimeNodeNames ?? /* @__PURE__ */ new Map();
163339
163794
  for (const run of input.runs) {
163340
163795
  const usage = usageOf(run);
163341
163796
  if (!usage) {
@@ -163466,11 +163921,27 @@ function buildOrganizationUsageSummary(input) {
163466
163921
  (e) => e.share,
163467
163922
  (e, s2) => ({ ...e, share: s2 })
163468
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
+ }
163469
163940
  function resolveHostname(rtKey, kind) {
163470
163941
  const h = runtimeHostnames.get(rtKey);
163471
163942
  if (h) return h;
163472
- const parts = rtKey.split(":");
163473
- if (parts.length >= 3 && parts[0] === "runtime") return parts[1];
163943
+ const nodeId = parseNodeId(rtKey);
163944
+ if (nodeId) return nodeId;
163474
163945
  return kind;
163475
163946
  }
163476
163947
  const runtimeRows = normalizeShares(
@@ -163498,6 +163969,8 @@ function buildOrganizationUsageSummary(input) {
163498
163969
  );
163499
163970
  return {
163500
163971
  runtimeInstanceId: rtKey,
163972
+ nodeId: parseNodeId(rtKey),
163973
+ displayName: resolveDisplayName(rtKey, kind),
163501
163974
  hostname: resolveHostname(rtKey, kind),
163502
163975
  runtimeKind: kind,
163503
163976
  usage,
@@ -163535,12 +164008,12 @@ function nameKey(name) {
163535
164008
  function cleanName(name) {
163536
164009
  return name.trim().replace(/\s+/g, " ");
163537
164010
  }
163538
- 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;
163539
164012
  var init_tags = __esm({
163540
164013
  "../server/src/domains/collab/tags.ts"() {
163541
164014
  "use strict";
163542
164015
  import_node_fs12 = __toESM(require("node:fs"), 1);
163543
- import_node_path14 = __toESM(require("node:path"), 1);
164016
+ import_node_path15 = __toESM(require("node:path"), 1);
163544
164017
  import_node_crypto27 = __toESM(require("node:crypto"), 1);
163545
164018
  SEP = "::";
163546
164019
  keyOf = (companyId, id) => `${companyId}${SEP}${id}`;
@@ -163626,7 +164099,7 @@ var init_tags = __esm({
163626
164099
  }
163627
164100
  persist() {
163628
164101
  try {
163629
- 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 });
163630
164103
  const tmp = `${this.file}.tmp`;
163631
164104
  import_node_fs12.default.writeFileSync(tmp, JSON.stringify(Object.fromEntries(this.map), null, 2));
163632
164105
  import_node_fs12.default.renameSync(tmp, this.file);
@@ -163767,17 +164240,22 @@ function collabDomain(opts) {
163767
164240
  return { status: 400, body: { error: { code: "bad_request", message: "timeZone \u65E0\u6548" } } };
163768
164241
  }
163769
164242
  const { kernel } = await resolveCtx(req.auth.companyId);
163770
- const [runs, runtimeRecords] = await Promise.all([
164243
+ const [runs, runtimeRecords, nodeRecords] = await Promise.all([
163771
164244
  opts.trace.listUsageRuns({ from, to }),
163772
- opts.listRuntimes ? opts.listRuntimes().catch(() => []) : Promise.resolve([])
164245
+ opts.listRuntimes ? opts.listRuntimes().catch(() => []) : Promise.resolve([]),
164246
+ opts.listNodes ? opts.listNodes().catch(() => []) : Promise.resolve([])
163773
164247
  ]);
163774
164248
  const runtimeHostnames = /* @__PURE__ */ new Map();
163775
164249
  for (const rt of runtimeRecords) {
163776
164250
  if (rt.id && rt.hostname) runtimeHostnames.set(rt.id, rt.hostname);
163777
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
+ }
163778
164256
  return {
163779
164257
  status: 200,
163780
- body: buildOrganizationUsageSummary({ model: kernel.model, runs, bucketKind: bucket, timeZone, runtimeHostnames })
164258
+ body: buildOrganizationUsageSummary({ model: kernel.model, runs, bucketKind: bucket, timeZone, runtimeHostnames, runtimeNodeNames })
163781
164259
  };
163782
164260
  });
163783
164261
  router.get("/api/workorders", async (req) => {
@@ -164577,10 +165055,10 @@ function tokenType(p2) {
164577
165055
  function parseOtlpMetricsUsage(body) {
164578
165056
  const req = body;
164579
165057
  const out = [];
164580
- for (const rm4 of req.resourceMetrics ?? []) {
164581
- 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");
164582
165060
  if (!runId) continue;
164583
- const actorId = attrString(rm4.resource?.attributes, "oasis.actor_id");
165061
+ const actorId = attrString(rm5.resource?.attributes, "oasis.actor_id");
164584
165062
  let input = 0;
164585
165063
  let output = 0;
164586
165064
  let cacheRead = 0;
@@ -164588,7 +165066,7 @@ function parseOtlpMetricsUsage(body) {
164588
165066
  let costUsd = 0;
164589
165067
  let sawToken = false;
164590
165068
  let sawCost = false;
164591
- for (const sm of rm4.scopeMetrics ?? []) {
165069
+ for (const sm of rm5.scopeMetrics ?? []) {
164592
165070
  for (const m2 of sm.metrics ?? []) {
164593
165071
  const name = (m2.name ?? "").toLowerCase();
164594
165072
  const points = m2.sum?.dataPoints ?? m2.gauge?.dataPoints ?? [];
@@ -164964,6 +165442,30 @@ var init_routes6 = __esm({
164964
165442
  }
164965
165443
  });
164966
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
+
164967
165469
  // ../server/src/domains/trace/index.ts
164968
165470
  function createTraceDomain(opts) {
164969
165471
  const service = new TraceService({ store: opts.store });
@@ -164978,6 +165480,8 @@ var init_trace2 = __esm({
164978
165480
  init_routes6();
164979
165481
  init_sink();
164980
165482
  init_chat_parts();
165483
+ init_trace_health();
165484
+ init_resilient_appender();
164981
165485
  }
164982
165486
  });
164983
165487
 
@@ -167049,12 +167553,12 @@ function normalize(patch) {
167049
167553
  ...reviewRoles !== void 0 ? { reviewRoles: [...new Set(reviewRoles.map((r) => r.trim()).filter(Boolean))] } : {}
167050
167554
  };
167051
167555
  }
167052
- 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;
167053
167557
  var init_overrides = __esm({
167054
167558
  "../server/src/domains/playbooks/overrides.ts"() {
167055
167559
  "use strict";
167056
167560
  import_node_fs13 = __toESM(require("node:fs"), 1);
167057
- import_node_path15 = __toESM(require("node:path"), 1);
167561
+ import_node_path16 = __toESM(require("node:path"), 1);
167058
167562
  SEP2 = "::";
167059
167563
  keyOf2 = (companyId, ref2, nodeKey) => `${companyId}${SEP2}${ref2}${SEP2}${nodeKey}`;
167060
167564
  prefixOf2 = (companyId, ref2) => `${companyId}${SEP2}${ref2}${SEP2}`;
@@ -167093,7 +167597,7 @@ var init_overrides = __esm({
167093
167597
  }
167094
167598
  persist() {
167095
167599
  try {
167096
- 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 });
167097
167601
  const tmp = `${this.file}.tmp`;
167098
167602
  import_node_fs13.default.writeFileSync(tmp, JSON.stringify(Object.fromEntries(this.map), null, 2));
167099
167603
  import_node_fs13.default.renameSync(tmp, this.file);
@@ -167532,12 +168036,12 @@ var init_daemon_adapter = __esm({
167532
168036
  });
167533
168037
 
167534
168038
  // ../server/src/side-map.ts
167535
- var import_node_fs14, import_node_path16, FileSideMap;
168039
+ var import_node_fs14, import_node_path17, FileSideMap;
167536
168040
  var init_side_map = __esm({
167537
168041
  "../server/src/side-map.ts"() {
167538
168042
  "use strict";
167539
168043
  import_node_fs14 = __toESM(require("node:fs"), 1);
167540
- import_node_path16 = __toESM(require("node:path"), 1);
168044
+ import_node_path17 = __toESM(require("node:path"), 1);
167541
168045
  FileSideMap = class {
167542
168046
  constructor(file) {
167543
168047
  this.file = file;
@@ -167564,7 +168068,7 @@ var init_side_map = __esm({
167564
168068
  }
167565
168069
  persist() {
167566
168070
  try {
167567
- 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 });
167568
168072
  const tmp = `${this.file}.tmp`;
167569
168073
  import_node_fs14.default.writeFileSync(tmp, JSON.stringify(Object.fromEntries(this.map), null, 2));
167570
168074
  import_node_fs14.default.renameSync(tmp, this.file);
@@ -169020,26 +169524,26 @@ ${ctx.nodeFault}
169020
169524
 
169021
169525
  // ../server/src/governance/builtin-skills.ts
169022
169526
  async function collectDir(root, dir, out) {
169023
- const entries = await (0, import_promises7.readdir)(dir, { withFileTypes: true });
169527
+ const entries = await (0, import_promises8.readdir)(dir, { withFileTypes: true });
169024
169528
  for (const e of entries) {
169025
- const abs = (0, import_node_path17.join)(dir, e.name);
169529
+ const abs = (0, import_node_path18.join)(dir, e.name);
169026
169530
  if (e.isDirectory()) {
169027
169531
  await collectDir(root, abs, out);
169028
169532
  } else if (e.isFile()) {
169029
- const info = await (0, import_promises7.stat)(abs);
169533
+ const info = await (0, import_promises8.stat)(abs);
169030
169534
  if (info.size > MAX_FILE_BYTES3) {
169031
169535
  console.warn(`[builtin-skills] skip oversized file (${info.size}B): ${abs}`);
169032
169536
  continue;
169033
169537
  }
169034
- const rel = (0, import_node_path17.relative)(root, abs).split(/[\\/]/).join("/");
169035
- 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");
169036
169540
  }
169037
169541
  }
169038
169542
  }
169039
169543
  async function loadBuiltinSkills(skillsDir) {
169040
169544
  let dirents;
169041
169545
  try {
169042
- dirents = await (0, import_promises7.readdir)(skillsDir, { withFileTypes: true });
169546
+ dirents = await (0, import_promises8.readdir)(skillsDir, { withFileTypes: true });
169043
169547
  } catch {
169044
169548
  return [];
169045
169549
  }
@@ -169047,7 +169551,7 @@ async function loadBuiltinSkills(skillsDir) {
169047
169551
  for (const d of dirents) {
169048
169552
  if (!d.isDirectory()) continue;
169049
169553
  const slug6 = d.name;
169050
- const skillDir = (0, import_node_path17.join)(skillsDir, slug6);
169554
+ const skillDir = (0, import_node_path18.join)(skillsDir, slug6);
169051
169555
  const files = {};
169052
169556
  try {
169053
169557
  await collectDir(skillDir, skillDir, files);
@@ -169075,12 +169579,12 @@ function materializeBuiltinSkills(builtins, skillsDirPrefix) {
169075
169579
  }
169076
169580
  return out;
169077
169581
  }
169078
- var import_promises7, import_node_path17, MAX_FILE_BYTES3;
169582
+ var import_promises8, import_node_path18, MAX_FILE_BYTES3;
169079
169583
  var init_builtin_skills = __esm({
169080
169584
  "../server/src/governance/builtin-skills.ts"() {
169081
169585
  "use strict";
169082
- import_promises7 = require("node:fs/promises");
169083
- import_node_path17 = require("node:path");
169586
+ import_promises8 = require("node:fs/promises");
169587
+ import_node_path18 = require("node:path");
169084
169588
  init_skill_fetcher();
169085
169589
  init_skill_materializer();
169086
169590
  MAX_FILE_BYTES3 = 1 << 20;
@@ -174572,6 +175076,21 @@ var init_postgres = __esm({
174572
175076
  });
174573
175077
 
174574
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
+ }
174575
175094
  var ident4, PostgresRegistryStore, rowToActor, rowToConfig;
174576
175095
  var init_postgres_registry = __esm({
174577
175096
  "../storage/src/postgres-registry.ts"() {
@@ -174703,9 +175222,21 @@ var init_postgres_registry = __esm({
174703
175222
  value_encrypted text NOT NULL,
174704
175223
  updated_at timestamptz NOT NULL
174705
175224
  )`);
174706
- await pool.query(`CREATE UNIQUE INDEX IF NOT EXISTS "${s2}_variables_key_actor_uq" ON "${s2}".variables (key, COALESCE(actor_id, ''))`);
174707
175225
  await pool.query(`ALTER TABLE IF EXISTS "${s2}".variables ADD COLUMN IF NOT EXISTS overrides text`);
174708
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, ''))`);
174709
175240
  await pool.query(`
174710
175241
  CREATE TABLE IF NOT EXISTS "${s2}".runtime_configs (
174711
175242
  runtime_id text PRIMARY KEY,
@@ -175149,27 +175680,36 @@ var init_postgres_registry = __esm({
175149
175680
  }
175150
175681
  /* ---------- 变量 ---------- */
175151
175682
  async putVariable(v2) {
175152
- const aid = v2.actorId ?? null;
175153
175683
  await this.pool.query(
175154
- `INSERT INTO ${this.s}.variables (key, scope, actor_id, connector_id, overrides, value_encrypted, updated_at)
175155
- VALUES ($1,$2,$3,$4,$5,$6,$7)
175156
- ON CONFLICT (key, COALESCE(actor_id, '')) DO UPDATE
175157
- SET scope=$2, actor_id=$3, connector_id=$4, overrides=$5, value_encrypted=$6, updated_at=$7`,
175158
- [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
+ ]
175159
175699
  );
175160
175700
  }
175161
175701
  async patchVariableValue(key, actorId, valueEncrypted, updatedAt) {
175162
175702
  const aid = actorId ?? null;
175163
175703
  await this.pool.query(
175164
175704
  `UPDATE ${this.s}.variables SET value_encrypted=$3, updated_at=$4
175165
- 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,'')=''`,
175166
175706
  [key, aid, valueEncrypted, updatedAt]
175167
175707
  );
175168
175708
  }
175169
175709
  async getVariableCiphertext(key, actorId) {
175170
175710
  const aid = actorId ?? null;
175171
175711
  const r = await this.pool.query(
175172
- `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,'')=''`,
175173
175713
  [key, aid]
175174
175714
  );
175175
175715
  return r.rows[0]?.value_encrypted ?? null;
@@ -175184,21 +175724,21 @@ var init_postgres_registry = __esm({
175184
175724
  } else {
175185
175725
  r = await this.pool.query(`SELECT * FROM ${this.s}.variables ORDER BY key`);
175186
175726
  }
175187
- return r.rows.map((row) => ({
175188
- key: row.key,
175189
- scope: row.scope === "connector" ? "global" : row.scope,
175190
- ...row.actor_id !== null && row.actor_id !== void 0 ? { actorId: row.actor_id } : {},
175191
- ...row.connector_id !== null ? { connectorId: row.connector_id } : {},
175192
- ...row.overrides !== null ? { overrides: row.overrides } : {},
175193
- valueEncrypted: row.value_encrypted,
175194
- updatedAt: new Date(row.updated_at).toISOString()
175195
- }));
175727
+ return r.rows.map((row) => rowToVariable(row));
175196
175728
  }
175197
- async deleteVariable(key, actorId) {
175198
- 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) {
175199
175737
  await this.pool.query(
175200
- `DELETE FROM ${this.s}.variables WHERE key=$1 AND COALESCE(actor_id,'')=COALESCE($2::text,'')`,
175201
- [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]
175202
175742
  );
175203
175743
  }
175204
175744
  };
@@ -176017,6 +176557,7 @@ var init_postgres_projects = __esm({
176017
176557
  work_order_id text PRIMARY KEY,
176018
176558
  project_id text NOT NULL
176019
176559
  )`);
176560
+ await pool.query(`ALTER TABLE IF EXISTS "${s2}".workspace_bindings ADD COLUMN IF NOT EXISTS environment text`);
176020
176561
  await pool.query(`
176021
176562
  CREATE TABLE IF NOT EXISTS "${s2}".workspace_dispatch_hold (
176022
176563
  work_order_id text PRIMARY KEY
@@ -176886,6 +177427,38 @@ var init_postgres_control_plane = __esm({
176886
177427
  }
176887
177428
  });
176888
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
+
176889
177462
  // ../storage/src/postgres-trace.ts
176890
177463
  var ident9, PostgresTraceStore, rowToRun, rowToEvent2, rowToToolCall, rowToArtifact2;
176891
177464
  var init_postgres_trace = __esm({
@@ -176893,6 +177466,7 @@ var init_postgres_trace = __esm({
176893
177466
  "use strict";
176894
177467
  init_esm2();
176895
177468
  init_src();
177469
+ init_pg_sanitize();
176896
177470
  ident9 = (s2) => {
176897
177471
  if (!/^[a-z_][a-z0-9_]*$/.test(s2)) throw new Error(`invalid schema name: ${s2}`);
176898
177472
  return s2;
@@ -177153,12 +177727,12 @@ var init_postgres_trace = __esm({
177153
177727
  run.exitCode,
177154
177728
  run.exitReason,
177155
177729
  run.errorCode,
177156
- run.errorMessage,
177730
+ scrubPgString(run.errorMessage),
177157
177731
  run.usage === null ? null : JSON.stringify(run.usage),
177158
177732
  run.effectiveModel,
177159
177733
  run.modelSource,
177160
177734
  run.transcriptRef,
177161
- JSON.stringify(run.metadata),
177735
+ JSON.stringify(scrubPgJson(run.metadata)),
177162
177736
  run.createdAt,
177163
177737
  run.updatedAt
177164
177738
  ]
@@ -177201,7 +177775,7 @@ var init_postgres_trace = __esm({
177201
177775
  for (const [key, spec] of Object.entries(_PostgresTraceStore.RUN_COLS)) {
177202
177776
  if (!(key in patch)) continue;
177203
177777
  const v2 = patch[key];
177204
- 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));
177205
177779
  sets.push(spec.json ? `${spec.col}=$${args.length}::jsonb` : `${spec.col}=$${args.length}`);
177206
177780
  }
177207
177781
  args.push(this.now());
@@ -177340,8 +177914,8 @@ var init_postgres_trace = __esm({
177340
177914
  stream,
177341
177915
  level,
177342
177916
  inp.color ?? null,
177343
- inp.message ?? null,
177344
- JSON.stringify(payload),
177917
+ scrubPgString(inp.message ?? null),
177918
+ JSON.stringify(scrubPgJson(payload)),
177345
177919
  inp.blobRef ?? null,
177346
177920
  inp.spanId ?? null,
177347
177921
  inp.parentSpanId ?? null,
@@ -177416,11 +177990,11 @@ var init_postgres_trace = __esm({
177416
177990
  c.durationMs ?? null,
177417
177991
  c.inputRef ?? null,
177418
177992
  c.outputRef ?? null,
177419
- c.inputPreview ?? null,
177420
- c.outputPreview ?? null,
177993
+ scrubPgString(c.inputPreview ?? null),
177994
+ scrubPgString(c.outputPreview ?? null),
177421
177995
  c.errorCode ?? null,
177422
- c.errorMessage ?? null,
177423
- JSON.stringify(c.metadata ?? {})
177996
+ scrubPgString(c.errorMessage ?? null),
177997
+ JSON.stringify(scrubPgJson(c.metadata ?? {}))
177424
177998
  ]
177425
177999
  );
177426
178000
  }
@@ -177465,12 +178039,12 @@ var init_postgres_trace = __esm({
177465
178039
  a.id,
177466
178040
  a.runId,
177467
178041
  a.kind,
177468
- a.name,
178042
+ scrubPgString(a.name),
177469
178043
  a.contentType ?? null,
177470
178044
  a.size ?? null,
177471
178045
  a.blobRef,
177472
178046
  a.createdAt,
177473
- JSON.stringify(a.metadata ?? {})
178047
+ JSON.stringify(scrubPgJson(a.metadata ?? {}))
177474
178048
  ]
177475
178049
  );
177476
178050
  }
@@ -179182,7 +179756,10 @@ __export(src_exports, {
179182
179756
  PostgresTraceStore: () => PostgresTraceStore,
179183
179757
  PostgresTypeRegistryStore: () => PostgresTypeRegistryStore,
179184
179758
  backfillTypeRegistryFromFile: () => backfillTypeRegistryFromFile,
179185
- createPgPool: () => createPgPool
179759
+ createPgPool: () => createPgPool,
179760
+ hasPgUnstorable: () => hasPgUnstorable,
179761
+ scrubPgJson: () => scrubPgJson,
179762
+ scrubPgString: () => scrubPgString
179186
179763
  });
179187
179764
  var init_src8 = __esm({
179188
179765
  "../storage/src/index.ts"() {
@@ -179195,6 +179772,7 @@ var init_src8 = __esm({
179195
179772
  init_pool();
179196
179773
  init_postgres_control_plane();
179197
179774
  init_postgres_trace();
179775
+ init_pg_sanitize();
179198
179776
  init_postgres_automations();
179199
179777
  init_postgres_chat_sessions();
179200
179778
  init_postgres_nodes();
@@ -179594,15 +180172,18 @@ function splitIdentityFiles3(prompt) {
179594
180172
  }
179595
180173
  return out;
179596
180174
  }
179597
- async function buildActorProvision(service, actorId) {
179598
- const raw = await service.resolveActorEnv(actorId);
179599
- 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;
179600
180179
  const provision = await buildConnectorProvision({
179601
180180
  vars: raw,
179602
180181
  actorName: raw["GIT_AUTHOR_NAME"] ?? actorId.split(":").pop() ?? "oasis-agent"
179603
180182
  });
180183
+ const env = { ...raw };
179604
180184
  for (const k2 of provision.sensitiveVars) delete env[k2];
179605
180185
  for (const [k2, v2] of Object.entries(provision.envOverrides)) env[k2] = v2;
180186
+ applyRefDelivery(env, entries);
179606
180187
  return {
179607
180188
  env,
179608
180189
  wrapperPaths: [oasisWrapperScript(), ...provision.wrapperPaths],
@@ -179828,6 +180409,45 @@ async function startServe(opts) {
179828
180409
  });
179829
180410
  for (const l of registryListeners) l({ kind: record6.kind, actor: record6.actor, target: record6.target, timestamp: record6.timestamp });
179830
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
+ };
179831
180451
  await syncRegistryRolesToKernel(registryStore, kernel);
179832
180452
  let builtinSkills = [];
179833
180453
  let connectorSkills = [];
@@ -179844,6 +180464,13 @@ async function startServe(opts) {
179844
180464
  blobs: assets,
179845
180465
  trace: () => traceStoreForActors,
179846
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,
179847
180474
  // CO-302 数据面隔离(actors 域试点):按当前公司取其引擎,用各自 registry 服务该域请求。
179848
180475
  // engineRouter 在下方(行 ~411)以 const 声明;此箭头只在请求时调用,那时已初始化,闭包引用合法
179849
180476
  // (TDZ 只在构造时访问才报错,这里不访问)。默认公司命中现有单实例(registry===registryStore),
@@ -179974,7 +180601,13 @@ async function startServe(opts) {
179974
180601
  traceStoreForActors = traceStore;
179975
180602
  const trajReader = new FsTrajectorySink(opts.dir);
179976
180603
  const trace = createTraceDomain({ store: traceStore, readBundleFile: (id, rel) => trajReader.readBundleFile(id, rel) });
179977
- 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
+ });
179978
180611
  const automationStore = pgPool ? await PostgresAutomationStore.open(pgPool, pgSchema) : new MemoryAutomationStore();
179979
180612
  console.log(`[serve] \u81EA\u52A8\u5316\u4F53\u7CFB\uFF1A${pgPool ? `Postgres schema=${pgSchema}` : "\u5185\u5B58 dev store"}`);
179980
180613
  const automations = createAutomationsDomain({ store: automationStore, kernel });
@@ -180438,6 +181071,8 @@ async function startServe(opts) {
180438
181071
  // 组织汇总用——从 node_runtimes 取 hostname,解析运行时实例标签(kind @ hostname)
180439
181072
  listRuntimes: (nodeId) => nodeStore.listRuntimes(nodeId),
180440
181073
  dispatchJournal: () => journalRing.slice(-200),
181074
+ // 组织汇总用——从 nodes 取 name(用户友好名),优先于 hostname 作为展示标签
181075
+ listNodes: () => nodeStore.listNodes(),
180441
181076
  // 诊断 92ac8f18-83e5fde6 §④ D-2:按**请求公司**取其 dispatcher 的 queued/backoff 信号。
180442
181077
  // CO-302 每家公司各有一套 dispatcher(dispatchers 以 companyId 为键)——固定读默认公司会让
180443
181078
  // 非默认公司请求拿到错的/空的信号(code-review 打回)。该公司未起 dispatcher(老部署无 --dispatch、
@@ -180577,10 +181212,14 @@ async function startServe(opts) {
180577
181212
  const traceRunId = `chat-run:${(0, import_node_crypto37.randomUUID)()}`;
180578
181213
  const artifactId = `artifact:chat:${(0, import_node_crypto37.randomUUID)()}`;
180579
181214
  const traceStartedAt = (/* @__PURE__ */ new Date()).toISOString();
180580
- let traceSeq = 0;
180581
181215
  let lastProgressTouchMs = 0;
180582
181216
  const PROGRESS_TOUCH_THROTTLE_MS2 = 2e4;
180583
- 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
+ });
180584
181223
  const analyzedRunId = chatSessionId ? (await chatSessionStore.getSession(chatSessionId).catch(() => null))?.analyzedRunId ?? void 0 : void 0;
180585
181224
  let traceChain = traceStore.createRun({
180586
181225
  id: traceRunId,
@@ -180601,16 +181240,14 @@ async function startServe(opts) {
180601
181240
  ...analyzedRunId ? { analyzedRunId } : {}
180602
181241
  }
180603
181242
  }).then(async () => {
180604
- await traceStore.appendEvents(traceRunId, [
181243
+ await traceAppender.append([
180605
181244
  {
180606
- seq: ++traceSeq,
180607
181245
  eventType: "run.started",
180608
181246
  stream: "runtime",
180609
181247
  message: `chat ${actorId}`,
180610
181248
  startedAt: traceStartedAt
180611
181249
  },
180612
181250
  {
180613
- seq: ++traceSeq,
180614
181251
  eventType: "input.message",
180615
181252
  stream: "model",
180616
181253
  actorId,
@@ -180620,16 +181257,12 @@ async function startServe(opts) {
180620
181257
  }
180621
181258
  ]);
180622
181259
  }).catch((err) => {
180623
- traceEnabled = false;
180624
- console.warn(`[trace] chat run create failed: ${String(err)}`);
181260
+ traceHealth.markUnhealthy(traceRunId);
181261
+ console.warn(`[trace] chat run create failed (continuing): ${String(err)}`);
180625
181262
  });
180626
181263
  const enqueueTrace = (step) => {
180627
- if (!traceEnabled) return;
180628
- traceChain = traceChain.then(async () => {
180629
- if (traceEnabled) await step();
180630
- }).catch((err) => {
180631
- traceEnabled = false;
180632
- 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)}`);
180633
181266
  });
180634
181267
  };
180635
181268
  const provision = await buildActorProvision(actorService, actorId);
@@ -180776,7 +181409,7 @@ async function startServe(opts) {
180776
181409
  const run = await traceStore.getRun(traceRunId);
180777
181410
  if (!run) return;
180778
181411
  const base = run.metadata && typeof run.metadata === "object" && !Array.isArray(run.metadata) ? run.metadata : {};
180779
- await traceStore.updateRun(traceRunId, {
181412
+ await traceAppender.update({
180780
181413
  metadata: { ...base, dispatchId: handle.id },
180781
181414
  // 恢复路径拿不到 dispatchChat 闭包里的 fallback 解析结果,开跑时先写进账本;退出帧若上报
180782
181415
  // 更精确 model 仍会覆盖为 runtime 来源。
@@ -180798,23 +181431,23 @@ async function startServe(opts) {
180798
181431
  const ms = Date.parse(event.ts);
180799
181432
  if (!Number.isNaN(ms) && ms - lastProgressTouchMs >= PROGRESS_TOUCH_THROTTLE_MS2) {
180800
181433
  lastProgressTouchMs = ms;
180801
- enqueueTrace(() => traceStore.updateRun(traceRunId, { lastProgressAt: event.ts }).then(() => void 0));
181434
+ enqueueTrace(() => traceAppender.update({ lastProgressAt: event.ts }).then(() => void 0));
180802
181435
  }
180803
181436
  return;
180804
181437
  }
180805
- enqueueTrace(() => traceStore.appendEvents(traceRunId, [trajectoryEventToRunEvent(event, ++traceSeq)]).then(() => void 0));
181438
+ enqueueTrace(() => traceAppender.append([trajectoryEventToRunEvent(event)]).then(() => void 0));
180806
181439
  });
180807
181440
  let capturedNativeSessionId;
180808
181441
  const done = new Promise((resolve8, reject) => {
180809
181442
  handle.onExit((info) => {
180810
181443
  chatLiveSessions.delete(chatJobKey);
181444
+ traceHealth.forget(traceRunId);
180811
181445
  void provision.cleanup();
180812
181446
  const endedAt = (/* @__PURE__ */ new Date()).toISOString();
180813
181447
  const durationMs = Date.parse(endedAt) - Date.parse(traceStartedAt);
180814
181448
  if (info.runtimeSessionId) capturedNativeSessionId = info.runtimeSessionId;
180815
181449
  enqueueTrace(async () => {
180816
- await traceStore.appendEvents(traceRunId, [{
180817
- seq: ++traceSeq,
181450
+ await traceAppender.append([{
180818
181451
  eventType: "run.finished",
180819
181452
  stream: "runtime",
180820
181453
  message: `exit code=${info.code ?? "killed"}`,
@@ -180823,7 +181456,7 @@ async function startServe(opts) {
180823
181456
  const usage = info.usage ?? null;
180824
181457
  const effectiveModel = info.model ?? chatModel ?? null;
180825
181458
  const modelSource = info.model ? "runtime" : chatModelSource;
180826
- await traceStore.updateRun(traceRunId, {
181459
+ await traceAppender.update({
180827
181460
  status: info.reason === "timeout" ? "timeout" : info.reason === "cancelled" ? "cancelled" : info.code === 0 ? "succeeded" : "failed",
180828
181461
  endedAt,
180829
181462
  durationMs,
@@ -180928,6 +181561,7 @@ async function startServe(opts) {
180928
181561
  unregister: () => {
180929
181562
  chatLiveSessions.delete(jobKey);
180930
181563
  },
181564
+ traceHealth,
180931
181565
  log: (m2) => console.log(m2)
180932
181566
  });
180933
181567
  console.log(`[chat-recovery] \u91CD\u6302 chat \u8F6E\uFF1Asession=${plan.chatSessionId} run=${plan.runId} node=${daemonId}`);
@@ -181148,7 +181782,7 @@ async function startServe(opts) {
181148
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` : ""}`);
181149
181783
  return { actor: replacement.id, originalActor: actor, routeReason: "disabled-owner-fallback" };
181150
181784
  },
181151
- provision: async (actorId) => buildActorProvision((await runActors()).service, actorId),
181785
+ provision: async (actorId, artifactId) => buildActorProvision((await runActors()).service, actorId, artifactId ? await resolveDispatchScope(artifactId) : void 0),
181152
181786
  resolveActorContext: async (actorId) => buildActorContext((await runActors()).service, actorId),
181153
181787
  // 收尾评审的角色分工段(reviewer 此前彼此不知道对方存在 → 越界 / 重复劳动)。
181154
181788
  // 职责取自**岗位目录**(配置面),不在代码里硬编码任何岗位——加角色自动就在。
@@ -181771,7 +182405,7 @@ ${nodeFault}` : "");
181771
182405
  resolveManager: (id) => resolveManagerBinding(registryStore, id),
181772
182406
  schemaTypes: schema.map((d) => d.name),
181773
182407
  schemaTypeDescriptions: Object.fromEntries(schema.filter((d) => d.description).map((d) => [d.name, d.description])),
181774
- provision: (actorId) => buildActorProvision(actors.service, actorId),
182408
+ provision: async (actorId, artifactId) => buildActorProvision(actors.service, actorId, artifactId ? await resolveDispatchScope(artifactId) : void 0),
181775
182409
  // 阻塞诊断(proposal D1/D3):派发器运行时信号(queued/fused/backoff/hasInFlight)注入诊断,
181776
182410
  // 让协调者把"资源 / 运行时"病因和"图病"分开——资源排队的节点不会被误唤去砍流程。
181777
182411
  runtimeSignals: (id) => {
@@ -181811,6 +182445,9 @@ ${nodeFault}` : "");
181811
182445
  if (c) return Promise.resolve(c.kill()).then(() => true).catch(() => false);
181812
182446
  return dispatcherByJob.get(jobKey)?.killSession(jobKey) ?? Promise.resolve(false);
181813
182447
  },
182448
+ // trace 写不进去的 run:冻结的事件钟是"观测降级"不是"真静默",按"信息不足→不判"跳过、交 wallClock
182449
+ // 兜底(brief §限制1 / 诊断 §6)。真卡死的 run trace 健康、isTraceHealthy=true,照常被杀。
182450
+ isTraceHealthy: (sessionId) => traceHealth.isHealthy(sessionId),
181814
182451
  now: Date.now(),
181815
182452
  silentThresholdMs,
181816
182453
  log: (m2) => console.log(m2)
@@ -181973,7 +182610,7 @@ var SessionManager = class {
181973
182610
 
181974
182611
  // ../cli/src/daemon/ws-client.ts
181975
182612
  init_wrapper();
181976
- var import_node_os7 = require("node:os");
182613
+ var import_node_os8 = require("node:os");
181977
182614
  var import_node_crypto38 = require("node:crypto");
181978
182615
  init_src5();
181979
182616
 
@@ -182029,7 +182666,7 @@ function detectRuntimes() {
182029
182666
 
182030
182667
  // ../cli/src/daemon/workdir-handler.ts
182031
182668
  var import_node_fs16 = __toESM(require("node:fs"), 1);
182032
- var import_node_path18 = __toESM(require("node:path"), 1);
182669
+ var import_node_path19 = __toESM(require("node:path"), 1);
182033
182670
  init_src4();
182034
182671
  init_src();
182035
182672
  var WORKDIR_READ_MAX_BYTES2 = 2 * 1024 * 1024;
@@ -182048,10 +182685,10 @@ function isSensitiveSegment(segment) {
182048
182685
  }
182049
182686
  function relPathIsSensitive(rel) {
182050
182687
  if (!rel) return false;
182051
- 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));
182052
182689
  }
182053
182690
  function withinBase(p2, base) {
182054
- return p2 === base || p2.startsWith(base + import_node_path18.default.sep);
182691
+ return p2 === base || p2.startsWith(base + import_node_path19.default.sep);
182055
182692
  }
182056
182693
  function normalizeRel(raw) {
182057
182694
  const trimmed = (raw ?? "").trim();
@@ -182068,7 +182705,7 @@ async function trustedCanonicalContainer(req, logicalBase, dirKind) {
182068
182705
  } catch {
182069
182706
  return null;
182070
182707
  }
182071
- 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);
182072
182709
  }
182073
182710
  async function resolveWithinWorkdir(req) {
182074
182711
  const dirKind = sessionDirKind(req.runtimeKind);
@@ -182086,11 +182723,11 @@ async function resolveWithinWorkdir(req) {
182086
182723
  } catch {
182087
182724
  return { ok: false, code: "NOT_FOUND" };
182088
182725
  }
182089
- 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" };
182090
182727
  const rel = normalizeRel(req.path);
182091
- const requested = import_node_path18.default.resolve(base, rel);
182728
+ const requested = import_node_path19.default.resolve(base, rel);
182092
182729
  if (!withinBase(requested, base)) return { ok: false, code: "PATH_ESCAPE" };
182093
- const cleanRel = base === requested ? "" : import_node_path18.default.relative(base, requested);
182730
+ const cleanRel = base === requested ? "" : import_node_path19.default.relative(base, requested);
182094
182731
  if (relPathIsSensitive(cleanRel)) return { ok: false, code: "SENSITIVE" };
182095
182732
  let real;
182096
182733
  try {
@@ -182100,7 +182737,7 @@ async function resolveWithinWorkdir(req) {
182100
182737
  return { ok: false, code: "PATH_ESCAPE" };
182101
182738
  }
182102
182739
  if (!withinBase(real, base)) return { ok: false, code: "PATH_ESCAPE" };
182103
- const realRel = base === real ? "" : import_node_path18.default.relative(base, real);
182740
+ const realRel = base === real ? "" : import_node_path19.default.relative(base, real);
182104
182741
  if (relPathIsSensitive(realRel)) return { ok: false, code: "SENSITIVE" };
182105
182742
  return { ok: true, base, real };
182106
182743
  }
@@ -182136,7 +182773,7 @@ async function handleWorkdirList(req) {
182136
182773
  const slice = truncated ? visible.slice(0, WORKDIR_LIST_MAX_ENTRIES) : visible;
182137
182774
  const entries = [];
182138
182775
  for (const d of slice) {
182139
- const abs = import_node_path18.default.join(anchor, d.name);
182776
+ const abs = import_node_path19.default.join(anchor, d.name);
182140
182777
  try {
182141
182778
  const st = await import_node_fs16.default.promises.lstat(abs);
182142
182779
  if (st.isSymbolicLink()) continue;
@@ -182168,7 +182805,7 @@ async function verifyOpenedFd(fh, base, fallback) {
182168
182805
  const fdReal = await fdCanonicalPath(fh);
182169
182806
  if (fdReal === null) return { anchor: fallback };
182170
182807
  if (!withinBase(fdReal, base)) return { error: { ok: false, code: "PATH_ESCAPE" } };
182171
- const fdRel = base === fdReal ? "" : import_node_path18.default.relative(base, fdReal);
182808
+ const fdRel = base === fdReal ? "" : import_node_path19.default.relative(base, fdReal);
182172
182809
  if (relPathIsSensitive(fdRel)) return { error: { ok: false, code: "SENSITIVE" } };
182173
182810
  return { anchor: `/proc/self/fd/${fh.fd}` };
182174
182811
  }
@@ -182257,7 +182894,7 @@ function looksBinary(bytes) {
182257
182894
  function contentTypeFor(absPath, bytes) {
182258
182895
  const sniffed = sniffContentType(bytes);
182259
182896
  if (sniffed) return sniffed;
182260
- const ext = import_node_path18.default.extname(absPath).toLowerCase();
182897
+ const ext = import_node_path19.default.extname(absPath).toLowerCase();
182261
182898
  if (EXT_CONTENT_TYPE[ext]) return EXT_CONTENT_TYPE[ext];
182262
182899
  return looksBinary(bytes) ? "application/octet-stream" : "text/plain; charset=utf-8";
182263
182900
  }
@@ -182600,7 +183237,7 @@ var DaemonWsClient = class {
182600
183237
  buildMeta() {
182601
183238
  const activeSessions = this.sessions.activeSessions();
182602
183239
  return {
182603
- hostname: (0, import_node_os7.hostname)(),
183240
+ hostname: (0, import_node_os8.hostname)(),
182604
183241
  adapters: this.adapters,
182605
183242
  ...this.reportRuntimes ? { runtimes: this.runtimes } : {},
182606
183243
  nodeVersion: process.version,
@@ -182630,18 +183267,18 @@ var RuntimeRouterAdapter = class {
182630
183267
 
182631
183268
  // ../cli/src/daemon/reap-claude-projects.ts
182632
183269
  var import_node_fs17 = __toESM(require("node:fs"), 1);
182633
- var import_node_os8 = __toESM(require("node:os"), 1);
182634
- 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);
182635
183272
  function claudeProjectSlug(cwd) {
182636
183273
  return cwd.replace(/[^a-zA-Z0-9]/g, "-");
182637
183274
  }
182638
183275
  function claudeProjectsRoot() {
182639
- const configDir = process.env["CLAUDE_CONFIG_DIR"] || import_node_path19.default.join(import_node_os8.default.homedir(), ".claude");
182640
- 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");
182641
183278
  }
182642
183279
  function reapClaudeProjects(workdir, runtimeKind) {
182643
183280
  if (runtimeKind !== "claude" && runtimeKind !== "claude-code") return;
182644
- const dir = import_node_path19.default.join(claudeProjectsRoot(), claudeProjectSlug(workdir));
183281
+ const dir = import_node_path20.default.join(claudeProjectsRoot(), claudeProjectSlug(workdir));
182645
183282
  if (!import_node_fs17.default.existsSync(dir)) return;
182646
183283
  try {
182647
183284
  import_node_fs17.default.rmSync(dir, { recursive: true, force: true });
@@ -182651,7 +183288,7 @@ function reapClaudeProjects(workdir, runtimeKind) {
182651
183288
 
182652
183289
  // ../cli/src/node.ts
182653
183290
  init_src4();
182654
- var import_node_os9 = __toESM(require("node:os"), 1);
183291
+ var import_node_os10 = __toESM(require("node:os"), 1);
182655
183292
  var RESILIENCE = {
182656
183293
  codex: { idleTimeoutMs: 6e5 }
182657
183294
  };
@@ -182715,7 +183352,7 @@ async function startNode(opts) {
182715
183352
  console.log(`[oasis node] ${opts.nodeId} \u2192 ${opts.serverUrl}`);
182716
183353
  client.start();
182717
183354
  const { workRoot, legacyRoot } = resolveWorkRoots(opts.workRoot);
182718
- const workRootIsEphemeral = workRoot.startsWith(import_node_os9.default.tmpdir());
183355
+ const workRootIsEphemeral = workRoot.startsWith(import_node_os10.default.tmpdir());
182719
183356
  if (opts.gcEnabled === false && !workRootIsEphemeral) {
182720
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`);
182721
183358
  }
@@ -182750,7 +183387,7 @@ async function startNode(opts) {
182750
183387
  var import_node_child_process16 = require("node:child_process");
182751
183388
  var import_node_fs18 = require("node:fs");
182752
183389
  var import_node_crypto39 = require("node:crypto");
182753
- var import_node_os10 = require("node:os");
183390
+ var import_node_os11 = require("node:os");
182754
183391
  function linuxMachineId() {
182755
183392
  for (const p2 of ["/etc/machine-id", "/var/lib/dbus/machine-id"]) {
182756
183393
  try {
@@ -182790,13 +183427,13 @@ function windowsMachineId() {
182790
183427
  }
182791
183428
  function fallbackFingerprint() {
182792
183429
  const macs = [];
182793
- const ifaces = (0, import_node_os10.networkInterfaces)();
183430
+ const ifaces = (0, import_node_os11.networkInterfaces)();
182794
183431
  for (const name of Object.keys(ifaces).sort()) {
182795
183432
  for (const ni of ifaces[name] ?? []) {
182796
183433
  if (!ni.internal && ni.mac && ni.mac !== "00:00:00:00:00:00") macs.push(ni.mac);
182797
183434
  }
182798
183435
  }
182799
- 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"}`;
182800
183437
  }
182801
183438
  function machineFingerprint() {
182802
183439
  const byOs = process.platform === "darwin" ? macMachineId() : process.platform === "win32" ? windowsMachineId() : linuxMachineId();
@@ -182806,7 +183443,7 @@ var defaultSources = {
182806
183443
  machineFingerprint,
182807
183444
  osUser: () => {
182808
183445
  try {
182809
- return (0, import_node_os10.userInfo)().username;
183446
+ return (0, import_node_os11.userInfo)().username;
182810
183447
  } catch {
182811
183448
  return process.env["USER"] ?? process.env["USERNAME"] ?? "unknown";
182812
183449
  }
@@ -183810,6 +184447,19 @@ var COMMAND_DECLS = {
183810
184447
  description: "\u4EA4\u4ED8\u7269\u6587\u6863\u4F53\u7CFB\u64CD\u4F5C\uFF08artifact revision \u5B50\u547D\u4EE4\u96C6\uFF09\u3002",
183811
184448
  subCommands: ["artifact revision register", "artifact revision approve", "artifact revision reject"],
183812
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"]
183813
184463
  }
183814
184464
  };
183815
184465
  function formatCommandHelp(decl) {
@@ -185475,6 +186125,33 @@ ${res.warning}`);
185475
186125
  println(`\u5DF2\u5220\u9664 ${memId}`);
185476
186126
  break;
185477
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
+ }
185478
186155
  case "pin": {
185479
186156
  const { message } = await api.cmd("pin", {
185480
186157
  artifactId: needPos(positional, 0, "oasis pin <artifactId> --to u --rev r"),
@@ -186174,7 +186851,7 @@ function syncRuntimeAssets(candidateRoots, binDir) {
186174
186851
  }
186175
186852
 
186176
186853
  // src/index.ts
186177
- var PKG_VERSION = true ? "0.1.87" : "dev";
186854
+ var PKG_VERSION = true ? "0.1.89" : "dev";
186178
186855
  var OASIS_DIR = path29.join(os9.homedir(), ".oasis");
186179
186856
  var CONFIG_FILE = path29.join(OASIS_DIR, "node-config.json");
186180
186857
  var PID_FILE = path29.join(OASIS_DIR, "node.pid");