oasis_test_v2 2.2.81 → 2.2.83

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 +1299 -485
  2. package/package.json +2 -2
package/dist/index.js CHANGED
@@ -2950,6 +2950,25 @@ async function fetchResultPackage(base, fetchImpl, runId) {
2950
2950
  }));
2951
2951
  return { artifacts, fileCount: body2.project?.fileCount ?? artifacts.length };
2952
2952
  }
2953
+ function toEpochMs(v2) {
2954
+ if (typeof v2 === "number" && Number.isFinite(v2)) return v2;
2955
+ if (typeof v2 === "string" && v2.length > 0) {
2956
+ const ms = Date.parse(v2);
2957
+ return Number.isNaN(ms) ? void 0 : ms;
2958
+ }
2959
+ return void 0;
2960
+ }
2961
+ function runTime(r) {
2962
+ return r.createdAt ?? r.updatedAt ?? Number.NEGATIVE_INFINITY;
2963
+ }
2964
+ function latestSucceededRun(runs) {
2965
+ let best;
2966
+ for (const r of runs) {
2967
+ if (r.status !== "succeeded") continue;
2968
+ if (best === void 0 || runTime(r) > runTime(best)) best = r;
2969
+ }
2970
+ return best;
2971
+ }
2953
2972
  async function fetchBoardStatus(base, fetchImpl, artifactId) {
2954
2973
  const boardKey = deriveBoardKey(artifactId);
2955
2974
  let boardExists = true;
@@ -2975,10 +2994,20 @@ async function fetchBoardStatus(base, fetchImpl, artifactId) {
2975
2994
  "GET",
2976
2995
  `/api/runs?projectId=${encodeURIComponent(boardKey)}`
2977
2996
  );
2978
- runs = (body2.runs ?? []).map((r) => ({
2979
- runId: String(r["runId"] ?? r["id"] ?? ""),
2980
- status: String(r["status"] ?? "unknown")
2981
- }));
2997
+ runs = (body2.runs ?? []).map((r) => {
2998
+ const run = {
2999
+ runId: String(r["runId"] ?? r["id"] ?? ""),
3000
+ status: String(r["status"] ?? "unknown")
3001
+ };
3002
+ if (typeof r["agentId"] === "string" && r["agentId"].length > 0) run.agentId = r["agentId"];
3003
+ if (typeof r["error"] === "string" && r["error"].trim().length > 0) run.error = r["error"];
3004
+ const createdAt = toEpochMs(r["createdAt"]);
3005
+ if (createdAt !== void 0) run.createdAt = createdAt;
3006
+ const updatedAt = toEpochMs(r["updatedAt"]);
3007
+ if (updatedAt !== void 0) run.updatedAt = updatedAt;
3008
+ return run;
3009
+ });
3010
+ runs.sort((a, b2) => runTime(b2) - runTime(a) || 0);
2982
3011
  } catch {
2983
3012
  }
2984
3013
  }
@@ -35159,7 +35188,7 @@ var require_websocket = __commonJS({
35159
35188
  var http2 = require("http");
35160
35189
  var net = require("net");
35161
35190
  var tls = require("tls");
35162
- var { randomBytes: randomBytes14, createHash: createHash45 } = require("crypto");
35191
+ var { randomBytes: randomBytes15, createHash: createHash45 } = require("crypto");
35163
35192
  var { Duplex, Readable } = require("stream");
35164
35193
  var { URL: URL2 } = require("url");
35165
35194
  var PerMessageDeflate2 = require_permessage_deflate();
@@ -35697,7 +35726,7 @@ var require_websocket = __commonJS({
35697
35726
  }
35698
35727
  }
35699
35728
  const defaultPort = isSecure ? 443 : 80;
35700
- const key2 = randomBytes14(16).toString("base64");
35729
+ const key2 = randomBytes15(16).toString("base64");
35701
35730
  const request2 = isSecure ? https.request : http2.request;
35702
35731
  const protocolSet = /* @__PURE__ */ new Set();
35703
35732
  let perMessageDeflate;
@@ -130108,7 +130137,7 @@ var require_utils3 = __commonJS({
130108
130137
  var nodeCrypto = require("crypto");
130109
130138
  module2.exports = {
130110
130139
  postgresMd5PasswordHash,
130111
- randomBytes: randomBytes14,
130140
+ randomBytes: randomBytes15,
130112
130141
  deriveKey,
130113
130142
  sha256: sha2563,
130114
130143
  hashByName,
@@ -130118,7 +130147,7 @@ var require_utils3 = __commonJS({
130118
130147
  var webCrypto = nodeCrypto.webcrypto || globalThis.crypto;
130119
130148
  var subtleCrypto = webCrypto.subtle;
130120
130149
  var textEncoder = new TextEncoder();
130121
- function randomBytes14(length) {
130150
+ function randomBytes15(length) {
130122
130151
  return webCrypto.getRandomValues(Buffer.alloc(length));
130123
130152
  }
130124
130153
  async function md5(string7) {
@@ -208676,11 +208705,17 @@ var init_app_adopt = __esm({
208676
208705
  });
208677
208706
 
208678
208707
  // ../connectors/src/github/app-manifest.ts
208708
+ function randomAppNameSuffix() {
208709
+ const alphabet = "abcdefghijklmnopqrstuvwxyz0123456789";
208710
+ return Array.from((0, import_node_crypto42.randomBytes)(4), (b2) => alphabet[b2 % alphabet.length]).join("");
208711
+ }
208679
208712
  function buildAppManifest(args) {
208680
208713
  const base = args.baseUrl.replace(/\/+$/, "");
208714
+ const suffix = args.nameSuffix ?? randomAppNameSuffix();
208715
+ const room = GITHUB_APP_NAME_MAX - args.namePrefix.length - suffix.length - 2;
208716
+ const middle = args.employeeSlug.slice(0, Math.max(0, room)).replace(/-+$/, "");
208681
208717
  return {
208682
- name: `${args.namePrefix}-${args.employeeSlug}`.slice(0, 34),
208683
- // GitHub App 名上限 34 字符
208718
+ name: [args.namePrefix, middle, suffix].filter(Boolean).join("-"),
208684
208719
  url: base,
208685
208720
  redirect_url: `${base}${args.redirectPath}`,
208686
208721
  public: false,
@@ -208758,10 +208793,11 @@ function checkAppOwnership(conv, want) {
208758
208793
  }
208759
208794
  return null;
208760
208795
  }
208761
- var GITHUB_APP_DEFAULT_PERMISSIONS, GITHUB_APP_PERMISSION_LABELS, PENDING_TTL_MS, REQUIRED_WRITE_PERMISSIONS, PERMISSION_RANK, FORBIDDEN_IMPORT_PERMISSION_KEYS;
208796
+ var import_node_crypto42, GITHUB_APP_DEFAULT_PERMISSIONS, GITHUB_APP_PERMISSION_LABELS, GITHUB_APP_NAME_MAX, PENDING_TTL_MS, REQUIRED_WRITE_PERMISSIONS, PERMISSION_RANK, FORBIDDEN_IMPORT_PERMISSION_KEYS;
208762
208797
  var init_app_manifest = __esm({
208763
208798
  "../connectors/src/github/app-manifest.ts"() {
208764
208799
  "use strict";
208800
+ import_node_crypto42 = require("node:crypto");
208765
208801
  GITHUB_APP_DEFAULT_PERMISSIONS = {
208766
208802
  contents: "write",
208767
208803
  // clone / push
@@ -208790,6 +208826,7 @@ var init_app_manifest = __esm({
208790
208826
  { key: "workflows", label: "\u5DE5\u4F5C\u6D41\u6587\u4EF6\uFF08\u8BFB\u5199\uFF09", desc: "\u4FEE\u6539 .github/workflows \u4E0B\u7684 CI \u914D\u7F6E" },
208791
208827
  { key: "issues", label: "Issue\uFF08\u8BFB\u5199\uFF09", desc: "\u521B\u5EFA\u4E0E\u8BC4\u8BBA Issue" }
208792
208828
  ];
208829
+ GITHUB_APP_NAME_MAX = 34;
208793
208830
  PENDING_TTL_MS = 15 * 60 * 1e3;
208794
208831
  REQUIRED_WRITE_PERMISSIONS = ["contents", "pull_requests"];
208795
208832
  PERMISSION_RANK = { read: 1, write: 2, admin: 3 };
@@ -208806,6 +208843,195 @@ var init_app_manifest = __esm({
208806
208843
  }
208807
208844
  });
208808
208845
 
208846
+ // ../connectors/src/github/app-inventory.ts
208847
+ function permissionVerdict(perms) {
208848
+ const missingRequired = REQUIRED_WRITE.filter((k2) => perms[k2] !== "write" && perms[k2] !== "admin");
208849
+ const excess = excessAppPermissions(perms);
208850
+ return {
208851
+ missingRequired: [...missingRequired],
208852
+ missing: missingAppPermissions(perms),
208853
+ excess,
208854
+ usable: missingRequired.length === 0 && excess.length === 0
208855
+ };
208856
+ }
208857
+ async function listOrgInstalledApps(args, deps = {}) {
208858
+ const d = { ...defaultDeps3, ...deps };
208859
+ let resp;
208860
+ try {
208861
+ resp = await d.fetch(`${GITHUB_API2}/orgs/${encodeURIComponent(args.org)}/installations?per_page=100`, {
208862
+ headers: {
208863
+ authorization: `Bearer ${args.token}`,
208864
+ accept: "application/vnd.github+json",
208865
+ "x-github-api-version": "2022-11-28"
208866
+ }
208867
+ });
208868
+ } catch (e) {
208869
+ return { status: "error", message: String(e) };
208870
+ }
208871
+ if (resp.status === 401 || resp.status === 403) return { status: "needsAuth", httpStatus: resp.status };
208872
+ const body2 = await resp.json().catch(() => ({}));
208873
+ if (!resp.ok) return { status: "error", message: `GitHub \u8FD4\u56DE HTTP ${resp.status} ${String(body2["message"] ?? "")}`.trim() };
208874
+ const list2 = Array.isArray(body2["installations"]) ? body2["installations"] : [];
208875
+ const all2 = list2.flatMap((raw) => {
208876
+ const item = raw;
208877
+ const appId = item["app_id"] === void 0 ? "" : String(item["app_id"]);
208878
+ const installationId = item["id"] === void 0 ? "" : String(item["id"]);
208879
+ if (!appId || !installationId) return [];
208880
+ const rawPerms = item["permissions"];
208881
+ const permissions = {};
208882
+ for (const [k2, v2] of Object.entries(rawPerms ?? {})) if (typeof v2 === "string") permissions[k2] = v2;
208883
+ return [{ appId, slug: String(item["app_slug"] ?? ""), installationId, org: args.org, permissions }];
208884
+ });
208885
+ const owned = await Promise.all(all2.map(async (app) => {
208886
+ if (!app.slug) return app;
208887
+ try {
208888
+ const r = await d.fetch(`${GITHUB_API2}/apps/${encodeURIComponent(app.slug)}`, {
208889
+ headers: {
208890
+ authorization: `Bearer ${args.token}`,
208891
+ accept: "application/vnd.github+json",
208892
+ "x-github-api-version": "2022-11-28"
208893
+ }
208894
+ });
208895
+ if (!r.ok) return app;
208896
+ const meta = await r.json().catch(() => ({}));
208897
+ const login = meta.owner?.login;
208898
+ return login && login.toLowerCase() !== args.org.toLowerCase() ? null : app;
208899
+ } catch {
208900
+ return app;
208901
+ }
208902
+ }));
208903
+ return { status: "ok", apps: owned.filter((a) => a !== null) };
208904
+ }
208905
+ async function uninstallAppInstallation(args, deps = {}) {
208906
+ const d = { ...defaultDeps3, ...deps };
208907
+ const jwt = createAppJwt(args.appId, args.privateKeyPem, d.nowMs());
208908
+ const resp = await d.fetch(`${GITHUB_API2}/app/installations/${encodeURIComponent(args.installationId)}`, {
208909
+ method: "DELETE",
208910
+ headers: {
208911
+ authorization: `Bearer ${jwt}`,
208912
+ accept: "application/vnd.github+json",
208913
+ "x-github-api-version": "2022-11-28"
208914
+ }
208915
+ });
208916
+ if (resp.status === 204 || resp.status === 404) return;
208917
+ const body2 = await resp.json().catch(() => ({}));
208918
+ throw new Error(`github app: uninstall failed HTTP ${resp.status} ${String(body2["message"] ?? "")}`.trim());
208919
+ }
208920
+ function appSettingsUrl(owner, slug6) {
208921
+ const s2 = encodeURIComponent(slug6);
208922
+ return owner.kind === "org" ? `https://github.com/organizations/${encodeURIComponent(owner.login)}/settings/apps/${s2}` : `https://github.com/settings/apps/${s2}`;
208923
+ }
208924
+ function appInstallPageUrl(slug6) {
208925
+ return `https://github.com/apps/${encodeURIComponent(slug6)}/installations/new`;
208926
+ }
208927
+ function appDeleteUrl(owner, slug6) {
208928
+ return `${appSettingsUrl(owner, slug6)}/advanced`;
208929
+ }
208930
+ function ownerMatches(app, owner) {
208931
+ if (owner.kind === "org") {
208932
+ return app.ownerType === "Organization" && app.ownerLogin.toLowerCase() === owner.login.toLowerCase();
208933
+ }
208934
+ if (app.ownerType !== "User") return false;
208935
+ return !owner.login || app.ownerLogin.toLowerCase() === owner.login.toLowerCase();
208936
+ }
208937
+ function buildReuseCandidates(args) {
208938
+ const { owner } = args;
208939
+ const known = new Set(args.oasisApps.map((a2) => a2.appId));
208940
+ const a = args.oasisApps.filter((app) => ownerMatches(app, owner)).map((app) => {
208941
+ const verdict = app.permissions ? permissionVerdict(app.permissions) : null;
208942
+ const selectable = verdict ? verdict.usable : true;
208943
+ const action = !selectable ? "blocked" : app.installationIds.length === 0 ? "install" : "bind";
208944
+ return {
208945
+ kind: "oasis",
208946
+ action,
208947
+ appId: app.appId,
208948
+ slug: app.slug,
208949
+ ...app.installationIds[0] ? { installationId: app.installationIds[0] } : {},
208950
+ usedBy: app.usedBy,
208951
+ isOrgDefault: app.isOrgDefault,
208952
+ permissionsUnknown: !verdict,
208953
+ verdict,
208954
+ selectable,
208955
+ settingsUrl: appSettingsUrl(owner, app.slug),
208956
+ permissionsUrl: `${appSettingsUrl(owner, app.slug)}/permissions`,
208957
+ installUrl: appInstallPageUrl(app.slug)
208958
+ };
208959
+ });
208960
+ const b2 = owner.kind === "org" ? args.installed.filter((i) => !known.has(i.appId) && i.org.toLowerCase() === owner.login.toLowerCase()).map((i) => {
208961
+ const verdict = permissionVerdict(i.permissions);
208962
+ return {
208963
+ kind: "installed",
208964
+ action: verdict.usable ? "import" : "blocked",
208965
+ appId: i.appId,
208966
+ slug: i.slug,
208967
+ installationId: i.installationId,
208968
+ usedBy: [],
208969
+ isOrgDefault: false,
208970
+ permissionsUnknown: false,
208971
+ verdict,
208972
+ selectable: verdict.usable,
208973
+ settingsUrl: appSettingsUrl(owner, i.slug),
208974
+ permissionsUrl: `${appSettingsUrl(owner, i.slug)}/permissions`,
208975
+ installUrl: appInstallPageUrl(i.slug)
208976
+ };
208977
+ }) : [];
208978
+ return [...a, ...b2].sort((x2, y) => Number(y.selectable) - Number(x2.selectable));
208979
+ }
208980
+ function buildAppInventory(args) {
208981
+ const rows = /* @__PURE__ */ new Map();
208982
+ const ownerOf = (login, type) => !login ? null : type === "Organization" ? { kind: "org", login } : { kind: "user", login };
208983
+ for (const app of args.oasisApps) {
208984
+ const inUse = app.isOrgDefault || app.usedBy.length > 0;
208985
+ const owner = ownerOf(app.ownerLogin, app.ownerType);
208986
+ rows.set(app.appId, {
208987
+ appId: app.appId,
208988
+ slug: app.slug,
208989
+ ownerLogin: app.ownerLogin,
208990
+ hasKey: true,
208991
+ usedBy: app.usedBy,
208992
+ isOrgDefault: app.isOrgDefault,
208993
+ inUse,
208994
+ installationIds: [...app.installationIds],
208995
+ canUninstall: !inUse && app.installationIds.length > 0,
208996
+ deleteUrl: owner && app.slug ? appDeleteUrl(owner, app.slug) : ""
208997
+ });
208998
+ }
208999
+ for (const i of args.installed) {
209000
+ const prev = rows.get(i.appId);
209001
+ if (prev) {
209002
+ if (!prev.installationIds.includes(i.installationId)) prev.installationIds.push(i.installationId);
209003
+ prev.canUninstall = !prev.inUse && prev.installationIds.length > 0;
209004
+ if (!prev.ownerLogin) prev.ownerLogin = i.org;
209005
+ if (!prev.deleteUrl && prev.slug) prev.deleteUrl = appDeleteUrl({ kind: "org", login: i.org }, prev.slug);
209006
+ continue;
209007
+ }
209008
+ rows.set(i.appId, {
209009
+ appId: i.appId,
209010
+ slug: i.slug,
209011
+ ownerLogin: i.org,
209012
+ hasKey: false,
209013
+ usedBy: [],
209014
+ isOrgDefault: false,
209015
+ inUse: false,
209016
+ installationIds: [i.installationId],
209017
+ canUninstall: false,
209018
+ deleteUrl: i.slug ? appDeleteUrl({ kind: "org", login: i.org }, i.slug) : ""
209019
+ });
209020
+ }
209021
+ return [...rows.values()].sort((x2, y) => Number(x2.inUse) - Number(y.inUse) || x2.slug.localeCompare(y.slug));
209022
+ }
209023
+ var GITHUB_API2, defaultDeps3, REQUIRED_WRITE;
209024
+ var init_app_inventory = __esm({
209025
+ "../connectors/src/github/app-inventory.ts"() {
209026
+ "use strict";
209027
+ init_app_auth();
209028
+ init_app_manifest();
209029
+ GITHUB_API2 = "https://api.github.com";
209030
+ defaultDeps3 = { fetch: (...a) => globalThis.fetch(...a), nowMs: () => Date.now() };
209031
+ REQUIRED_WRITE = ["contents", "pull_requests"];
209032
+ }
209033
+ });
209034
+
208809
209035
  // ../connectors/src/registry.ts
208810
209036
  function createConnectorBySlug(slug6) {
208811
209037
  switch (slug6) {
@@ -209262,6 +209488,7 @@ var init_src8 = __esm({
209262
209488
  init_app_auth();
209263
209489
  init_app_adopt();
209264
209490
  init_app_manifest();
209491
+ init_app_inventory();
209265
209492
  init_registry2();
209266
209493
  init_provision();
209267
209494
  init_prepare_job();
@@ -209270,6 +209497,118 @@ var init_src8 = __esm({
209270
209497
  }
209271
209498
  });
209272
209499
 
209500
+ // ../server/src/github-app-inventory.ts
209501
+ async function collectOasisGithubApps(svc) {
209502
+ const rows = (await svc.listVariables()).filter((r) => r.scope === "global" || r.scope === "personal");
209503
+ const agents = await svc.listActors({ kind: "agent", status: "active" });
209504
+ const nameOf2 = new Map(agents.map((a) => [a.id, a.name || a.id]));
209505
+ const plain = (key2, actorId) => rows.find((r) => r.key === key2 && r.actorId === actorId && !r.encrypted)?.maskedValue ?? "";
209506
+ const byAppId = /* @__PURE__ */ new Map();
209507
+ for (const r of rows) {
209508
+ if (r.key !== "GITHUB_APP_ID" || r.encrypted || !r.maskedValue) continue;
209509
+ const e = byAppId.get(r.maskedValue) ?? {
209510
+ slug: "",
209511
+ installationId: "",
209512
+ keyActorId: void 0,
209513
+ usedBy: [],
209514
+ isOrgDefault: false,
209515
+ sources: []
209516
+ };
209517
+ e.sources.push(r.actorId);
209518
+ if (!r.actorId) e.isOrgDefault = true;
209519
+ else if (nameOf2.has(r.actorId) && !e.usedBy.includes(r.actorId)) e.usedBy.push(r.actorId);
209520
+ byAppId.set(r.maskedValue, e);
209521
+ }
209522
+ const out = await Promise.all([...byAppId.entries()].map(async ([appId, e]) => {
209523
+ let pem = null;
209524
+ let keyActorId;
209525
+ for (const src of [...e.sources].sort((a, b2) => Number(Boolean(a)) - Number(Boolean(b2)))) {
209526
+ pem = await svc.revealVariable("GITHUB_APP_PRIVATE_KEY", src).catch(() => null);
209527
+ if (pem) {
209528
+ keyActorId = src;
209529
+ break;
209530
+ }
209531
+ }
209532
+ if (!pem) return null;
209533
+ const slug6 = plain("GITHUB_APP_SLUG", keyActorId);
209534
+ const storedInstallationId = plain("GITHUB_APP_INSTALLATION_ID", keyActorId);
209535
+ const [meta, installs] = await Promise.all([
209536
+ fetchAppSelfMetadata({ appId, privateKeyPem: pem }).catch(() => null),
209537
+ listAppInstallations({ appId, privateKeyPem: pem }).catch(() => null)
209538
+ ]);
209539
+ const stored = installs?.find((i) => i.installationId === storedInstallationId) ?? installs?.[0];
209540
+ const permissions = stored && Object.keys(stored.permissions).length > 0 ? stored.permissions : meta?.permissions ?? null;
209541
+ const app = {
209542
+ appId,
209543
+ slug: meta?.slug || slug6,
209544
+ ownerLogin: meta?.ownerLogin ?? "",
209545
+ ownerType: meta?.ownerType ?? "",
209546
+ usedBy: e.usedBy,
209547
+ usedByNames: e.usedBy.map((id) => nameOf2.get(id) ?? id),
209548
+ isOrgDefault: e.isOrgDefault,
209549
+ // 列不出来(私钥失效等)时退回库里记的那一处,卸载时 404 也当成功。
209550
+ installationIds: installs ? installs.map((i) => i.installationId) : storedInstallationId ? [storedInstallationId] : [],
209551
+ permissions,
209552
+ keyActorId
209553
+ };
209554
+ return app;
209555
+ }));
209556
+ return out.filter((a) => a !== null);
209557
+ }
209558
+ async function ghUser(token, path47) {
209559
+ const r = await fetch(`https://api.github.com${path47}`, {
209560
+ headers: {
209561
+ authorization: `Bearer ${token}`,
209562
+ accept: "application/vnd.github+json",
209563
+ "x-github-api-version": "2022-11-28"
209564
+ }
209565
+ });
209566
+ return { status: r.status, body: r.ok ? await r.json().catch(() => null) : null };
209567
+ }
209568
+ async function githubLoginOf(token) {
209569
+ const r = await ghUser(token, "/user").catch(() => null);
209570
+ return r?.body?.login ?? "";
209571
+ }
209572
+ async function listMemberships(token) {
209573
+ const r = await ghUser(
209574
+ token,
209575
+ "/user/memberships/orgs?state=active&per_page=100"
209576
+ );
209577
+ if (!r.body) return null;
209578
+ return r.body.flatMap((m2) => m2.organization?.login ? [{ login: m2.organization.login, role: m2.role ?? "member" }] : []);
209579
+ }
209580
+ async function orgInstalledAppsFor(token, org) {
209581
+ if (!token) return { state: { status: "needsAuth" }, apps: [] };
209582
+ const m2 = await ghUser(token, `/user/memberships/orgs/${encodeURIComponent(org)}`);
209583
+ if (m2.status === 401) return { state: { status: "needsAuth" }, apps: [] };
209584
+ if (m2.body && m2.body.role !== "admin") return { state: { status: "notAdmin" }, apps: [] };
209585
+ if (!m2.body && m2.status === 404) return { state: { status: "notAdmin" }, apps: [] };
209586
+ if (!m2.body) return { state: { status: "needsAuth" }, apps: [] };
209587
+ const r = await listOrgInstalledApps({ token, org });
209588
+ if (r.status === "ok") return { state: { status: "ok" }, apps: r.apps };
209589
+ if (r.status === "needsAuth") return { state: { status: "needsAuth" }, apps: [] };
209590
+ return { state: { status: "error", message: r.message }, apps: [] };
209591
+ }
209592
+ async function pickUserToken(svc) {
209593
+ const rows = await svc.listVariables();
209594
+ const sources = [void 0, ...rows.filter((r) => r.key === "GITHUB_TOKEN" && r.scope === "personal" && r.actorId).map((r) => r.actorId)];
209595
+ const tried = /* @__PURE__ */ new Set();
209596
+ for (const src of sources) {
209597
+ const token = await svc.revealVariable("GITHUB_TOKEN", src).catch(() => null);
209598
+ if (!token || tried.has(token)) continue;
209599
+ tried.add(token);
209600
+ const memberships = await listMemberships(token).catch(() => null);
209601
+ if (memberships) return { token, memberships };
209602
+ }
209603
+ return null;
209604
+ }
209605
+ var init_github_app_inventory = __esm({
209606
+ "../server/src/github-app-inventory.ts"() {
209607
+ "use strict";
209608
+ init_src8();
209609
+ }
209610
+ });
209611
+
209273
209612
  // ../server/src/server.ts
209274
209613
  function isBundleFrozen(err) {
209275
209614
  return typeof err === "object" && err !== null && err.code === "TENANT_BUNDLE_FROZEN";
@@ -212711,7 +213050,7 @@ async function startOasisServer(opts) {
212711
213050
  entry = {
212712
213051
  // 旧 CLI 未带 waiterId 时给它塞一个服务端本地生成的——它绝不可能出现在 leave 上,
212713
213052
  // 也就绝不会误命中 leftWaiterIds、绝不会被别人的 leave 摘掉。行为与旧 CLI 相同。
212714
- waiterId: clientWaiterId ?? `server:${(0, import_node_crypto42.randomUUID)()}`,
213053
+ waiterId: clientWaiterId ?? `server:${(0, import_node_crypto43.randomUUID)()}`,
212715
213054
  onResolve,
212716
213055
  expiresAt: expiresAt2,
212717
213056
  cleanup: () => clearTimeout(expiryTimer),
@@ -214849,57 +215188,71 @@ ${composed}`;
214849
215188
  }
214850
215189
  if (url.pathname === "/api/connectors/github/app/available" && req.method === "POST") {
214851
215190
  try {
215191
+ const { actorId, owner } = JSON.parse(rawBody || "{}");
214852
215192
  const svc = await connectorActorsService();
214853
215193
  if (!svc) throw new Error("actors service unavailable");
214854
- const rows = await svc.listVariables();
214855
- const plain = (key2, actorId) => rows.find((r) => r.key === key2 && r.actorId === actorId && !r.encrypted)?.maskedValue ?? "";
214856
- const byAppId = /* @__PURE__ */ new Map();
214857
- for (const r of rows) {
214858
- if (r.key !== "GITHUB_APP_ID" || r.encrypted || !r.maskedValue) continue;
214859
- const appId = r.maskedValue;
214860
- const prev = byAppId.get(appId);
214861
- if (prev) {
214862
- prev.usedBy += 1;
214863
- if (!r.actorId && prev.scope === "personal") {
214864
- prev.scope = "global";
214865
- delete prev.actorId;
214866
- prev.installationId = plain("GITHUB_APP_INSTALLATION_ID");
214867
- prev.slug = plain("GITHUB_APP_SLUG");
214868
- }
214869
- continue;
214870
- }
214871
- byAppId.set(appId, {
214872
- appId,
214873
- slug: plain("GITHUB_APP_SLUG", r.actorId),
214874
- installationId: plain("GITHUB_APP_INSTALLATION_ID", r.actorId),
214875
- scope: r.actorId ? "personal" : "global",
214876
- ...r.actorId ? { actorId: r.actorId } : {},
214877
- usedBy: 1
214878
- });
215194
+ const token = await svc.revealResolvedVariable("GITHUB_TOKEN", actorId);
215195
+ const appOwner = owner?.kind === "org" && owner.login ? { kind: "org", login: owner.login } : { kind: "user", ...token ? { login: await githubLoginOf(token) } : {} };
215196
+ const [oasisApps, installed] = await Promise.all([
215197
+ collectOasisGithubApps(svc),
215198
+ appOwner.kind === "org" ? orgInstalledAppsFor(token, appOwner.login) : Promise.resolve({ state: { status: "ok" }, apps: [] })
215199
+ ]);
215200
+ const names = new Map(oasisApps.map((a) => [a.appId, a.usedByNames]));
215201
+ const candidates = buildReuseCandidates({ owner: appOwner, oasisApps, installed: installed.apps }).map((c) => ({ ...c, usedBy: names.get(c.appId) ?? [] }));
215202
+ res.writeHead(200, { "content-type": "application/json" }).end(JSON.stringify({ candidates, installed: installed.state }));
215203
+ } catch (e) {
215204
+ res.writeHead(500, { "content-type": "application/json" }).end(JSON.stringify({ error: String(e) }));
215205
+ }
215206
+ return;
215207
+ }
215208
+ if (url.pathname === "/api/connectors/github/app/inventory" && req.method === "POST") {
215209
+ try {
215210
+ const svc = await connectorActorsService();
215211
+ if (!svc) throw new Error("actors service unavailable");
215212
+ const [oasisApps, picked] = await Promise.all([collectOasisGithubApps(svc), pickUserToken(svc)]);
215213
+ const adminOrgs = picked ? picked.memberships.filter((m2) => m2.role === "admin").map((m2) => m2.login) : [];
215214
+ const perOrg = picked ? await Promise.all(adminOrgs.map((org) => orgInstalledAppsFor(picked.token, org))) : [];
215215
+ const rows = buildAppInventory({ oasisApps, installed: perOrg.flatMap((r) => r.apps) });
215216
+ const names = new Map(oasisApps.map((a) => [a.appId, a.usedByNames]));
215217
+ res.writeHead(200, { "content-type": "application/json" }).end(JSON.stringify({
215218
+ rows: rows.map((r) => ({ ...r, usedBy: names.get(r.appId) ?? [] })),
215219
+ /** 组织里装着的那部分列没列出来:needsAuth = 没有可用的 GitHub 登录;notAdmin = 不是任何组织的管理员。 */
215220
+ installed: !picked ? { status: "needsAuth" } : adminOrgs.length === 0 ? { status: "notAdmin" } : perOrg.find((r) => r.state.status !== "ok")?.state ?? { status: "ok" },
215221
+ orgs: adminOrgs
215222
+ }));
215223
+ } catch (e) {
215224
+ res.writeHead(500, { "content-type": "application/json" }).end(JSON.stringify({ error: String(e) }));
215225
+ }
215226
+ return;
215227
+ }
215228
+ if (url.pathname === "/api/connectors/github/app/uninstall" && req.method === "POST") {
215229
+ try {
215230
+ const { appIds } = JSON.parse(rawBody || "{}");
215231
+ if (!Array.isArray(appIds) || appIds.length === 0 || !appIds.every((x2) => typeof x2 === "string")) {
215232
+ res.writeHead(400, { "content-type": "application/json" }).end(JSON.stringify({ error: "\u8BF7\u5148\u52FE\u9009\u8981\u5378\u8F7D\u7684 App" }));
215233
+ return;
214879
215234
  }
214880
- const apps = await Promise.all([...byAppId.values()].map(async (a) => {
214881
- const base = {
214882
- appId: a.appId,
214883
- slug: a.slug,
214884
- account: a.slug ? `${a.slug}[bot]` : a.appId,
214885
- isOrgDefault: a.scope === "global",
214886
- usedBy: a.usedBy,
214887
- ...a.slug ? { settingsUrl: appPermissionSettingsUrl({ slug: a.slug }) } : {}
214888
- };
215235
+ const svc = await connectorActorsService();
215236
+ if (!svc) throw new Error("actors service unavailable");
215237
+ const apps = new Map((await collectOasisGithubApps(svc)).map((a) => [a.appId, a]));
215238
+ const results = await Promise.all(appIds.map(async (appId) => {
215239
+ const app = apps.get(appId);
215240
+ if (!app) return { appId, ok: false, error: "Oasis \u6CA1\u6709\u8FD9\u4E2A App \u7684\u79C1\u94A5\uFF0C\u5378\u8F7D\u4E0D\u4E86\uFF0C\u8BF7\u53BB GitHub \u9875\u9762\u5904\u7406" };
215241
+ if (app.isOrgDefault || app.usedBy.length > 0) {
215242
+ return { appId, slug: app.slug, ok: false, error: `\u8FD8\u6709 agent \u5728\u7528\uFF08${app.isOrgDefault ? "\u7EC4\u7EC7\u9ED8\u8BA4" : app.usedByNames.join("\u3001")}\uFF09\uFF0C\u4E0D\u80FD\u5378\u8F7D` };
215243
+ }
215244
+ const pem = await svc.revealVariable("GITHUB_APP_PRIVATE_KEY", app.keyActorId);
215245
+ if (!pem) return { appId, slug: app.slug, ok: false, error: "\u79C1\u94A5\u8BFB\u4E0D\u51FA\u6765\uFF0C\u5378\u8F7D\u4E0D\u4E86" };
214889
215246
  try {
214890
- const pem = await svc.revealVariable("GITHUB_APP_PRIVATE_KEY", a.actorId);
214891
- if (!pem || !a.installationId) return { ...base, permissionsUnknown: true };
214892
- const perms = await fetchInstallationPermissions({
214893
- appId: a.appId,
214894
- privateKeyPem: pem,
214895
- installationId: a.installationId
214896
- });
214897
- return { ...base, missing: missingAppPermissions(perms) };
214898
- } catch {
214899
- return { ...base, permissionsUnknown: true };
215247
+ for (const installationId of app.installationIds) {
215248
+ await uninstallAppInstallation({ appId, privateKeyPem: pem, installationId });
215249
+ }
215250
+ return { appId, slug: app.slug, ok: true };
215251
+ } catch (e) {
215252
+ return { appId, slug: app.slug, ok: false, error: `GitHub \u62D2\u7EDD\u4E86\u5378\u8F7D\uFF1A${String(e).replace(/^Error:\s*/, "")}` };
214900
215253
  }
214901
215254
  }));
214902
- res.writeHead(200, { "content-type": "application/json" }).end(JSON.stringify({ apps }));
215255
+ res.writeHead(200, { "content-type": "application/json" }).end(JSON.stringify({ results }));
214903
215256
  } catch (e) {
214904
215257
  res.writeHead(500, { "content-type": "application/json" }).end(JSON.stringify({ error: String(e) }));
214905
215258
  }
@@ -214928,9 +215281,11 @@ ${composed}`;
214928
215281
  return;
214929
215282
  }
214930
215283
  const pick3 = (key2) => rows.find((r) => r.key === key2 && r.actorId === srcActor && !r.encrypted)?.maskedValue ?? "";
214931
- const installationId = pick3("GITHUB_APP_INSTALLATION_ID");
215284
+ const storedInstallationId = pick3("GITHUB_APP_INSTALLATION_ID");
215285
+ const live = await listAppInstallations({ appId, privateKeyPem: pem }).catch(() => null);
215286
+ const installationId = live ? (live.find((i) => i.installationId === storedInstallationId) ?? live[0])?.installationId ?? "" : storedInstallationId;
214932
215287
  if (!installationId) {
214933
- res.writeHead(409, { "content-type": "application/json" }).end(JSON.stringify({ error: "\u8FD9\u4E2A App \u8FD8\u6CA1\u6709\u5B89\u88C5\u8BB0\u5F55\uFF0C\u65E0\u6CD5\u94F8 token" }));
215288
+ res.writeHead(409, { "content-type": "application/json" }).end(JSON.stringify({ error: "\u8FD9\u4E2A App \u8FD8\u6CA1\u88C5\u5230\u4EFB\u4F55\u8D26\u53F7\u2014\u2014\u5148\u5728 GitHub \u4E0A\u5B89\u88C5\u5B83\uFF0C\u88C5\u597D\u540E\u518D\u70B9\u8FDE\u63A5" }));
214934
215289
  return;
214935
215290
  }
214936
215291
  const slug6 = pick3("GITHUB_APP_SLUG");
@@ -214968,7 +215323,7 @@ ${composed}`;
214968
215323
  const msg = String(e);
214969
215324
  bad(
214970
215325
  400,
214971
- /HTTP 401/.test(msg) ? "App ID \u4E0E\u79C1\u94A5\u5BF9\u4E0D\u4E0A\u2014\u2014\u786E\u8BA4 App ID \u6284\u81EA\u540C\u4E00\u4E2A App \u7684\u8BBE\u7F6E\u9875" : /私钥解析失败/.test(msg) ? msg.replace(/^Error:\s*/, "").replace(/^github app:\s*/, "") : `GitHub \u62D2\u7EDD\u4E86\u8FD9\u5BF9\u51ED\u636E\uFF1A${msg}`
215326
+ /HTTP 401/.test(msg) ? "\u8FD9\u4E2A\u79C1\u94A5\u548C\u6240\u9009\u7684 App \u5BF9\u4E0D\u4E0A\u2014\u2014\u8BF7\u786E\u8BA4\u5B83\u662F\u5728\u8FD9\u4E2A App \u7684\u8BBE\u7F6E\u9875\u70B9 Generate a private key \u4E0B\u8F7D\u7684\u90A3\u4E00\u4EFD\uFF08\u624B\u586B App ID \u65F6\u4E5F\u6838\u5BF9\u4E00\u4E0B\u6709\u6CA1\u6709\u6284\u9519\uFF09" : /私钥解析失败/.test(msg) ? msg.replace(/^Error:\s*/, "").replace(/^github app:\s*/, "") : `GitHub \u62D2\u7EDD\u4E86\u8FD9\u5BF9\u51ED\u636E\uFF1A${msg}`
214972
215327
  );
214973
215328
  return;
214974
215329
  }
@@ -215638,14 +215993,14 @@ async function markDeliveredWithRetry(store, messageId, attempts = 3) {
215638
215993
  }
215639
215994
  return false;
215640
215995
  }
215641
- var http, import_node_crypto42, CONNECTOR_AUTH_SNAPSHOT_TTL_MS, defaultResolveActor, enc, CHAT_OUTPUT_LIMIT, APPEND_TURN_START_WAIT_MS, STREAMING_PATHS, PLANNER_BLOCKING_CODES, WILL_VERBS, GOVERNANCE_COMMANDS, READ_ONLY_COMMANDS, isAgent, isHuman;
215996
+ var http, import_node_crypto43, CONNECTOR_AUTH_SNAPSHOT_TTL_MS, defaultResolveActor, enc, CHAT_OUTPUT_LIMIT, APPEND_TURN_START_WAIT_MS, STREAMING_PATHS, PLANNER_BLOCKING_CODES, WILL_VERBS, GOVERNANCE_COMMANDS, READ_ONLY_COMMANDS, isAgent, isHuman;
215642
215997
  var init_server3 = __esm({
215643
215998
  "../server/src/server.ts"() {
215644
215999
  "use strict";
215645
216000
  init_src();
215646
216001
  init_file_limits2();
215647
216002
  http = __toESM(require("node:http"), 1);
215648
- import_node_crypto42 = require("node:crypto");
216003
+ import_node_crypto43 = require("node:crypto");
215649
216004
  init_chat_session();
215650
216005
  init_chat_system_message();
215651
216006
  init_sealed_node_receipt();
@@ -215711,6 +216066,7 @@ var init_server3 = __esm({
215711
216066
  init_child_live_turn();
215712
216067
  init_registry_audit_sink();
215713
216068
  init_src8();
216069
+ init_github_app_inventory();
215714
216070
  init_continuation3();
215715
216071
  CONNECTOR_AUTH_SNAPSHOT_TTL_MS = 30 * 60 * 1e3;
215716
216072
  defaultResolveActor = (token) => token.startsWith("token:") ? token.slice("token:".length) : null;
@@ -215825,7 +216181,7 @@ function createNodeTokenStore(file) {
215825
216181
  return {
215826
216182
  issue(nodeId) {
215827
216183
  const table = read3();
215828
- const token = `ont_${(0, import_node_crypto43.randomBytes)(24).toString("base64url")}`;
216184
+ const token = `ont_${(0, import_node_crypto44.randomBytes)(24).toString("base64url")}`;
215829
216185
  table[token] = nodeId;
215830
216186
  write(table);
215831
216187
  return token;
@@ -215885,7 +216241,7 @@ function createEnrollTokenStore(defaultTtlMs = 30 * 60 * 1e3, file) {
215885
216241
  return {
215886
216242
  issue(name, ttlMs = defaultTtlMs, companyId) {
215887
216243
  const m2 = prune();
215888
- const token = `ent_${(0, import_node_crypto43.randomBytes)(24).toString("base64url")}`;
216244
+ const token = `ent_${(0, import_node_crypto44.randomBytes)(24).toString("base64url")}`;
215889
216245
  const expiresAt2 = Date.now() + ttlMs;
215890
216246
  m2.set(token, { name, expiresAt: expiresAt2, ...companyId !== void 0 ? { companyId } : {} });
215891
216247
  write(m2);
@@ -215934,33 +216290,33 @@ function createEnrollTokenStore(defaultTtlMs = 30 * 60 * 1e3, file) {
215934
216290
  }
215935
216291
  };
215936
216292
  }
215937
- var import_node_crypto43, fs22, path21, SESSION_TOKEN_PREFIX, b64url2, fromB64url, readOrCreateSecret, sign, signatureMatches, isTokenClaims;
216293
+ var import_node_crypto44, fs22, path21, SESSION_TOKEN_PREFIX, b64url2, fromB64url, readOrCreateSecret, sign, signatureMatches, isTokenClaims;
215938
216294
  var init_tokens = __esm({
215939
216295
  "../server/src/tokens.ts"() {
215940
216296
  "use strict";
215941
- import_node_crypto43 = require("node:crypto");
216297
+ import_node_crypto44 = require("node:crypto");
215942
216298
  fs22 = __toESM(require("node:fs"), 1);
215943
216299
  path21 = __toESM(require("node:path"), 1);
215944
216300
  SESSION_TOKEN_PREFIX = "oat_v2_";
215945
216301
  b64url2 = (value2) => Buffer.from(value2).toString("base64url");
215946
216302
  fromB64url = (value2) => Buffer.from(value2, "base64url").toString("utf8");
215947
216303
  readOrCreateSecret = (file) => {
215948
- if (!file) return (0, import_node_crypto43.randomBytes)(32);
216304
+ if (!file) return (0, import_node_crypto44.randomBytes)(32);
215949
216305
  try {
215950
216306
  const raw = fs22.readFileSync(file, "utf8").trim();
215951
216307
  if (raw) return Buffer.from(raw, "base64url");
215952
216308
  } catch {
215953
216309
  }
215954
- const secret = (0, import_node_crypto43.randomBytes)(32);
216310
+ const secret = (0, import_node_crypto44.randomBytes)(32);
215955
216311
  fs22.mkdirSync(path21.dirname(file), { recursive: true });
215956
216312
  fs22.writeFileSync(file, secret.toString("base64url"), { mode: 384 });
215957
216313
  return secret;
215958
216314
  };
215959
- sign = (secret, payload2) => (0, import_node_crypto43.createHmac)("sha256", secret).update(payload2).digest("base64url");
216315
+ sign = (secret, payload2) => (0, import_node_crypto44.createHmac)("sha256", secret).update(payload2).digest("base64url");
215960
216316
  signatureMatches = (actual, expected) => {
215961
216317
  const a = Buffer.from(actual);
215962
216318
  const b2 = Buffer.from(expected);
215963
- return a.length === b2.length && (0, import_node_crypto43.timingSafeEqual)(a, b2);
216319
+ return a.length === b2.length && (0, import_node_crypto44.timingSafeEqual)(a, b2);
215964
216320
  };
215965
216321
  isTokenClaims = (value2) => {
215966
216322
  if (value2 === null || typeof value2 !== "object") return false;
@@ -216022,31 +216378,31 @@ function createDataplaneTokenIssuer(opts = {}) {
216022
216378
  }
216023
216379
  };
216024
216380
  }
216025
- var import_node_crypto44, fs23, path22, PREFIX2, b64url3, fromB64url2, sign2, sigMatches, readOrCreateSecret2, isClaims, isDataplaneToken;
216381
+ var import_node_crypto45, fs23, path22, PREFIX2, b64url3, fromB64url2, sign2, sigMatches, readOrCreateSecret2, isClaims, isDataplaneToken;
216026
216382
  var init_session_dataplane_token = __esm({
216027
216383
  "../server/src/session-dataplane-token.ts"() {
216028
216384
  "use strict";
216029
- import_node_crypto44 = require("node:crypto");
216385
+ import_node_crypto45 = require("node:crypto");
216030
216386
  fs23 = __toESM(require("node:fs"), 1);
216031
216387
  path22 = __toESM(require("node:path"), 1);
216032
216388
  init_src();
216033
216389
  PREFIX2 = "osd_";
216034
216390
  b64url3 = (v2) => Buffer.from(v2, "utf8").toString("base64url");
216035
216391
  fromB64url2 = (v2) => Buffer.from(v2, "base64url").toString("utf8");
216036
- sign2 = (secret, payload2) => (0, import_node_crypto44.createHmac)("sha256", secret).update(payload2).digest("base64url");
216392
+ sign2 = (secret, payload2) => (0, import_node_crypto45.createHmac)("sha256", secret).update(payload2).digest("base64url");
216037
216393
  sigMatches = (actual, expected) => {
216038
216394
  const a = Buffer.from(actual);
216039
216395
  const b2 = Buffer.from(expected);
216040
- return a.length === b2.length && (0, import_node_crypto44.timingSafeEqual)(a, b2);
216396
+ return a.length === b2.length && (0, import_node_crypto45.timingSafeEqual)(a, b2);
216041
216397
  };
216042
216398
  readOrCreateSecret2 = (file) => {
216043
- if (!file) return (0, import_node_crypto44.randomBytes)(32);
216399
+ if (!file) return (0, import_node_crypto45.randomBytes)(32);
216044
216400
  try {
216045
216401
  const raw = fs23.readFileSync(file, "utf8").trim();
216046
216402
  if (raw) return Buffer.from(raw, "base64url");
216047
216403
  } catch {
216048
216404
  }
216049
- const secret = (0, import_node_crypto44.randomBytes)(32);
216405
+ const secret = (0, import_node_crypto45.randomBytes)(32);
216050
216406
  fs23.mkdirSync(path22.dirname(file), { recursive: true });
216051
216407
  fs23.writeFileSync(file, secret.toString("base64url"), { mode: 384 });
216052
216408
  return secret;
@@ -218354,13 +218710,22 @@ function cloneOffboarding(op) {
218354
218710
  assignmentDecisions: op.assignmentDecisions.map((d) => ({ ...d, toPrincipal: { ...d.toPrincipal } }))
218355
218711
  };
218356
218712
  }
218357
- var memberKey, scopedKey, bridgeKey, MemoryControlPlaneStore;
218713
+ var memberKey, scopedKey, bridgeKey, migrationRow, MemoryControlPlaneStore;
218358
218714
  var init_memory_control_plane_store = __esm({
218359
218715
  "../testkit/src/memory-control-plane-store.ts"() {
218360
218716
  "use strict";
218361
218717
  memberKey = (companyId, accountId) => `${companyId} ${accountId}`;
218362
218718
  scopedKey = (companyId, id) => `${companyId}\0${id}`;
218363
218719
  bridgeKey = (companyId, type, id) => `${companyId}\0${type}\0${id}`;
218720
+ migrationRow = (companyId, accountId, roleBefore, roleAfter, audit2) => ({
218721
+ companyId,
218722
+ accountId,
218723
+ roleBefore,
218724
+ roleAfter,
218725
+ evidence: audit2.evidence,
218726
+ ...audit2.evidence === "manifest" && audit2.manifestVersion !== void 0 ? { manifestVersion: audit2.manifestVersion } : {},
218727
+ appliedAt: audit2.appliedAt
218728
+ });
218364
218729
  MemoryControlPlaneStore = class {
218365
218730
  companies = /* @__PURE__ */ new Map();
218366
218731
  members = /* @__PURE__ */ new Map();
@@ -218480,6 +218845,21 @@ var init_memory_control_plane_store = __esm({
218480
218845
  this.members.delete(key2);
218481
218846
  return { ok: true, member: { ...target } };
218482
218847
  }
218848
+ async removeMemberAndReleaseOwnedAgents(companyId, accountId, at2) {
218849
+ const target = this.members.get(memberKey(companyId, accountId));
218850
+ if (!target) return { ok: false, reason: "not_found" };
218851
+ if (target.role === "owner") return { ok: false, reason: "owner_protected" };
218852
+ const releasedAgents = [];
218853
+ for (const [key2, e] of this.digitalEmployees) {
218854
+ if (e.companyId !== companyId || e.ownerAccountId !== accountId) continue;
218855
+ const { ownerAccountId: _cleared, ...rest } = e;
218856
+ this.digitalEmployees.set(key2, { ...rest, updatedAt: at2 });
218857
+ releasedAgents.push(e.id);
218858
+ }
218859
+ this.principalBridges.delete(bridgeKey(companyId, "human", accountId));
218860
+ this.members.delete(memberKey(companyId, accountId));
218861
+ return { ok: true, member: { ...target }, releasedAgents };
218862
+ }
218483
218863
  async updateMemberRoleIfNotLastOwner(companyId, accountId, role) {
218484
218864
  const key2 = memberKey(companyId, accountId);
218485
218865
  const target = this.members.get(key2);
@@ -218506,7 +218886,7 @@ var init_memory_control_plane_store = __esm({
218506
218886
  return { ok: false, reason: "target_not_listed" };
218507
218887
  }
218508
218888
  const acc = this.accounts.get(toAccountId);
218509
- if (acc && acc.status !== "active") return { ok: false, reason: "target_inactive" };
218889
+ if (!acc || acc.status !== "active") return { ok: false, reason: "target_inactive" };
218510
218890
  const others = [];
218511
218891
  for (const m2 of this.members.values()) {
218512
218892
  if (m2.companyId === companyId && m2.role === "owner" && m2.accountId !== toAccountId) others.push(m2);
@@ -218523,6 +218903,37 @@ var init_memory_control_plane_store = __esm({
218523
218903
  this.members.set(memberKey(companyId, toAccountId), owner);
218524
218904
  return { ok: true, owner: { ...owner }, demoted };
218525
218905
  }
218906
+ // —— 存量身份映射(ADR D3)——
218907
+ async migrateSoleCompanyOwner(companyId, toAccountId, audit2) {
218908
+ const target = this.members.get(memberKey(companyId, toAccountId));
218909
+ if (!target) return { ok: false, reason: "not_found" };
218910
+ const others = [];
218911
+ for (const m2 of this.members.values()) {
218912
+ if (m2.companyId === companyId && m2.role === "owner" && m2.accountId !== toAccountId) others.push(m2);
218913
+ }
218914
+ if (others.length === 0 && target.role === "owner") {
218915
+ return { ok: true, owner: { ...target }, demoted: [], auditRows: [] };
218916
+ }
218917
+ const auditRows = [];
218918
+ const demoted = others.map((m2) => {
218919
+ const next = { ...m2, role: "admin" };
218920
+ this.members.set(memberKey(companyId, m2.accountId), next);
218921
+ auditRows.push(migrationRow(companyId, m2.accountId, "owner", "admin", audit2));
218922
+ return { ...next };
218923
+ });
218924
+ if (target.role !== "owner") auditRows.push(migrationRow(companyId, toAccountId, target.role, "owner", audit2));
218925
+ const owner = { ...target, role: "owner" };
218926
+ this.members.set(memberKey(companyId, toAccountId), owner);
218927
+ for (const r of auditRows) this.roleMigrations.push({ ...r });
218928
+ return { ok: true, owner: { ...owner }, demoted, auditRows };
218929
+ }
218930
+ async setMemberRoleOnly(companyId, accountId, role) {
218931
+ const key2 = memberKey(companyId, accountId);
218932
+ const cur = this.members.get(key2);
218933
+ if (!cur) return false;
218934
+ this.members.set(key2, { ...cur, role });
218935
+ return true;
218936
+ }
218526
218937
  // —— 存量身份映射审计(ADR D3.4):只追加 ——
218527
218938
  async appendMemberRoleMigrations(rows) {
218528
218939
  for (const r of rows) this.roleMigrations.push({ ...r });
@@ -218793,11 +219204,11 @@ function onlyHits(pool, q2) {
218793
219204
  if (!q2.requireMatch || q2.keywords.length === 0) return [...pool];
218794
219205
  return pool.filter((r) => memoryMatchScore(r, q2.keywords) > 0);
218795
219206
  }
218796
- var import_node_crypto45, MemoryActorMemoryStore, toIndexEntry;
219207
+ var import_node_crypto46, MemoryActorMemoryStore, toIndexEntry;
218797
219208
  var init_memory_actor_memory_store = __esm({
218798
219209
  "../testkit/src/memory-actor-memory-store.ts"() {
218799
219210
  "use strict";
218800
- import_node_crypto45 = require("node:crypto");
219211
+ import_node_crypto46 = require("node:crypto");
218801
219212
  init_src();
218802
219213
  MemoryActorMemoryStore = class {
218803
219214
  rows = /* @__PURE__ */ new Map();
@@ -218851,7 +219262,7 @@ var init_memory_actor_memory_store = __esm({
218851
219262
  async write(input, now) {
218852
219263
  if (input.memId === void 0) {
218853
219264
  const rec2 = {
218854
- memId: `mem:${(0, import_node_crypto45.randomUUID)()}`,
219265
+ memId: `mem:${(0, import_node_crypto46.randomUUID)()}`,
218855
219266
  actorId: input.actorId,
218856
219267
  projectId: input.projectId,
218857
219268
  keywords: [...input.keywords],
@@ -220550,13 +220961,13 @@ var init_stats = __esm({
220550
220961
 
220551
220962
  // ../server/src/domains/projects/project-id.ts
220552
220963
  function randomProjectId() {
220553
- return projectIdFromUuid((0, import_node_crypto46.randomUUID)());
220964
+ return projectIdFromUuid((0, import_node_crypto47.randomUUID)());
220554
220965
  }
220555
- var import_node_crypto46;
220966
+ var import_node_crypto47;
220556
220967
  var init_project_id = __esm({
220557
220968
  "../server/src/domains/projects/project-id.ts"() {
220558
220969
  "use strict";
220559
- import_node_crypto46 = require("node:crypto");
220970
+ import_node_crypto47 = require("node:crypto");
220560
220971
  init_src();
220561
220972
  }
220562
220973
  });
@@ -222631,12 +223042,12 @@ function applyTurnPatch(t, patch) {
222631
223042
  ...patch.lastError !== void 0 ? { lastError: patch.lastError } : {}
222632
223043
  };
222633
223044
  }
222634
- var import_node_crypto47, fs24, path23, NdjsonOplogStore, DirBlobStore, MUTATORS, FileTypeRegistryStore, FileRoleRegistryStore, FileRegistryStore, FileAssistantBindStore, FileHumanPrefsStore, FileProjectStateStore, FileArtifactStateStore, FileTraceStore, MemoryChatSessionStore, FileChatSessionStore, MemoryChatTurnStore, FileChatTurnStore, MemoryReadMarkerStore, FileReadMarkerStore;
223045
+ var import_node_crypto48, fs24, path23, NdjsonOplogStore, DirBlobStore, MUTATORS, FileTypeRegistryStore, FileRoleRegistryStore, FileRegistryStore, FileAssistantBindStore, FileHumanPrefsStore, FileProjectStateStore, FileArtifactStateStore, FileTraceStore, MemoryChatSessionStore, FileChatSessionStore, MemoryChatTurnStore, FileChatTurnStore, MemoryReadMarkerStore, FileReadMarkerStore;
222635
223046
  var init_dev_store = __esm({
222636
223047
  "../server/src/dev-store.ts"() {
222637
223048
  "use strict";
222638
223049
  init_src();
222639
- import_node_crypto47 = require("node:crypto");
223050
+ import_node_crypto48 = require("node:crypto");
222640
223051
  fs24 = __toESM(require("node:fs"), 1);
222641
223052
  path23 = __toESM(require("node:path"), 1);
222642
223053
  init_src();
@@ -222690,7 +223101,7 @@ var init_dev_store = __esm({
222690
223101
  return path23.join(this.dir, hash3);
222691
223102
  }
222692
223103
  async put(bytes2) {
222693
- const hash3 = (0, import_node_crypto47.createHash)("sha256").update(bytes2).digest("hex");
223104
+ const hash3 = (0, import_node_crypto48.createHash)("sha256").update(bytes2).digest("hex");
222694
223105
  const file = this.fileOf(hash3);
222695
223106
  if (!fs24.existsSync(file)) fs24.writeFileSync(file, bytes2);
222696
223107
  return hash3;
@@ -223813,6 +224224,9 @@ var init_control_plane_file_store = __esm({
223813
224224
  "removeMemberIfNotLastOwner",
223814
224225
  "updateMemberRoleIfNotLastOwner",
223815
224226
  "setSoleCompanyOwner",
224227
+ "removeMemberAndReleaseOwnedAgents",
224228
+ "migrateSoleCompanyOwner",
224229
+ "setMemberRoleOnly",
223816
224230
  "appendMemberRoleMigrations",
223817
224231
  "createInvitation",
223818
224232
  "updateInvitationStatus",
@@ -223982,7 +224396,7 @@ var init_company_engine_router = __esm({
223982
224396
 
223983
224397
  // ../server/src/skill-market-client.ts
223984
224398
  function digestOf2(bytes2) {
223985
- return `sha256:${(0, import_node_crypto48.createHash)("sha256").update(bytes2).digest("hex")}`;
224399
+ return `sha256:${(0, import_node_crypto49.createHash)("sha256").update(bytes2).digest("hex")}`;
223986
224400
  }
223987
224401
  function createSkillMarketClientFromEnv(env = process.env) {
223988
224402
  const base = env["SKILL_MARKET_BASE_URL"];
@@ -223993,11 +224407,11 @@ function createSkillMarketClientFromEnv(env = process.env) {
223993
224407
  function sleep4(ms) {
223994
224408
  return new Promise((r) => setTimeout(r, ms));
223995
224409
  }
223996
- var import_node_crypto48, DisabledSkillMarketClient, FakeSkillMarketClient, HttpSkillMarketClient;
224410
+ var import_node_crypto49, DisabledSkillMarketClient, FakeSkillMarketClient, HttpSkillMarketClient;
223997
224411
  var init_skill_market_client = __esm({
223998
224412
  "../server/src/skill-market-client.ts"() {
223999
224413
  "use strict";
224000
- import_node_crypto48 = require("node:crypto");
224414
+ import_node_crypto49 = require("node:crypto");
224001
224415
  init_src();
224002
224416
  DisabledSkillMarketClient = class {
224003
224417
  async listSkills(_input) {
@@ -224848,7 +225262,7 @@ async function copySessionOutputs(deps) {
224848
225262
  continue;
224849
225263
  }
224850
225264
  const saved = existing?.[file.path];
224851
- if (saved && savedContentHash(saved) === (0, import_node_crypto49.createHash)("sha256").update(bytes2).digest("hex")) {
225265
+ if (saved && savedContentHash(saved) === (0, import_node_crypto50.createHash)("sha256").update(bytes2).digest("hex")) {
224852
225266
  const aligned = { ...saved, size: bytes2.length, mtime: file.mtime };
224853
225267
  delete aligned.editedAt;
224854
225268
  realigned.push(aligned);
@@ -224945,11 +225359,11 @@ function createOutputCopyScheduler(deps) {
224945
225359
  }
224946
225360
  };
224947
225361
  }
224948
- var import_node_crypto49, OUTPUT_COPY_LIMITS;
225362
+ var import_node_crypto50, OUTPUT_COPY_LIMITS;
224949
225363
  var init_outputs_copy = __esm({
224950
225364
  "../server/src/domains/chat-sessions/outputs-copy.ts"() {
224951
225365
  "use strict";
224952
- import_node_crypto49 = require("node:crypto");
225366
+ import_node_crypto50 = require("node:crypto");
224953
225367
  init_src();
224954
225368
  OUTPUT_COPY_LIMITS = {
224955
225369
  maxFileBytes: FILE_CONTENT_MAX_BYTES,
@@ -225260,38 +225674,17 @@ async function backfillOrgIdentity(deps, opts = {}) {
225260
225674
  const desc = row.changes.map((c) => `${c.accountId} ${c.roleBefore}\u2192${c.roleAfter}`).join("\uFF0C");
225261
225675
  log3(` ${row.companyId}\uFF1A${desc}${opts.apply ? "" : "\uFF08dry-run\uFF09"}`);
225262
225676
  if (!opts.apply) continue;
225263
- const res = await deps.store.setSoleCompanyOwner(row.companyId, target);
225264
- if (!res.ok) {
225265
- throw new Error(
225266
- `${row.companyId} \u6536\u655B\u5230 ${target} \u5931\u8D25\uFF1A${res.reason}\uFF08target_not_listed = \u90A3\u4E2A\u4EBA\u8FD8\u6CA1\u8D70\u5B8C\u65B0\u624B\u6307\u5F15\uFF1Btarget_inactive = \u8D26\u53F7\u5DF2\u505C\u7528\uFF09`
225267
- );
225268
- }
225269
- applied += 1;
225270
- const at2 = now();
225271
225677
  const evidence = row.decision === "manifest" ? "manifest" : "E1";
225272
- const rows = res.demoted.map((m2) => ({
225273
- companyId: row.companyId,
225274
- accountId: m2.accountId,
225275
- roleBefore: "owner",
225276
- roleAfter: "admin",
225678
+ const res = await deps.store.migrateSoleCompanyOwner(row.companyId, target, {
225277
225679
  evidence,
225278
- ...opts.manifest?.manifestVersion !== void 0 && evidence === "manifest" ? { manifestVersion: opts.manifest.manifestVersion } : {},
225279
- appliedAt: at2
225280
- }));
225281
- const promoted = row.changes.find((c) => c.accountId === target);
225282
- if (promoted) {
225283
- rows.push({
225284
- companyId: row.companyId,
225285
- accountId: target,
225286
- roleBefore: promoted.roleBefore,
225287
- roleAfter: "owner",
225288
- evidence,
225289
- ...opts.manifest?.manifestVersion !== void 0 && evidence === "manifest" ? { manifestVersion: opts.manifest.manifestVersion } : {},
225290
- appliedAt: at2
225291
- });
225680
+ ...opts.manifest?.manifestVersion !== void 0 ? { manifestVersion: opts.manifest.manifestVersion } : {},
225681
+ appliedAt: now()
225682
+ });
225683
+ if (!res.ok) {
225684
+ throw new Error(`${row.companyId} \u6536\u655B\u5230 ${target} \u5931\u8D25\uFF1A${res.reason}\uFF08\u90A3\u4E00\u884C\u4E0D\u5728\u8BE5\u7EC4\u7EC7\u7684\u6210\u5458\u8868\u91CC\uFF09`);
225292
225685
  }
225293
- await deps.store.appendMemberRoleMigrations(rows);
225294
- auditRows += rows.length;
225686
+ applied += 1;
225687
+ auditRows += res.auditRows.length;
225295
225688
  }
225296
225689
  return { ...inv, applied, auditRows };
225297
225690
  }
@@ -225311,7 +225704,7 @@ async function rollbackOrgIdentity(deps, opts) {
225311
225704
  plan.push({ companyId: r.companyId, accountId: r.accountId, from: member.role, to: r.roleBefore });
225312
225705
  log3(` ${r.companyId} ${r.accountId} ${member.role}\u2192${r.roleBefore}${opts.apply ? "" : "\uFF08dry-run\uFF09"}`);
225313
225706
  if (!opts.apply) continue;
225314
- await deps.store.upsertMember({ ...member, role: r.roleBefore });
225707
+ await deps.store.setMemberRoleOnly(r.companyId, r.accountId, r.roleBefore);
225315
225708
  reverted += 1;
225316
225709
  }
225317
225710
  return { plan, reverted };
@@ -225355,11 +225748,11 @@ function typeDefError(def, mode) {
225355
225748
  }
225356
225749
  return null;
225357
225750
  }
225358
- var import_node_crypto50, RESERVED_FIELD_NAMES, ARTIFACT_TYPE_ACCENTS, TYPE_DEF_SCHEMA, TypesAdminService;
225751
+ var import_node_crypto51, RESERVED_FIELD_NAMES, ARTIFACT_TYPE_ACCENTS, TYPE_DEF_SCHEMA, TypesAdminService;
225359
225752
  var init_types_admin = __esm({
225360
225753
  "../server/src/types-admin.ts"() {
225361
225754
  "use strict";
225362
- import_node_crypto50 = require("node:crypto");
225755
+ import_node_crypto51 = require("node:crypto");
225363
225756
  init_zod();
225364
225757
  init_src7();
225365
225758
  init_config_audit();
@@ -225486,7 +225879,7 @@ var init_types_admin = __esm({
225486
225879
  async trace(change, target, by, detail) {
225487
225880
  try {
225488
225881
  await this.audit?.({
225489
- id: `reg_${(0, import_node_crypto50.randomUUID)()}`,
225882
+ id: `reg_${(0, import_node_crypto51.randomUUID)()}`,
225490
225883
  actor: by ?? SYSTEM_CONFIG_ACTOR,
225491
225884
  kind: "registry_change",
225492
225885
  target,
@@ -225634,11 +226027,11 @@ function roleDefError(def, mode) {
225634
226027
  }
225635
226028
  return null;
225636
226029
  }
225637
- var import_node_crypto51, ACCENTS, ROLE_DEF_SCHEMA, RolesAdminService;
226030
+ var import_node_crypto52, ACCENTS, ROLE_DEF_SCHEMA, RolesAdminService;
225638
226031
  var init_roles_admin = __esm({
225639
226032
  "../server/src/roles-admin.ts"() {
225640
226033
  "use strict";
225641
- import_node_crypto51 = require("node:crypto");
226034
+ import_node_crypto52 = require("node:crypto");
225642
226035
  init_zod();
225643
226036
  init_src7();
225644
226037
  init_config_audit();
@@ -225663,7 +226056,7 @@ var init_roles_admin = __esm({
225663
226056
  async trace(change, target, by, detail) {
225664
226057
  try {
225665
226058
  await this.deps.audit?.({
225666
- id: `reg_${(0, import_node_crypto51.randomUUID)()}`,
226059
+ id: `reg_${(0, import_node_crypto52.randomUUID)()}`,
225667
226060
  actor: by ?? SYSTEM_CONFIG_ACTOR,
225668
226061
  kind: "registry_change",
225669
226062
  target,
@@ -228541,8 +228934,8 @@ var init_chat_recovery = __esm({
228541
228934
 
228542
228935
  // ../server/src/auth/crypto.ts
228543
228936
  function hashPassword(password) {
228544
- const salt = (0, import_node_crypto52.randomBytes)(16);
228545
- const dk = (0, import_node_crypto52.scryptSync)(password, salt, SCRYPT_KEYLEN, { N: SCRYPT_N, maxmem: 64 * 1024 * 1024 });
228937
+ const salt = (0, import_node_crypto53.randomBytes)(16);
228938
+ const dk = (0, import_node_crypto53.scryptSync)(password, salt, SCRYPT_KEYLEN, { N: SCRYPT_N, maxmem: 64 * 1024 * 1024 });
228546
228939
  return `scrypt$${SCRYPT_N}$${salt.toString("base64url")}$${dk.toString("base64url")}`;
228547
228940
  }
228548
228941
  function verifyPassword(password, stored) {
@@ -228559,8 +228952,8 @@ function verifyPassword(password, stored) {
228559
228952
  return false;
228560
228953
  }
228561
228954
  if (expected.length === 0) return false;
228562
- const dk = (0, import_node_crypto52.scryptSync)(password, salt, expected.length, { N, maxmem: 64 * 1024 * 1024 });
228563
- return dk.length === expected.length && (0, import_node_crypto52.timingSafeEqual)(dk, expected);
228955
+ const dk = (0, import_node_crypto53.scryptSync)(password, salt, expected.length, { N, maxmem: 64 * 1024 * 1024 });
228956
+ return dk.length === expected.length && (0, import_node_crypto53.timingSafeEqual)(dk, expected);
228564
228957
  }
228565
228958
  function b64urlJson(value2) {
228566
228959
  return Buffer.from(JSON.stringify(value2)).toString("base64url");
@@ -228568,17 +228961,17 @@ function b64urlJson(value2) {
228568
228961
  function signSession(claims, secret) {
228569
228962
  const head = b64urlJson({ alg: "HS256", typ: "JWT" });
228570
228963
  const body2 = b64urlJson(claims);
228571
- const sig = (0, import_node_crypto52.createHmac)("sha256", secret).update(`${head}.${body2}`).digest("base64url");
228964
+ const sig = (0, import_node_crypto53.createHmac)("sha256", secret).update(`${head}.${body2}`).digest("base64url");
228572
228965
  return `${head}.${body2}.${sig}`;
228573
228966
  }
228574
228967
  function verifySession(token, secret, nowMs) {
228575
228968
  const parts = token.split(".");
228576
228969
  if (parts.length !== 3) return null;
228577
228970
  const [head, body2, sig] = parts;
228578
- const expected = (0, import_node_crypto52.createHmac)("sha256", secret).update(`${head}.${body2}`).digest("base64url");
228971
+ const expected = (0, import_node_crypto53.createHmac)("sha256", secret).update(`${head}.${body2}`).digest("base64url");
228579
228972
  const got = Buffer.from(sig);
228580
228973
  const exp = Buffer.from(expected);
228581
- if (got.length !== exp.length || !(0, import_node_crypto52.timingSafeEqual)(got, exp)) return null;
228974
+ if (got.length !== exp.length || !(0, import_node_crypto53.timingSafeEqual)(got, exp)) return null;
228582
228975
  let claims;
228583
228976
  try {
228584
228977
  claims = JSON.parse(Buffer.from(body2, "base64url").toString("utf8"));
@@ -228590,21 +228983,21 @@ function verifySession(token, secret, nowMs) {
228590
228983
  return claims;
228591
228984
  }
228592
228985
  function generateCode() {
228593
- return String((0, import_node_crypto52.randomInt)(0, 1e6)).padStart(6, "0");
228986
+ return String((0, import_node_crypto53.randomInt)(0, 1e6)).padStart(6, "0");
228594
228987
  }
228595
228988
  function hashCode(code2, secret) {
228596
- return (0, import_node_crypto52.createHmac)("sha256", secret).update(`code:${code2}`).digest("base64url");
228989
+ return (0, import_node_crypto53.createHmac)("sha256", secret).update(`code:${code2}`).digest("base64url");
228597
228990
  }
228598
228991
  function safeEqualHash(a, b2) {
228599
228992
  const ba = Buffer.from(a);
228600
228993
  const bb = Buffer.from(b2);
228601
- return ba.length === bb.length && (0, import_node_crypto52.timingSafeEqual)(ba, bb);
228994
+ return ba.length === bb.length && (0, import_node_crypto53.timingSafeEqual)(ba, bb);
228602
228995
  }
228603
- var import_node_crypto52, SCRYPT_N, SCRYPT_KEYLEN;
228996
+ var import_node_crypto53, SCRYPT_N, SCRYPT_KEYLEN;
228604
228997
  var init_crypto2 = __esm({
228605
228998
  "../server/src/auth/crypto.ts"() {
228606
228999
  "use strict";
228607
- import_node_crypto52 = require("node:crypto");
229000
+ import_node_crypto53 = require("node:crypto");
228608
229001
  SCRYPT_N = 16384;
228609
229002
  SCRYPT_KEYLEN = 32;
228610
229003
  }
@@ -229988,18 +230381,18 @@ function variableKeyFromEnv(env = process.env) {
229988
230381
  return buf;
229989
230382
  }
229990
230383
  if (env.NODE_ENV === "production") throw new Error("OASIS_VAR_KEY is required in production");
229991
- return (0, import_node_crypto53.createHash)("sha256").update("oasis-dev-only-variable-key").digest();
230384
+ return (0, import_node_crypto54.createHash)("sha256").update("oasis-dev-only-variable-key").digest();
229992
230385
  }
229993
230386
  function dedupe2(ids2) {
229994
230387
  return [...new Set(ids2.filter((x2) => typeof x2 === "string" && x2.length > 0))];
229995
230388
  }
229996
- var import_node_crypto53, REDACTED_MARKER, ActorsService, MASK_TAIL_MIN_LENGTH, mask, changedKeys;
230389
+ var import_node_crypto54, REDACTED_MARKER, ActorsService, MASK_TAIL_MIN_LENGTH, mask, changedKeys;
229997
230390
  var init_service5 = __esm({
229998
230391
  "../server/src/domains/actors/service.ts"() {
229999
230392
  "use strict";
230000
230393
  init_file_limits2();
230001
230394
  init_keyed_mutex();
230002
- import_node_crypto53 = require("node:crypto");
230395
+ import_node_crypto54 = require("node:crypto");
230003
230396
  init_skill_fetcher();
230004
230397
  init_identity();
230005
230398
  init_migrate();
@@ -230623,7 +231016,7 @@ ${input.description}
230623
231016
  */
230624
231017
  buildSkillFile(skillId, path47, content3, now) {
230625
231018
  const bytes2 = new TextEncoder().encode(content3);
230626
- const blobHash = (0, import_node_crypto53.createHash)("sha256").update(bytes2).digest("hex");
231019
+ const blobHash = (0, import_node_crypto54.createHash)("sha256").update(bytes2).digest("hex");
230627
231020
  return { skillId, path: path47, content: content3, blobHash, size: bytes2.length, updatedAt: now };
230628
231021
  }
230629
231022
  /**
@@ -231746,15 +232139,15 @@ ${input.description}
231746
232139
  return hit ? { value: hit.value, ...hit.origin ? { origin: hit.origin } : {} } : null;
231747
232140
  }
231748
232141
  encrypt(plain) {
231749
- const iv = (0, import_node_crypto53.randomBytes)(12);
231750
- const cipher = (0, import_node_crypto53.createCipheriv)("aes-256-gcm", this.opts.variableKey, iv);
232142
+ const iv = (0, import_node_crypto54.randomBytes)(12);
232143
+ const cipher = (0, import_node_crypto54.createCipheriv)("aes-256-gcm", this.opts.variableKey, iv);
231751
232144
  const enc3 = Buffer.concat([cipher.update(plain, "utf8"), cipher.final()]);
231752
232145
  return [iv.toString("base64"), cipher.getAuthTag().toString("base64"), enc3.toString("base64")].join(".");
231753
232146
  }
231754
232147
  decrypt(packed) {
231755
232148
  const [iv, tag, data] = packed.split(".");
231756
232149
  if (!iv || !tag || typeof data !== "string") throw new Error("malformed ciphertext");
231757
- const decipher = (0, import_node_crypto53.createDecipheriv)("aes-256-gcm", this.opts.variableKey, Buffer.from(iv, "base64"));
232150
+ const decipher = (0, import_node_crypto54.createDecipheriv)("aes-256-gcm", this.opts.variableKey, Buffer.from(iv, "base64"));
231758
232151
  decipher.setAuthTag(Buffer.from(tag, "base64"));
231759
232152
  return Buffer.concat([decipher.update(Buffer.from(data, "base64")), decipher.final()]).toString("utf8");
231760
232153
  }
@@ -232368,7 +232761,7 @@ async function listSkillMetadataForActor(args) {
232368
232761
  usedDirs.add(dir);
232369
232762
  const sorted = [...metas].sort((a, b2) => a.path.localeCompare(b2.path));
232370
232763
  let latest = "1970-01-01T00:00:00.000Z";
232371
- const hasher = (0, import_node_crypto54.createHash)("sha256");
232764
+ const hasher = (0, import_node_crypto55.createHash)("sha256");
232372
232765
  for (const m2 of sorted) {
232373
232766
  if (m2.updatedAt > latest) latest = m2.updatedAt;
232374
232767
  hasher.update(m2.path);
@@ -232393,11 +232786,11 @@ async function listSkillMetadataForActor(args) {
232393
232786
  const dir = sanitizeSkillDir(c.name, c.slug);
232394
232787
  if (usedDirs.has(dir)) continue;
232395
232788
  usedDirs.add(dir);
232396
- const hasher = (0, import_node_crypto54.createHash)("sha256");
232789
+ const hasher = (0, import_node_crypto55.createHash)("sha256");
232397
232790
  for (const p2 of paths) {
232398
232791
  hasher.update(p2);
232399
232792
  hasher.update("\0");
232400
- hasher.update((0, import_node_crypto54.createHash)("sha256").update(c.files[p2]).digest("hex"));
232793
+ hasher.update((0, import_node_crypto55.createHash)("sha256").update(c.files[p2]).digest("hex"));
232401
232794
  hasher.update("\0");
232402
232795
  }
232403
232796
  out.push({
@@ -232418,11 +232811,11 @@ async function listSkillMetadataForActor(args) {
232418
232811
  const dir = sanitizeSkillDir(b2.name, b2.id);
232419
232812
  if (usedDirs.has(dir)) continue;
232420
232813
  usedDirs.add(dir);
232421
- const hasher = (0, import_node_crypto54.createHash)("sha256");
232814
+ const hasher = (0, import_node_crypto55.createHash)("sha256");
232422
232815
  for (const p2 of paths) {
232423
232816
  hasher.update(p2);
232424
232817
  hasher.update("\0");
232425
- hasher.update((0, import_node_crypto54.createHash)("sha256").update(b2.files[p2]).digest("hex"));
232818
+ hasher.update((0, import_node_crypto55.createHash)("sha256").update(b2.files[p2]).digest("hex"));
232426
232819
  hasher.update("\0");
232427
232820
  }
232428
232821
  out.push({
@@ -232442,7 +232835,7 @@ function builtinSkillToFiles(b2) {
232442
232835
  skillId: `builtin:${b2.id}`,
232443
232836
  path: path47,
232444
232837
  content: content3,
232445
- blobHash: (0, import_node_crypto54.createHash)("sha256").update(content3).digest("hex"),
232838
+ blobHash: (0, import_node_crypto55.createHash)("sha256").update(content3).digest("hex"),
232446
232839
  size: Buffer.byteLength(content3, "utf8"),
232447
232840
  updatedAt: "1970-01-01T00:00:00.000Z"
232448
232841
  }));
@@ -232466,7 +232859,7 @@ function sanitizeSkillDir(name, fallbackId) {
232466
232859
  if (byName) return byName;
232467
232860
  const byId = fallbackId ? fold2(fallbackId) : "";
232468
232861
  if (byId) return byId;
232469
- return `skill-${(0, import_node_crypto54.createHash)("sha256").update(name).digest("hex").slice(0, 8)}`;
232862
+ return `skill-${(0, import_node_crypto55.createHash)("sha256").update(name).digest("hex").slice(0, 8)}`;
232470
232863
  }
232471
232864
  async function materializeSkillFiles(args) {
232472
232865
  const skills = await args.service.listInstalledSkillsForActor(args.actorId);
@@ -232499,7 +232892,7 @@ function connectorSkillToFiles(c) {
232499
232892
  skillId: c.id,
232500
232893
  path: path47,
232501
232894
  content: content3,
232502
- blobHash: (0, import_node_crypto54.createHash)("sha256").update(content3).digest("hex"),
232895
+ blobHash: (0, import_node_crypto55.createHash)("sha256").update(content3).digest("hex"),
232503
232896
  size: Buffer.byteLength(content3, "utf8"),
232504
232897
  updatedAt: "1970-01-01T00:00:00.000Z"
232505
232898
  }));
@@ -232516,11 +232909,11 @@ function connectorSkillDisplayRows(connectorSkills, enabledConnectors) {
232516
232909
  ...s2.version !== void 0 ? { version: s2.version } : {}
232517
232910
  }));
232518
232911
  }
232519
- var import_node_crypto54;
232912
+ var import_node_crypto55;
232520
232913
  var init_skill_materializer = __esm({
232521
232914
  "../server/src/domains/actors/skill-materializer.ts"() {
232522
232915
  "use strict";
232523
- import_node_crypto54 = require("node:crypto");
232916
+ import_node_crypto55 = require("node:crypto");
232524
232917
  }
232525
232918
  });
232526
232919
 
@@ -232799,6 +233192,12 @@ function actorsDomain(opts) {
232799
233192
  }
232800
233193
  await asForbidden(() => assertMayManageAgent(opts.agentManageGuard, ctx, { id: actor.id, positions: actor.roles }));
232801
233194
  };
233195
+ const guardPositionsChange = async (req, existing, nextPositions) => {
233196
+ if (existing.kind !== "agent" || nextPositions === void 0) return;
233197
+ if (isAssistantAgent({ positions: existing.roles }) === isAssistantAgent({ positions: nextPositions })) return;
233198
+ const ctx = { companyId: req.auth.companyId, callerActorId: req.auth.actor };
233199
+ await asForbidden(() => assertMayManageAgent(opts.agentManageGuard, ctx, { id: existing.id, positions: nextPositions }));
233200
+ };
232802
233201
  const guardExistingActorWrite = async (req, service, actorId) => {
232803
233202
  const existing = await service.getActor(actorId);
232804
233203
  if (!existing) throw new ApiError(404, "NOT_FOUND", `\u6CA1\u6709\u8FD9\u4E2A\u5458\u5DE5\uFF1A${actorId}`);
@@ -232891,6 +233290,7 @@ function actorsDomain(opts) {
232891
233290
  const positions = positionsFromPayload(b2);
232892
233291
  const avatar = avatarRefFromPayload(b2);
232893
233292
  const tags = tagsFromPayload(b2);
233293
+ await guardPositionsChange(req, existing, positions);
232894
233294
  const {
232895
233295
  positions: _positions,
232896
233296
  id: _id,
@@ -234620,7 +235020,7 @@ function createActorsDomain(opts) {
234620
235020
  ...kernel !== void 0 ? { onRolesChanged: (a, r) => kernel.setActorRoles(a, r) } : {},
234621
235021
  audit: async (entry) => {
234622
235022
  await opts.audit?.({
234623
- id: `reg_${(0, import_node_crypto55.randomUUID)()}`,
235023
+ id: `reg_${(0, import_node_crypto56.randomUUID)()}`,
234624
235024
  actor: entry.by,
234625
235025
  kind: "registry_change",
234626
235026
  target: entry.actorId,
@@ -234660,11 +235060,11 @@ function createActorsDomain(opts) {
234660
235060
  register: actorsDomain({ resolveCtx, ...opts.resolveTrace ? { resolveTrace: opts.resolveTrace } : {}, ...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.companyMemberRole ? { companyMemberRole: opts.companyMemberRole } : {}, ...opts.credentialAudit ? { credentialAudit: opts.credentialAudit } : {}, ...opts.readCredentialAudit ? { readCredentialAudit: opts.readCredentialAudit } : {}, ...opts.credentialRevealLimits ? { credentialRevealLimits: opts.credentialRevealLimits } : {}, ...opts.skillMarket ? { skillMarket: opts.skillMarket } : {}, ...opts.resolveNodeTenancy ? { resolveNodeTenancy: opts.resolveNodeTenancy } : {}, ...opts.agentManageGuard ? { agentManageGuard: opts.agentManageGuard } : {} })
234661
235061
  };
234662
235062
  }
234663
- var import_node_crypto55;
235063
+ var import_node_crypto56;
234664
235064
  var init_actors = __esm({
234665
235065
  "../server/src/domains/actors/index.ts"() {
234666
235066
  "use strict";
234667
- import_node_crypto55 = require("node:crypto");
235067
+ import_node_crypto56 = require("node:crypto");
234668
235068
  init_service5();
234669
235069
  init_routes3();
234670
235070
  init_service5();
@@ -236353,6 +236753,13 @@ function isPendingActor(id) {
236353
236753
  function isAgentActor(id) {
236354
236754
  return typeof id === "string" && id.startsWith("actor:agent:");
236355
236755
  }
236756
+ function isBlockingBackpressure(i) {
236757
+ if (i.kind === "gap" || i.kind === "escalation") return false;
236758
+ if (i.intent === "advisory") return false;
236759
+ const from = i.raisedByNodeId;
236760
+ const about = i.aboutNodeId;
236761
+ return !!from && !!about && from !== about;
236762
+ }
236356
236763
  function isDispatchLayerFailure(detail) {
236357
236764
  const reason = detail?.reason;
236358
236765
  return typeof reason === "string" && reason.startsWith("dispatch ");
@@ -236577,12 +236984,8 @@ function buildWorkorderActivity(input) {
236577
236984
  } else if (r.decision === "approved" || r.decision === "completed") h.to = h.to ?? r.decidedAt ?? r.at;
236578
236985
  }
236579
236986
  for (const i of snap.issues) {
236580
- if (i.kind === "gap" || i.kind === "escalation") continue;
236581
- if (i.intent === "advisory") continue;
236582
- const from = i.raisedByNodeId;
236583
- const about = i.aboutNodeId;
236584
- if (!from || !about || from === about) continue;
236585
- addHold(from, { kind: "backpressure", from: i.createdAt, to: i.resolvedAt ?? null, upstream: about });
236987
+ if (!isBlockingBackpressure(i)) continue;
236988
+ addHold(i.raisedByNodeId, { kind: "backpressure", from: i.createdAt, to: i.resolvedAt ?? null, upstream: i.aboutNodeId });
236586
236989
  }
236587
236990
  const HOLD_RANK = { paused: 0, continuationDenied: 1, continuation: 2, backpressure: 3 };
236588
236991
  const holdOf2 = (w2) => {
@@ -236602,11 +237005,50 @@ function buildWorkorderActivity(input) {
236602
237005
  };
236603
237006
  const lastStampOf = (w2) => [w2.createdAt, w2.startedAt, w2.endedAt, w2.deadAt, w2.cancelledAt].filter((t) => !!t).sort().at(-1);
236604
237007
  const concludedRootBriefNodeIds = /* @__PURE__ */ new Set();
237008
+ const blockingBackpressure = snap.issues.filter((i) => isBlockingBackpressure(i));
237009
+ const blockingBackpressureIds = new Set(blockingBackpressure.map((i) => i.id));
237010
+ const backpressureRaisedBy = /* @__PURE__ */ new Map();
237011
+ for (const i of [...blockingBackpressure].sort((a, b2) => a.createdAt.localeCompare(b2.createdAt) || a.id.localeCompare(b2.id))) {
237012
+ const from = i.raisedByNodeId;
237013
+ backpressureRaisedBy.set(from, [...backpressureRaisedBy.get(from) ?? [], i]);
237014
+ }
237015
+ const backpressureOnRaiserCard = /* @__PURE__ */ new Set();
237016
+ const backpressureDriving = /* @__PURE__ */ new Map();
237017
+ {
237018
+ const add = (workId, issueId) => {
237019
+ if (!issueId || !blockingBackpressureIds.has(issueId)) return;
237020
+ const list2 = backpressureDriving.get(workId) ?? [];
237021
+ if (!list2.includes(issueId)) list2.push(issueId);
237022
+ backpressureDriving.set(workId, list2);
237023
+ };
237024
+ for (const rec2 of events) {
237025
+ const ev = rec2.event;
237026
+ if (ev.kind !== "work.create" || !ev.workId) continue;
237027
+ for (const r of ev.reasons ?? []) if (r.kind === "issue") add(ev.workId, r.issueId);
237028
+ }
237029
+ for (const w2 of snap.works) for (const id of w2.frozenIssueIds ?? []) add(w2.id, id);
237030
+ }
237031
+ const backpressureTargetName = (nodeId) => nodeId && rootBriefNodeIds.has(nodeId) ? "\u4EFB\u52A1\u8BF4\u660E" : nodeName(nodeId);
237032
+ const backpressureOwnerOf = (nodeId) => {
237033
+ const assignee = nodeId ? nodeById.get(nodeId)?.assigneeActorId : void 0;
237034
+ return assignee && !isPendingActor(assignee) ? assignee : ownerId;
237035
+ };
237036
+ const backpressureRaisersOf = (workId) => {
237037
+ const ids2 = backpressureDriving.get(workId);
237038
+ if (!ids2?.length) return void 0;
237039
+ const authors = [...new Set(ids2.map((id) => issueById.get(id)?.authorActorId).filter((a) => !!a))];
237040
+ return authors.length ? authors.map(nameOf2).join("\u3001") : void 0;
237041
+ };
236605
237042
  for (const [nodeId, works] of worksOfNode) {
236606
237043
  const interrupts = [...interruptsOfNode.get(nodeId) ?? []].sort((a, b2) => a.at.localeCompare(b2.at) || a.issueId.localeCompare(b2.issueId));
236607
237044
  const trips = stopLossOfNode.get(nodeId) ?? [];
236608
237045
  const interventions = [...interventionsOfNode.get(nodeId) ?? []].sort();
236609
237046
  const humanBetween = (prev, w2) => !!prev && interventions.some((t) => t >= lastStampOf(prev) && t <= w2.createdAt);
237047
+ const upstreamRoundBetween = (prev, w2) => {
237048
+ if (!prev) return false;
237049
+ const ancestors = ancestorsOf(nodeId);
237050
+ return snap.works.some((u) => u.lane !== "reply" && ancestors.has(u.nodeId) && u.createdAt > prev.createdAt && u.createdAt <= w2.createdAt);
237051
+ };
236610
237052
  const node2 = nodeById.get(nodeId);
236611
237053
  const nodeCancelledAt = node2?.cancelledAt ?? null;
236612
237054
  const name = nodeName(nodeId);
@@ -236618,17 +237060,20 @@ function buildWorkorderActivity(input) {
236618
237060
  const st = statusOf2(w2);
236619
237061
  const nextStart = nextStartOf.get(w2.id) ?? null;
236620
237062
  const interrupt = interrupts.find((t) => t.at >= w2.createdAt && (!nextStart || t.at < nextStart));
237063
+ const roundEndedAt = w2.endedAt ?? w2.deadAt ?? w2.cancelledAt;
237064
+ const raisedHere = !w2.conclusion && !interrupt ? (backpressureRaisedBy.get(nodeId) ?? []).filter((i) => i.createdAt >= w2.createdAt && (!nextStart || i.createdAt < nextStart) && (!roundEndedAt || i.createdAt <= roundEndedAt)) : [];
237065
+ const halted = !!interrupt || raisedHere.length > 0;
236621
237066
  const tripsHere = trips.filter((t) => t.at >= w2.createdAt && (!nextStart || t.at < nextStart));
236622
237067
  const removedHere = !!nodeCancelledAt && nodeCancelledAt >= w2.createdAt && (!next || nodeCancelledAt < next.createdAt);
236623
237068
  const reassigned = !!next && next.assigneeActorId !== w2.assigneeActorId;
236624
237069
  const killedByPlan = !!(w2.deadAt || w2.cancelledAt) && !!next && (reassigned || (next.nodeVersion ?? 0) > (w2.nodeVersion ?? 0));
236625
237070
  const killedAtOfRound = w2.deadAt ?? w2.cancelledAt;
236626
- const supersededBy = !w2.conclusion && !interrupt && !removedHere && !killedByPlan && killedAtOfRound && next && !killedByWorkorderPause(w2, killedAtOfRound) ? supersedingUpstreamVersion(nodeId, w2, killedAtOfRound, next) : void 0;
236627
- const failedRound = !w2.conclusion && !interrupt && !removedHere && !killedByPlan && !supersededBy && (!!w2.deadAt || !!w2.cancelledAt || st === "failed" || st === "retry" || noOutputRound(w2));
237071
+ const supersededBy = !w2.conclusion && !halted && !removedHere && !killedByPlan && killedAtOfRound && next && !killedByWorkorderPause(w2, killedAtOfRound) ? supersedingUpstreamVersion(nodeId, w2, killedAtOfRound, next) : void 0;
237072
+ const failedRound = !w2.conclusion && !halted && !removedHere && !killedByPlan && !supersededBy && (!!w2.deadAt || !!w2.cancelledAt || st === "failed" || st === "retry" || noOutputRound(w2));
236628
237073
  const foldableFailure = failedRound && !(killedAtOfRound && killedByWorkorderPause(w2, killedAtOfRound));
236629
- const ackedRound = st === "success" && !!w2.upstreamAck && !w2.conclusion && artifacts.length === 0 && !interrupt && !removedHere && !killedByPlan && !supersededBy && !failedRound;
236630
- const held = !w2.conclusion && !interrupt && !removedHere && !killedByPlan && !supersededBy && !failedRound && !ackedRound ? holdOf2(w2) : void 0;
236631
- const handedOverRound = !w2.conclusion && !interrupt && !removedHere && !killedByPlan && !supersededBy && !failedRound && !held && !ackedRound && st === "success" && !!w2.endedAt && !!next;
237074
+ const ackedRound = st === "success" && !!w2.upstreamAck && !w2.conclusion && artifacts.length === 0 && !halted && !removedHere && !killedByPlan && !supersededBy && !failedRound;
237075
+ const held = !w2.conclusion && !halted && !removedHere && !killedByPlan && !supersededBy && !failedRound && !ackedRound ? holdOf2(w2) : void 0;
237076
+ const handedOverRound = !w2.conclusion && !halted && !removedHere && !killedByPlan && !supersededBy && !failedRound && !held && !ackedRound && st === "success" && !!w2.endedAt && !!next;
236632
237077
  const refiredByFuse = tripsHere.some((t) => t.kind === "output_stall");
236633
237078
  const brokenByHuman = humanBetween(works[idx - 1], w2) && !refiredByFuse;
236634
237079
  if (foldableFailure && fold2 && !brokenByHuman) {
@@ -236641,7 +237086,7 @@ function buildWorkorderActivity(input) {
236641
237086
  if (tripsHere.length) fold2.stopLossTrips = [...fold2.stopLossTrips ?? [], ...tripsHere];
236642
237087
  continue;
236643
237088
  }
236644
- const absorb2 = fold2 && !brokenByHuman && !!w2.conclusion && fold2.executorId === w2.assigneeActorId ? fold2 : null;
237089
+ const absorb2 = fold2 && !brokenByHuman && !!w2.conclusion && fold2.executorId === w2.assigneeActorId && !upstreamRoundBetween(works[idx - 1], w2) ? fold2 : null;
236645
237090
  fold2 = null;
236646
237091
  if (handedOverRound && handoverFold && handoverFold.executorId === w2.assigneeActorId) {
236647
237092
  handoverFold.traceWorkIds.push(w2.id);
@@ -236676,6 +237121,7 @@ function buildWorkorderActivity(input) {
236676
237121
  }
236677
237122
  executionCards.push(card2);
236678
237123
  cardOfWork.set(w2.id, card2);
237124
+ if (isHumanActorId(card2.executorId) && backpressureDriving.has(w2.id)) card2.humanTrace = true;
236679
237125
  if (w2.conclusion) {
236680
237126
  card2.phase = "done";
236681
237127
  if (rootBriefNodeIds.has(nodeId)) {
@@ -236691,6 +237137,10 @@ function buildWorkorderActivity(input) {
236691
237137
  card2.status = awaits ? ACTIVITY_COPY.doneAwaitingReview(name) : ACTIVITY_COPY.done(name);
236692
237138
  }
236693
237139
  }
237140
+ const bpRaisers = backpressureRaisersOf(w2.id);
237141
+ if (bpRaisers && (card2.status === ACTIVITY_COPY.done(name) || card2.status === ACTIVITY_COPY.goalConfirmed || card2.status === ACTIVITY_COPY.goalRevised)) {
237142
+ card2.status = ACTIVITY_COPY.backpressureAddressed(bpRaisers, backpressureTargetName(nodeId));
237143
+ }
236694
237144
  } else if (interrupt) {
236695
237145
  card2.phase = "stuck";
236696
237146
  card2.segmentOpen = false;
@@ -236699,6 +237149,18 @@ function buildWorkorderActivity(input) {
236699
237149
  card2.actions = [{ kind: "open_thread", label: BUTTON.view, target: gapThreadSessionId(interrupt.authorId), variant: "quiet" }];
236700
237150
  }
236701
237151
  if (interrupt.at > card2.updatedAt) card2.updatedAt = interrupt.at;
237152
+ } else if (raisedHere.length) {
237153
+ card2.phase = "stuck";
237154
+ card2.segmentOpen = false;
237155
+ card2.awaitingHuman = false;
237156
+ const upstreams = [...new Set(raisedHere.map((i) => i.aboutNodeId))];
237157
+ const targets = upstreams.map(backpressureTargetName);
237158
+ const owners = [...new Set(upstreams.map((u) => nameOf2(backpressureOwnerOf(u))))];
237159
+ card2.status = artifacts.length ? ACTIVITY_COPY.backpressureAfterStage(name, targets, owners) : ACTIVITY_COPY.backpressure(nameOf2(card2.executorId), targets, owners);
237160
+ for (const i of raisedHere) {
237161
+ backpressureOnRaiserCard.add(i.id);
237162
+ if (i.createdAt > card2.updatedAt) card2.updatedAt = i.createdAt;
237163
+ }
236702
237164
  } else if (supersededBy) {
236703
237165
  card2.phase = "stuck";
236704
237166
  card2.segmentOpen = false;
@@ -236861,7 +237323,7 @@ function buildWorkorderActivity(input) {
236861
237323
  const forcedBy = str5(reasons.find((r) => r.kind === "forced")?.actorId);
236862
237324
  const parentWorkId = str5(workById.get(workId)?.parentWorkId);
236863
237325
  const reworkOfRejected = !!parentWorkId && workById.get(parentWorkId)?.acceptanceState === "rejected";
236864
- const status = reasonKinds.has("rework") || reworkOfRejected ? ACTIVITY_COPY.reworking(nodeName(nodeId)) : reasonKinds.has("resumed") ? ACTIVITY_COPY.resumed(nameOf2(d.executorId), nodeName(nodeId)) : reasonKinds.has("spec_changed") ? ACTIVITY_COPY.specChanged(nodeName(nodeId)) : reasonKinds.has("issue") ? ACTIVITY_COPY.issueDriven(nodeName(nodeId)) : forcedBy ? ACTIVITY_COPY.forcedRerun(nameOf2(forcedBy), nodeName(nodeId)) : ACTIVITY_COPY.working(nodeName(nodeId));
237326
+ const status = reasonKinds.has("rework") || reworkOfRejected ? ACTIVITY_COPY.reworking(nodeName(nodeId)) : reasonKinds.has("resumed") ? ACTIVITY_COPY.resumed(nameOf2(d.executorId), nodeName(nodeId)) : reasonKinds.has("spec_changed") ? ACTIVITY_COPY.specChanged(nodeName(nodeId)) : reasonKinds.has("issue") ? backpressureRaisersOf(workId) ? ACTIVITY_COPY.backpressureInProgress(backpressureRaisersOf(workId), backpressureTargetName(nodeId)) : ACTIVITY_COPY.issueDriven(nodeName(nodeId)) : forcedBy ? ACTIVITY_COPY.forcedRerun(nameOf2(forcedBy), nodeName(nodeId)) : ACTIVITY_COPY.working(nodeName(nodeId));
236865
237327
  for (const r of reasons) {
236866
237328
  if (r.kind !== "issue") continue;
236867
237329
  const issueId = str5(r.issueId);
@@ -236979,6 +237441,7 @@ function buildWorkorderActivity(input) {
236979
237441
  const raisedFrom = str5(ev.raisedByNodeId);
236980
237442
  const isBackpressure = !isEscalation && !isGap && !!raisedFrom && raisedFrom !== nodeId;
236981
237443
  if (isBackpressure) backpressureIssueIds.add(issueId);
237444
+ if (isBackpressure && backpressureOnRaiserCard.has(issueId)) break;
236982
237445
  const body2 = str5(ev.body) ?? "";
236983
237446
  if (isEscalation) {
236984
237447
  const linkedGapId = str5(ev.gapId);
@@ -237033,8 +237496,8 @@ function buildWorkorderActivity(input) {
237033
237496
  ...isBackpressure ? { segmentOpen: false, awaitingHuman: false } : {},
237034
237497
  status: isBackpressure ? ACTIVITY_COPY.backpressure(
237035
237498
  nameOf2(authorId),
237036
- nodeName(nodeId),
237037
- nameOf2((nodeId ? nodeById.get(nodeId)?.assigneeActorId : void 0) ?? ownerId)
237499
+ [backpressureTargetName(nodeId)],
237500
+ [nameOf2((nodeId ? nodeById.get(nodeId)?.assigneeActorId : void 0) ?? ownerId)]
237038
237501
  ) : ACTIVITY_COPY.commentRaised(nameOf2(authorId), nameOf2(commentTargetId ?? ownerId), nodeName(nodeId)),
237039
237502
  /* **正文一律不上卡**:缺口去缺口线、回压去产物批注、留言去留言线。
237040
237503
  此前只有缺口/回压被排除,留言仍是「正文当小状态 + 展开区再印一遍」,
@@ -237368,7 +237831,7 @@ function buildWorkorderActivity(input) {
237368
237831
  card2.phaseLabel = PHASE_LABEL.escalationPending;
237369
237832
  card2.segmentOpen = true;
237370
237833
  card2.awaitingHuman = true;
237371
- card2.status = ACTIVITY_COPY.backpressureAwaitingOwner(nameOf2(card2.executorId), nodeName(card2.nodeId));
237834
+ card2.status = ACTIVITY_COPY.backpressureAwaitingOwner(nameOf2(card2.executorId), backpressureTargetName(card2.nodeId));
237372
237835
  card2.actions = [{ kind: "open_issue", label: BUTTON.handle, target: issueId, variant: "primary" }];
237373
237836
  }
237374
237837
  for (const card2 of executionCards) {
@@ -237571,7 +238034,7 @@ function buildWorkorderActivity(input) {
237571
238034
  artifacts: d.artifacts,
237572
238035
  actions: d.actions,
237573
238036
  ...traced && d.traceAvailable ? { traceAvailable: true } : {},
237574
- ...d.traceWorkIds?.length ? { traceWorkIds: d.traceWorkIds, ...traced ? { traceAvailable: true } : {} } : {},
238037
+ ...d.traceWorkIds?.length ? { traceWorkIds: d.traceWorkIds, ...traced || d.humanTrace ? { traceAvailable: true } : {} } : {},
237575
238038
  .../* @__PURE__ */ ((r) => r ? { runId: r, traceAvailable: true } : {})(traced ? runIdOfCard(d) : void 0)
237576
238039
  };
237577
238040
  });
@@ -237828,6 +238291,7 @@ var init_activity2 = __esm({
237828
238291
  heldByContinuation: (node2) => `\u300A${node2}\u300B\u5DF2\u4EA4\u4ED8\u9636\u6BB5\u6210\u679C\uFF0C\u672C\u6279\u7EED\u8DD1\u989D\u5EA6\u5DF2\u7528\u5B8C\uFF0C\u7B49\u5F85\u6279\u51C6\u7EE7\u7EED\u3002`,
237829
238292
  heldByContinuationDenied: (node2) => `\u300A${node2}\u300B\u5DF2\u4EA4\u4ED8\u9636\u6BB5\u6210\u679C\uFF0C\u7EED\u8DD1\u7533\u8BF7\u672A\u83B7\u6279\u51C6\uFF0C\u8282\u70B9\u5DF2\u6682\u505C\uFF0C\u7B49\u5F85\u653E\u884C\u3002`,
237830
238293
  heldByWorkorderPause: (node2) => `\u300A${node2}\u300B\u5DF2\u4EA4\u4ED8\u9636\u6BB5\u6210\u679C\uFF0C\u4EFB\u52A1\u5DF2\u6682\u505C\uFF0C\u7B49\u5F85\u6062\u590D\u3002`,
238294
+ /** 只剩「交完稿、这一轮结束之后才补提」的那一档;同一轮里提的见 {@link ACTIVITY_COPY.backpressureAfterStage}(ADR 0623 补章)。 */
237831
238295
  heldByBackpressure: (node2, upstreams) => `\u300A${node2}\u300B\u5DF2\u4EA4\u4ED8\uFF0C\u7B49\u5F85${upstreams.map((u) => `\u300A${u}\u300B`).join("\u3001")}\u5904\u7406\u4FEE\u6539\u8981\u6C42\u540E\u518D\u6536\u53E3\u3002`,
237832
238296
  /** `works.status=retry`:这一轮没跑成,等引擎重派。谁按的重试由 `plan.node_retry` 补进这句。 */
237833
238297
  awaitingRedispatch: (executor, node2) => `${executor} \u8FD9\u4E00\u8F6E\u6CA1\u8DD1\u6210\u300A${node2}\u300B\uFF0C\u7B49\u5F85\u91CD\u6D3E\u3002`,
@@ -237880,8 +238344,27 @@ var init_activity2 = __esm({
237880
238344
  (「{X} 遇到处理不了的问题,需要 @{Y} 的协助」)——**别在别处复用**:系统代开的两种止损
237881
238345
  上报根本没有人「开口」,套这句话是假话(生产上它已经把 `actor:system` 渲染成了句子的主语)。
237882
238346
  ①「缺口卡」也删掉(F4):它和执行卡挂的是同一段运行、同一份轨迹,点哪张打开的都一样。 */
237883
- /** 回压(下游对上游提修改要求)——发起人 2026-09-08 认可的措辞。 */
237884
- backpressure: (raiser, upstream, owner) => `${raiser} \u5BF9\u300A${upstream}\u300B\u63D0\u51FA\u4FEE\u6539\u8981\u6C42\uFF0C\u9700\u8981 @${owner} \u5904\u7406\u3002`,
238347
+ /**
238348
+ * 回压(下游对上游提修改要求)——发起人 2026-09-08 认可的措辞。
238349
+ *
238350
+ * 2026-09-23 起它是**提出方那一轮执行卡的结局句**(奕璇定,与缺口 F4 同一做法):提要求这件事就发生在
238351
+ * 那一轮里,不再单独出一张 `issue:` 卡。同一轮对几个上游提了要求时并成一句(《A》、《B》 / @X、@Y)。
238352
+ * 🚨 **之后一个字不改**:要求关掉了也不翻署名、不改写成「已基于…更新了一版」——那是被要求方那张卡的事。
238353
+ */
238354
+ backpressure: (raiser, upstreams, owners) => `${raiser} \u5BF9${upstreams.map((u) => `\u300A${u}\u300B`).join("\u3001")}\u63D0\u51FA\u4FEE\u6539\u8981\u6C42\uFF0C\u9700\u8981 ${owners.map((o) => `@${o}`).join("\u3001")} \u5904\u7406\u3002`,
238355
+ /**
238356
+ * 同上,但提出方**这一轮先交了阶段成果**(propose 了、没 conclude)、同一轮又提了修改要求(奕璇 2026-09-23 定,
238357
+ * ADR 0623 补章):此前这一档出两张卡——自己那轮「已交付,等待《X》处理修改要求后再收口」+ 一张 `issue:` 回压卡。
238358
+ * 合成这一句:「交了什么」由卡上 / 轨迹里的交付物说,句子说「停在哪、等谁」。之后同样一个字不改。
238359
+ */
238360
+ backpressureAfterStage: (node2, upstreams, owners) => `\u300A${node2}\u300B\u5DF2\u63D0\u4EA4\u9636\u6BB5\u6210\u679C\uFF0C\u5BF9${upstreams.map((u) => `\u300A${u}\u300B`).join("\u3001")}\u63D0\u51FA\u4FEE\u6539\u8981\u6C42\uFF0C\u9700\u8981 ${owners.map((o) => `@${o}`).join("\u3001")} \u5904\u7406\u3002`,
238361
+ /** 被要求方是 agent、正在按修改要求改的那一轮(取代通用的「正在根据反馈调整」:说清是谁提的)。 */
238362
+ backpressureInProgress: (raisers, node2) => `\u6B63\u5728\u6839\u636E ${raisers} \u7684\u4FEE\u6539\u8981\u6C42\u8C03\u6574\u300A${node2}\u300B\u3002`,
238363
+ /**
238364
+ * 被要求方按修改要求交出了新一版(agent / 真人同一句)。取代「《X》已完成」「已修订目标」——
238365
+ * 此前同一件事还另有一张回压卡收口成「{X} 已基于 {Z} 的留言更新了一版」,一件事三张卡。
238366
+ */
238367
+ backpressureAddressed: (raisers, node2) => `\u5DF2\u6309 ${raisers} \u7684\u4FEE\u6539\u8981\u6C42\u66F4\u65B0\u300A${node2}\u300B\u3002`,
237885
238368
  /**
237886
238369
  * 被回压节点的负责人是**真人**时,系统为他开的那一轮(奕璇 2026-09-22 定):这一轮不会自己跑,
237887
238370
  * 是在等他动手——不能再说「正在根据反馈调整」(`issueDriven`),那句会让一张没人在动的卡挂成「进行中」。
@@ -238040,6 +238523,26 @@ function buildWorkorderActivityTrace(input) {
238040
238523
  if (!briefing) continue;
238041
238524
  firstBriefing ??= briefing;
238042
238525
  const sources = sourcesOfWork(work, input, briefing);
238526
+ const humanBackpressureRound = isHumanActorId(work.assigneeActorId) && sources.some((src) => {
238527
+ const issue2 = src.kind === "comment" ? snap.issues.find((i) => i.id === src.issueId) : void 0;
238528
+ return !!issue2 && isBlockingBackpressure(issue2);
238529
+ });
238530
+ if (humanBackpressureRound) {
238531
+ const before = previousVersionOf(work, snap);
238532
+ works.push({
238533
+ workId,
238534
+ at: work.createdAt,
238535
+ nodeTitle: briefing.nodeTitle,
238536
+ ...briefing.spec ? { spec: briefing.spec } : {},
238537
+ sources,
238538
+ attempts: [],
238539
+ artifacts: [
238540
+ ...before ? artifactsOfWork(before, input).map((a) => ({ ...a, name: `${a.name}\uFF08\u6539\u524D\uFF09` })) : [],
238541
+ ...artifactsOfWork(work, input).map((a) => ({ ...a, name: `${a.name}\uFF08\u6539\u540E\uFF09` }))
238542
+ ]
238543
+ });
238544
+ continue;
238545
+ }
238043
238546
  const artifacts = single.attempts.at(-1)?.artifacts ?? artifactsOfWork(work, input);
238044
238547
  works.push({
238045
238548
  workId,
@@ -238113,6 +238616,13 @@ function sourcesOfWork(work, input, briefing) {
238113
238616
  ...briefing.spec ? { content: briefing.spec } : {}
238114
238617
  }];
238115
238618
  }
238619
+ function previousVersionOf(work, snap) {
238620
+ const delivered = new Set(snap.artifacts.map((a) => a.workId));
238621
+ const parentId = work.parentWorkId;
238622
+ const parent = parentId ? snap.works.find((w2) => w2.id === parentId && delivered.has(w2.id)) : void 0;
238623
+ if (parent) return parent;
238624
+ return snap.works.filter((w2) => w2.nodeId === work.nodeId && w2.id !== work.id && w2.createdAt < work.createdAt && delivered.has(w2.id)).sort((a, b2) => a.createdAt.localeCompare(b2.createdAt) || a.id.localeCompare(b2.id)).at(-1);
238625
+ }
238116
238626
  function artifactsOfWork(work, input) {
238117
238627
  const snap = input.snap;
238118
238628
  const workId = work.id;
@@ -238271,6 +238781,7 @@ var init_activity_trace = __esm({
238271
238781
  "use strict";
238272
238782
  init_src6();
238273
238783
  init_review_activity();
238784
+ init_src();
238274
238785
  init_activity2();
238275
238786
  init_workorder_manager();
238276
238787
  init_src7();
@@ -239182,11 +239693,11 @@ function fromRow(row) {
239182
239693
  for (const [k2, v2] of opt) if (v2 !== void 0 && v2 !== null) r[k2] = v2;
239183
239694
  return r;
239184
239695
  }
239185
- var import_node_crypto56, import_node_fs16, DEFAULT_PAGE, MAX_PAGE, pageSize, MemoryNotificationStore, ident3, iso2, str2, PostgresNotificationStore;
239696
+ var import_node_crypto57, import_node_fs16, DEFAULT_PAGE, MAX_PAGE, pageSize, MemoryNotificationStore, ident3, iso2, str2, PostgresNotificationStore;
239186
239697
  var init_notification_store = __esm({
239187
239698
  "../server/src/domains/collab/notification-store.ts"() {
239188
239699
  "use strict";
239189
- import_node_crypto56 = require("node:crypto");
239700
+ import_node_crypto57 = require("node:crypto");
239190
239701
  import_node_fs16 = __toESM(require("node:fs"), 1);
239191
239702
  DEFAULT_PAGE = 50;
239192
239703
  MAX_PAGE = 200;
@@ -239219,7 +239730,7 @@ var init_notification_store = __esm({
239219
239730
  async insert(n) {
239220
239731
  const existing = this.findLive(n.recipient, n.sourceKey, n.category);
239221
239732
  if (existing) return existing;
239222
- const row = { ...n, id: `ntf:${(0, import_node_crypto56.randomUUID)()}`, updatedAt: n.enteredAt };
239733
+ const row = { ...n, id: `ntf:${(0, import_node_crypto57.randomUUID)()}`, updatedAt: n.enteredAt };
239223
239734
  this.rows.set(row.id, row);
239224
239735
  this.save();
239225
239736
  return row;
@@ -239352,7 +239863,7 @@ var init_notification_store = __esm({
239352
239863
  return new _PostgresNotificationStore(sql, schema);
239353
239864
  }
239354
239865
  async insert(n) {
239355
- const id = `ntf:${(0, import_node_crypto56.randomUUID)()}`;
239866
+ const id = `ntf:${(0, import_node_crypto57.randomUUID)()}`;
239356
239867
  const r = await this.sql.query(
239357
239868
  `INSERT INTO ${this.t} (id, recipient, category, source_key, kind, workorder_id, node_id, card_id, actor_id,
239358
239869
  title, workspace_title, entered_at, updated_at, status, read_at, item)
@@ -240481,13 +240992,13 @@ function nameKey(name) {
240481
240992
  function cleanName(name) {
240482
240993
  return name.trim().replace(/\s+/g, " ");
240483
240994
  }
240484
- var import_node_fs17, import_node_path21, import_node_crypto57, SEP, keyOf, prefixOf, MemoryWorkorderTagStore, FileWorkorderTagStore;
240995
+ var import_node_fs17, import_node_path21, import_node_crypto58, SEP, keyOf, prefixOf, MemoryWorkorderTagStore, FileWorkorderTagStore;
240485
240996
  var init_tags = __esm({
240486
240997
  "../server/src/domains/collab/tags.ts"() {
240487
240998
  "use strict";
240488
240999
  import_node_fs17 = __toESM(require("node:fs"), 1);
240489
241000
  import_node_path21 = __toESM(require("node:path"), 1);
240490
- import_node_crypto57 = __toESM(require("node:crypto"), 1);
241001
+ import_node_crypto58 = __toESM(require("node:crypto"), 1);
240491
241002
  SEP = "::";
240492
241003
  keyOf = (companyId, id) => `${companyId}${SEP}${id}`;
240493
241004
  prefixOf = (companyId) => `${companyId}${SEP}`;
@@ -240505,7 +241016,7 @@ var init_tags = __esm({
240505
241016
  if (!name) return null;
240506
241017
  if (this.entries(companyId).some((t) => nameKey(t.name) === nameKey(name))) return null;
240507
241018
  const tag = {
240508
- id: `tag-${import_node_crypto57.default.randomBytes(6).toString("hex")}`,
241019
+ id: `tag-${import_node_crypto58.default.randomBytes(6).toString("hex")}`,
240509
241020
  name,
240510
241021
  ...input.color?.trim() ? { color: input.color.trim() } : {},
240511
241022
  ...input.description?.trim() ? { description: input.description.trim() } : {}
@@ -241894,11 +242405,11 @@ async function isEmployedElsewhere(store, companyId, accountId) {
241894
242405
  const memberships = await store.listMembershipsForAccount(accountId);
241895
242406
  return memberships.some((m2) => m2.companyId !== companyId);
241896
242407
  }
241897
- var import_node_crypto58, ROLES, INVITABLE_ROLES, INVITATION_TTL_MS, INVITE_LINK_TTL_MS, INVITE_LINK_TOKEN_BYTES, INVITE_LINK_ROLE, SLUG_RE, CompanyError, CompaniesService;
242408
+ var import_node_crypto59, ROLES, INVITABLE_ROLES, INVITATION_TTL_MS, INVITE_LINK_TTL_MS, INVITE_LINK_TOKEN_BYTES, INVITE_LINK_ROLE, SLUG_RE, CompanyError, CompaniesService;
241898
242409
  var init_service6 = __esm({
241899
242410
  "../server/src/domains/companies/service.ts"() {
241900
242411
  "use strict";
241901
- import_node_crypto58 = require("node:crypto");
242412
+ import_node_crypto59 = require("node:crypto");
241902
242413
  init_console_url();
241903
242414
  ROLES = ["owner", "admin", "member"];
241904
242415
  INVITABLE_ROLES = ["admin", "member"];
@@ -241997,9 +242508,13 @@ var init_service6 = __esm({
241997
242508
  await this.store.updateCompany(id, { name });
241998
242509
  return { ...company, name };
241999
242510
  }
242000
- /** 设置/清除公司头像引用("blob:<哈希>";null = 回到默认首字母标识)。 */
242511
+ /**
242512
+ * 设置/清除公司头像引用("blob:<哈希>";null = 回到默认首字母标识)。
242513
+ * 权限(本组织 owner/admin)由路由层 `requireOwnerOrAdmin` 强制;这里只守「组织还可用」
242514
+ * ——与本域其余写面同一条判据(D8.2:闸不只长在路由上),解散之后 404 COMPANY_NOT_FOUND。
242515
+ */
242001
242516
  async setAvatar(id, ref2) {
242002
- const company = await this.requireCompany(id);
242517
+ const company = await this.requireActiveCompany(id);
242003
242518
  await this.store.updateCompany(id, { avatar: ref2 });
242004
242519
  const next = ref2 === null ? (({ avatar: _drop, ...rest }) => rest)(company) : { ...company, avatar: ref2 };
242005
242520
  return next;
@@ -242013,7 +242528,7 @@ var init_service6 = __esm({
242013
242528
  const existing = await this.store.getAccountByEmail(normalized4);
242014
242529
  if (existing) return existing;
242015
242530
  const account = {
242016
- id: `actor:human:${(0, import_node_crypto58.randomUUID)()}`,
242531
+ id: `actor:human:${(0, import_node_crypto59.randomUUID)()}`,
242017
242532
  email: normalized4,
242018
242533
  name: name ?? normalized4,
242019
242534
  status: "active"
@@ -242321,8 +242836,8 @@ var init_service6 = __esm({
242321
242836
  const target = await this.store.getMember(companyId, accountId);
242322
242837
  if (!target) throw new CompanyError(404, "NOT_FOUND", `\u4E0D\u662F\u8BE5\u516C\u53F8\u6210\u5458\uFF1A${accountId}`);
242323
242838
  if (target.role === "owner") throw this.ownerProtected();
242324
- const res = await this.store.removeMemberIfNotLastOwner(companyId, accountId);
242325
- if (res.ok) return res.member;
242839
+ const res = await this.store.removeMemberAndReleaseOwnedAgents(companyId, accountId, this.now());
242840
+ if (res.ok) return { member: res.member, releasedAgents: res.releasedAgents };
242326
242841
  if (res.reason === "not_found") throw new CompanyError(404, "NOT_FOUND", `\u4E0D\u662F\u8BE5\u516C\u53F8\u6210\u5458\uFF1A${accountId}`);
242327
242842
  throw this.ownerProtected();
242328
242843
  }
@@ -242411,7 +242926,7 @@ var init_service6 = __esm({
242411
242926
  }
242412
242927
  const nowMs = Date.parse(this.now());
242413
242928
  const invitation = {
242414
- id: `invitation:${(0, import_node_crypto58.randomUUID)()}`,
242929
+ id: `invitation:${(0, import_node_crypto59.randomUUID)()}`,
242415
242930
  companyId,
242416
242931
  email: mail,
242417
242932
  role,
@@ -242542,7 +243057,7 @@ var init_service6 = __esm({
242542
243057
  const nowMs = Date.parse(this.now());
242543
243058
  const link = {
242544
243059
  companyId,
242545
- token: (0, import_node_crypto58.randomBytes)(INVITE_LINK_TOKEN_BYTES).toString("base64url"),
243060
+ token: (0, import_node_crypto59.randomBytes)(INVITE_LINK_TOKEN_BYTES).toString("base64url"),
242546
243061
  role: INVITE_LINK_ROLE,
242547
243062
  createdAt: new Date(nowMs).toISOString(),
242548
243063
  expiresAt: new Date(nowMs + INVITE_LINK_TTL_MS).toISOString(),
@@ -242599,7 +243114,7 @@ var init_service6 = __esm({
242599
243114
  const reusable = existing.find((inv) => !this.isExpired(inv));
242600
243115
  if (reusable) return reusable;
242601
243116
  const invitation = {
242602
- id: `invitation:${(0, import_node_crypto58.randomUUID)()}`,
243117
+ id: `invitation:${(0, import_node_crypto59.randomUUID)()}`,
242603
243118
  companyId: link.companyId,
242604
243119
  email: mail,
242605
243120
  role: link.role,
@@ -242645,9 +243160,9 @@ var init_service6 = __esm({
242645
243160
  * 登录身份查自己那条 membership;不是成员 → 404。
242646
243161
  */
242647
243162
  async completeMemberOnboarding(companyId, accountId) {
242648
- await this.requireCompany(companyId);
242649
243163
  const member = await this.store.getMember(companyId, accountId);
242650
243164
  if (!member) throw new CompanyError(404, "NOT_FOUND", `\u4E0D\u662F\u8BE5\u516C\u53F8\u6210\u5458\uFF1A${accountId}`);
243165
+ await this.requireActiveCompany(companyId);
242651
243166
  if (member.onboardingCompletedAt) return member;
242652
243167
  const next = { ...member, onboardingCompletedAt: this.now() };
242653
243168
  await this.store.upsertMember(next);
@@ -242671,9 +243186,9 @@ var init_service6 = __esm({
242671
243186
  async setMemberOnboardingStep(companyId, accountId, step) {
242672
243187
  const trimmed = step?.trim();
242673
243188
  if (!trimmed) throw new CompanyError(400, "BAD_REQUEST", "step \u4E0D\u80FD\u4E3A\u7A7A");
242674
- await this.requireCompany(companyId);
242675
243189
  const member = await this.store.getMember(companyId, accountId);
242676
243190
  if (!member) throw new CompanyError(404, "NOT_FOUND", `\u4E0D\u662F\u8BE5\u516C\u53F8\u6210\u5458\uFF1A${accountId}`);
243191
+ await this.requireActiveCompany(companyId);
242677
243192
  const next = { ...member, onboardingStep: trimmed };
242678
243193
  await this.store.upsertMember(next);
242679
243194
  return next;
@@ -242833,11 +243348,11 @@ var init_service6 = __esm({
242833
243348
  });
242834
243349
 
242835
243350
  // ../server/src/domains/companies/offboarding.ts
242836
- var import_node_crypto59, OWNER_PROTECTED_MESSAGE, MemberOffboardingService;
243351
+ var import_node_crypto60, OWNER_PROTECTED_MESSAGE, MemberOffboardingService;
242837
243352
  var init_offboarding = __esm({
242838
243353
  "../server/src/domains/companies/offboarding.ts"() {
242839
243354
  "use strict";
242840
- import_node_crypto59 = require("node:crypto");
243355
+ import_node_crypto60 = require("node:crypto");
242841
243356
  init_service6();
242842
243357
  OWNER_PROTECTED_MESSAGE = "\u7EC4\u7EC7\u7BA1\u7406\u5458\u4E0D\u53EF\u88AB\u79FB\u9664\uFF0C\u53EA\u80FD\u7531\u672C\u4EBA\u8F6C\u4EA4\u6216\u89E3\u6563\u7EC4\u7EC7";
242843
243358
  MemberOffboardingService = class {
@@ -242846,12 +243361,14 @@ var init_offboarding = __esm({
242846
243361
  listInFlight;
242847
243362
  transferAssignment;
242848
243363
  disableActor;
243364
+ restoreActor;
242849
243365
  constructor(opts) {
242850
243366
  this.store = opts.store;
242851
243367
  this.now = opts.now ?? (() => (/* @__PURE__ */ new Date()).toISOString());
242852
243368
  this.listInFlight = opts.listInFlightAssignments;
242853
243369
  this.transferAssignment = opts.transferAssignment;
242854
243370
  this.disableActor = opts.disableActor;
243371
+ this.restoreActor = opts.restoreActor;
242855
243372
  }
242856
243373
  /** 该成员是不是公司最后一个 owner(最后一个 owner 立即阻断,先于任何交接讨论)。 */
242857
243374
  async isLastOwner(companyId, accountId) {
@@ -242877,14 +243394,13 @@ var init_offboarding = __esm({
242877
243394
  */
242878
243395
  async blockersForRemoval(companyId, accountId) {
242879
243396
  const blockers = [];
242880
- const member = await this.store.getMember(companyId, accountId);
243397
+ const members = await this.store.listMembers(companyId);
243398
+ const member = members.find((m2) => m2.accountId === accountId);
242881
243399
  if (!member) {
242882
243400
  return [{ code: "NOT_FOUND", message: `\u4E0D\u662F\u8BE5\u516C\u53F8\u6210\u5458\uFF1A${accountId}` }];
242883
243401
  }
242884
243402
  if (member.role === "owner") {
242885
243403
  blockers.push({ code: "OWNER_PROTECTED", message: OWNER_PROTECTED_MESSAGE });
242886
- } else if (await this.isLastOwner(companyId, accountId)) {
242887
- blockers.push({ code: "LAST_OWNER", message: "\u516C\u53F8\u81F3\u5C11\u8981\u7559 1 \u4E2A owner\uFF0C\u4E0D\u80FD\u79FB\u9664\u6700\u540E\u4E00\u4E2A owner" });
242888
243404
  }
242889
243405
  return blockers;
242890
243406
  }
@@ -242913,7 +243429,7 @@ var init_offboarding = __esm({
242913
243429
  const { ownedAgents, inFlight } = await this.snapshot(companyId, accountId);
242914
243430
  const at2 = this.now();
242915
243431
  const started = await this.store.startMemberOffboarding({
242916
- operationId: `off:${(0, import_node_crypto59.randomUUID)()}`,
243432
+ operationId: `off:${(0, import_node_crypto60.randomUUID)()}`,
242917
243433
  companyId,
242918
243434
  accountId,
242919
243435
  initiatedBy,
@@ -242953,12 +243469,9 @@ var init_offboarding = __esm({
242953
243469
  if (op.version !== expectedVersion) {
242954
243470
  throw new CompanyError(409, "STALE_PLAN", `\u8BA1\u5212\u7248\u672C\u5DF2\u53D8\uFF08\u5F53\u524D ${op.version}\uFF0C\u4F60\u7ED9\u7684\u662F ${expectedVersion}\uFF09\uFF0C\u8BF7\u91CD\u65B0\u53D6\u8BA1\u5212`);
242955
243471
  }
242956
- const target = await this.store.getMember(companyId, accountId);
243472
+ const members = await this.store.listMembers(companyId);
243473
+ const target = members.find((m2) => m2.accountId === accountId);
242957
243474
  if (target?.role === "owner") throw new CompanyError(403, "OWNER_PROTECTED", OWNER_PROTECTED_MESSAGE);
242958
- if (await this.isLastOwner(companyId, accountId)) {
242959
- throw new CompanyError(409, "LAST_OWNER", "\u516C\u53F8\u81F3\u5C11\u8981\u7559 1 \u4E2A owner\uFF0C\u4E0D\u80FD\u79FB\u9664\u6700\u540E\u4E00\u4E2A owner");
242960
- }
242961
- const released = await this.releaseOwnedAgents(companyId, accountId);
242962
243475
  const remaining = await this.blockersForRemoval(companyId, accountId);
242963
243476
  if (remaining.length > 0) {
242964
243477
  await this.store.updateMemberOffboardingIfVersion(
@@ -242970,7 +243483,7 @@ var init_offboarding = __esm({
242970
243483
  );
242971
243484
  throw new CompanyError(409, remaining[0].code, `\u4ECD\u6709\u672A\u4EA4\u63A5\u9879\uFF1A${remaining.map((b2) => b2.message).join("; ")}`);
242972
243485
  }
242973
- await this.applyMemberRemoval(companyId, accountId);
243486
+ const released = await this.applyMemberRemoval(companyId, accountId);
242974
243487
  await this.store.updateMemberOffboardingIfVersion(
242975
243488
  companyId,
242976
243489
  operationId,
@@ -242981,35 +243494,63 @@ var init_offboarding = __esm({
242981
243494
  return { status: "removed", transferred: [], left: released };
242982
243495
  }
242983
243496
  /**
242984
- * 移除一条成员关系的**全套副作用**,顺序固定,三条移除路径共用:
242985
- * ① 清名下 agent 的负责人字段(`digital_employee.owner` 的复合 FK 是 ON DELETE RESTRICT,
242986
- * 不清就是一个裸 FK 违例);**只清 owner**——不改状态、不撤凭证、不解绑助理(PRD v1.5);
242987
- * ② 停用花名册里这个人(**还在别家公司在职就不停用**——停用打的是全局那一行);
242988
- * ③ 删 human principal bridge(对 company_member 是 CASCADE,但文件/内存实现没有级联,
242989
- * 显式删一次让「桥随主体走」在应用层也成立);
242990
- * ④ 删成员行(走 `removeMemberIfNotLastOwner`,组织管理员那一位在这里被纵深防御挡住)。
243497
+ * 移除一条成员关系的**全套副作用**,三条移除路径共用(管理员移除他人 `commit`、
243498
+ * 本人退出组织 `DELETE …/members/me`、注销账号 `DELETE /api/accounts/me`)。
242991
243499
  *
242992
- * 顺序不能反:成员行一删,本公司的 scoped registry 就不再认这个 actor,②会静默 no-op。
242993
- * 任何一步失败都停在「人已停用、成员还在」——可重试,每一步都幂等。
243500
+ * 顺序与原子性(2026-09-23 代码评审打回后重排):
243501
+ * ① 读一遍成员行:不存在 → 404;**已经是组织管理员 → 当场 403,一个副作用都不做**(D5.2)。
243502
+ * ② 停用花名册里这个人(**还在别家公司在职就不停用**——停用打的是全局那一行)。
243503
+ * 这一步必须在③之前:成员行一删,本公司的 scoped registry 就不再认这个 actor,
243504
+ * 停用会静默 no-op,人被移除了却还挂在花名册上(QA D3 修过一次的病)。
243505
+ * ③ **一个事务**做完控制面三件事:锁内复核目标不是组织管理员 → 清名下 agent 的负责人字段
243506
+ * (`digital_employee.owner` 的复合 FK 是 ON DELETE RESTRICT,不清就是裸 FK 违例;
243507
+ * **只清 owner**,不改状态、不撤凭证、不解绑助理,PRD v1.5)→ 删 human principal bridge
243508
+ * → 删成员行(`store.removeMemberAndReleaseOwnedAgents`)。
243509
+ * ④ ③失败(并发转交把这个人升成了组织管理员,A09)→ 把②CAS 还原回去。
242994
243510
  *
242995
- * 三个调用点:管理员移除他人(`commit`)、本人退出组织(`DELETE …/members/me`)、
242996
- * 注销账号(逐家执行,`DELETE /api/accounts/me`)。
243511
+ * **为什么③非要是一个事务**:以前①②③是「先清负责人、再停用、再删桥,最后才去拿锁删成员行」
243512
+ * 四段独立的写。A09 那条并发(`O` 转交给 `A1` 的同时另一位管理员删 `A1`)走到最后一步才被
243513
+ * `OWNER_PROTECTED` 拒——而此刻 `A1` 名下 Agent 的负责人已经被清空、桥已经被删、人已经被停用。
243514
+ * 请求失败了,破坏却留下了:新上任的组织管理员丢了名下全部 Agent 的归属、还从花名册上消失。
243515
+ * 现在控制面那三件事共一把锁(与转交锁同一组行),失败时一行都没写;跨不进这把锁的只有员工侧
243516
+ * 停用,用 CAS 补偿还原——「停用之后有别人写过这个 actor」就跳过,不撤销他人的合法写(QA R9)。
242997
243517
  */
242998
243518
  async applyMemberRemoval(companyId, accountId) {
242999
- const released = await this.releaseOwnedAgents(companyId, accountId);
243000
243519
  const member = await this.store.getMember(companyId, accountId);
243001
- if (member && !await isEmployedElsewhere(this.store, companyId, accountId)) {
243002
- await this.disableActor?.(companyId, member.actorId);
243520
+ if (!member) throw new CompanyError(404, "NOT_FOUND", `\u4E0D\u662F\u8BE5\u516C\u53F8\u6210\u5458\uFF1A${accountId}`);
243521
+ if (member.role === "owner") throw new CompanyError(403, "OWNER_PROTECTED", OWNER_PROTECTED_MESSAGE);
243522
+ let ticket = null;
243523
+ if (!await isEmployedElsewhere(this.store, companyId, accountId)) {
243524
+ ticket = await this.disableActor?.(companyId, member.actorId) ?? null;
243003
243525
  }
243004
- await this.store.deletePrincipalBridge(companyId, "human", accountId);
243005
- const removed = await this.store.removeMemberIfNotLastOwner(companyId, accountId);
243526
+ const removed = await this.store.removeMemberAndReleaseOwnedAgents(companyId, accountId, this.now()).catch(async (e) => {
243527
+ await this.compensateDisable(companyId, ticket);
243528
+ throw e;
243529
+ });
243006
243530
  if (!removed.ok) {
243531
+ await this.compensateDisable(companyId, ticket);
243007
243532
  if (removed.reason === "not_found") {
243008
243533
  throw new CompanyError(404, "NOT_FOUND", `\u4E0D\u662F\u8BE5\u516C\u53F8\u6210\u5458\uFF1A${accountId}`);
243009
243534
  }
243010
243535
  throw new CompanyError(403, "OWNER_PROTECTED", OWNER_PROTECTED_MESSAGE);
243011
243536
  }
243012
- return released;
243537
+ return removed.releasedAgents;
243538
+ }
243539
+ /**
243540
+ * 把②那次停用还原回去。**只还原本次自己造成的那一次**:`restoreActor` 走 CAS,
243541
+ * 停用之后但凡有别人写过这个 actor 就跳过——留下「成员还在 + actor 已停用」这个合法终态
243542
+ * (与员工页 `DELETE /api/actors/:id` 的终态同构),比静默撤销他人的合法停用安全。
243543
+ * 补偿本身失败不改写主错误:调用方要看到的是「为什么没删成」,不是补偿的二级故障。
243544
+ */
243545
+ async compensateDisable(companyId, ticket) {
243546
+ if (!ticket || !this.restoreActor) return;
243547
+ try {
243548
+ await this.restoreActor(companyId, ticket);
243549
+ } catch (e) {
243550
+ console.error(
243551
+ `[offboarding] restore actor ${ticket.actorId} failed after removal was rejected: ${e instanceof Error ? e.message : String(e)}`
243552
+ );
243553
+ }
243013
243554
  }
243014
243555
  /**
243015
243556
  * 把该成员名下所有 agent 的负责人字段清空,返回被清的 agent id。
@@ -243098,10 +243639,18 @@ function companiesDomain(opts) {
243098
243639
  }
243099
243640
  return n;
243100
243641
  };
243642
+ const requireActiveCompany = async (companyId) => {
243643
+ const company = await service.get(companyId);
243644
+ if (!company || company.status !== "active") {
243645
+ throw new ApiError(404, "COMPANY_NOT_FOUND", `\u7EC4\u7EC7\u4E0D\u5B58\u5728\u6216\u5DF2\u4E0D\u53EF\u7528\uFF1A${companyId}`);
243646
+ }
243647
+ };
243101
243648
  const requireOwnerOrAdmin = async (req, companyId) => {
243102
243649
  const me = await whoami(req);
243103
243650
  const member = await service.getMember(companyId, me.accountId);
243104
- if (!member || member.role !== "owner" && member.role !== "admin") {
243651
+ if (!member) throw new ApiError(403, "FORBIDDEN", "\u9700\u8981\u8BE5\u516C\u53F8\u7684 owner/admin \u6743\u9650");
243652
+ await requireActiveCompany(companyId);
243653
+ if (member.role !== "owner" && member.role !== "admin") {
243105
243654
  throw new ApiError(403, "FORBIDDEN", "\u9700\u8981\u8BE5\u516C\u53F8\u7684 owner/admin \u6743\u9650");
243106
243655
  }
243107
243656
  };
@@ -243109,8 +243658,16 @@ function companiesDomain(opts) {
243109
243658
  const me = await whoami(req);
243110
243659
  const member = await service.getMember(companyId, me.accountId);
243111
243660
  if (!member) throw new ApiError(403, "NOT_A_MEMBER", "\u4E0D\u662F\u8BE5\u516C\u53F8\u6210\u5458\uFF0C\u8BBF\u95EE\u88AB\u62D2");
243661
+ await requireActiveCompany(companyId);
243112
243662
  return me;
243113
243663
  };
243664
+ const requireActiveSelfMember = async (req, companyId) => {
243665
+ const me = await whoami(req);
243666
+ const member = await service.getMember(companyId, me.accountId);
243667
+ if (!member) throw new ApiError(404, "NOT_FOUND", `\u4E0D\u662F\u8BE5\u516C\u53F8\u6210\u5458\uFF1A${me.accountId}`);
243668
+ await requireActiveCompany(companyId);
243669
+ return { me, member };
243670
+ };
243114
243671
  return (router) => {
243115
243672
  router.get("/api/companies", async (req) => {
243116
243673
  const me = await whoami(req);
@@ -243160,13 +243717,17 @@ function companiesDomain(opts) {
243160
243717
  return { status: 201, body: company };
243161
243718
  });
243162
243719
  router.post("/api/companies/:id/avatar", async (req) => {
243720
+ const cid = req.params.id;
243721
+ await requireOwnerOrAdmin(req, cid);
243163
243722
  if (!blobs) throw new ApiError(501, "NOT_IMPLEMENTED", "blob \u5B58\u50A8\u672A\u63A5\u5165");
243164
243723
  const ref2 = await storeImageBlob(blobs, req.body?.data);
243165
- const company = await service.setAvatar(req.params.id, ref2).catch(mapErr);
243724
+ const company = await service.setAvatar(cid, ref2).catch(mapErr);
243166
243725
  return { status: 201, body: { avatar: company.avatar } };
243167
243726
  });
243168
243727
  router.delete("/api/companies/:id/avatar", async (req) => {
243169
- await service.setAvatar(req.params.id, null).catch(mapErr);
243728
+ const cid = req.params.id;
243729
+ await requireOwnerOrAdmin(req, cid);
243730
+ await service.setAvatar(cid, null).catch(mapErr);
243170
243731
  return { status: 200, body: { ok: true } };
243171
243732
  });
243172
243733
  router.get("/api/companies/:id/members", async (req) => {
@@ -243176,23 +243737,23 @@ function companiesDomain(opts) {
243176
243737
  return { status: 200, body: { items } };
243177
243738
  });
243178
243739
  router.get("/api/companies/:id/members/me", async (req) => {
243179
- const me = await whoami(req);
243180
- const member = await service.getMember(req.params.id, me.accountId);
243181
- if (!member) throw new ApiError(404, "NOT_FOUND", `\u4E0D\u662F\u8BE5\u516C\u53F8\u6210\u5458\uFF1A${me.accountId}`);
243740
+ const { member } = await requireActiveSelfMember(req, req.params.id);
243182
243741
  return { status: 200, body: member };
243183
243742
  });
243184
243743
  router.post("/api/companies/:id/members/me/onboarding-complete", async (req) => {
243185
- const me = await whoami(req);
243186
- const member = await service.completeMemberOnboarding(req.params.id, me.accountId).catch(mapErr);
243744
+ const cid = req.params.id;
243745
+ const { me } = await requireActiveSelfMember(req, cid);
243746
+ const member = await service.completeMemberOnboarding(cid, me.accountId).catch(mapErr);
243187
243747
  return { status: 200, body: member };
243188
243748
  });
243189
243749
  router.post("/api/companies/:id/members/me/onboarding-step", async (req) => {
243190
- const me = await whoami(req);
243750
+ const cid = req.params.id;
243751
+ const { me } = await requireActiveSelfMember(req, cid);
243191
243752
  const body2 = req.body ?? {};
243192
243753
  if (typeof body2.step !== "string") {
243193
243754
  throw new ApiError(400, "BAD_REQUEST", "\u7F3A\u5C11 step: string");
243194
243755
  }
243195
- const member = await service.setMemberOnboardingStep(req.params.id, me.accountId, body2.step).catch(mapErr);
243756
+ const member = await service.setMemberOnboardingStep(cid, me.accountId, body2.step).catch(mapErr);
243196
243757
  return { status: 200, body: member };
243197
243758
  });
243198
243759
  router.patch("/api/companies/:id/members/:accountId", async (req) => {
@@ -243232,7 +243793,6 @@ function companiesDomain(opts) {
243232
243793
  const aid = req.params.accountId;
243233
243794
  await requireOwnerOrAdmin(req, cid);
243234
243795
  const member = await service.peekRemovableMember(cid, aid).catch(mapErr);
243235
- if (offboarding) await offboarding.releaseOwnedAgents(cid, aid).catch(mapErr);
243236
243796
  let actorsSvc;
243237
243797
  let ownedDisableBefore = null;
243238
243798
  let disableSeq = 0;
@@ -243491,18 +244051,18 @@ var init_routes6 = __esm({
243491
244051
 
243492
244052
  // ../server/src/domains/companies/agent-credentials.ts
243493
244053
  function hashAgentToken(token, secret) {
243494
- return HASH_PREFIX + (0, import_node_crypto60.createHmac)("sha256", secret).update(`agent-token:${token}`).digest("base64url");
244054
+ return HASH_PREFIX + (0, import_node_crypto61.createHmac)("sha256", secret).update(`agent-token:${token}`).digest("base64url");
243495
244055
  }
243496
244056
  function safeEqual(a, b2) {
243497
244057
  const ba = Buffer.from(a);
243498
244058
  const bb = Buffer.from(b2);
243499
- return ba.length === bb.length && (0, import_node_crypto60.timingSafeEqual)(ba, bb);
244059
+ return ba.length === bb.length && (0, import_node_crypto61.timingSafeEqual)(ba, bb);
243500
244060
  }
243501
- var import_node_crypto60, HASH_PREFIX, TOKEN_PREFIX, AgentCredentialService;
244061
+ var import_node_crypto61, HASH_PREFIX, TOKEN_PREFIX, AgentCredentialService;
243502
244062
  var init_agent_credentials = __esm({
243503
244063
  "../server/src/domains/companies/agent-credentials.ts"() {
243504
244064
  "use strict";
243505
- import_node_crypto60 = require("node:crypto");
244065
+ import_node_crypto61 = require("node:crypto");
243506
244066
  init_service6();
243507
244067
  HASH_PREFIX = "ac1$";
243508
244068
  TOKEN_PREFIX = "oak_";
@@ -243527,7 +244087,7 @@ var init_agent_credentials = __esm({
243527
244087
  if (employee.status !== "active") {
243528
244088
  throw new CompanyError(409, "AGENT_LEFT", `\u6570\u5B57\u5458\u5DE5\u5DF2\u79BB\u5F00\uFF0C\u4E0D\u80FD\u7B7E\u53D1\u51ED\u8BC1\uFF1A${agentId}`);
243529
244089
  }
243530
- const token = `${TOKEN_PREFIX}${(0, import_node_crypto60.randomBytes)(32).toString("base64url")}`;
244090
+ const token = `${TOKEN_PREFIX}${(0, import_node_crypto61.randomBytes)(32).toString("base64url")}`;
243531
244091
  const credential = {
243532
244092
  tokenHash: hashAgentToken(token, this.secret),
243533
244093
  companyId,
@@ -243628,9 +244188,30 @@ function createCompaniesDomain(opts) {
243628
244188
  ...opts.resolveActorsCtx !== void 0 ? {
243629
244189
  // 未认领转 left 的 agent、以及 commit 移除掉的那位成员,都要把员工侧 ActorRecord
243630
244190
  // 一并软停用——否则控制面说「已离开/已移除」、花名册还显示在岗,两个事实源当场分叉。
244191
+ //
244192
+ // 用 `disableActorIfCurrentlyActive` 而不是 `disableActor`,并把 `{before, seq}` 回给
244193
+ // offboarding:只有**真的把这个 actor 从 active 拉到 disabled** 的那一次调用才拿得到
244194
+ // 返回值,也才「拥有」这次状态变更;已被并发请求或员工页先停用的一律 ok:false,
244195
+ // 本次就不 own、移除失败也不补偿(QA R10 的判据,两条移除路径同一套)。
243631
244196
  disableActor: async (companyId, actorId) => {
243632
244197
  const ctx = await opts.resolveActorsCtx(companyId);
243633
- await ctx.service.disableActorIfCurrentlyActive(actorId, "system:offboarding");
244198
+ const res = await ctx.service.disableActorIfCurrentlyActive(actorId, "system:offboarding");
244199
+ return res.ok ? { actorId, before: res.before, seq: res.seq } : null;
244200
+ },
244201
+ // 移除最终失败时的补偿:**CAS** 写回停用前那一行。`expectedSeq` 对不上(停用之后
244202
+ // 有任何第三方写过这个 actor)就跳过——不静默撤销他人的合法停用 / 改名(QA R9)。
244203
+ restoreActor: async (companyId, ticket) => {
244204
+ const ctx = await opts.resolveActorsCtx(companyId);
244205
+ const res = await ctx.service.casUpsertActor(
244206
+ { ...ticket.before, status: "active" },
244207
+ ticket.seq,
244208
+ "system:offboarding"
244209
+ );
244210
+ if (!res.ok) {
244211
+ console.info(
244212
+ `[companies] restore skipped for ${ticket.actorId}: another write intervened after the disable`
244213
+ );
244214
+ }
243634
244215
  }
243635
244216
  } : {}
243636
244217
  });
@@ -244018,7 +244599,7 @@ function gatewayInstanceSlug(gatewayUrl) {
244018
244599
  }
244019
244600
  function connectInstanceName(gatewayUrl, token, companyId) {
244020
244601
  const scope2 = companyId?.trim();
244021
- const suffix = scope2 ? `c${(0, import_node_crypto61.createHash)("sha256").update(`${gatewayUrl}\0${scope2}`).digest("hex").slice(0, 10)}` : token.replace(/[^a-z0-9]/gi, "").slice(-8).toLowerCase() || "enroll";
244602
+ const suffix = scope2 ? `c${(0, import_node_crypto62.createHash)("sha256").update(`${gatewayUrl}\0${scope2}`).digest("hex").slice(0, 10)}` : token.replace(/[^a-z0-9]/gi, "").slice(-8).toLowerCase() || "enroll";
244022
244603
  const base = gatewayInstanceSlug(gatewayUrl);
244023
244604
  return base.slice(0, Math.max(1, 64 - suffix.length - 1)) + "-" + suffix;
244024
244605
  }
@@ -244347,12 +244928,12 @@ function buildNpxCmd(p2) {
244347
244928
  const prefix = daemonNpmPrefix();
244348
244929
  return `npm install -g --prefer-online --prefix ${prefix} ${DAEMON_NPM_PACKAGE}@latest && ${prefix}/bin/oasis daemon start --instance ${shellQuote(instanceName)} --server-ws ${shellQuote(p2.gatewayUrl)}${companyPart}${namePart} --pkg ${shellQuote(DAEMON_NPM_PACKAGE)} --upgrade --enroll-token ${shellQuote(p2.token)}`;
244349
244930
  }
244350
- var import_node_crypto61;
244931
+ var import_node_crypto62;
244351
244932
  var init_connect_script = __esm({
244352
244933
  "../server/src/domains/nodes/connect-script.ts"() {
244353
244934
  "use strict";
244354
244935
  init_src();
244355
- import_node_crypto61 = require("node:crypto");
244936
+ import_node_crypto62 = require("node:crypto");
244356
244937
  }
244357
244938
  });
244358
244939
 
@@ -244812,7 +245393,7 @@ function nodesDomain(deps) {
244812
245393
  if (!daemon.meta.adapters.some((kind) => QUOTA_CAPABLE_KINDS.has(kind))) {
244813
245394
  return { ok: false, code: "no-quota-runtime", reason: "\u8FD9\u53F0\u673A\u5668\u4E0A\u6CA1\u6709\u4F1A\u4E0A\u62A5\u989D\u5EA6\u7684\u8FD0\u884C\u65F6\uFF08\u53EA\u6709 Claude Code \u548C Codex \u80FD\u67E5\u5230\u8BA2\u9605\u989D\u5EA6\uFF09" };
244814
245395
  }
244815
- const requestId = (0, import_node_crypto62.randomUUID)();
245396
+ const requestId = (0, import_node_crypto63.randomUUID)();
244816
245397
  const task = new Promise((resolve12) => {
244817
245398
  const timer = setTimeout(() => {
244818
245399
  quotaRefreshWaiters.delete(requestId);
@@ -245188,7 +245769,7 @@ function nodesDomain(deps) {
245188
245769
  }
245189
245770
  deps.enrollTokens.setIssuedNodeId(enrollToken, nodeId);
245190
245771
  } else {
245191
- nodeId = `node-${(0, import_node_crypto62.randomUUID)().slice(0, 8)}`;
245772
+ nodeId = `node-${(0, import_node_crypto63.randomUUID)().slice(0, 8)}`;
245192
245773
  deps.enrollTokens.setIssuedNodeId(enrollToken, nodeId);
245193
245774
  }
245194
245775
  const existingNode = await deps.nodeStore.getNode(nodeId);
@@ -245454,16 +246035,16 @@ function normalizeGatewayUrl(raw) {
245454
246035
  }
245455
246036
  }
245456
246037
  function newNodeId() {
245457
- return "node-" + (0, import_node_crypto62.randomUUID)().replace(/-/g, "").slice(0, 12);
246038
+ return "node-" + (0, import_node_crypto63.randomUUID)().replace(/-/g, "").slice(0, 12);
245458
246039
  }
245459
- var import_node_crypto62, import_node_fs18, import_node_url6, import_node_path22, NODE_NAME_MAX_LENGTH;
246040
+ var import_node_crypto63, import_node_fs18, import_node_url6, import_node_path22, NODE_NAME_MAX_LENGTH;
245460
246041
  var init_routes7 = __esm({
245461
246042
  "../server/src/domains/nodes/routes.ts"() {
245462
246043
  "use strict";
245463
246044
  init_src();
245464
246045
  init_faults();
245465
246046
  init_runtime_status();
245466
- import_node_crypto62 = require("node:crypto");
246047
+ import_node_crypto63 = require("node:crypto");
245467
246048
  import_node_fs18 = require("node:fs");
245468
246049
  import_node_url6 = require("node:url");
245469
246050
  import_node_path22 = require("node:path");
@@ -247130,7 +247711,7 @@ function createBundle(root, target, input) {
247130
247711
  }
247131
247712
  }
247132
247713
  const bytes2 = fs29.statSync(absBundle).size;
247133
- const digest = (0, import_node_crypto63.createHash)("sha256").update(fs29.readFileSync(absBundle)).digest("hex");
247714
+ const digest = (0, import_node_crypto64.createHash)("sha256").update(fs29.readFileSync(absBundle)).digest("hex");
247134
247715
  const relBase = `inputs/git-bundles/${safeRepo}-${commit.slice(0, 12)}`;
247135
247716
  return {
247136
247717
  absoluteBundlePath: absBundle,
@@ -247226,12 +247807,12 @@ function classifyGitError(fallback, err, where) {
247226
247807
  function oneLine2(msg) {
247227
247808
  return msg.split("\n").map((s2) => s2.trim()).filter(Boolean).slice(0, 3).join(" ");
247228
247809
  }
247229
- var import_node_child_process17, import_node_crypto63, fs29, os8, path26, GitReadonlyBundleError, HEX40, FETCH_TIMEOUT_MS2;
247810
+ var import_node_child_process17, import_node_crypto64, fs29, os8, path26, GitReadonlyBundleError, HEX40, FETCH_TIMEOUT_MS2;
247230
247811
  var init_readonly_bundle = __esm({
247231
247812
  "../server/src/git/readonly-bundle.ts"() {
247232
247813
  "use strict";
247233
247814
  import_node_child_process17 = require("node:child_process");
247234
- import_node_crypto63 = require("node:crypto");
247815
+ import_node_crypto64 = require("node:crypto");
247235
247816
  fs29 = __toESM(require("node:fs"), 1);
247236
247817
  os8 = __toESM(require("node:os"), 1);
247237
247818
  path26 = __toESM(require("node:path"), 1);
@@ -247252,7 +247833,7 @@ var init_readonly_bundle = __esm({
247252
247833
  // ../server/src/domains/collab/create-seeded-workorder.ts
247253
247834
  function makeSeededWorkorderCreator(deps) {
247254
247835
  const locks = createKeyedMutex();
247255
- const genWorkspace = deps.genWorkspace ?? (() => `ws:wo-${(0, import_node_crypto64.randomUUID)().slice(0, 8)}`);
247836
+ const genWorkspace = deps.genWorkspace ?? (() => `ws:wo-${(0, import_node_crypto65.randomUUID)().slice(0, 8)}`);
247256
247837
  const create = async (input) => {
247257
247838
  const company = input.companyId && deps.resolveCompany ? await deps.resolveCompany(input.companyId) : void 0;
247258
247839
  const kernel = company?.kernel ?? deps.kernel;
@@ -247339,7 +247920,7 @@ function makeSeededWorkorderCreator(deps) {
247339
247920
  return await finishCreation({ workspace, rootArtifactId: planned.rootArtifactId, spawned: planned.spawned });
247340
247921
  };
247341
247922
  return async (input) => {
247342
- const release = await locks.acquire(`${input.companyId ?? ""}\0${input.idempotencyKey ?? (0, import_node_crypto64.randomUUID)()}`);
247923
+ const release = await locks.acquire(`${input.companyId ?? ""}\0${input.idempotencyKey ?? (0, import_node_crypto65.randomUUID)()}`);
247343
247924
  try {
247344
247925
  return await create(input);
247345
247926
  } finally {
@@ -247348,7 +247929,7 @@ function makeSeededWorkorderCreator(deps) {
247348
247929
  };
247349
247930
  }
247350
247931
  function workspaceForKey(key2) {
247351
- return `ws:wo-${(0, import_node_crypto64.createHash)("sha256").update(key2).digest("hex").slice(0, 8)}`;
247932
+ return `ws:wo-${(0, import_node_crypto65.createHash)("sha256").update(key2).digest("hex").slice(0, 8)}`;
247352
247933
  }
247353
247934
  function existingWorkorder(snapshot, workspace, actor) {
247354
247935
  if (!snapshot?.nodes.length) return null;
@@ -247360,11 +247941,11 @@ function existingWorkorder(snapshot, workspace, actor) {
247360
247941
  spawned: snapshot.nodes.map((node2) => ({ id: node2.id, type: node2.type, owner: node2.assigneeActorId ?? actor }))
247361
247942
  };
247362
247943
  }
247363
- var import_node_crypto64;
247944
+ var import_node_crypto65;
247364
247945
  var init_create_seeded_workorder = __esm({
247365
247946
  "../server/src/domains/collab/create-seeded-workorder.ts"() {
247366
247947
  "use strict";
247367
- import_node_crypto64 = require("node:crypto");
247948
+ import_node_crypto65 = require("node:crypto");
247368
247949
  init_uncategorized_project();
247369
247950
  init_src();
247370
247951
  init_planner();
@@ -248867,12 +249448,12 @@ function stateFromAttempt(attempt, handoff, checkpoint, now, stalledAfterMs, ope
248867
249448
  if (attempt.status === "no-output") return handoff?.unresolvedIssues.length ? "blocked" : "no_change";
248868
249449
  return "failed";
248869
249450
  }
248870
- var import_node_crypto65, BLOCKING_STATES, HANDOFF_OUTCOMES2, RESUME_EXTERNAL_EFFECT_LIMIT, RESUME_SNAPSHOT_PREFIX, RESUME_SNAPSHOT_SUFFIX, ExecutionContinuityService;
249451
+ var import_node_crypto66, BLOCKING_STATES, HANDOFF_OUTCOMES2, RESUME_EXTERNAL_EFFECT_LIMIT, RESUME_SNAPSHOT_PREFIX, RESUME_SNAPSHOT_SUFFIX, ExecutionContinuityService;
248871
249452
  var init_service9 = __esm({
248872
249453
  "../server/src/domains/execution-continuity/service.ts"() {
248873
249454
  "use strict";
248874
249455
  init_src();
248875
- import_node_crypto65 = require("node:crypto");
249456
+ import_node_crypto66 = require("node:crypto");
248876
249457
  init_src7();
248877
249458
  BLOCKING_STATES = /* @__PURE__ */ new Set(["stalled", "failed", "blocked"]);
248878
249459
  HANDOFF_OUTCOMES2 = /* @__PURE__ */ new Set(["succeeded", "no-output", "failed", "cancelled", "timeout", "orphaned"]);
@@ -249103,7 +249684,7 @@ var init_service9 = __esm({
249103
249684
  if (typeof expectedHash !== "string" || !this.readBundleFile) return null;
249104
249685
  const injectedResume = this.readBundleFile(attempt.id, "execution/RESUME.md");
249105
249686
  if (!injectedResume) return null;
249106
- const actualHash = (0, import_node_crypto65.createHash)("sha256").update(injectedResume).digest("hex");
249687
+ const actualHash = (0, import_node_crypto66.createHash)("sha256").update(injectedResume).digest("hex");
249107
249688
  if (actualHash !== expectedHash) return null;
249108
249689
  const recoverySnapshot = parseResumePackSnapshot(injectedResume);
249109
249690
  if (!recoverySnapshot || recoverySnapshot.scenario !== "redispatch" || !recoverySnapshot.previousAttempt || recoverySnapshot.node.nodeId !== attempt.nodeId || recoverySnapshot.jobKey !== attempt.jobKey || recoverySnapshot.part !== attempt.part) return null;
@@ -250285,7 +250866,7 @@ function createChatSessionsDomain(opts) {
250285
250866
  if (!text7.trim()) throw new ApiError(400, "BAD_REQUEST", "text required");
250286
250867
  const now = Date.now();
250287
250868
  sweepDrafts(now);
250288
- const id = `cd_${(0, import_node_crypto66.randomUUID)()}`;
250869
+ const id = `cd_${(0, import_node_crypto67.randomUUID)()}`;
250289
250870
  drafts.set(id, { text: text7, actor: req.auth.actor, expiresAt: now + DRAFT_TTL_MS });
250290
250871
  return { status: 201, body: { id, expiresInMs: DRAFT_TTL_MS } };
250291
250872
  });
@@ -250362,7 +250943,7 @@ function createChatSessionsDomain(opts) {
250362
250943
  const runtimeId = agentDefault.binding?.nodeId ?? "unknown";
250363
250944
  const now = (/* @__PURE__ */ new Date()).toISOString();
250364
250945
  const session = {
250365
- id: (0, import_node_crypto66.randomUUID)(),
250946
+ id: (0, import_node_crypto67.randomUUID)(),
250366
250947
  humanActorId: req.auth.actor,
250367
250948
  // 注:这里**刻意不写 companyId**。新会话要不要带公司归属,按 ws:wo-6d588f8e 的 ADR
250368
250949
  // (每公司 schema、最终移除表内 company 列)统一定,不在本节点自行发明。
@@ -250497,7 +251078,7 @@ function createChatSessionsDomain(opts) {
250497
251078
  if (body2.role !== "user" && body2.role !== "assistant") throw new ApiError(400, "BAD_REQUEST", "role must be user or assistant");
250498
251079
  const role = body2.role;
250499
251080
  const msg = await store2.appendMessage({
250500
- id: (0, import_node_crypto66.randomUUID)(),
251081
+ id: (0, import_node_crypto67.randomUUID)(),
250501
251082
  sessionId: req.params.id,
250502
251083
  role,
250503
251084
  content: role === "user" ? stripInjectedChatContext(body2.content) : body2.content,
@@ -250539,7 +251120,7 @@ function createChatSessionsDomain(opts) {
250539
251120
  const known = key2 ? cur.find((x2) => x2.clientKey === key2) : void 0;
250540
251121
  if (!known && cur.length >= QUEUE_MAX) throw new ApiError(409, "QUEUE_FULL", `\u961F\u5217\u5DF2\u6EE1\uFF08\u4E0A\u9650 ${QUEUE_MAX} \u6761\uFF09`);
250541
251122
  const item = await store2.enqueuePendingMessage({
250542
- id: (0, import_node_crypto66.randomUUID)(),
251123
+ id: (0, import_node_crypto67.randomUUID)(),
250543
251124
  chatSessionId: sid,
250544
251125
  text: text7,
250545
251126
  ...attachments.length ? { attachments } : {},
@@ -251392,12 +251973,12 @@ async function legacySendFailureCards(entries, deps) {
251392
251973
  }
251393
251974
  return out;
251394
251975
  }
251395
- var import_node_crypto66, DEFAULT_SNAPSHOT_ITEMS, INFLIGHT_INSERT_SETTLE_WAIT_MS, MAX_SNAPSHOT_ITEMS, PROJECTED_CARD_LIMIT, SUBMISSION_NOT_DELIVERED_NOTE, SUBMISSION_UNCONFIRMED_NOTE, LEGACY_CARD_LIMIT;
251976
+ var import_node_crypto67, DEFAULT_SNAPSHOT_ITEMS, INFLIGHT_INSERT_SETTLE_WAIT_MS, MAX_SNAPSHOT_ITEMS, PROJECTED_CARD_LIMIT, SUBMISSION_NOT_DELIVERED_NOTE, SUBMISSION_UNCONFIRMED_NOTE, LEGACY_CARD_LIMIT;
251396
251977
  var init_chat_sessions2 = __esm({
251397
251978
  "../server/src/domains/chat-sessions/index.ts"() {
251398
251979
  "use strict";
251399
251980
  init_file_limits2();
251400
- import_node_crypto66 = require("node:crypto");
251981
+ import_node_crypto67 = require("node:crypto");
251401
251982
  init_src();
251402
251983
  init_chat_session();
251403
251984
  init_persistent_files();
@@ -252857,7 +253438,7 @@ function busyError(chatSessionId, blockedBy) {
252857
253438
  return `\u76EE\u6807\u4F1A\u8BDD ${chatSessionId} \u6B63\u6709\u4E00\u8F6E\u5728\u8DD1${blockedBy ? `\uFF08${blockedBy}\uFF09` : ""}\u2014\u2014\u672C\u6B21\u89E6\u53D1\u5DF2\u4E22\u5F03\uFF0C\u4E0D\u6392\u961F\u3001\u4E0D\u8865\u8DD1`;
252858
253439
  }
252859
253440
  function genWebhookToken() {
252860
- return `owt_${(0, import_node_crypto67.randomBytes)(32).toString("base64url")}`;
253441
+ return `owt_${(0, import_node_crypto68.randomBytes)(32).toString("base64url")}`;
252861
253442
  }
252862
253443
  function validateTitleTemplate(tpl) {
252863
253444
  const residue = tpl.replace(DATE_TOKEN, "");
@@ -252913,11 +253494,11 @@ function appendSourceNote(brief, automation, source, atIso) {
252913
253494
  ---
252914
253495
  \u672C\u5DE5\u5355\u7531\u81EA\u52A8\u5316\u300C${automation.name}\u300D\u4E8E ${atIso} \u89E6\u53D1\uFF08\u6765\u6E90\uFF1A${source}\uFF09\u3002\u5F00\u5DE5\u540E\u8BF7\u628A\u5DE5\u5355\u6807\u9898\u6539\u6210\u51C6\u786E\u53CD\u6620\u5B9E\u9645\u5DE5\u4F5C\u7684\u63CF\u8FF0\u3002`;
252915
253496
  }
252916
- var import_node_crypto67, DATE_TOKEN, AutomationsService, EVENT_RETRY_BASE_MS, EVENT_RETRY_MAX_MS, EVENT_RETRY_MAX_ATTEMPTS, eventAttempts, eventLastAttemptAt, eventLastError, CHAT_REPLY_MAX;
253497
+ var import_node_crypto68, DATE_TOKEN, AutomationsService, EVENT_RETRY_BASE_MS, EVENT_RETRY_MAX_MS, EVENT_RETRY_MAX_ATTEMPTS, eventAttempts, eventLastAttemptAt, eventLastError, CHAT_REPLY_MAX;
252917
253498
  var init_service10 = __esm({
252918
253499
  "../server/src/domains/automations/service.ts"() {
252919
253500
  "use strict";
252920
- import_node_crypto67 = require("node:crypto");
253501
+ import_node_crypto68 = require("node:crypto");
252921
253502
  init_src();
252922
253503
  init_registry3();
252923
253504
  init_attribution();
@@ -253731,11 +254312,11 @@ function verifySignature(rawBody, headers, secret) {
253731
254312
  if (!secret) return "not_required";
253732
254313
  const given = header1(headers, "x-hub-signature-256");
253733
254314
  if (!given) return "missing";
253734
- const expected = `sha256=${(0, import_node_crypto68.createHmac)("sha256", secret).update(rawBody).digest("hex")}`;
254315
+ const expected = `sha256=${(0, import_node_crypto69.createHmac)("sha256", secret).update(rawBody).digest("hex")}`;
253735
254316
  const a = Buffer.from(given);
253736
254317
  const b2 = Buffer.from(expected);
253737
254318
  if (a.length !== b2.length) return "invalid";
253738
- return (0, import_node_crypto68.timingSafeEqual)(a, b2) ? "valid" : "invalid";
254319
+ return (0, import_node_crypto69.timingSafeEqual)(a, b2) ? "valid" : "invalid";
253739
254320
  }
253740
254321
  function eventAllowed(filters, envelope) {
253741
254322
  if (!filters || filters.length === 0) return true;
@@ -253765,11 +254346,11 @@ function splitEvent(envelope) {
253765
254346
  }
253766
254347
  return { eventName, actionCandidates: candidates };
253767
254348
  }
253768
- var import_node_crypto68, MAX_BODY_BYTES, AutomationWebhookService, header1, PROVIDER_PREFIXES;
254349
+ var import_node_crypto69, MAX_BODY_BYTES, AutomationWebhookService, header1, PROVIDER_PREFIXES;
253769
254350
  var init_webhook = __esm({
253770
254351
  "../server/src/domains/automations/webhook.ts"() {
253771
254352
  "use strict";
253772
- import_node_crypto68 = require("node:crypto");
254353
+ import_node_crypto69 = require("node:crypto");
253773
254354
  MAX_BODY_BYTES = 256 * 1024;
253774
254355
  AutomationWebhookService = class {
253775
254356
  store;
@@ -254921,7 +255502,7 @@ function playbooksDomain(opts) {
254921
255502
  opts.overrides.set(companyOf(req), ref2, nodeKey, nextOverride);
254922
255503
  try {
254923
255504
  await opts.audit?.({
254924
- id: `reg_${(0, import_node_crypto69.randomUUID)()}`,
255505
+ id: `reg_${(0, import_node_crypto70.randomUUID)()}`,
254925
255506
  actor: req.auth.actor,
254926
255507
  kind: "registry_change",
254927
255508
  target: `playbook:${ref2}#${nodeKey}`,
@@ -254938,11 +255519,11 @@ function playbooksDomain(opts) {
254938
255519
  });
254939
255520
  };
254940
255521
  }
254941
- var import_node_crypto69;
255522
+ var import_node_crypto70;
254942
255523
  var init_routes12 = __esm({
254943
255524
  "../server/src/domains/playbooks/routes.ts"() {
254944
255525
  "use strict";
254945
- import_node_crypto69 = require("node:crypto");
255526
+ import_node_crypto70 = require("node:crypto");
254946
255527
  init_router();
254947
255528
  init_registry3();
254948
255529
  init_planner();
@@ -255071,17 +255652,17 @@ function completionFingerprint(workspace, members) {
255071
255652
  workspace,
255072
255653
  members: [...members].sort((a, b2) => a.artifactId < b2.artifactId ? -1 : a.artifactId > b2.artifactId ? 1 : 0).map((m2) => ({ artifactId: m2.artifactId, currentRevisionId: m2.currentRevisionId ?? null }))
255073
255654
  });
255074
- return (0, import_node_crypto70.createHash)("sha256").update(canonical2).digest("hex");
255655
+ return (0, import_node_crypto71.createHash)("sha256").update(canonical2).digest("hex");
255075
255656
  }
255076
255657
  function eventConsumptionKey(args) {
255077
255658
  const raw = `project-event:${EVENT_KEY_VERSION}:${args.triggerId}:${args.event}:${args.workspace}:${args.fingerprint}`;
255078
- return raw.length <= 200 ? raw : `project-event:${EVENT_KEY_VERSION}:sha256:${(0, import_node_crypto70.createHash)("sha256").update(raw).digest("hex")}`;
255659
+ return raw.length <= 200 ? raw : `project-event:${EVENT_KEY_VERSION}:sha256:${(0, import_node_crypto71.createHash)("sha256").update(raw).digest("hex")}`;
255079
255660
  }
255080
- var import_node_crypto70, EVENT_KEY_VERSION, EMPTY_METRICS, AUTOMATION_EVENT_HANDLERS, ProjectEventPoller;
255661
+ var import_node_crypto71, EVENT_KEY_VERSION, EMPTY_METRICS, AUTOMATION_EVENT_HANDLERS, ProjectEventPoller;
255081
255662
  var init_project_event_poller = __esm({
255082
255663
  "../server/src/automations/project-event-poller.ts"() {
255083
255664
  "use strict";
255084
- import_node_crypto70 = require("node:crypto");
255665
+ import_node_crypto71 = require("node:crypto");
255085
255666
  EVENT_KEY_VERSION = "v1";
255086
255667
  EMPTY_METRICS = () => ({
255087
255668
  scanDurationMs: 0,
@@ -255291,7 +255872,7 @@ function queryInputReceipt(hub, nodeId, dispatchId, clientMessageKey, pending, t
255291
255872
  const viaSession = hub.hasSession?.(dispatchId) === true;
255292
255873
  const supported = viaSession ? hub.supportsInputReceiptQuery?.(dispatchId) === true : daemonMetaOf(hub, nodeId)?.supportsInputReceiptQuery === true;
255293
255874
  if (!supported) return Promise.resolve({ status: "unknown", reason: "unsupported" });
255294
- const requestId = (0, import_node_crypto71.randomUUID)();
255875
+ const requestId = (0, import_node_crypto72.randomUUID)();
255295
255876
  return new Promise((resolve12) => {
255296
255877
  const timer = setTimeout(() => {
255297
255878
  pending.delete(requestId);
@@ -255353,7 +255934,7 @@ function sendAppendInput(hub, nodeId, dispatchId, entry, input, timeoutMs, sessi
255353
255934
  if (input.requireConsumptionReceipt && !hub.supportsInputConsumptionReceipts?.(dispatchId)) {
255354
255935
  return Promise.resolve({ accepted: false, reason: "consumption-unsupported" });
255355
255936
  }
255356
- const requestId = (0, import_node_crypto71.randomUUID)();
255937
+ const requestId = (0, import_node_crypto72.randomUUID)();
255357
255938
  const sent = routeToSession(hub, nodeId, dispatchId, { type: "append_input", dispatchId, input, requestId });
255358
255939
  if (!sent) return Promise.resolve({ accepted: false, reason: "session-closing", ...unknownIfInterrupting });
255359
255940
  return new Promise((resolve12) => {
@@ -255366,11 +255947,11 @@ function sendAppendInput(hub, nodeId, dispatchId, entry, input, timeoutMs, sessi
255366
255947
  entry.appendWaiters.push({ requestId, requireConsumptionReceipt: input.requireConsumptionReceipt, resolve: resolve12, timer });
255367
255948
  });
255368
255949
  }
255369
- var import_node_crypto71, STASH_TTL_MS, STASH_MAX_FRAMES_PER_ID, STASH_MAX_TOTAL_FRAMES, STASH_MAX_IDS, STASH_LOSS_MAX_IDS, STASH_LOSS_TTL_MS, DaemonHubAdapter, BindingRouterAdapter, WorkdirBridge;
255950
+ var import_node_crypto72, STASH_TTL_MS, STASH_MAX_FRAMES_PER_ID, STASH_MAX_TOTAL_FRAMES, STASH_MAX_IDS, STASH_LOSS_MAX_IDS, STASH_LOSS_TTL_MS, DaemonHubAdapter, BindingRouterAdapter, WorkdirBridge;
255370
255951
  var init_daemon_adapter = __esm({
255371
255952
  "../server/src/daemon-adapter.ts"() {
255372
255953
  "use strict";
255373
- import_node_crypto71 = require("node:crypto");
255954
+ import_node_crypto72 = require("node:crypto");
255374
255955
  init_src();
255375
255956
  STASH_TTL_MS = 5 * 6e4;
255376
255957
  STASH_MAX_FRAMES_PER_ID = 1e4;
@@ -255810,7 +256391,7 @@ var init_daemon_adapter = __esm({
255810
256391
  async spawn(job) {
255811
256392
  const nodeId = job.binding?.nodeId;
255812
256393
  if (!nodeId) throw new Error("DaemonHubAdapter \u9700\u8981 job.binding.nodeId\uFF08\u8DEF\u7531\u9519\u8BEF\uFF09");
255813
- const dispatchId = job.dispatchId ?? `dispatch:${(0, import_node_crypto71.randomUUID)()}`;
256394
+ const dispatchId = job.dispatchId ?? `dispatch:${(0, import_node_crypto72.randomUUID)()}`;
255814
256395
  if (job.dispatchId && this.pending.has(dispatchId)) {
255815
256396
  return this.recover(dispatchId, nodeId, job.binding?.runtimeKind, job.actor);
255816
256397
  }
@@ -256066,7 +256647,7 @@ var init_daemon_adapter = __esm({
256066
256647
  return daemon?.meta.supportsWorkdirWalk === true;
256067
256648
  }
256068
256649
  request(nodeId, buildFrame) {
256069
- const requestId = (0, import_node_crypto71.randomUUID)();
256650
+ const requestId = (0, import_node_crypto72.randomUUID)();
256070
256651
  const sent = this.hub.dispatch(nodeId, buildFrame(requestId));
256071
256652
  if (!sent) return Promise.resolve({ ok: false, code: "NODE_OFFLINE" });
256072
256653
  return new Promise((resolve12) => {
@@ -256174,13 +256755,13 @@ function classifyPage(page, lastPushedHash, serviceAccount) {
256174
256755
  if (sha(body2) === lastPushedHash) return { kind: "echo" };
256175
256756
  return { kind: "human-edit", content: body2, updatedBy: page.updatedBy };
256176
256757
  }
256177
- var import_node_crypto72, sha, enc2, dec, MirrorEngine, MemoryMirrorStateStore, MapMirrorIdentities;
256758
+ var import_node_crypto73, sha, enc2, dec, MirrorEngine, MemoryMirrorStateStore, MapMirrorIdentities;
256178
256759
  var init_engine = __esm({
256179
256760
  "../server/src/mirror/engine.ts"() {
256180
256761
  "use strict";
256181
- import_node_crypto72 = require("node:crypto");
256762
+ import_node_crypto73 = require("node:crypto");
256182
256763
  init_src();
256183
- sha = (s2) => (0, import_node_crypto72.createHash)("sha256").update(s2, "utf8").digest("hex");
256764
+ sha = (s2) => (0, import_node_crypto73.createHash)("sha256").update(s2, "utf8").digest("hex");
256184
256765
  enc2 = (s2) => new TextEncoder().encode(s2);
256185
256766
  dec = (b2) => new TextDecoder().decode(b2);
256186
256767
  MirrorEngine = class {
@@ -256461,11 +257042,11 @@ function humanExitCause(exit3) {
256461
257042
  const base = (exit3.reason ? label2[exit3.reason] : void 0) ?? `\u4F1A\u8BDD\u5F02\u5E38\u7ED3\u675F\uFF08${exit3.reason ?? "\u65E0\u9000\u51FA\u4FE1\u606F"}\uFF09`;
256462
257043
  return exit3.errorMessage ? `${base}\uFF1A${exit3.errorMessage.slice(0, 200)}` : base;
256463
257044
  }
256464
- var import_node_crypto73, AUTONOMOUS_ETHOS, CONVERSATIONAL_ETHOS, SHARED_GRAPH_BODY, AUTONOMOUS_RECOVERY_TOOLS, HIGH_RISK_COMMANDS, REJECTED_PROPOSALS_IN_TASK, COORDINATOR_SYSTEM_PROMPT, CONVERSATIONAL_RECOVERY_TOOLS, CONVERSATIONAL_ESCALATION_TOOLS, CONVERSATIONAL_MANAGER_BODY, MACHINE_EXIT_CODES, CoordinatorWorker;
257045
+ var import_node_crypto74, AUTONOMOUS_ETHOS, CONVERSATIONAL_ETHOS, SHARED_GRAPH_BODY, AUTONOMOUS_RECOVERY_TOOLS, HIGH_RISK_COMMANDS, REJECTED_PROPOSALS_IN_TASK, COORDINATOR_SYSTEM_PROMPT, CONVERSATIONAL_RECOVERY_TOOLS, CONVERSATIONAL_ESCALATION_TOOLS, CONVERSATIONAL_MANAGER_BODY, MACHINE_EXIT_CODES, CoordinatorWorker;
256465
257046
  var init_worker = __esm({
256466
257047
  "../server/src/coordinator/worker.ts"() {
256467
257048
  "use strict";
256468
- import_node_crypto73 = require("node:crypto");
257049
+ import_node_crypto74 = require("node:crypto");
256469
257050
  init_src7();
256470
257051
  init_identity();
256471
257052
  AUTONOMOUS_ETHOS = `\u4F60\u662F\u8FD9\u4E2A\u5DE5\u5355\u7684**\u7BA1\u7406\u8005**\u2014\u2014\u804C\u8D23\u662F\u8BA9\u5B83\u987A\u7545\u8DD1\u5B8C\u3002\u7CFB\u7EDF\u5728\u67D0\u4E2A\u8282\u70B9\u5361\u4F4F\u3001\u673A\u68B0\u5206\u8BCA\u786E\u8BA4"\u9700\u8981\u4F60"\u65F6\u5524\u8D77\u4F60\uFF08\u65E0\u4EBA\u5728\u573A\uFF0C\u4F60\u8FD9\u4E00\u8F6E\u628A\u80FD\u505A\u7684\u505A\u6389\uFF09\u3002\u4F60\u7684\u624B\u6BB5\u5F88\u5BBD\uFF1A
@@ -256735,7 +257316,7 @@ ${HIGH_RISK_COMMANDS}`;
256735
257316
  return this.deps.kernel.model.lastSeq.get(artifactId) ?? 0;
256736
257317
  }
256737
257318
  evaluationKey(kind, artifactId, evidence) {
256738
- const fingerprint2 = (0, import_node_crypto73.createHash)("sha256").update(JSON.stringify(evidence)).digest("hex");
257319
+ const fingerprint2 = (0, import_node_crypto74.createHash)("sha256").update(JSON.stringify(evidence)).digest("hex");
256739
257320
  return `${kind}:${artifactId}:${fingerprint2}`;
256740
257321
  }
256741
257322
  artifactEvidence(id) {
@@ -257124,8 +257705,8 @@ ${ctx.nodeFault}
257124
257705
  const taskWithRejections = rejectedSection ? `${task}
257125
257706
 
257126
257707
  ${rejectedSection}` : task;
257127
- const runtimeSessionId = opts?.resumeSessionId ?? (0, import_node_crypto73.randomUUID)();
257128
- const coordinatorRunId = `coordinate:${(0, import_node_crypto73.randomUUID)()}`;
257708
+ const runtimeSessionId = opts?.resumeSessionId ?? (0, import_node_crypto74.randomUUID)();
257709
+ const coordinatorRunId = `coordinate:${(0, import_node_crypto74.randomUUID)()}`;
257129
257710
  const taskWithContext = coordinatorContext ? [
257130
257711
  taskWithRejections,
257131
257712
  ``,
@@ -257169,7 +257750,7 @@ ${rejectedSection}` : task;
257169
257750
  const startedAtMs = Date.now();
257170
257751
  let trajectoryStarted = false;
257171
257752
  if (this.deps.trajectory) {
257172
- const bundleManifest = Object.fromEntries(Object.entries(job.bundle.files).map(([name, content3]) => [name, (0, import_node_crypto73.createHash)("sha256").update(content3).digest("hex")]));
257753
+ const bundleManifest = Object.fromEntries(Object.entries(job.bundle.files).map(([name, content3]) => [name, (0, import_node_crypto74.createHash)("sha256").update(content3).digest("hex")]));
257173
257754
  const session = {
257174
257755
  runId: coordinatorRunId,
257175
257756
  runtimeSessionId,
@@ -257380,11 +257961,11 @@ var init_pg_ident = __esm({
257380
257961
  });
257381
257962
 
257382
257963
  // ../storage/src/postgres.ts
257383
- var import_node_crypto74, ident4, isUniqueViolation, PostgresOplogStore, PostgresBlobStore;
257964
+ var import_node_crypto75, ident4, isUniqueViolation, PostgresOplogStore, PostgresBlobStore;
257384
257965
  var init_postgres = __esm({
257385
257966
  "../storage/src/postgres.ts"() {
257386
257967
  "use strict";
257387
- import_node_crypto74 = require("node:crypto");
257968
+ import_node_crypto75 = require("node:crypto");
257388
257969
  init_pg_ident();
257389
257970
  init_esm();
257390
257971
  init_src();
@@ -257564,7 +258145,7 @@ var init_postgres = __esm({
257564
258145
  return new _PostgresBlobStore(pool, schema);
257565
258146
  }
257566
258147
  async put(bytes2) {
257567
- const hash3 = (0, import_node_crypto74.createHash)("sha256").update(bytes2).digest("hex");
258148
+ const hash3 = (0, import_node_crypto75.createHash)("sha256").update(bytes2).digest("hex");
257568
258149
  await this.pool.query(
257569
258150
  `INSERT INTO ${this.t} (hash, bytes, size, content_type) VALUES ($1, $2, $3, $4) ON CONFLICT (hash) DO NOTHING`,
257570
258151
  [hash3, Buffer.from(bytes2), bytes2.byteLength, sniffContentType(bytes2) ?? null]
@@ -257612,7 +258193,7 @@ function stageAtLeast(stage, min2) {
257612
258193
  return STAGES.indexOf(stage) >= STAGES.indexOf(min2);
257613
258194
  }
257614
258195
  function newVariableId() {
257615
- return `var_${(0, import_node_crypto75.randomUUID)()}`;
258196
+ return `var_${(0, import_node_crypto76.randomUUID)()}`;
257616
258197
  }
257617
258198
  async function declareRecordsAware(db) {
257618
258199
  await db.query(`SELECT set_config('${RECORDS_AWARE_SETTING}', 'on', true)`);
@@ -257968,11 +258549,11 @@ async function listBindingTargetIds(db, s2, kind) {
257968
258549
  [kind]
257969
258550
  )).rows.map((x2) => x2.target_id);
257970
258551
  }
257971
- var import_node_crypto75, STAGES, RECORDS_AWARE_SETTING, markLockName;
258552
+ var import_node_crypto76, STAGES, RECORDS_AWARE_SETTING, markLockName;
257972
258553
  var init_variable_bindings = __esm({
257973
258554
  "../storage/src/variable-bindings.ts"() {
257974
258555
  "use strict";
257975
- import_node_crypto75 = require("node:crypto");
258556
+ import_node_crypto76 = require("node:crypto");
257976
258557
  init_src();
257977
258558
  STAGES = ["off", "dual", "read", "records"];
257978
258559
  RECORDS_AWARE_SETTING = "oasis.variable_records_aware";
@@ -260652,7 +261233,7 @@ async function ensureOneOwnerInvariant(pool, s2) {
260652
261233
  );
260653
261234
  }
260654
261235
  }
260655
- var ident8, PostgresControlPlaneStore, rowToDigitalEmployee, rowToAgentCredential, rowToBridge, rowToOffboarding, toIso, rowToCompany, rowToRoleMigration, rowToMember, rowToInvitation, rowToInviteLink, rowToAccount, rowToVerificationCode;
261236
+ var ident8, PostgresControlPlaneStore, rowToDigitalEmployee, rowToAgentCredential, rowToBridge, rowToOffboarding, toIso, rowToCompany, rowToRoleMigration, migrationRow2, rowToMember, rowToInvitation, rowToInviteLink, rowToAccount, rowToVerificationCode;
260656
261237
  var init_postgres_control_plane = __esm({
260657
261238
  "../storage/src/postgres-control-plane.ts"() {
260658
261239
  "use strict";
@@ -261043,6 +261624,48 @@ var init_postgres_control_plane = __esm({
261043
261624
  client.release();
261044
261625
  }
261045
261626
  }
261627
+ async removeMemberAndReleaseOwnedAgents(companyId, accountId, at2) {
261628
+ const client = await this.pool.connect();
261629
+ try {
261630
+ await client.query("BEGIN");
261631
+ const all2 = await client.query(
261632
+ `SELECT company_id, account_id, actor_id, role, display_name, onboarding_completed_at, onboarding_step
261633
+ FROM ${this.s}.company_members WHERE company_id = $1 FOR UPDATE`,
261634
+ [companyId]
261635
+ );
261636
+ const target = all2.rows.map(rowToMember).find((m2) => m2.accountId === accountId);
261637
+ if (!target) {
261638
+ await client.query("ROLLBACK");
261639
+ return { ok: false, reason: "not_found" };
261640
+ }
261641
+ if (target.role === "owner") {
261642
+ await client.query("ROLLBACK");
261643
+ return { ok: false, reason: "owner_protected" };
261644
+ }
261645
+ const released = await client.query(
261646
+ `UPDATE ${this.s}.digital_employees SET owner_account_id = NULL, updated_at = $3
261647
+ WHERE company_id = $1 AND owner_account_id = $2
261648
+ RETURNING id`,
261649
+ [companyId, accountId, at2]
261650
+ );
261651
+ await client.query(
261652
+ `DELETE FROM ${this.s}.principal_bridge WHERE company_id = $1 AND principal_type = 'human' AND principal_id = $2`,
261653
+ [companyId, accountId]
261654
+ );
261655
+ await client.query(
261656
+ `DELETE FROM ${this.s}.company_members WHERE company_id = $1 AND account_id = $2`,
261657
+ [companyId, accountId]
261658
+ );
261659
+ await client.query("COMMIT");
261660
+ return { ok: true, member: target, releasedAgents: released.rows.map((r) => r.id) };
261661
+ } catch (e) {
261662
+ await client.query("ROLLBACK").catch(() => {
261663
+ });
261664
+ throw e;
261665
+ } finally {
261666
+ client.release();
261667
+ }
261668
+ }
261046
261669
  async updateMemberRoleIfNotLastOwner(companyId, accountId, role) {
261047
261670
  const client = await this.pool.connect();
261048
261671
  try {
@@ -261111,7 +261734,7 @@ var init_postgres_control_plane = __esm({
261111
261734
  `SELECT status FROM ${this.s}.accounts WHERE id = $1`,
261112
261735
  [toAccountId]
261113
261736
  );
261114
- if (acc.rows[0] && acc.rows[0].status !== "active") {
261737
+ if (!acc.rows[0] || acc.rows[0].status !== "active") {
261115
261738
  await client.query("ROLLBACK");
261116
261739
  return { ok: false, reason: "target_inactive" };
261117
261740
  }
@@ -261148,6 +261771,73 @@ var init_postgres_control_plane = __esm({
261148
261771
  client.release();
261149
261772
  }
261150
261773
  }
261774
+ // —— 存量身份映射(ADR D3)——
261775
+ async migrateSoleCompanyOwner(companyId, toAccountId, audit2) {
261776
+ const client = await this.pool.connect();
261777
+ try {
261778
+ await client.query("BEGIN");
261779
+ const all2 = await client.query(
261780
+ `SELECT company_id, account_id, actor_id, role, display_name, onboarding_completed_at, onboarding_step
261781
+ FROM ${this.s}.company_members WHERE company_id = $1 FOR UPDATE`,
261782
+ [companyId]
261783
+ );
261784
+ const rows = all2.rows.map(rowToMember);
261785
+ const target = rows.find((m2) => m2.accountId === toAccountId);
261786
+ if (!target) {
261787
+ await client.query("ROLLBACK");
261788
+ return { ok: false, reason: "not_found" };
261789
+ }
261790
+ const others = rows.filter((m2) => m2.role === "owner" && m2.accountId !== toAccountId);
261791
+ if (others.length === 0 && target.role === "owner") {
261792
+ await client.query("COMMIT");
261793
+ return { ok: true, owner: target, demoted: [], auditRows: [] };
261794
+ }
261795
+ if (others.length > 0) {
261796
+ await client.query(
261797
+ `UPDATE ${this.s}.company_members SET role = 'admin' WHERE company_id = $1 AND role = 'owner' AND account_id <> $2`,
261798
+ [companyId, toAccountId]
261799
+ );
261800
+ }
261801
+ if (target.role !== "owner") {
261802
+ await client.query(
261803
+ `UPDATE ${this.s}.company_members SET role = 'owner' WHERE company_id = $1 AND account_id = $2`,
261804
+ [companyId, toAccountId]
261805
+ );
261806
+ }
261807
+ const auditRows = [
261808
+ ...others.map((m2) => migrationRow2(companyId, m2.accountId, "owner", "admin", audit2)),
261809
+ ...target.role !== "owner" ? [migrationRow2(companyId, toAccountId, target.role, "owner", audit2)] : []
261810
+ ];
261811
+ for (const r of auditRows) {
261812
+ await client.query(
261813
+ `INSERT INTO ${this.s}.company_member_role_migrations
261814
+ (company_id, account_id, role_before, role_after, evidence, manifest_version, applied_at)
261815
+ VALUES ($1,$2,$3,$4,$5,$6,$7)`,
261816
+ [r.companyId, r.accountId, r.roleBefore, r.roleAfter, r.evidence, r.manifestVersion ?? null, r.appliedAt]
261817
+ );
261818
+ }
261819
+ await client.query("COMMIT");
261820
+ return {
261821
+ ok: true,
261822
+ owner: { ...target, role: "owner" },
261823
+ demoted: others.map((m2) => ({ ...m2, role: "admin" })),
261824
+ auditRows
261825
+ };
261826
+ } catch (e) {
261827
+ await client.query("ROLLBACK").catch(() => {
261828
+ });
261829
+ throw e;
261830
+ } finally {
261831
+ client.release();
261832
+ }
261833
+ }
261834
+ async setMemberRoleOnly(companyId, accountId, role) {
261835
+ const r = await this.pool.query(
261836
+ `UPDATE ${this.s}.company_members SET role = $3 WHERE company_id = $1 AND account_id = $2`,
261837
+ [companyId, accountId, role]
261838
+ );
261839
+ return (r.rowCount ?? 0) > 0;
261840
+ }
261151
261841
  // —— 存量身份映射审计(ADR D3.4)——
261152
261842
  async appendMemberRoleMigrations(rows) {
261153
261843
  if (rows.length === 0) return;
@@ -261661,6 +262351,15 @@ var init_postgres_control_plane = __esm({
261661
262351
  ...row.manifest_version != null ? { manifestVersion: row.manifest_version } : {},
261662
262352
  appliedAt: toIso(row.applied_at)
261663
262353
  });
262354
+ migrationRow2 = (companyId, accountId, roleBefore, roleAfter, audit2) => ({
262355
+ companyId,
262356
+ accountId,
262357
+ roleBefore,
262358
+ roleAfter,
262359
+ evidence: audit2.evidence,
262360
+ ...audit2.evidence === "manifest" && audit2.manifestVersion !== void 0 ? { manifestVersion: audit2.manifestVersion } : {},
262361
+ appliedAt: audit2.appliedAt
262362
+ });
261664
262363
  rowToMember = (row) => ({
261665
262364
  companyId: row.company_id,
261666
262365
  accountId: row.account_id,
@@ -267916,11 +268615,11 @@ var init_postgres_delegations = __esm({
267916
268615
  });
267917
268616
 
267918
268617
  // ../storage/src/postgres-nodes.ts
267919
- var import_node_crypto76, ident13, PostgresNodeStore, PostgresNodeTokenStore, rowToNode, rowToFault, rowToQuota, rowToRuntime;
268618
+ var import_node_crypto77, ident13, PostgresNodeStore, PostgresNodeTokenStore, rowToNode, rowToFault, rowToQuota, rowToRuntime;
267920
268619
  var init_postgres_nodes = __esm({
267921
268620
  "../storage/src/postgres-nodes.ts"() {
267922
268621
  "use strict";
267923
- import_node_crypto76 = require("node:crypto");
268622
+ import_node_crypto77 = require("node:crypto");
267924
268623
  init_pg_ident();
267925
268624
  init_esm();
267926
268625
  ident13 = (s2) => {
@@ -268270,7 +268969,7 @@ var init_postgres_nodes = __esm({
268270
268969
  return store;
268271
268970
  }
268272
268971
  issue(nodeId) {
268273
- const token = `ont_${(0, import_node_crypto76.randomBytes)(24).toString("base64url")}`;
268972
+ const token = `ont_${(0, import_node_crypto77.randomBytes)(24).toString("base64url")}`;
268274
268973
  this.cache.set(token, nodeId);
268275
268974
  void this.pool.query(`INSERT INTO ${this.s}.node_tokens (token,node_id) VALUES ($1,$2)`, [token, nodeId]);
268276
268975
  return token;
@@ -268572,11 +269271,11 @@ var init_postgres_type_registry = __esm({
268572
269271
  });
268573
269272
 
268574
269273
  // ../storage/src/postgres-actor-memory.ts
268575
- var import_node_crypto77, matchClause, ident15, PostgresActorMemoryStore, rowToIndexEntry, rowToRecord;
269274
+ var import_node_crypto78, matchClause, ident15, PostgresActorMemoryStore, rowToIndexEntry, rowToRecord;
268576
269275
  var init_postgres_actor_memory = __esm({
268577
269276
  "../storage/src/postgres-actor-memory.ts"() {
268578
269277
  "use strict";
268579
- import_node_crypto77 = require("node:crypto");
269278
+ import_node_crypto78 = require("node:crypto");
268580
269279
  init_pg_ident();
268581
269280
  init_src();
268582
269281
  matchClause = (q2) => q2.requireMatch && q2.keywords.length > 0 ? " WHERE m > 0" : "";
@@ -268786,7 +269485,7 @@ var init_postgres_actor_memory = __esm({
268786
269485
  }
268787
269486
  async write(input, now) {
268788
269487
  if (input.memId === void 0) {
268789
- const memId = `mem:${(0, import_node_crypto77.randomUUID)()}`;
269488
+ const memId = `mem:${(0, import_node_crypto78.randomUUID)()}`;
268790
269489
  const r2 = await this.pool.query(
268791
269490
  `INSERT INTO ${this.s}.actor_memories
268792
269491
  (mem_id, actor_id, project_id, keywords, content, version, created_at, updated_at, accessed_at, source_artifact_id, source_session_id)
@@ -270357,14 +271056,14 @@ function companySchemaName(companyId) {
270357
271056
  if (typeof companyId !== "string" || !companyId.trim()) {
270358
271057
  throw new Error("company schema mapping \u9700\u8981\u975E\u7A7A companyId\uFF08fail-closed\uFF1A\u4E0D\u56DE\u9000\u9ED8\u8BA4\u516C\u53F8\uFF09");
270359
271058
  }
270360
- const hex = (0, import_node_crypto78.createHash)("sha256").update(companyId, "utf8").digest("hex");
271059
+ const hex = (0, import_node_crypto79.createHash)("sha256").update(companyId, "utf8").digest("hex");
270361
271060
  return `${COMPANY_SCHEMA_PREFIX}${hex.slice(0, DIGEST_HEX_LEN)}`;
270362
271061
  }
270363
- var import_node_crypto78, COMPANY_SCHEMA_PREFIX, DIGEST_HEX_LEN, SchemaMappingConflictError, TenantStorageLocationStore;
271062
+ var import_node_crypto79, COMPANY_SCHEMA_PREFIX, DIGEST_HEX_LEN, SchemaMappingConflictError, TenantStorageLocationStore;
270364
271063
  var init_tenant_schema_mapping = __esm({
270365
271064
  "../storage/src/tenant-schema-mapping.ts"() {
270366
271065
  "use strict";
270367
- import_node_crypto78 = require("node:crypto");
271066
+ import_node_crypto79 = require("node:crypto");
270368
271067
  init_esm();
270369
271068
  init_pg_ident();
270370
271069
  COMPANY_SCHEMA_PREFIX = "c_";
@@ -270438,7 +271137,7 @@ var init_tenant_schema_mapping = __esm({
270438
271137
 
270439
271138
  // ../storage/src/company-data-plane-schema.ts
270440
271139
  function canonicalTypeDefHash(def) {
270441
- return `sha256:${(0, import_node_crypto79.createHash)("sha256").update(JSON.stringify(def), "utf8").digest("hex")}`;
271140
+ return `sha256:${(0, import_node_crypto80.createHash)("sha256").update(JSON.stringify(def), "utf8").digest("hex")}`;
270442
271141
  }
270443
271142
  function defaultArtifactTypeTemplate() {
270444
271143
  return [
@@ -270620,11 +271319,11 @@ async function seedDefaultArtifactTypes(pool, schema, templates = defaultArtifac
270620
271319
  }
270621
271320
  return { inserted };
270622
271321
  }
270623
- var import_node_crypto79, DEFAULT_ARTIFACT_TYPE_TEMPLATE_VERSION;
271322
+ var import_node_crypto80, DEFAULT_ARTIFACT_TYPE_TEMPLATE_VERSION;
270624
271323
  var init_company_data_plane_schema = __esm({
270625
271324
  "../storage/src/company-data-plane-schema.ts"() {
270626
271325
  "use strict";
270627
- import_node_crypto79 = require("node:crypto");
271326
+ import_node_crypto80 = require("node:crypto");
270628
271327
  init_esm();
270629
271328
  init_pg_ident();
270630
271329
  DEFAULT_ARTIFACT_TYPE_TEMPLATE_VERSION = "4c368e5c-types-v1";
@@ -270703,11 +271402,11 @@ function rowToFile2(row) {
270703
271402
  updatedAt: new Date(row.updated_at).toISOString()
270704
271403
  };
270705
271404
  }
270706
- var import_node_crypto80, COMPANY_CONFLICT_MSG, CompanyIdentifierConflictError, CompanyOwnedRegistryStore;
271405
+ var import_node_crypto81, COMPANY_CONFLICT_MSG, CompanyIdentifierConflictError, CompanyOwnedRegistryStore;
270707
271406
  var init_company_owned_registry_store = __esm({
270708
271407
  "../storage/src/company-owned-registry-store.ts"() {
270709
271408
  "use strict";
270710
- import_node_crypto80 = require("node:crypto");
271409
+ import_node_crypto81 = require("node:crypto");
270711
271410
  init_esm();
270712
271411
  init_src();
270713
271412
  init_pg_ident();
@@ -271092,7 +271791,7 @@ var init_company_owned_registry_store = __esm({
271092
271791
  enabled: skill.enabled,
271093
271792
  files: files.map((f2) => ({ path: f2.path, blobHash: f2.blobHash }))
271094
271793
  });
271095
- return (0, import_node_crypto80.createHash)("sha256").update(body2).digest("hex");
271794
+ return (0, import_node_crypto81.createHash)("sha256").update(body2).digest("hex");
271096
271795
  }
271097
271796
  };
271098
271797
  }
@@ -273902,7 +274601,7 @@ function sourceSetConservation(input) {
273902
274601
  return input.sourceFinal === input.copied + input.approvedRuntimeConversion + input.approvedArchive + input.approvedDiscard;
273903
274602
  }
273904
274603
  function payloadChecksum(value2) {
273905
- return (0, import_node_crypto81.createHash)("sha256").update(JSON.stringify(value2)).digest("hex");
274604
+ return (0, import_node_crypto82.createHash)("sha256").update(JSON.stringify(value2)).digest("hex");
273906
274605
  }
273907
274606
  async function copyWorkorderDraftsIdempotent(pool, companyId, companySchema, manifestId, opts) {
273908
274607
  const cs = quoteIdent2(companySchema);
@@ -274294,11 +274993,11 @@ async function copyTraceFamilyIdempotent(pool, companyId, companySchema, manifes
274294
274993
  sequences
274295
274994
  };
274296
274995
  }
274297
- var import_node_crypto81, MissingInstallerActorsError, TypeCatalogNotCoveredError, ORG_REGISTRY_TABLES, TRACE_FAMILY;
274996
+ var import_node_crypto82, MissingInstallerActorsError, TypeCatalogNotCoveredError, ORG_REGISTRY_TABLES, TRACE_FAMILY;
274298
274997
  var init_tenant_migration_operator = __esm({
274299
274998
  "../storage/src/tenant-migration-operator.ts"() {
274300
274999
  "use strict";
274301
- import_node_crypto81 = require("node:crypto");
275000
+ import_node_crypto82 = require("node:crypto");
274302
275001
  init_esm();
274303
275002
  init_pg_ident();
274304
275003
  init_tenancy_attribution();
@@ -274534,8 +275233,8 @@ var init_tenant_new_company_provision = __esm({
274534
275233
  // ../storage/src/knowledge-snapshot-crypto.ts
274535
275234
  function sealSnapshot(snapshot, key2) {
274536
275235
  if (snapshot.content.startsWith(PREFIX3)) return structuredClone(snapshot);
274537
- const iv = (0, import_node_crypto82.randomBytes)(12);
274538
- const cipher = (0, import_node_crypto82.createCipheriv)("aes-256-gcm", bytes(key2), iv);
275236
+ const iv = (0, import_node_crypto83.randomBytes)(12);
275237
+ const cipher = (0, import_node_crypto83.createCipheriv)("aes-256-gcm", bytes(key2), iv);
274539
275238
  cipher.setAAD(aad2(snapshot));
274540
275239
  const encrypted = Buffer.concat([cipher.update(snapshot.content, "utf8"), cipher.final()]);
274541
275240
  return { ...structuredClone(snapshot), content: `${PREFIX3}${iv.toString("base64url")}.${cipher.getAuthTag().toString("base64url")}.${encrypted.toString("base64url")}` };
@@ -274545,7 +275244,7 @@ function openSnapshot(snapshot, key2) {
274545
275244
  const packed = snapshot.content.slice(PREFIX3.length).split(".");
274546
275245
  if (packed.length !== 3 || packed.some((part) => !part)) throw new Error(`knowledge snapshot ${snapshot.ref} ciphertext is malformed`);
274547
275246
  const [iv, tag, data] = packed;
274548
- const decipher = (0, import_node_crypto82.createDecipheriv)("aes-256-gcm", bytes(key2), Buffer.from(iv, "base64url"));
275247
+ const decipher = (0, import_node_crypto83.createDecipheriv)("aes-256-gcm", bytes(key2), Buffer.from(iv, "base64url"));
274549
275248
  decipher.setAAD(aad2(snapshot));
274550
275249
  decipher.setAuthTag(Buffer.from(tag, "base64url"));
274551
275250
  return {
@@ -274553,11 +275252,11 @@ function openSnapshot(snapshot, key2) {
274553
275252
  content: Buffer.concat([decipher.update(Buffer.from(data, "base64url")), decipher.final()]).toString("utf8")
274554
275253
  };
274555
275254
  }
274556
- var import_node_crypto82, PREFIX3, bytes, aad2;
275255
+ var import_node_crypto83, PREFIX3, bytes, aad2;
274557
275256
  var init_knowledge_snapshot_crypto = __esm({
274558
275257
  "../storage/src/knowledge-snapshot-crypto.ts"() {
274559
275258
  "use strict";
274560
- import_node_crypto82 = require("node:crypto");
275259
+ import_node_crypto83 = require("node:crypto");
274561
275260
  PREFIX3 = "oasis-knowledge:v1:";
274562
275261
  bytes = (key2) => {
274563
275262
  const value2 = Buffer.from(key2);
@@ -275449,7 +276148,7 @@ function reportedCost(v2) {
275449
276148
  }
275450
276149
  function newUsageBackfillManifestId(now = /* @__PURE__ */ new Date()) {
275451
276150
  const stamp = now.toISOString().replace(/[-:]/g, "").replace(/\.\d+Z$/, "Z");
275452
- return `ulb-${stamp}-${(0, import_node_crypto83.randomBytes)(3).toString("hex")}`;
276151
+ return `ulb-${stamp}-${(0, import_node_crypto84.randomBytes)(3).toString("hex")}`;
275453
276152
  }
275454
276153
  async function backfillUsageLedger(ledger, opts) {
275455
276154
  if (!opts.dryRun && !opts.manifestId) throw new Error("execute \u9700\u8981 manifestId");
@@ -275998,11 +276697,11 @@ async function audit(c, s2, manifestId, runId, field, before, after, createdAt)
275998
276697
  [manifestId, runId, field, before == null ? null : JSON.stringify(before), after == null ? null : JSON.stringify(after), createdAt]
275999
276698
  );
276000
276699
  }
276001
- var import_node_crypto83, BUCKETS, ENDED_SQL, START_EVIDENCE_SQL, CHAT_RUN_SQL, ALLOWED_MODEL_EVIDENCE_TIERS, emptyStep, MODEL_BACKFILL_SOURCE, fourOf, sameUsage, ChatRunLookup;
276700
+ var import_node_crypto84, BUCKETS, ENDED_SQL, START_EVIDENCE_SQL, CHAT_RUN_SQL, ALLOWED_MODEL_EVIDENCE_TIERS, emptyStep, MODEL_BACKFILL_SOURCE, fourOf, sameUsage, ChatRunLookup;
276002
276701
  var init_usage_ledger_backfill = __esm({
276003
276702
  "../storage/src/usage-ledger-backfill.ts"() {
276004
276703
  "use strict";
276005
- import_node_crypto83 = require("node:crypto");
276704
+ import_node_crypto84 = require("node:crypto");
276006
276705
  init_src();
276007
276706
  init_pg_ident();
276008
276707
  init_postgres_trace();
@@ -276581,31 +277280,64 @@ var init_connector_tool_versions = __esm({
276581
277280
  });
276582
277281
 
276583
277282
  // ../server/src/design/board-watch.ts
276584
- function decideBoardLetter(c, nowMs, quietMs) {
276585
- if (c.hasOpenWatchLetter) return false;
276586
- if (c.freshTerminalRunId !== null) return true;
276587
- if (c.boardMtime === null) return false;
276588
- if (!c.hasRevision) return false;
276589
- if (c.lastOpAtMs === null || c.boardMtime <= c.lastOpAtMs) return false;
276590
- if (nowMs - c.boardMtime < quietMs) return false;
276591
- if (c.lastWatchMtime !== null && c.boardMtime <= c.lastWatchMtime) return false;
276592
- return true;
277283
+ function parseOdTime(v2) {
277284
+ if (typeof v2 === "number") return Number.isFinite(v2) ? v2 : null;
277285
+ if (typeof v2 !== "string" || v2.trim().length === 0) return null;
277286
+ const s2 = v2.trim();
277287
+ if (/^\d+(\.\d+)?$/.test(s2)) return Number(s2);
277288
+ const t = Date.parse(s2);
277289
+ return Number.isFinite(t) ? t : null;
277290
+ }
277291
+ function nonEmpty2(v2) {
277292
+ return typeof v2 === "string" && v2.trim().length > 0 ? v2 : null;
277293
+ }
277294
+ function roundOfMessage(m2, conversationId = null) {
277295
+ if (m2["role"] !== "assistant") return null;
277296
+ const status = nonEmpty2(m2["runStatus"]);
277297
+ if (status === null || !TERMINAL_RUN_STATUS.has(status)) return null;
277298
+ const endedAt = parseOdTime(m2["endedAt"]);
277299
+ if (endedAt === null) return null;
277300
+ const runId = nonEmpty2(m2["runId"]);
277301
+ const messageId = nonEmpty2(m2["id"]);
277302
+ if (runId === null && messageId === null) return null;
277303
+ const files = Array.isArray(m2["producedFiles"]) ? m2["producedFiles"] : [];
277304
+ const producedFiles = files.map((f2) => typeof f2 === "string" ? f2 : f2 && typeof f2 === "object" ? nonEmpty2(f2["path"]) ?? nonEmpty2(f2["name"]) : null).filter((s2) => s2 !== null);
277305
+ const { error: error2, errorCode } = messageError(m2, status);
277306
+ return {
277307
+ key: runId ?? `msg:${messageId}`,
277308
+ conversationId,
277309
+ runId,
277310
+ status,
277311
+ agentId: nonEmpty2(m2["agentId"]),
277312
+ endedAt,
277313
+ producedFiles,
277314
+ error: error2,
277315
+ errorCode
277316
+ };
277317
+ }
277318
+ function messageError(m2, status) {
277319
+ const events = Array.isArray(m2["events"]) ? m2["events"] : [];
277320
+ for (let i = events.length - 1; i >= 0; i--) {
277321
+ const e = events[i];
277322
+ if (!e || typeof e !== "object") continue;
277323
+ const ev = e;
277324
+ if (ev["kind"] !== "status" || ev["label"] !== "error") continue;
277325
+ const detail = nonEmpty2(ev["detail"]);
277326
+ if (detail !== null) return { error: detail, errorCode: nonEmpty2(ev["code"]) };
277327
+ }
277328
+ if (status !== "succeeded") {
277329
+ const content3 = nonEmpty2(m2["content"]);
277330
+ if (content3 !== null) return { error: content3, errorCode: null };
277331
+ }
277332
+ return { error: null, errorCode: null };
276593
277333
  }
276594
- var MARKER_PREFIX, boardWatchMarker, parseMarker, RUN_MARKER_PREFIX, boardWatchRunMarker, parseRunMarker, TERMINAL_RUN_STATUS, BoardWatcher;
277334
+ var RUN_MARKER_PREFIX, boardWatchRunMarker, parseRunMarker, SINCE_MARKER_PREFIX, boardWatchSinceMarker, parseSinceMarker, TERMINAL_RUN_STATUS, MAX_REASON_CHARS, MAX_LISTED_FILES, BoardWatcher;
276595
277335
  var init_board_watch = __esm({
276596
277336
  "../server/src/design/board-watch.ts"() {
276597
277337
  "use strict";
276598
277338
  init_src7();
276599
- MARKER_PREFIX = "[od-board-watch mtime:";
276600
- boardWatchMarker = (mtimeMs) => `${MARKER_PREFIX}${mtimeMs}]`;
276601
- parseMarker = (body2) => {
276602
- const i = body2.indexOf(MARKER_PREFIX);
276603
- if (i < 0) return null;
276604
- const n = Number.parseInt(body2.slice(i + MARKER_PREFIX.length), 10);
276605
- return Number.isFinite(n) ? n : null;
276606
- };
276607
277339
  RUN_MARKER_PREFIX = "[od-board-watch run:";
276608
- boardWatchRunMarker = (runId) => `${RUN_MARKER_PREFIX}${runId}]`;
277340
+ boardWatchRunMarker = (roundKey) => `${RUN_MARKER_PREFIX}${roundKey}]`;
276609
277341
  parseRunMarker = (body2) => {
276610
277342
  const i = body2.indexOf(RUN_MARKER_PREFIX);
276611
277343
  if (i < 0) return null;
@@ -276614,113 +277346,183 @@ var init_board_watch = __esm({
276614
277346
  const id = body2.slice(i + RUN_MARKER_PREFIX.length, j);
276615
277347
  return id.length > 0 ? id : null;
276616
277348
  };
277349
+ SINCE_MARKER_PREFIX = "[od-board-watch since:";
277350
+ boardWatchSinceMarker = (sinceMs) => `${SINCE_MARKER_PREFIX}${sinceMs}]`;
277351
+ parseSinceMarker = (body2) => {
277352
+ const i = body2.indexOf(SINCE_MARKER_PREFIX);
277353
+ if (i < 0) return null;
277354
+ const n = Number.parseInt(body2.slice(i + SINCE_MARKER_PREFIX.length), 10);
277355
+ return Number.isFinite(n) ? n : null;
277356
+ };
276617
277357
  TERMINAL_RUN_STATUS = /* @__PURE__ */ new Set(["succeeded", "failed", "canceled", "cancelled"]);
277358
+ MAX_REASON_CHARS = 500;
277359
+ MAX_LISTED_FILES = 10;
276618
277360
  BoardWatcher = class {
276619
277361
  constructor(deps) {
276620
277362
  this.deps = deps;
276621
- }
276622
- odGet(path47) {
277363
+ if (!deps.kernel && !(deps.listCompanies && deps.kernelFor)) throw new Error("BoardWatcher \u9700\u8981 kernel\uFF0C\u6216 listCompanies + kernelFor");
277364
+ }
277365
+ /** `<公司>\0<artifactId>` → 本进程第一次读成功时钉住的起算点(还没写过带 since 的信时用),免得在第一封信之前随时间往后漂。 */
277366
+ pinnedSince = /* @__PURE__ */ new Map();
277367
+ /** `<公司>\0<boardKey>\0<conversationId>` → 本进程上次把这个对话的轮次处理完时它的 updatedAt。 */
277368
+ seenConvAt = /* @__PURE__ */ new Map();
277369
+ /** 公司 → 上次记下的失败原文(同样的错只记一次;成功一轮就清掉)。 */
277370
+ companyLastError = /* @__PURE__ */ new Map();
277371
+ async odGet(path47) {
276623
277372
  const fetchImpl = this.deps.fetchImpl ?? fetch;
276624
- return fetchImpl(`${this.deps.odBase}${path47}`, this.deps.odToken ? { headers: { authorization: `Bearer ${this.deps.odToken}` } } : void 0);
276625
- }
276626
- /** 查一块板的交付物(排 task-context/、只算带 artifactManifest 的)。404/网络错 → null(板不存在)。 */
276627
- async boardFiles(boardKey) {
276628
277373
  try {
276629
- const res = await this.odGet(`/api/projects/${encodeURIComponent(boardKey)}/files`);
276630
- if (!res.ok) return null;
276631
- const body2 = await res.json();
276632
- return (body2.files ?? []).filter((f2) => !f2.path.startsWith("task-context/") && f2.artifactManifest !== void 0).map((f2) => ({ path: f2.path, kind: f2.kind ?? null, mtime: f2.mtime ?? null }));
276633
- } catch {
276634
- return null;
277374
+ const res = await fetchImpl(`${this.deps.odBase}${path47}`, this.deps.odToken ? { headers: { authorization: `Bearer ${this.deps.odToken}` } } : void 0);
277375
+ if (res.status === 401 || res.status === 403) return { kind: "denied", status: res.status };
277376
+ if (res.status === 404) return { kind: "missing" };
277377
+ if (!res.ok) return { kind: "error", detail: `HTTP ${res.status}` };
277378
+ return { kind: "ok", value: await res.json() };
277379
+ } catch (err) {
277380
+ return { kind: "error", detail: String(err) };
276635
277381
  }
276636
277382
  }
276637
277383
  /**
276638
- * 查一块板的 run 列表(`GET /api/runs?projectId=`,与 CLI `design status` 同源)。
276639
- * run 注册表是 od 的**内存态**——重启即空,查不到不代表没跑过;所以这里失败一律降级成空数组,
276640
- * 由板文件 mtime 那条慢路径兜底,绝不因为查不到 run 就压掉写信。
277384
+ * 列出一块板上结束时间可能晚于 since 的轮次(跨对话;顺序不重要,调用方按 endedAt 排)。
277385
+ *
277386
+ * 省流:od 的 assistant 消息带整段 events 与 content,每分钟把每个对话的 /messages 全拉一遍,轮次多的板
277387
+ * 一分钟就是几 MB。所以先用 /conversations 列表里的 updatedAt / latestRun.endedAt(取较大)筛:
277388
+ * - 不晚于 since → 这个对话里不可能有要写信的轮次,跳过;
277389
+ * - 和本进程上次看完时一样 → 没变,跳过(seenConvAt,serve 重启即清空,退回只按 since 筛,run marker 去重兜着)。
277390
+ * 两个时间都没有的对话照拉,不猜。返回每个拉过的对话的 updatedAt,由 tick 在它的轮次都写完信后记进 seenConvAt。
276641
277391
  */
276642
- async boardRuns(boardKey) {
276643
- try {
276644
- const res = await this.odGet(`/api/runs?projectId=${encodeURIComponent(boardKey)}`);
276645
- if (!res.ok) return [];
276646
- const body2 = await res.json();
276647
- return (body2.runs ?? []).map((r) => ({ runId: String(r["runId"] ?? r["id"] ?? ""), status: String(r["status"] ?? "unknown") })).filter((r) => r.runId.length > 0);
276648
- } catch {
276649
- return [];
276650
- }
277392
+ async boardRounds(company, boardKey, since) {
277393
+ const base = `/api/projects/${encodeURIComponent(boardKey)}/conversations`;
277394
+ const convs = await this.odGet(base);
277395
+ if (convs.kind !== "ok") return convs;
277396
+ const rounds = [];
277397
+ const fetched = /* @__PURE__ */ new Map();
277398
+ for (const c of convs.value.conversations ?? []) {
277399
+ const cid = nonEmpty2(c["id"]);
277400
+ if (cid === null) continue;
277401
+ const latestRun = c["latestRun"] && typeof c["latestRun"] === "object" ? c["latestRun"] : {};
277402
+ const times = [parseOdTime(c["updatedAt"]), parseOdTime(latestRun["endedAt"])].filter((t) => t !== null);
277403
+ const convAt = times.length > 0 ? Math.max(...times) : null;
277404
+ if (convAt !== null && (convAt <= since || this.seenConvAt.get(`${company}\0${boardKey}\0${cid}`) === convAt)) continue;
277405
+ const msgs = await this.odGet(`${base}/${encodeURIComponent(cid)}/messages`);
277406
+ if (msgs.kind === "missing") continue;
277407
+ if (msgs.kind !== "ok") return msgs;
277408
+ fetched.set(cid, convAt);
277409
+ for (const m2 of msgs.value.messages ?? []) {
277410
+ if (!m2 || typeof m2 !== "object") continue;
277411
+ const r = roundOfMessage(m2, cid);
277412
+ if (r !== null) rounds.push(r);
277413
+ }
277414
+ }
277415
+ return { kind: "ok", value: { rounds, fetched } };
277416
+ }
277417
+ /** 失败轮消息上没有错误原文时,去 `/api/runs` 按 runId 找 od 记的 error;找不到(含 od 重启过)→ null。 */
277418
+ async runErrorFromRegistry(boardKey, runId) {
277419
+ const res = await this.odGet(`/api/runs?projectId=${encodeURIComponent(boardKey)}`);
277420
+ if (res.kind !== "ok") return null;
277421
+ const hit = (res.value.runs ?? []).find((r) => String(r["runId"] ?? r["id"] ?? "") === runId);
277422
+ return hit ? nonEmpty2(hit["error"]) : null;
276651
277423
  }
276652
277424
  async tick(now = Date.now()) {
276653
- const { kernel, boardKeyOf, log: log3 } = this.deps;
276654
- const quietMs = this.deps.quietMs ?? 10 * 6e4;
277425
+ const { log: log3 } = this.deps;
277426
+ const denied = [];
277427
+ const { listCompanies, kernelFor } = this.deps;
277428
+ if (listCompanies && kernelFor) {
277429
+ for (const companyId of await listCompanies()) {
277430
+ try {
277431
+ await this.tickCompany(companyId, await kernelFor(companyId), now, denied);
277432
+ this.companyLastError.delete(companyId);
277433
+ } catch (err) {
277434
+ const msg = String(err);
277435
+ if (this.companyLastError.get(companyId) === msg) continue;
277436
+ this.companyLastError.set(companyId, msg);
277437
+ log3?.(`[board-watch] [${companyId}] \u8FD9\u4E00\u8F6E\u6CA1\u5DE1\u6210\uFF08\u5176\u4F59\u516C\u53F8\u7EE7\u7EED\uFF09\uFF1A${msg}`);
277438
+ }
277439
+ }
277440
+ } else {
277441
+ await this.tickCompany("", this.deps.kernel, now, denied);
277442
+ }
277443
+ if (denied.length > 0) {
277444
+ const codes = [...new Set(denied.map((d) => d.status))].join("/");
277445
+ const eg = denied[0];
277446
+ log3?.(
277447
+ `[board-watch] od \u62D2\u7EDD\u4E86\u5DE1\u68C0\u8BF7\u6C42\uFF08HTTP ${codes}\uFF0C${denied.length} \u5757\u677F\uFF0C\u5982 ${eg.company ? `[${eg.company}] ` : ""}${eg.boardKey}\uFF09\uFF1A${this.deps.odToken ? "\u5E26\u4E86 OASIS_OPEN_DESIGN_TOKEN \u4F46 od \u4E0D\u8BA4\uFF0C\u6838\u5BF9\u5B83\u4E0E od \u7684 OD_API_TOKEN \u662F\u5426\u4E00\u81F4" : "\u6CA1\u914D OASIS_OPEN_DESIGN_TOKEN\uFF0Cod \u8981\u4EE4\u724C"}\u3002\u8FD9\u4E9B\u677F\u672C\u8F6E\u6CA1\u770B\u6210`
277448
+ );
277449
+ }
277450
+ }
277451
+ /** 巡一家公司(company 为空串 = 单 kernel 模式);被拒的板记进 denied,由 tick 统一汇总成一行。 */
277452
+ async tickCompany(company, kernel, now, denied) {
277453
+ const { boardKeyOf, log: log3 } = this.deps;
277454
+ const graceMs = this.deps.firstLookGraceMs ?? 10 * 6e4;
276655
277455
  const maxPerTick = this.deps.maxLettersPerTick ?? 2;
276656
277456
  const actor = this.deps.actor ?? "actor:system";
277457
+ const tag = company ? `[${company}] ` : "";
276657
277458
  const model = kernel.model;
276658
277459
  let written = 0;
276659
- for (const a of model.artifacts.values()) {
276660
- if (a.type !== "design") continue;
276661
- if (lifecycleOf(model, a.id) !== "active") continue;
277460
+ for (const a of [...model.artifacts.values()]) {
276662
277461
  if (written >= maxPerTick) {
276663
- log3?.(`[board-watch] \u672C\u8F6E\u5199\u4FE1\u5DF2\u8FBE\u4E0A\u9650 ${maxPerTick}\uFF0C\u5176\u4F59\u677F\u4E0B\u8F6E\u518D\u770B`);
277462
+ log3?.(`[board-watch] ${tag}\u672C\u8F6E\u5199\u4FE1\u5DF2\u8FBE\u4E0A\u9650 ${maxPerTick}\uFF0C\u5176\u4F59\u677F\u4E0B\u8F6E\u518D\u770B`);
276664
277463
  break;
276665
277464
  }
277465
+ if (a.type !== "design") continue;
277466
+ if (lifecycleOf(model, a.id) !== "active") continue;
276666
277467
  const boardKey = boardKeyOf(a.id);
276667
- const files = await this.boardFiles(boardKey);
276668
- if (files === null) continue;
276669
- const runs = await this.boardRuns(boardKey);
276670
- if (files.length === 0 && runs.length === 0) continue;
276671
- const boardMtime = files.reduce((mx, f2) => f2.mtime !== null && (mx === null || f2.mtime > mx) ? f2.mtime : mx, null);
276672
- const watchLetters = annotationsOf(model, a.id).filter(
276673
- (x2) => x2.author === actor && (parseMarker(x2.body) !== null || parseRunMarker(x2.body) !== null)
276674
- );
276675
- const letteredRuns = new Set(
276676
- watchLetters.map((x2) => parseRunMarker(x2.body)).filter((s2) => s2 !== null)
276677
- );
276678
- const newestTerminalRun = runs.find((r) => TERMINAL_RUN_STATUS.has(r.status)) ?? null;
276679
- const lastOpAt = model.lastOpAt.get(a.id);
276680
- const candidate = {
276681
- boardMtime,
276682
- hasRevision: revisionsOf(model, a.id).length > 0,
276683
- lastOpAtMs: lastOpAt ? Date.parse(lastOpAt) : null,
276684
- hasOpenWatchLetter: watchLetters.some((x2) => x2.state === "open"),
276685
- lastWatchMtime: watchLetters.reduce((mx, x2) => {
276686
- const m2 = parseMarker(x2.body);
276687
- return m2 !== null && (mx === null || m2 > mx) ? m2 : mx;
276688
- }, null),
276689
- freshTerminalRunId: newestTerminalRun !== null && !letteredRuns.has(newestTerminalRun.runId) ? newestTerminalRun.runId : null
276690
- };
276691
- if (!decideBoardLetter(candidate, now, quietMs)) continue;
276692
- const fmt = (fs52) => fs52.slice(0, 5).map((f2) => `- ${f2.path}\uFF08${f2.kind ?? "?"}\uFF09\xB7 ${f2.mtime ? new Date(f2.mtime).toISOString() : "?"}`).join("\n");
276693
- let body2;
276694
- let logLine;
276695
- if (candidate.freshTerminalRunId !== null) {
276696
- const status = newestTerminalRun.status;
276697
- const ok3 = status === "succeeded";
276698
- const listed = files.length > 0 ? fmt([...files].sort((x2, y) => (y.mtime ?? 0) - (x2.mtime ?? 0))) : "\uFF08\u677F\u4E0A\u6682\u65E0\u4EA4\u4ED8\u7269\uFF09";
276699
- body2 = [
276700
- // 两个 marker 都带:run marker 管快路径去重,mtime marker 顺手把慢路径也盖住——
276701
- // 否则这封信被 resolve 后,慢路径会就**同一批板内容**再写一封(两路去重各管各的)。
276702
- boardMtime !== null ? `${boardWatchRunMarker(candidate.freshTerminalRunId)} ${boardWatchMarker(boardMtime)}` : boardWatchRunMarker(candidate.freshTerminalRunId),
276703
- ok3 ? `\u753B\u677F ${boardKey} \u7684\u5F15\u64CE\u8DD1\u5B8C\u4E86\uFF08run ${candidate.freshTerminalRunId.slice(0, 8)} \u7EC8\u6001=${status}\uFF09\uFF0C\u8D27\u5728\u677F\u4E0A\u7B49\u4F60\u6536\uFF1A` : `\u753B\u677F ${boardKey} \u7684\u5F15\u64CE\u8FD9\u4E00\u8F6E**\u6CA1\u8DD1\u6210**\uFF08run ${candidate.freshTerminalRunId.slice(0, 8)} \u7EC8\u6001=${status}\uFF09\u3002\u677F\u4E0A\u5F53\u524D\u5185\u5BB9\uFF1A`,
276704
- listed,
276705
- ``,
276706
- ok3 ? `\u53D6\u8D27\u6838\u5BF9\uFF1A\`oasis design status ${a.id} --pull\` \u2192 \u5BF9\u7740 DESIGN_BRIEF.md \u4E0E inputs/ \u9010\u5B57\u6BB5\u6838\uFF08\u6570\u636E\u5BF9\u4E0D\u5BF9\u3001\u72B6\u6001\u5168\u4E0D\u5168\u3001\u6709\u6CA1\u6709\u7F16\u9020\uFF09\u3002\u591F\u597D\u5C31 \`oasis propose ${a.id} --from-dir <\u76EE\u5F55> --reason "<\u8FD9\u7248\u505A\u4E86\u4EC0\u4E48>"\`\uFF1B\u8981\u6539\u5C31\u56DE\u677F\u4E0A\u518D\u6765\u4E00\u8F6E \`oasis design chat ${a.id} --turn continue --message "<\u8981\u6539\u4EC0\u4E48>" --async\`\uFF08\u26D4 \u522B\u628A --pull \u4E0B\u6765\u7684\u6587\u4EF6\u5728\u672C\u5730\u624B\u6539\u540E propose\uFF09\u3002` : `\u5148 \`oasis design status ${a.id}\` \u770B\u677F\u4E0A\u6709\u6CA1\u6709\u53EF\u7528\u7684\u4E0A\u4E00\u7248\uFF1A\u6709\u5C31\u7167\u5E38\u53D6\u8D27\u6838\u5BF9\uFF1B\u6CA1\u6709\u5C31\u628A\u9700\u6C42\u4E0E\u771F\u5B9E\u6570\u636E\u9489\u5F97\u66F4\u6B7B\u540E\u91CD\u8D77\u4E00\u8F6E \`oasis design chat ${a.id} --message "<\u2026>" --async\`\uFF0C\u4ECD\u4E0D\u884C\u7528 \`oasis gap ${a.id} --desc "<\u5361\u5728\u54EA>"\` \u5192\u6CE1\u7ED9\u4EBA\u3002`
276707
- ].join("\n");
276708
- logLine = `[board-watch] ${a.id} \u2190 \u677F ${boardKey} \u5F15\u64CE\u6536\u5C3E\uFF08run ${candidate.freshTerminalRunId.slice(0, 8)} ${status}\uFF09\u2192 \u5DF2\u5199\u4FE1\u5524 owner`;
276709
- } else {
276710
- const newest = files.filter((f2) => f2.mtime !== null && candidate.lastOpAtMs !== null && f2.mtime > candidate.lastOpAtMs).sort((x2, y) => (y.mtime ?? 0) - (x2.mtime ?? 0));
276711
- body2 = [
276712
- boardWatchMarker(boardMtime),
276713
- `\u753B\u677F ${boardKey} \u4E0A\u6709 Oasis \u4E4B\u5916\u7684\u65B0\u52A8\u9759\uFF08\u4EBA\u5728\u677F\u4E0A\u5BF9\u8BDD/\u6539\u7A3F\uFF0C\u6216\u5F15\u64CE\u4EA7\u4E86\u6CA1\u4EBA\u6536\u7684\u65B0\u7248\uFF09\uFF0C\u6BD4\u672C\u4EA7\u7269\u6700\u8FD1\u4E00\u6B21\u64CD\u4F5C\uFF08${lastOpAt ?? "?"}\uFF09\u65B0\u3001\u4E14\u5DF2\u9759\u9ED8 ${Math.round(quietMs / 6e4)} \u5206\u949F\uFF1A`,
276714
- fmt(newest.length > 0 ? newest : files),
276715
- ``,
276716
- `\u8BF7\u53D6\u8D27\u6838\u5BF9\uFF1A\`oasis design status ${a.id} --pull\` \u2192 \u5185\u5BB9\u5408\u610F\u5C31 \`oasis propose ${a.id} --from-dir <\u76EE\u5F55> --reason "<\u6536\u5F55\u677F\u4E0A\u4EBA\u5DE5\u8FED\u4EE3>"\` \u6536\u5F55\u4E3A\u65B0\u7248\u672C\uFF1B\u4E0D\u5408\u610F/\u4E0D\u5FC5\u6536\u5F55\uFF0C\u56DE\u5E16\u8BF4\u660E\u540E\u6309 wontfix \u5904\u7406\u5373\u53EF\uFF08\u540C\u4E00\u7248\u4E0D\u4F1A\u518D\u6765\u4FE1\uFF09\u3002`
276717
- ].join("\n");
276718
- logLine = `[board-watch] ${a.id} \u2190 \u677F ${boardKey} \u6709\u65B0\u5185\u5BB9\uFF08mtime=${new Date(boardMtime).toISOString()}\uFF09\u2192 \u5DF2\u5199\u4FE1\u5524 owner`;
277468
+ const pinKey = `${company}\0${a.id}`;
277469
+ const watchLetters = annotationsOf(model, a.id).filter((x2) => x2.author === actor && x2.body.includes("[od-board-watch "));
277470
+ const lettered = new Set(watchLetters.map((x2) => parseRunMarker(x2.body)).filter((s2) => s2 !== null));
277471
+ const recordedSince = watchLetters.reduce((mn, x2) => {
277472
+ const s2 = parseSinceMarker(x2.body);
277473
+ return s2 !== null && (mn === null || s2 < mn) ? s2 : mn;
277474
+ }, null);
277475
+ const since = recordedSince ?? this.pinnedSince.get(pinKey) ?? now - graceMs;
277476
+ const read3 = await this.boardRounds(company, boardKey, since);
277477
+ if (read3.kind === "missing") continue;
277478
+ if (read3.kind === "denied") {
277479
+ denied.push({ company, boardKey, status: read3.status });
277480
+ continue;
276719
277481
  }
276720
- await kernel.annotate({ artifactId: a.id, actor, body: body2, kind: "comment" });
276721
- written++;
276722
- log3?.(logLine);
277482
+ if (read3.kind === "error") {
277483
+ log3?.(`[board-watch] ${tag}\u8BFB\u753B\u677F ${boardKey} \u5931\u8D25\uFF08${read3.detail}\uFF09\uFF0C\u672C\u8F6E\u8DF3\u8FC7`);
277484
+ continue;
277485
+ }
277486
+ if (recordedSince === null && !this.pinnedSince.has(pinKey)) this.pinnedSince.set(pinKey, since);
277487
+ const fresh = read3.value.rounds.filter((r) => r.endedAt > since && !lettered.has(r.key)).sort((x2, y) => x2.endedAt - y.endedAt);
277488
+ const seen = /* @__PURE__ */ new Set();
277489
+ for (const r of fresh) {
277490
+ if (written >= maxPerTick) break;
277491
+ if (seen.has(r.key)) continue;
277492
+ seen.add(r.key);
277493
+ const ok3 = r.status === "succeeded";
277494
+ const reason = ok3 ? null : r.error ?? (r.runId !== null ? await this.runErrorFromRegistry(boardKey, r.runId) : null);
277495
+ await kernel.annotate({ artifactId: a.id, actor, body: this.letterBody(a.id, boardKey, r, since, reason), kind: "comment" });
277496
+ written++;
277497
+ log3?.(`[board-watch] ${tag}${a.id} \u2190 \u677F ${boardKey} \u4E00\u8F6E\u5BF9\u8BDD\u7ED3\u675F\uFF08${r.key.slice(0, 12)} ${r.status}${r.agentId ? ` ${r.agentId}` : ""}\uFF09\u2192 \u5DF2\u5199\u4FE1\u5524 owner`);
277498
+ }
277499
+ const pending = new Set(fresh.filter((r) => !seen.has(r.key)).map((r) => r.conversationId));
277500
+ for (const [cid, convAt] of read3.value.fetched) {
277501
+ if (convAt !== null && !pending.has(cid)) this.seenConvAt.set(`${company}\0${boardKey}\0${cid}`, convAt);
277502
+ }
277503
+ }
277504
+ }
277505
+ letterBody(artifactId, boardKey, r, since, reason) {
277506
+ const marker = `${boardWatchRunMarker(r.key)} ${boardWatchSinceMarker(since)}`;
277507
+ const round = `${r.runId !== null ? `run ${r.runId.slice(0, 8)}` : `\u6D88\u606F ${r.key.slice(4, 12)}`}\uFF0C\u5F15\u64CE=${r.agentId ?? "?"}\uFF0C\u7ED3\u675F\u4E8E ${new Date(r.endedAt).toISOString()}`;
277508
+ if (r.status === "succeeded") {
277509
+ const files = r.producedFiles.length > 0 ? [...r.producedFiles.slice(0, MAX_LISTED_FILES).map((f2) => `- ${f2}`), ...r.producedFiles.length > MAX_LISTED_FILES ? [`- \u2026\u53E6 ${r.producedFiles.length - MAX_LISTED_FILES} \u4E2A`] : []].join("\n") : "\uFF08\u8FD9\u4E00\u8F6E\u6CA1\u6709\u767B\u8BB0\u4EA7\u51FA\u6587\u4EF6\uFF09";
277510
+ return [
277511
+ marker,
277512
+ `\u753B\u677F ${boardKey} \u4E0A\u6709\u4E00\u8F6E\u5BF9\u8BDD\u8DD1\u5B8C\u4E86\uFF08${round}\uFF09\uFF0C\u8FD9\u4E00\u8F6E\u4EA7\u51FA\uFF1A`,
277513
+ files,
277514
+ ``,
277515
+ `\u53D6\u8D27\u6838\u5BF9\uFF1A\`oasis design status ${artifactId} --pull\` \u2192 \u5BF9\u7740 DESIGN_BRIEF.md \u4E0E inputs/ \u9010\u5B57\u6BB5\u6838\uFF08\u6570\u636E\u5BF9\u4E0D\u5BF9\u3001\u72B6\u6001\u5168\u4E0D\u5168\u3001\u6709\u6CA1\u6709\u7F16\u9020\uFF09\u3002\u591F\u597D\u5C31 \`oasis propose ${artifactId} --from-dir <\u76EE\u5F55> --reason "<\u8FD9\u7248\u505A\u4E86\u4EC0\u4E48>"\`\uFF1B\u8981\u6539\u5C31\u56DE\u677F\u4E0A\u518D\u6765\u4E00\u8F6E \`oasis design chat ${artifactId} --turn continue --message "<\u8981\u6539\u4EC0\u4E48>" --async\`\uFF08\u26D4 \u522B\u628A --pull \u4E0B\u6765\u7684\u6587\u4EF6\u5728\u672C\u5730\u624B\u6539\u540E propose\uFF09\u3002\u8FD9\u4E00\u8F6E\u82E5\u662F\u4EBA\u76F4\u63A5\u5728\u677F\u4E0A\u804A\u51FA\u6765\u7684\u3001\u5185\u5BB9\u4E0D\u5FC5\u6536\u5F55\uFF0C\u56DE\u5E16\u8BF4\u660E\u540E\u6309 wontfix \u5904\u7406\u5373\u53EF\uFF08\u540C\u4E00\u8F6E\u4E0D\u4F1A\u518D\u6765\u4FE1\uFF09\u3002`
277516
+ ].join("\n");
276723
277517
  }
277518
+ const quoted = reason === null ? null : reason.length > MAX_REASON_CHARS ? `${reason.slice(0, MAX_REASON_CHARS)}\u2026` : reason;
277519
+ return [
277520
+ marker,
277521
+ `\u753B\u677F ${boardKey} \u4E0A\u6709\u4E00\u8F6E\u5BF9\u8BDD**\u6CA1\u8DD1\u6210**\uFF08\u7EC8\u6001=${r.status}\uFF0C${round}\uFF09\u3002`,
277522
+ quoted !== null ? `od \u8BB0\u7684\u539F\u56E0\uFF08\u539F\u6587\uFF09\uFF1A${quoted}${r.errorCode !== null ? `\uFF08\u9519\u8BEF\u7801 ${r.errorCode}\uFF09` : ""}` : `od \u6CA1\u6709\u8BB0\u5F55\u8FD9\u4E00\u8F6E\u7684\u5931\u8D25\u539F\u56E0\u3002`,
277523
+ ``,
277524
+ `\u5148 \`oasis design status ${artifactId}\` \u770B\u677F\u4E0A\u6709\u6CA1\u6709\u53EF\u7528\u7684\u4E0A\u4E00\u7248\uFF1A\u6709\u5C31\u7167\u5E38\u53D6\u8D27\u6838\u5BF9\u3002\u8981\u91CD\u8DD1\u5C31\u81EA\u5DF1\u5224\u65AD\uFF1A\u50CF\u662F\u989D\u5EA6/\u9650\u6D41\u5C31\u7B49\u6062\u590D\u540E\u518D\u6765\uFF0C\u6216\u6362\u4E2A\u5F15\u64CE \`oasis design chat ${artifactId} --agent <\u5F15\u64CE> --message "<\u2026>" --async\`\uFF1B\u50CF\u662F\u9700\u6C42/\u6570\u636E\u95EE\u9898\u5C31\u628A\u5B83\u4EEC\u9489\u5F97\u66F4\u6B7B\u540E\u91CD\u8D77\u4E00\u8F6E\uFF1B\u4ECD\u4E0D\u884C\u7528 \`oasis gap ${artifactId} --desc "<\u5361\u5728\u54EA>"\` \u5192\u6CE1\u7ED9\u4EBA\u3002`
277525
+ ].join("\n");
276724
277526
  }
276725
277527
  };
276726
277528
  }
@@ -277602,8 +278404,8 @@ __export(src_exports2, {
277602
278404
  bindWorkorderProject: () => bindWorkorderProject,
277603
278405
  bindingResolverFromRegistry: () => bindingResolverFromRegistry,
277604
278406
  bindingRuntimeState: () => bindingRuntimeState,
277605
- boardWatchMarker: () => boardWatchMarker,
277606
278407
  boardWatchRunMarker: () => boardWatchRunMarker,
278408
+ boardWatchSinceMarker: () => boardWatchSinceMarker,
277607
278409
  broadcastMessageId: () => broadcastMessageId,
277608
278410
  broadcastMessageText: () => broadcastMessageText,
277609
278411
  bucketRuntimeRuns: () => bucketRuntimeRuns,
@@ -277707,7 +278509,6 @@ __export(src_exports2, {
277707
278509
  cronNext: () => cronNext,
277708
278510
  cronNextN: () => cronNextN,
277709
278511
  deadlineClockTick: () => deadlineClockTick,
277710
- decideBoardLetter: () => decideBoardLetter,
277711
278512
  defaultAvatarRefOf: () => defaultAvatarRefOf,
277712
278513
  delegationArtifactsView: () => delegationArtifactsView,
277713
278514
  delegationBundleAdditions: () => delegationBundleAdditions,
@@ -277858,6 +278659,7 @@ __export(src_exports2, {
277858
278659
  parseConnectorSkillId: () => parseConnectorSkillId,
277859
278660
  parseKnowledgeAnswerOutput: () => parseKnowledgeAnswerOutput,
277860
278661
  parseKnowledgePlan: () => parseKnowledgePlan,
278662
+ parseOdTime: () => parseOdTime,
277861
278663
  parseReadScope: () => parseReadScope,
277862
278664
  parseSkillFrontmatter: () => parseSkillFrontmatter,
277863
278665
  parseSymrefHead: () => parseSymrefHead,
@@ -277947,6 +278749,7 @@ __export(src_exports2, {
277947
278749
  retireCdGateInTypeStore: () => retireCdGateInTypeStore,
277948
278750
  rolesDomain: () => rolesDomain,
277949
278751
  rollbackOrgIdentity: () => rollbackOrgIdentity,
278752
+ roundOfMessage: () => roundOfMessage,
277950
278753
  routeFusedSignal: () => routeFusedSignal,
277951
278754
  runtimeConnectionStatus: () => runtimeConnectionStatus,
277952
278755
  runtimeDriftBlock: () => runtimeDriftBlock,
@@ -278512,14 +279315,14 @@ init_src();
278512
279315
  var os16 = __toESM(require("node:os"));
278513
279316
  var path46 = __toESM(require("node:path"));
278514
279317
  var import_node_child_process26 = require("node:child_process");
278515
- var import_node_crypto94 = require("node:crypto");
279318
+ var import_node_crypto95 = require("node:crypto");
278516
279319
 
278517
279320
  // ../cli/src/cli.ts
278518
279321
  init_src();
278519
279322
  var fs44 = __toESM(require("node:fs"), 1);
278520
279323
  var os13 = __toESM(require("node:os"), 1);
278521
279324
  var path39 = __toESM(require("node:path"), 1);
278522
- var import_node_crypto92 = require("node:crypto");
279325
+ var import_node_crypto93 = require("node:crypto");
278523
279326
 
278524
279327
  // ../cli/src/serve.ts
278525
279328
  init_src11();
@@ -278531,11 +279334,11 @@ var fs36 = __toESM(require("node:fs"), 1);
278531
279334
  var os9 = __toESM(require("node:os"), 1);
278532
279335
  var path30 = __toESM(require("node:path"), 1);
278533
279336
  var import_node_child_process18 = require("node:child_process");
278534
- var import_node_crypto88 = require("node:crypto");
279337
+ var import_node_crypto89 = require("node:crypto");
278535
279338
  var import_node_url7 = require("node:url");
278536
279339
 
278537
279340
  // ../cli/src/artifact-discovery-source.ts
278538
- var import_node_crypto84 = require("node:crypto");
279341
+ var import_node_crypto85 = require("node:crypto");
278539
279342
  init_src7();
278540
279343
  var ArtifactDiscoverySourceUnreadableError = class extends Error {
278541
279344
  constructor(message) {
@@ -278565,7 +279368,7 @@ async function resolveArtifactDiscoverySourceContent(blobs, input) {
278565
279368
  throw new ArtifactDiscoverySourceUnreadableError(`Artifact Revision ${input.revisionId} \u7684 contentKind \u4E0D\u53D7\u652F\u6301\uFF1A${String(input.contentKind)}`);
278566
279369
  }
278567
279370
  const rootBytes = await blobs.get(input.contentRef);
278568
- const sourceFingerprint = (0, import_node_crypto84.createHash)("sha256").update(rootBytes).digest("hex");
279371
+ const sourceFingerprint = (0, import_node_crypto85.createHash)("sha256").update(rootBytes).digest("hex");
278569
279372
  if (input.contentKind === "inline-blob") {
278570
279373
  const sourceContent = decodeText3(rootBytes);
278571
279374
  if (sourceContent === null) {
@@ -278589,7 +279392,7 @@ ${entry.text}`).join("\n\n"),
278589
279392
  }
278590
279393
 
278591
279394
  // ../server/src/domains/discussions/service.ts
278592
- var import_node_crypto85 = require("node:crypto");
279395
+ var import_node_crypto86 = require("node:crypto");
278593
279396
  init_zod();
278594
279397
  init_discussions();
278595
279398
  init_router();
@@ -278651,7 +279454,7 @@ function remapTopics(topics, removed) {
278651
279454
  return [...out].map(([topic_id, summary]) => ({ topic_id, summary }));
278652
279455
  }
278653
279456
  var entryId = (projectId2, id) => JSON.stringify([projectId2, id]);
278654
- var hash2 = (s2) => (0, import_node_crypto85.createHash)("sha256").update(s2).digest("hex").slice(0, 24);
279457
+ var hash2 = (s2) => (0, import_node_crypto86.createHash)("sha256").update(s2).digest("hex").slice(0, 24);
278655
279458
  var canonical = (s2) => s2.normalize("NFKC").trim().toLocaleLowerCase();
278656
279459
  function decisionFingerprint(d) {
278657
279460
  return hash2(JSON.stringify([d.statement, d.rationale, d.alternatives]));
@@ -278871,7 +279674,7 @@ var DiscussionService = class {
278871
279674
  throw new ApiError(409, "DISCUSSION_CONFLICT", "\u9879\u76EE\u8BAE\u9898\u6216\u51B3\u7B56\u5DF2\u53D8\u5316\uFF0C\u8BF7\u8BFB\u53D6\u6700\u65B0\u5185\u5BB9\u540E\u4FEE\u6B63");
278872
279675
  }
278873
279676
  }
278874
- const operation = (value2) => tx.put("operation", (0, import_node_crypto85.randomUUID)(), {
279677
+ const operation = (value2) => tx.put("operation", (0, import_node_crypto86.randomUUID)(), {
278875
279678
  ...value2,
278876
279679
  projectId: projectId2,
278877
279680
  actorId: ctx.actorId,
@@ -278888,7 +279691,7 @@ var DiscussionService = class {
278888
279691
  if (id.startsWith("new:")) {
278889
279692
  if (BUG_TOPIC.test(t.title.trim())) invalid("topics.title", `\u300C${t.title}\u300D\u662F\u5355\u4E2A Bug\uFF1A\u4FEE\u590D\u60C5\u51B5\u5199\u8FDB\u8BA8\u8BBA\u8BB0\u5F55\uFF0C\u5B9A\u51FA\u7684\u89C4\u5219\u6302\u5230\u5BF9\u5E94\u529F\u80FD\u8BAE\u9898\u4E0B\uFF0C\u4E0D\u6309 Bug \u53F7\u5EFA\u8BAE\u9898`);
278890
279693
  const existing = [...topicMap.values()].find((x2) => canonical(x2.title) === canonical(t.title));
278891
- id = existing?.topicId ?? `topic_${(0, import_node_crypto85.randomUUID)()}`;
279694
+ id = existing?.topicId ?? `topic_${(0, import_node_crypto86.randomUUID)()}`;
278892
279695
  if (!existing) createdTopics.add(id);
278893
279696
  } else if (!topicMap.has(id)) invalid("topics.topic_id", `\u8BAE\u9898 ${id} \u4E0D\u5B58\u5728\uFF1B\u65B0\u8BAE\u9898\u4F7F\u7528 new: \u524D\u7F00`);
278894
279697
  topicAliases.set(t.topic_id, id);
@@ -278940,7 +279743,7 @@ var DiscussionService = class {
278940
279743
  if (id.startsWith("new:")) {
278941
279744
  const candidateKey = JSON.stringify([topicId, canonical(d.record.statement)]);
278942
279745
  const existing = [...decisionMap.values()].find((x2) => x2.topicId === topicId && canonical(x2.record.statement) === canonical(d.record.statement));
278943
- id = existing?.decisionId ?? candidateIds.get(candidateKey) ?? `decision_${(0, import_node_crypto85.randomUUID)()}`;
279746
+ id = existing?.decisionId ?? candidateIds.get(candidateKey) ?? `decision_${(0, import_node_crypto86.randomUUID)()}`;
278944
279747
  candidateIds.set(candidateKey, id);
278945
279748
  } else if (!decisionMap.has(id)) invalid("decision_id", `\u51B3\u7B56 ${id} \u4E0D\u5B58\u5728\uFF1B\u65B0\u51B3\u7B56\u4F7F\u7528 new: \u524D\u7F00`);
278946
279749
  decisionAliases.set(d.decision_id, id);
@@ -279202,7 +280005,7 @@ var DiscussionService = class {
279202
280005
  ...row.value.confirmation ? { confirmation: { ...row.value.confirmation, fingerprint: decisionFingerprint(record9) } } : {}
279203
280006
  };
279204
280007
  await tx.put("decision", row.id, value2);
279205
- await tx.put("operation", (0, import_node_crypto85.randomUUID)(), {
280008
+ await tx.put("operation", (0, import_node_crypto86.randomUUID)(), {
279206
280009
  action: "edit",
279207
280010
  kind: "decision",
279208
280011
  projectId: projectId2,
@@ -279266,7 +280069,7 @@ var DiscussionService = class {
279266
280069
  if (!current || current.revision !== row.revision) throw new ApiError(409, "DISCUSSION_CONFLICT", `\u8BA8\u8BBA ${row.id} \u5DF2\u88AB\u4FEE\u6539\uFF0C\u8BF7\u8BFB\u53D6\u6700\u65B0\u5185\u5BB9\u540E\u91CD\u8BD5`);
279267
280070
  const summary = given.get(row.id);
279268
280071
  await tx.put("discussion", row.id, { ...current.value, record: { ...current.value.record, summary } });
279269
- await tx.put("operation", (0, import_node_crypto85.randomUUID)(), {
280072
+ await tx.put("operation", (0, import_node_crypto86.randomUUID)(), {
279270
280073
  action: "edit",
279271
280074
  kind: "discussion",
279272
280075
  projectId: current.value.record.project_id,
@@ -279282,7 +280085,7 @@ var DiscussionService = class {
279282
280085
  });
279283
280086
  }
279284
280087
  async operation(tx, actorId, kind, id, from, to) {
279285
- await tx.put("operation", (0, import_node_crypto85.randomUUID)(), { actorId, kind, id, from, to, at: new Date(this.now()).toISOString() });
280088
+ await tx.put("operation", (0, import_node_crypto86.randomUUID)(), { actorId, kind, id, from, to, at: new Date(this.now()).toISOString() });
279286
280089
  }
279287
280090
  };
279288
280091
  function pickDiscussionContent(d) {
@@ -279582,7 +280385,7 @@ var DiscussionContext = class {
279582
280385
  };
279583
280386
 
279584
280387
  // ../server/src/domains/discussions/worker.ts
279585
- var import_node_crypto86 = require("node:crypto");
280388
+ var import_node_crypto87 = require("node:crypto");
279586
280389
  var DISCUSSION_IDLE_MS = 15 * 6e4;
279587
280390
  var DISCUSSION_MAX_CORRECTIONS = 3;
279588
280391
  var DISCUSSION_LEASE_MS = 2 * 6e4;
@@ -279608,7 +280411,7 @@ var DiscussionWorker = class {
279608
280411
  if (await this.isReplying(sessionId)) return false;
279609
280412
  const cutoff = eligibleCutoff(await this.service.chat.listMessages(sessionId), this.service.now());
279610
280413
  if (!cutoff) return false;
279611
- const owner = (0, import_node_crypto86.randomUUID)();
280414
+ const owner = (0, import_node_crypto87.randomUUID)();
279612
280415
  const claimed = await this.service.store.transaction(async (tx) => {
279613
280416
  const old = (await tx.get("job", sessionId))?.value;
279614
280417
  if (old?.status === "running" && (old.leaseUntil ?? 0) > this.service.now()) return null;
@@ -280330,7 +281133,7 @@ function createDiscussionRuntime(options) {
280330
281133
  }
280331
281134
 
280332
281135
  // ../server/src/domains/discussions/store.ts
280333
- var import_node_crypto87 = require("node:crypto");
281136
+ var import_node_crypto88 = require("node:crypto");
280334
281137
  var import_promises17 = require("node:fs/promises");
280335
281138
  var import_node_path27 = require("node:path");
280336
281139
  var key = (kind, id) => JSON.stringify([kind, id]);
@@ -280375,7 +281178,7 @@ var MemoryDiscussionStore = class _MemoryDiscussionStore {
280375
281178
  if (changed) {
280376
281179
  if (this.filename) {
280377
281180
  await (0, import_promises17.mkdir)((0, import_node_path27.dirname)(this.filename), { recursive: true });
280378
- const temp = `${this.filename}.${(0, import_node_crypto87.randomUUID)()}.tmp`;
281181
+ const temp = `${this.filename}.${(0, import_node_crypto88.randomUUID)()}.tmp`;
280379
281182
  await (0, import_promises17.writeFile)(temp, JSON.stringify(next), "utf8");
280380
281183
  await (0, import_promises17.rename)(temp, this.filename);
280381
281184
  }
@@ -281324,7 +282127,7 @@ function oasisWrapperScript() {
281324
282127
  const repoRoot = (0, import_node_url7.fileURLToPath)(new URL("../../../", __esm_import_meta_url));
281325
282128
  const tsx = path30.join(repoRoot, "node_modules/.bin/tsx");
281326
282129
  const main = path30.join(repoRoot, "packages/cli/src/main.ts");
281327
- const key2 = (0, import_node_crypto88.createHash)("sha256").update(repoRoot).digest("hex").slice(0, 12);
282130
+ const key2 = (0, import_node_crypto89.createHash)("sha256").update(repoRoot).digest("hex").slice(0, 12);
281328
282131
  const uid = typeof process.getuid === "function" ? process.getuid() : "na";
281329
282132
  const stable = path30.join(os9.tmpdir(), `oasis-cli-wrapper-${uid}-${key2}`);
281330
282133
  let dir;
@@ -281717,7 +282520,7 @@ async function startServe(opts) {
281717
282520
  if (!hub) return Promise.resolve({ ok: false, reason: "\u672C\u8FDB\u7A0B\u6CA1\u6709\u8282\u70B9\u7F51\u5173" });
281718
282521
  const online = hub.connectedDaemons().some((d) => d.daemonId === nodeId);
281719
282522
  if (!online) return Promise.resolve({ ok: false, reason: "\u8282\u70B9\u5F53\u524D\u4E0D\u5728\u7EBF\uFF0C\u7528\u7684\u662F\u5B83\u4E0A\u6B21\u62A5\u7684\u6E05\u5355" });
281720
- const requestId = (0, import_node_crypto88.randomUUID)();
282523
+ const requestId = (0, import_node_crypto89.randomUUID)();
281721
282524
  const task = new Promise((resolve12) => {
281722
282525
  const timer = setTimeout(() => {
281723
282526
  modelRefreshWaiters.delete(requestId);
@@ -282546,7 +283349,7 @@ async function startServe(opts) {
282546
283349
  const finishedAt = (/* @__PURE__ */ new Date()).toISOString();
282547
283350
  if (buf || session.runId) {
282548
283351
  await turnStore.appendMessage({
282549
- id: (0, import_node_crypto88.randomUUID)(),
283352
+ id: (0, import_node_crypto89.randomUUID)(),
282550
283353
  sessionId: chatSessionId,
282551
283354
  role: "assistant",
282552
283355
  content: buf,
@@ -282592,7 +283395,7 @@ async function startServe(opts) {
282592
283395
  chatTargets: {
282593
283396
  inspect: inspectChatTarget,
282594
283397
  createTargetSession: async ({ companyId, agentId, ownerHumanActorId, title }) => {
282595
- const sessionId = (0, import_node_crypto88.randomUUID)();
283398
+ const sessionId = (0, import_node_crypto89.randomUUID)();
282596
283399
  const nowIso = (/* @__PURE__ */ new Date()).toISOString();
282597
283400
  const binding = await registryStore.getBinding(agentId).catch(() => null);
282598
283401
  await (await chatStoreFor(companyId)).createSession({
@@ -283994,7 +284797,7 @@ async function startServe(opts) {
283994
284797
  }));
283995
284798
  }
283996
284799
  const limits = { wallClockMs: wallClockForKind("planner") };
283997
- const artifactId = `artifact:planner:${(0, import_node_crypto88.randomUUID)()}`;
284800
+ const artifactId = `artifact:planner:${(0, import_node_crypto89.randomUUID)()}`;
283998
284801
  const handle = await chatRemoteAdapter.spawn({
283999
284802
  actor: planner.id,
284000
284803
  // companyId 同协调者:planner 用的就是默认公司的 actors 服务,漏签会让它一调公司域端点就 404。
@@ -284915,10 +285718,10 @@ async function startServe(opts) {
284915
285718
  if (isDeletedActor(dispatchTarget)) {
284916
285719
  throw new ApiError(409, ACTOR_DELETED_ERROR_CODE, ACTOR_DELETED_CHAT_MESSAGE);
284917
285720
  }
284918
- const runtimeSessionId = sessionId ?? (requestDispatchId ? deterministicUuid(`chat-runtime:${requestDispatchId}`) : (0, import_node_crypto88.randomUUID)());
285721
+ const runtimeSessionId = sessionId ?? (requestDispatchId ? deterministicUuid(`chat-runtime:${requestDispatchId}`) : (0, import_node_crypto89.randomUUID)());
284919
285722
  const resumeRuntimeSession = Boolean(sessionId);
284920
- const traceRunId = requestDispatchId ? stableChatTraceRunId(requestDispatchId) : `chat-run:${(0, import_node_crypto88.randomUUID)()}`;
284921
- const artifactId = requestDispatchId ? `artifact:chat:${deterministicUuid(`chat-artifact:${requestDispatchId}`)}` : `artifact:chat:${(0, import_node_crypto88.randomUUID)()}`;
285723
+ const traceRunId = requestDispatchId ? stableChatTraceRunId(requestDispatchId) : `chat-run:${(0, import_node_crypto89.randomUUID)()}`;
285724
+ const artifactId = requestDispatchId ? `artifact:chat:${deterministicUuid(`chat-artifact:${requestDispatchId}`)}` : `artifact:chat:${(0, import_node_crypto89.randomUUID)()}`;
284922
285725
  let stableDispatch = requestDispatchId ? await resolveStableChatDispatch({ store: await traceStoreFor(runCompanyId), runId: traceRunId }) : null;
284923
285726
  if (stableDispatch && !stableDispatch.shouldDispatch) {
284924
285727
  if (isHumanSubmitDispatchId(requestDispatchId)) {
@@ -286376,7 +287179,7 @@ async function startServe(opts) {
286376
287179
  console.log(`[new-engine] dispatchWork ${workId} \u662F human work\u2014\u2014\u4E0D\u6D3E runtime\uFF0C\u7B49\u5F85\u4EBA\u5DE5\u5904\u7406`);
286377
287180
  return;
286378
287181
  }
286379
- const ledgerId = opts2?.protocolRecovery?.dispatchId ?? `dispatch:${(0, import_node_crypto88.randomUUID)()}`;
287182
+ const ledgerId = opts2?.protocolRecovery?.dispatchId ?? `dispatch:${(0, import_node_crypto89.randomUUID)()}`;
286380
287183
  try {
286381
287184
  const inserted = await (await dispatchLedgerFor(companyId)).insertOpen({
286382
287185
  id: ledgerId,
@@ -286476,7 +287279,7 @@ async function startServe(opts) {
286476
287279
  const kernelModel = newKernel.model;
286477
287280
  const art = kernelModel.artifacts.get(nodeId);
286478
287281
  const revWid = art?.workspace ?? "";
286479
- const ledgerId = `dispatch:${(0, import_node_crypto88.randomUUID)()}`;
287282
+ const ledgerId = `dispatch:${(0, import_node_crypto89.randomUUID)()}`;
286480
287283
  try {
286481
287284
  await (await dispatchLedgerFor(companyId)).insertOpen({
286482
287285
  id: ledgerId,
@@ -286870,7 +287673,7 @@ async function startServe(opts) {
286870
287673
  resolveSupplementalContext: server.knowledgeGovernance ? async ({ actorId, artifactId, workOrderId, action, part, runtimeKind, taskMarkdown }) => {
286871
287674
  try {
286872
287675
  const governance = server.knowledgeGovernance;
286873
- const taskFingerprint = (0, import_node_crypto88.createHash)("sha256").update(JSON.stringify({ artifactId, workOrderId, action, part: part ?? null, taskMarkdown })).digest("hex");
287676
+ const taskFingerprint = (0, import_node_crypto89.createHash)("sha256").update(JSON.stringify({ artifactId, workOrderId, action, part: part ?? null, taskMarkdown })).digest("hex");
286874
287677
  const requestContext = {
286875
287678
  organizationId: companyId,
286876
287679
  actorId,
@@ -288333,7 +289136,12 @@ ${nodeFault}` : "");
288333
289136
  const odBase = (process.env["OASIS_OPEN_DESIGN_URL"] ?? "http://127.0.0.1:7457").replace(/\/$/, "");
288334
289137
  const odToken = process.env["OASIS_OPEN_DESIGN_TOKEN"]?.trim() || void 0;
288335
289138
  const boardWatcher = new BoardWatcher({
288336
- kernel: defaultCompanyKernel,
289139
+ listCompanies: async () => {
289140
+ if (!chatStoreRouter) return [defaultCompanyId];
289141
+ const active = (await controlPlaneStore.listCompanies()).filter((c) => c.status === "active").map((c) => c.id);
289142
+ return [defaultCompanyId, ...active.filter((id) => id !== defaultCompanyId)];
289143
+ },
289144
+ kernelFor: async (companyId) => (await getEngine(companyId)).kernel,
288337
289145
  odBase,
288338
289146
  ...odToken ? { odToken } : {},
288339
289147
  boardKeyOf: deriveBoardKey,
@@ -288342,7 +289150,7 @@ ${nodeFault}` : "");
288342
289150
  boardWatchTimer = setInterval(() => {
288343
289151
  void boardWatcher.tick().catch((err) => console.error(`[board-watch] tick \u5931\u8D25: ${String(err)}`));
288344
289152
  }, 6e4);
288345
- console.log(`[serve] \u753B\u677F\u76D1\u89C6\u5668\u5DF2\u542F\u7528\uFF08od=${odBase}${odToken ? "\uFF0C\u5E26\u4EE4\u724C" : ""}\uFF0C\u9759\u9ED8\u7A97 10min\uFF0C\u6BCF\u8F6E\u4E0A\u9650 2 \u5C01\u4FE1\uFF1BOASIS_BOARD_WATCH=0 \u53EF\u5173\uFF09`);
289153
+ console.log(`[serve] \u753B\u677F\u76D1\u89C6\u5668\u5DF2\u542F\u7528\uFF08od=${odBase}${odToken ? "\uFF0C\u5E26\u4EE4\u724C" : ""}\uFF0C\u5DE1\u6240\u6709\u516C\u53F8\uFF0C\u6BCF\u7ED3\u675F\u4E00\u8F6E\u5BF9\u8BDD\u5199\u4E00\u5C01\u4FE1\uFF0C\u6BCF\u8F6E\u6BCF\u5BB6\u516C\u53F8\u4E0A\u9650 2 \u5C01\uFF1BOASIS_BOARD_WATCH=0 \u53EF\u5173\uFF09`);
288346
289154
  } else {
288347
289155
  console.log(`[serve] \u753B\u677F\u76D1\u89C6\u5668\u5DF2\u88AB OASIS_BOARD_WATCH=${boardWatchFlag} \u663E\u5F0F\u5173\u95ED`);
288348
289156
  }
@@ -289589,7 +290397,7 @@ var RuntimeQuotaMonitor = class {
289589
290397
  init_wrapper();
289590
290398
  init_src();
289591
290399
  var import_node_os11 = require("node:os");
289592
- var import_node_crypto89 = require("node:crypto");
290400
+ var import_node_crypto90 = require("node:crypto");
289593
290401
  init_src8();
289594
290402
  init_src2();
289595
290403
 
@@ -290493,7 +291301,7 @@ var DaemonWsClient = class {
290493
291301
  */
290494
291302
  async queryArtifactStates(artifactIds, timeoutMs = 15e3) {
290495
291303
  if (artifactIds.length === 0) return {};
290496
- const requestId = (0, import_node_crypto89.randomUUID)();
291304
+ const requestId = (0, import_node_crypto90.randomUUID)();
290497
291305
  return new Promise((resolve12, reject) => {
290498
291306
  const timer = setTimeout(() => {
290499
291307
  this.gcPending.delete(requestId);
@@ -290907,7 +291715,7 @@ function forwardSignal(child, sig, killGroup = (pgid, signal) => {
290907
291715
  // ../cli/src/daemon/machine-id.ts
290908
291716
  var import_node_child_process23 = require("node:child_process");
290909
291717
  var import_node_fs24 = require("node:fs");
290910
- var import_node_crypto90 = require("node:crypto");
291718
+ var import_node_crypto91 = require("node:crypto");
290911
291719
  var import_node_os14 = require("node:os");
290912
291720
  function linuxMachineId() {
290913
291721
  for (const p2 of ["/etc/machine-id", "/var/lib/dbus/machine-id"]) {
@@ -290973,7 +291781,7 @@ var defaultSources = {
290973
291781
  function resolveNodeId(sources = {}) {
290974
291782
  const s2 = { ...defaultSources, ...sources };
290975
291783
  const material = `${s2.machineFingerprint()}:${s2.osUser()}`;
290976
- const digest = (0, import_node_crypto90.createHash)("sha256").update(material).digest("hex").slice(0, 12);
291784
+ const digest = (0, import_node_crypto91.createHash)("sha256").update(material).digest("hex").slice(0, 12);
290977
291785
  return `node-${digest}`;
290978
291786
  }
290979
291787
 
@@ -292845,13 +293653,13 @@ function ownFlagsFromDecl(command2) {
292845
293653
  }
292846
293654
 
292847
293655
  // ../cli/src/connector-effect-runner.ts
292848
- var import_node_crypto91 = require("node:crypto");
293656
+ var import_node_crypto92 = require("node:crypto");
292849
293657
  var import_node_child_process24 = require("node:child_process");
292850
293658
  init_src8();
292851
293659
  function stableKey(effect) {
292852
293660
  const keyFields = Object.fromEntries(Object.entries(effect.keyFields).sort(([a], [b2]) => a.localeCompare(b2)));
292853
293661
  const canonical2 = JSON.stringify({ kind: effect.kind, target: effect.target, keyFields });
292854
- return `sha256:${(0, import_node_crypto91.createHash)("sha256").update(canonical2).digest("hex")}`;
293662
+ return `sha256:${(0, import_node_crypto92.createHash)("sha256").update(canonical2).digest("hex")}`;
292855
293663
  }
292856
293664
  async function runConnectorEffect(connector, toolArgs, deps) {
292857
293665
  const effect = connector.describeExternalEffect?.(toolArgs) ?? null;
@@ -293406,7 +294214,7 @@ async function waitForCommandAuthorization(base, headers, draftId, fallback) {
293406
294214
  const timeoutMs = resolveAuthWaitMs();
293407
294215
  const controller = new AbortController();
293408
294216
  const timer = setTimeout(() => controller.abort(), timeoutMs);
293409
- const waiterId = (0, import_node_crypto92.randomUUID)();
294217
+ const waiterId = (0, import_node_crypto93.randomUUID)();
293410
294218
  process.stderr.write(
293411
294219
  `\u7B49\u5F85\u4EBA\u5DE5\u6388\u6743\u4E2D\u2026\u2026\u6700\u591A ${Math.round(timeoutMs / 1e3)} \u79D2\uFF0C\u8D85\u65F6\u4F1A\u81EA\u52A8\u8F6C\u4E3A\u5F85\u5BA1\uFF1B\u8FD9\u4E0D\u662F\u9519\u8BEF\uFF0C\u8BF7\u52FF\u91CD\u8BD5\u540C\u4E00\u6761\u547D\u4EE4\u3002
293412
294220
  `
@@ -293490,7 +294298,7 @@ async function waitForCommandAuthorization(base, headers, draftId, fallback) {
293490
294298
  }
293491
294299
  }
293492
294300
  }
293493
- var CLI_VERSION = "2.2.81" ? "2.2.81" : "dev";
294301
+ var CLI_VERSION = "2.2.83" ? "2.2.83" : "dev";
293494
294302
  function apiHeaders(token, company) {
293495
294303
  const headers = {
293496
294304
  authorization: `Bearer ${token}`,
@@ -293856,7 +294664,7 @@ async function runCli(argv, println = console.log, progressln = console.error) {
293856
294664
  const store = createNodeTokenStore(path39.join(dir, "node-tokens.json"));
293857
294665
  const sub = positional[0];
293858
294666
  if (sub === "issue") {
293859
- const id = flags.get("id") ?? `node-${(0, import_node_crypto92.randomUUID)()}`;
294667
+ const id = flags.get("id") ?? `node-${(0, import_node_crypto93.randomUUID)()}`;
293860
294668
  const token2 = store.issue(id);
293861
294669
  println(token2);
293862
294670
  process.stderr.write(
@@ -295006,7 +295814,7 @@ ${t.ref} ${t.name} [\u5267\u672C=${t.playbookRef}]`);
295006
295814
  if (!name) throw new Error("\u9879\u76EE\u540D\u79F0\u4E0D\u80FD\u4E3A\u7A7A");
295007
295815
  const goal = flags.get("goal")?.trim();
295008
295816
  const slug6 = flags.get("slug");
295009
- const id = slug6 !== void 0 ? projectSlug(slug6) : projectIdFromUuid((0, import_node_crypto92.randomUUID)());
295817
+ const id = slug6 !== void 0 ? projectSlug(slug6) : projectIdFromUuid((0, import_node_crypto93.randomUUID)());
295010
295818
  const result = await api.request("POST", "/api/projects", {
295011
295819
  ...id ? { id } : {},
295012
295820
  name,
@@ -295287,7 +296095,13 @@ ${res.warning}`);
295287
296095
  if (live.length > 0) {
295288
296096
  for (const r of live) println(`[design] \u5F15\u64CE\u5728\u8DD1\uFF1Arun ${r.runId.slice(0, 8)} \u72B6\u6001=${r.status}\u2014\u2014\u522B\u518D\u8D77\u65B0\u8F6E\uFF1B\u8FC7\u51E0\u5206\u949F\u518D status\uFF0C\u6216\u672C\u8F6E\u6536\u5DE5\u7B49\u91CD\u6D3E\u3002`);
295289
296097
  } else if (st.runs.length > 0) {
295290
- for (const r of st.runs.slice(0, 3)) println(`[design] \u8FD1\u671F run\uFF1A${r.runId.slice(0, 8)} \u7EC8\u6001=${r.status}`);
296098
+ for (const r of st.runs.slice(0, 3)) {
296099
+ println(`[design] \u8FD1\u671F run\uFF1A${r.runId.slice(0, 8)} \u7EC8\u6001=${r.status}${r.agentId ? ` \u5F15\u64CE=${r.agentId}` : ""}`);
296100
+ if (r.status === "failed" && r.error) {
296101
+ const reason = r.error.length > 300 ? `${r.error.slice(0, 300)}\u2026` : r.error;
296102
+ println(`[design] \u5931\u8D25\u539F\u56E0\uFF08od \u539F\u6587\uFF09\uFF1A${reason}`);
296103
+ }
296104
+ }
295291
296105
  } else {
295292
296106
  println(`[design] \u65E0\u5728\u4E16 run \u8BB0\u5F55\uFF08od \u91CD\u542F\u540E\u4E3A\u7A7A\uFF0C\u5C5E\u6B63\u5E38\uFF09\u2014\u2014\u770B\u4E0B\u9762\u677F\u4E0A\u4EA7\u7269\u5373\u6301\u4E45\u771F\u76F8\u3002`);
295293
296107
  }
@@ -295299,7 +296113,7 @@ ${res.warning}`);
295299
296113
  for (const a of st.artifacts) println(` - ${a.path}\uFF08${a.kind ?? "?"}\uFF09status=${a.status ?? "?"}${a.mtime ? ` \xB7 ${new Date(a.mtime).toISOString()}` : ""}`);
295300
296114
  if (flags.get("pull") === "true") {
295301
296115
  const odPublic = (flags.get("od-public-url") ?? process.env["OASIS_OPEN_DESIGN_PUBLIC_URL"] ?? "https://design.open-friday.com").replace(/\/$/, "");
295302
- const { dir: dir2, entry } = await harvestBoard(odBase, odFetch, odPublic, os13.tmpdir(), st.boardKey, st.runs[0]?.runId ?? "status-pull");
296116
+ const { dir: dir2, entry } = await harvestBoard(odBase, odFetch, odPublic, os13.tmpdir(), st.boardKey, latestSucceededRun(st.runs)?.runId ?? "status-pull");
295303
296117
  println(`[design] \u5DF2\u62C9\u5230\u672C\u5730\uFF1A${dir2}\uFF08\u5165\u53E3 ${entry}\uFF09`);
295304
296118
  println(`[design] \u8BFB\u5B8C\u6EE1\u610F\u5373\u4EA4\u7A3F\uFF1Aoasis propose ${artifactId} --from-dir ${dir2} --reason "<\u8FD9\u7248\u505A\u4E86\u4EC0\u4E48>"`);
295305
296119
  } else {
@@ -297271,13 +298085,13 @@ function runtimeAssetSourceRoots(input) {
297271
298085
  }
297272
298086
 
297273
298087
  // src/install.ts
297274
- var import_node_crypto93 = require("node:crypto");
298088
+ var import_node_crypto94 = require("node:crypto");
297275
298089
  var fs50 = __toESM(require("node:fs"));
297276
298090
  var path45 = __toESM(require("node:path"));
297277
298091
  function versionLabel(sourceFile, pkgVersion, npmPrefixDir, pkgName = LEGACY_PKG_NAME) {
297278
298092
  const tag = pkgName === LEGACY_PKG_NAME ? "" : `+pkg.${pkgName.replace(/[^A-Za-z0-9-]/g, "-")}`;
297279
298093
  if (isFromNpmPrefix(sourceFile, npmPrefixDir)) return `${pkgVersion}${tag}`;
297280
- const digest = (0, import_node_crypto93.createHash)("sha256").update(fs50.readFileSync(sourceFile)).digest("hex").slice(0, 8);
298094
+ const digest = (0, import_node_crypto94.createHash)("sha256").update(fs50.readFileSync(sourceFile)).digest("hex").slice(0, 8);
297281
298095
  return tag ? `${pkgVersion}${tag}.local.${digest}` : `${pkgVersion}+local.${digest}`;
297282
298096
  }
297283
298097
  function isFromNpmPrefix(sourceFile, npmPrefixDir) {
@@ -297400,7 +298214,7 @@ function shimScript() {
297400
298214
  }
297401
298215
 
297402
298216
  // src/index.ts
297403
- var PKG_VERSION = true ? "2.2.81" : "dev";
298217
+ var PKG_VERSION = true ? "2.2.83" : "dev";
297404
298218
  var LOCAL_BIN = localBin();
297405
298219
  var NPM_PREFIX = npmPrefix();
297406
298220
  var INSTANCE = DEFAULT_INSTANCE;
@@ -297708,7 +298522,7 @@ function newInstanceName() {
297708
298522
  const existing = new Set(listInstances());
297709
298523
  if (!existing.has(DEFAULT_INSTANCE)) return DEFAULT_INSTANCE;
297710
298524
  for (; ; ) {
297711
- const n = `inst-${(0, import_node_crypto94.randomBytes)(3).toString("hex")}`;
298525
+ const n = `inst-${(0, import_node_crypto95.randomBytes)(3).toString("hex")}`;
297712
298526
  if (!existing.has(n)) return n;
297713
298527
  }
297714
298528
  }