oasis_test_v2 2.2.92 → 2.2.94

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 +725 -268
  2. package/package.json +2 -2
package/dist/index.js CHANGED
@@ -913,7 +913,7 @@ var init_runtime = __esm({
913
913
  });
914
914
 
915
915
  // ../contract/src/runtime-allocation.ts
916
- var RUNTIME_FAULT_RECOVERY, RUNTIME_FAULT_PROBE_INITIAL_MS, RUNTIME_FAULT_PROBE_MAX_INTERVAL_MS, RUNTIME_FAULT_PROBE_MAX_TOTAL_MS, RUNTIME_QUOTA_DERATE_THRESHOLD_PERCENT, RUNTIME_ALLOCATION_MAX_ATTEMPTS;
916
+ var RUNTIME_FAULT_RECOVERY, RUNTIME_FAULT_SCOPE, RUNTIME_FAULT_PROBE_INITIAL_MS, RUNTIME_FAULT_PROBE_MAX_INTERVAL_MS, RUNTIME_FAULT_PROBE_MAX_TOTAL_MS, RUNTIME_QUOTA_DERATE_THRESHOLD_PERCENT, RUNTIME_ALLOCATION_MAX_ATTEMPTS;
917
917
  var init_runtime_allocation = __esm({
918
918
  "../contract/src/runtime-allocation.ts"() {
919
919
  "use strict";
@@ -928,6 +928,16 @@ var init_runtime_allocation = __esm({
928
928
  timeout: "half-open",
929
929
  "node-error": "half-open"
930
930
  };
931
+ RUNTIME_FAULT_SCOPE = {
932
+ "quota-exhausted": "runtime",
933
+ unauthenticated: "runtime",
934
+ "node-offline": "node",
935
+ "runtime-missing": "runtime",
936
+ "startup-failed": "runtime",
937
+ crashed: "runtime",
938
+ timeout: "runtime",
939
+ "node-error": "node"
940
+ };
931
941
  RUNTIME_FAULT_PROBE_INITIAL_MS = 6e4;
932
942
  RUNTIME_FAULT_PROBE_MAX_INTERVAL_MS = 30 * 6e4;
933
943
  RUNTIME_FAULT_PROBE_MAX_TOTAL_MS = 6 * 60 * 6e4;
@@ -11295,7 +11305,7 @@ async function resolveSessionRuntimeTarget(input) {
11295
11305
  if (!kinds.includes(override.runtimeKind)) {
11296
11306
  throw new SessionRuntimeOverrideError(
11297
11307
  "CHAT_SESSION_RUNTIME_UNAVAILABLE",
11298
- `\u8282\u70B9\u300C${nodeName}\u300D\u4E0A\u6CA1\u6709\u53EF\u7528\u7684\u8FD0\u884C\u65F6 ${override.runtimeKind}\uFF08\u53EF\u80FD\u5DF2\u5378\u8F7D\u3001\u8FD8\u6CA1\u88C5\u597D\uFF0C\u6216\u672A\u4E0A\u62A5\uFF09\u3002\u8BF7\u5728\u90A3\u53F0\u673A\u5668\u4E0A\u88C5\u597D\u540E\u518D\u53D1\uFF1B\u6216${CLEAR_SESSION_RUNTIME_HINT}`
11308
+ `\u673A\u5668\u300C${nodeName}\u300D\u4E0A\u6CA1\u6709\u53EF\u7528\u7684\u8FD0\u884C\u65F6 ${override.runtimeKind}\uFF08\u53EF\u80FD\u5DF2\u5378\u8F7D\u3001\u8FD8\u6CA1\u88C5\u597D\uFF0C\u6216\u8FD8\u6CA1\u68C0\u6D4B\u5230\uFF09\u3002\u8BF7\u5728\u90A3\u53F0\u673A\u5668\u4E0A\u88C5\u597D\u540E\u518D\u53D1\uFF1B\u6216${CLEAR_SESSION_RUNTIME_HINT}`
11299
11309
  );
11300
11310
  }
11301
11311
  if (!probe.binding) {
@@ -29203,8 +29213,8 @@ var require_axios = __commonJS({
29203
29213
  };
29204
29214
  var DEFAULT_CHUNK_SIZE = 64 * 1024;
29205
29215
  var { isFunction } = utils$1;
29206
- var globalFetchAPI = (({ Request, Response: Response2 }) => ({
29207
- Request,
29216
+ var globalFetchAPI = (({ Request: Request2, Response: Response2 }) => ({
29217
+ Request: Request2,
29208
29218
  Response: Response2
29209
29219
  }))(utils$1.global);
29210
29220
  var { ReadableStream: ReadableStream$1, TextEncoder: TextEncoder$1 } = utils$1.global;
@@ -29223,18 +29233,18 @@ var require_axios = __commonJS({
29223
29233
  globalFetchAPI,
29224
29234
  env
29225
29235
  );
29226
- const { fetch: envFetch, Request, Response: Response2 } = env;
29236
+ const { fetch: envFetch, Request: Request2, Response: Response2 } = env;
29227
29237
  const isFetchSupported = envFetch ? isFunction(envFetch) : typeof fetch === "function";
29228
- const isRequestSupported = isFunction(Request);
29238
+ const isRequestSupported = isFunction(Request2);
29229
29239
  const isResponseSupported = isFunction(Response2);
29230
29240
  if (!isFetchSupported) {
29231
29241
  return false;
29232
29242
  }
29233
29243
  const isReadableStreamSupported = isFetchSupported && isFunction(ReadableStream$1);
29234
- const encodeText = isFetchSupported && (typeof TextEncoder$1 === "function" ? /* @__PURE__ */ ((encoder) => (str5) => encoder.encode(str5))(new TextEncoder$1()) : async (str5) => new Uint8Array(await new Request(str5).arrayBuffer()));
29244
+ const encodeText = isFetchSupported && (typeof TextEncoder$1 === "function" ? /* @__PURE__ */ ((encoder) => (str5) => encoder.encode(str5))(new TextEncoder$1()) : async (str5) => new Uint8Array(await new Request2(str5).arrayBuffer()));
29235
29245
  const supportsRequestStream = isRequestSupported && isReadableStreamSupported && test(() => {
29236
29246
  let duplexAccessed = false;
29237
- const hasContentType = new Request(platform2.origin, {
29247
+ const hasContentType = new Request2(platform2.origin, {
29238
29248
  body: new ReadableStream$1(),
29239
29249
  method: "POST",
29240
29250
  get duplex() {
@@ -29271,7 +29281,7 @@ var require_axios = __commonJS({
29271
29281
  return body2.size;
29272
29282
  }
29273
29283
  if (utils$1.isSpecCompliantForm(body2)) {
29274
- const _request = new Request(platform2.origin, {
29284
+ const _request = new Request2(platform2.origin, {
29275
29285
  method: "POST",
29276
29286
  body: body2
29277
29287
  });
@@ -29319,7 +29329,7 @@ var require_axios = __commonJS({
29319
29329
  let requestContentLength;
29320
29330
  try {
29321
29331
  if (onUploadProgress && supportsRequestStream && method !== "get" && method !== "head" && (requestContentLength = await resolveBodyLength(headers, data)) !== 0) {
29322
- let _request = new Request(url2, {
29332
+ let _request = new Request2(url2, {
29323
29333
  method: "POST",
29324
29334
  body: data,
29325
29335
  duplex: "half"
@@ -29339,7 +29349,7 @@ var require_axios = __commonJS({
29339
29349
  if (!utils$1.isString(withCredentials)) {
29340
29350
  withCredentials = withCredentials ? "include" : "omit";
29341
29351
  }
29342
- const isCredentialsSupported = isRequestSupported && "credentials" in Request.prototype;
29352
+ const isCredentialsSupported = isRequestSupported && "credentials" in Request2.prototype;
29343
29353
  const resolvedOptions = {
29344
29354
  ...fetchOptions,
29345
29355
  signal: composedSignal,
@@ -29349,7 +29359,7 @@ var require_axios = __commonJS({
29349
29359
  duplex: "half",
29350
29360
  credentials: isCredentialsSupported ? withCredentials : void 0
29351
29361
  };
29352
- request2 = isRequestSupported && new Request(url2, resolvedOptions);
29362
+ request2 = isRequestSupported && new Request2(url2, resolvedOptions);
29353
29363
  let response = await (isRequestSupported ? _fetch(request2, fetchOptions) : _fetch(url2, resolvedOptions));
29354
29364
  const isStreamResponse = supportsResponseStream && (responseType === "stream" || responseType === "response");
29355
29365
  if (supportsResponseStream && (onDownloadProgress || isStreamResponse && unsubscribe)) {
@@ -29409,8 +29419,8 @@ var require_axios = __commonJS({
29409
29419
  var seedCache = /* @__PURE__ */ new Map();
29410
29420
  var getFetch = (config2) => {
29411
29421
  let env = config2 && config2.env || {};
29412
- const { fetch: fetch2, Request, Response: Response2 } = env;
29413
- const seeds = [Request, Response2, fetch2];
29422
+ const { fetch: fetch2, Request: Request2, Response: Response2 } = env;
29423
+ const seeds = [Request2, Response2, fetch2];
29414
29424
  let len = seeds.length, i = len, seed, target, map = seedCache;
29415
29425
  while (i--) {
29416
29426
  seed = seeds[i];
@@ -133376,9 +133386,9 @@ var require_client = __commonJS({
133376
133386
  user: params.user,
133377
133387
  database: params.database
133378
133388
  };
133379
- const appName = params.application_name || params.fallback_application_name;
133380
- if (appName) {
133381
- data.application_name = appName;
133389
+ const appName2 = params.application_name || params.fallback_application_name;
133390
+ if (appName2) {
133391
+ data.application_name = appName2;
133382
133392
  }
133383
133393
  if (params.replication) {
133384
133394
  data.replication = "" + params.replication;
@@ -146147,12 +146157,12 @@ var init_kernel_bridge = __esm({
146147
146157
  typeName: a.type
146148
146158
  };
146149
146159
  }
146150
- const shape = predicted.map((r) => ({
146160
+ const shape2 = predicted.map((r) => ({
146151
146161
  actor: r.reviewerActorId,
146152
146162
  source: r.source,
146153
146163
  reviewGroup: r.reviewGroup
146154
146164
  }));
146155
- return { ...reviewerPreviewOfRows(shape), predicted: true, typeName: a.type };
146165
+ return { ...reviewerPreviewOfRows(shape2), predicted: true, typeName: a.type };
146156
146166
  }
146157
146167
  /**
146158
146168
  * 草案节点的审核人预览:按**这台内核**的类型配置与花名册(roleIndex,含 registry-only 注入的机械投票者),
@@ -150947,9 +150957,9 @@ var init_types2 = __esm({
150947
150957
  _getCached() {
150948
150958
  if (this._cached !== null)
150949
150959
  return this._cached;
150950
- const shape = this._def.shape();
150951
- const keys = util.objectKeys(shape);
150952
- this._cached = { shape, keys };
150960
+ const shape2 = this._def.shape();
150961
+ const keys = util.objectKeys(shape2);
150962
+ this._cached = { shape: shape2, keys };
150953
150963
  return this._cached;
150954
150964
  }
150955
150965
  _parse(input) {
@@ -150964,7 +150974,7 @@ var init_types2 = __esm({
150964
150974
  return INVALID;
150965
150975
  }
150966
150976
  const { status, ctx } = this._processInputParams(input);
150967
- const { shape, keys: shapeKeys } = this._getCached();
150977
+ const { shape: shape2, keys: shapeKeys } = this._getCached();
150968
150978
  const extraKeys = [];
150969
150979
  if (!(this._def.catchall instanceof ZodNever && this._def.unknownKeys === "strip")) {
150970
150980
  for (const key2 in ctx.data) {
@@ -150975,7 +150985,7 @@ var init_types2 = __esm({
150975
150985
  }
150976
150986
  const pairs = [];
150977
150987
  for (const key2 of shapeKeys) {
150978
- const keyValidator = shape[key2];
150988
+ const keyValidator = shape2[key2];
150979
150989
  const value2 = ctx.data[key2];
150980
150990
  pairs.push({
150981
150991
  key: { status: "valid", value: key2 },
@@ -151181,27 +151191,27 @@ var init_types2 = __esm({
151181
151191
  });
151182
151192
  }
151183
151193
  pick(mask2) {
151184
- const shape = {};
151194
+ const shape2 = {};
151185
151195
  for (const key2 of util.objectKeys(mask2)) {
151186
151196
  if (mask2[key2] && this.shape[key2]) {
151187
- shape[key2] = this.shape[key2];
151197
+ shape2[key2] = this.shape[key2];
151188
151198
  }
151189
151199
  }
151190
151200
  return new _ZodObject({
151191
151201
  ...this._def,
151192
- shape: () => shape
151202
+ shape: () => shape2
151193
151203
  });
151194
151204
  }
151195
151205
  omit(mask2) {
151196
- const shape = {};
151206
+ const shape2 = {};
151197
151207
  for (const key2 of util.objectKeys(this.shape)) {
151198
151208
  if (!mask2[key2]) {
151199
- shape[key2] = this.shape[key2];
151209
+ shape2[key2] = this.shape[key2];
151200
151210
  }
151201
151211
  }
151202
151212
  return new _ZodObject({
151203
151213
  ...this._def,
151204
- shape: () => shape
151214
+ shape: () => shape2
151205
151215
  });
151206
151216
  }
151207
151217
  /**
@@ -151248,27 +151258,27 @@ var init_types2 = __esm({
151248
151258
  return createZodEnum(util.objectKeys(this.shape));
151249
151259
  }
151250
151260
  };
151251
- ZodObject.create = (shape, params) => {
151261
+ ZodObject.create = (shape2, params) => {
151252
151262
  return new ZodObject({
151253
- shape: () => shape,
151263
+ shape: () => shape2,
151254
151264
  unknownKeys: "strip",
151255
151265
  catchall: ZodNever.create(),
151256
151266
  typeName: ZodFirstPartyTypeKind.ZodObject,
151257
151267
  ...processCreateParams(params)
151258
151268
  });
151259
151269
  };
151260
- ZodObject.strictCreate = (shape, params) => {
151270
+ ZodObject.strictCreate = (shape2, params) => {
151261
151271
  return new ZodObject({
151262
- shape: () => shape,
151272
+ shape: () => shape2,
151263
151273
  unknownKeys: "strict",
151264
151274
  catchall: ZodNever.create(),
151265
151275
  typeName: ZodFirstPartyTypeKind.ZodObject,
151266
151276
  ...processCreateParams(params)
151267
151277
  });
151268
151278
  };
151269
- ZodObject.lazycreate = (shape, params) => {
151279
+ ZodObject.lazycreate = (shape2, params) => {
151270
151280
  return new ZodObject({
151271
- shape,
151281
+ shape: shape2,
151272
151282
  unknownKeys: "strip",
151273
151283
  catchall: ZodNever.create(),
151274
151284
  typeName: ZodFirstPartyTypeKind.ZodObject,
@@ -152980,9 +152990,9 @@ function stringifyPrimitive(value2) {
152980
152990
  return `"${value2}"`;
152981
152991
  return `${value2}`;
152982
152992
  }
152983
- function optionalKeys(shape) {
152984
- return Object.keys(shape).filter((k2) => {
152985
- return shape[k2]._zod.optin === "optional" && shape[k2]._zod.optout === "optional";
152993
+ function optionalKeys(shape2) {
152994
+ return Object.keys(shape2).filter((k2) => {
152995
+ return shape2[k2]._zod.optin === "optional" && shape2[k2]._zod.optout === "optional";
152986
152996
  });
152987
152997
  }
152988
152998
  function pick2(schema, mask2) {
@@ -153019,14 +153029,14 @@ function omit(schema, mask2) {
153019
153029
  checks: []
153020
153030
  });
153021
153031
  }
153022
- function extend(schema, shape) {
153023
- if (!isPlainObject(shape)) {
153032
+ function extend(schema, shape2) {
153033
+ if (!isPlainObject(shape2)) {
153024
153034
  throw new Error("Invalid input to extend: expected a plain object");
153025
153035
  }
153026
153036
  const def = {
153027
153037
  ...schema._zod.def,
153028
153038
  get shape() {
153029
- const _shape = { ...schema._zod.def.shape, ...shape };
153039
+ const _shape = { ...schema._zod.def.shape, ...shape2 };
153030
153040
  assignProp(this, "shape", _shape);
153031
153041
  return _shape;
153032
153042
  },
@@ -153050,7 +153060,7 @@ function merge(a, b2) {
153050
153060
  }
153051
153061
  function partial(Class2, schema, mask2) {
153052
153062
  const oldShape = schema._zod.def.shape;
153053
- const shape = { ...oldShape };
153063
+ const shape2 = { ...oldShape };
153054
153064
  if (mask2) {
153055
153065
  for (const key2 in mask2) {
153056
153066
  if (!(key2 in oldShape)) {
@@ -153058,14 +153068,14 @@ function partial(Class2, schema, mask2) {
153058
153068
  }
153059
153069
  if (!mask2[key2])
153060
153070
  continue;
153061
- shape[key2] = Class2 ? new Class2({
153071
+ shape2[key2] = Class2 ? new Class2({
153062
153072
  type: "optional",
153063
153073
  innerType: oldShape[key2]
153064
153074
  }) : oldShape[key2];
153065
153075
  }
153066
153076
  } else {
153067
153077
  for (const key2 in oldShape) {
153068
- shape[key2] = Class2 ? new Class2({
153078
+ shape2[key2] = Class2 ? new Class2({
153069
153079
  type: "optional",
153070
153080
  innerType: oldShape[key2]
153071
153081
  }) : oldShape[key2];
@@ -153073,28 +153083,28 @@ function partial(Class2, schema, mask2) {
153073
153083
  }
153074
153084
  return clone(schema, {
153075
153085
  ...schema._zod.def,
153076
- shape,
153086
+ shape: shape2,
153077
153087
  checks: []
153078
153088
  });
153079
153089
  }
153080
153090
  function required(Class2, schema, mask2) {
153081
153091
  const oldShape = schema._zod.def.shape;
153082
- const shape = { ...oldShape };
153092
+ const shape2 = { ...oldShape };
153083
153093
  if (mask2) {
153084
153094
  for (const key2 in mask2) {
153085
- if (!(key2 in shape)) {
153095
+ if (!(key2 in shape2)) {
153086
153096
  throw new Error(`Unrecognized key: "${key2}"`);
153087
153097
  }
153088
153098
  if (!mask2[key2])
153089
153099
  continue;
153090
- shape[key2] = new Class2({
153100
+ shape2[key2] = new Class2({
153091
153101
  type: "nonoptional",
153092
153102
  innerType: oldShape[key2]
153093
153103
  });
153094
153104
  }
153095
153105
  } else {
153096
153106
  for (const key2 in oldShape) {
153097
- shape[key2] = new Class2({
153107
+ shape2[key2] = new Class2({
153098
153108
  type: "nonoptional",
153099
153109
  innerType: oldShape[key2]
153100
153110
  });
@@ -153102,7 +153112,7 @@ function required(Class2, schema, mask2) {
153102
153112
  }
153103
153113
  return clone(schema, {
153104
153114
  ...schema._zod.def,
153105
- shape,
153115
+ shape: shape2,
153106
153116
  // optional: [],
153107
153117
  checks: []
153108
153118
  });
@@ -154579,10 +154589,10 @@ var init_schemas = __esm({
154579
154589
  };
154580
154590
  });
154581
154591
  defineLazy(inst._zod, "propValues", () => {
154582
- const shape = def.shape;
154592
+ const shape2 = def.shape;
154583
154593
  const propValues = {};
154584
- for (const key2 in shape) {
154585
- const field = shape[key2]._zod;
154594
+ for (const key2 in shape2) {
154595
+ const field = shape2[key2]._zod;
154586
154596
  if (field.values) {
154587
154597
  propValues[key2] ?? (propValues[key2] = /* @__PURE__ */ new Set());
154588
154598
  for (const v2 of field.values)
@@ -154591,7 +154601,7 @@ var init_schemas = __esm({
154591
154601
  }
154592
154602
  return propValues;
154593
154603
  });
154594
- const generateFastpass = (shape) => {
154604
+ const generateFastpass = (shape2) => {
154595
154605
  const doc = new Doc(["shape", "payload", "ctx"]);
154596
154606
  const normalized4 = _normalized.value;
154597
154607
  const parseStr = (key2) => {
@@ -154644,7 +154654,7 @@ var init_schemas = __esm({
154644
154654
  doc.write(`payload.value = newResult;`);
154645
154655
  doc.write(`return payload;`);
154646
154656
  const fn = doc.compile();
154647
- return (payload2, ctx) => fn(shape, payload2, ctx);
154657
+ return (payload2, ctx) => fn(shape2, payload2, ctx);
154648
154658
  };
154649
154659
  let fastpass;
154650
154660
  const isObject2 = isObject;
@@ -154672,9 +154682,9 @@ var init_schemas = __esm({
154672
154682
  payload2 = fastpass(payload2, ctx);
154673
154683
  } else {
154674
154684
  payload2.value = {};
154675
- const shape = value2.shape;
154685
+ const shape2 = value2.shape;
154676
154686
  for (const key2 of value2.keys) {
154677
- const el = shape[key2];
154687
+ const el = shape2[key2];
154678
154688
  const r = el._zod.run({ value: input[key2], issues: [] }, ctx);
154679
154689
  const isOptional = el._zod.optin === "optional" && el._zod.optout === "optional";
154680
154690
  if (r instanceof Promise) {
@@ -155961,22 +155971,22 @@ function never(params) {
155961
155971
  function array(element, params) {
155962
155972
  return _array(ZodArray2, element, params);
155963
155973
  }
155964
- function object(shape, params) {
155974
+ function object(shape2, params) {
155965
155975
  const def = {
155966
155976
  type: "object",
155967
155977
  get shape() {
155968
- util_exports.assignProp(this, "shape", { ...shape });
155978
+ util_exports.assignProp(this, "shape", { ...shape2 });
155969
155979
  return this.shape;
155970
155980
  },
155971
155981
  ...util_exports.normalizeParams(params)
155972
155982
  };
155973
155983
  return new ZodObject2(def);
155974
155984
  }
155975
- function looseObject(shape, params) {
155985
+ function looseObject(shape2, params) {
155976
155986
  return new ZodObject2({
155977
155987
  type: "object",
155978
155988
  get shape() {
155979
- util_exports.assignProp(this, "shape", { ...shape });
155989
+ util_exports.assignProp(this, "shape", { ...shape2 });
155980
155990
  return this.shape;
155981
155991
  },
155982
155992
  catchall: unknown(),
@@ -170153,7 +170163,7 @@ function escalationKindOf(issueId, authorActorId, source) {
170153
170163
  if (issueId.startsWith(REVIEW_EXHAUSTED_ESC_PREFIX2)) return "review_exhausted";
170154
170164
  if (typeof source === "string") {
170155
170165
  if (source.startsWith(SLA_STALL_ESC_SOURCE_PREFIX)) {
170156
- return STALL_KIND_BY_SOURCE[source.slice(SLA_STALL_ESC_SOURCE_PREFIX.length)] ?? "stall_idle_unexplained";
170166
+ return STALL_KIND_BY_SOURCE[source.slice(SLA_STALL_ESC_SOURCE_PREFIX.length).split(":")[0]] ?? "stall_idle_unexplained";
170157
170167
  }
170158
170168
  if (source === LEGACY_REVIEW_STALL_ESC_SOURCE) return "stall_idle_unexplained";
170159
170169
  if (source === SLA_BREAKER_ESC_SOURCE) return "sla_breaker";
@@ -172200,10 +172210,10 @@ var init_run_store = __esm({
172200
172210
  return result;
172201
172211
  }
172202
172212
  async persist(runs) {
172203
- const shape = { version: 1, runs };
172213
+ const shape2 = { version: 1, runs };
172204
172214
  const temp = `${this.file}.${process.pid}.${(0, import_node_crypto28.randomUUID)()}.tmp`;
172205
172215
  try {
172206
- await (0, import_promises3.writeFile)(temp, `${JSON.stringify(shape, null, 2)}
172216
+ await (0, import_promises3.writeFile)(temp, `${JSON.stringify(shape2, null, 2)}
172207
172217
  `, { mode: 384 });
172208
172218
  await (0, import_promises3.rename)(temp, this.file);
172209
172219
  } finally {
@@ -202288,23 +202298,23 @@ var init_governance_store = __esm({
202288
202298
  this.mutationChain = result.then(() => void 0, () => void 0);
202289
202299
  return result;
202290
202300
  }
202291
- async persist(shape) {
202301
+ async persist(shape2) {
202292
202302
  const temp = `${this.file}.${process.pid}.${(0, import_node_crypto35.randomUUID)()}.tmp`;
202293
202303
  try {
202294
- await (0, import_promises4.writeFile)(temp, `${JSON.stringify(this.sealSnapshots(shape), null, 2)}
202304
+ await (0, import_promises4.writeFile)(temp, `${JSON.stringify(this.sealSnapshots(shape2), null, 2)}
202295
202305
  `, { mode: 384 });
202296
202306
  await (0, import_promises4.rename)(temp, this.file);
202297
202307
  } finally {
202298
202308
  await (0, import_promises4.unlink)(temp).catch(() => void 0);
202299
202309
  }
202300
202310
  }
202301
- sealSnapshots(shape) {
202302
- const next = clone3(shape);
202311
+ sealSnapshots(shape2) {
202312
+ const next = clone3(shape2);
202303
202313
  next.records.snapshot = (next.records.snapshot ?? []).map((record9) => sealKnowledgeSnapshot(record9, this.snapshotKey));
202304
202314
  return next;
202305
202315
  }
202306
- static openSnapshots(shape, key2) {
202307
- const next = clone3(shape);
202316
+ static openSnapshots(shape2, key2) {
202317
+ const next = clone3(shape2);
202308
202318
  next.records.snapshot = (next.records.snapshot ?? []).map((record9) => openKnowledgeSnapshot(record9, key2));
202309
202319
  return next;
202310
202320
  }
@@ -209571,21 +209581,24 @@ var init_open_design2 = __esm({
209571
209581
  async function adoptGithubApp(store, args, deps = {}) {
209572
209582
  const scope2 = args.actorId ? { scope: "personal", actorId: args.actorId } : { scope: "global" };
209573
209583
  const put3 = (key2, value2, encrypted) => store.putVariable({ key: key2, value: value2, ...scope2, connectorId: "github", encrypted });
209584
+ const minting = args.actorId ? mintInstallationToken(
209585
+ { appId: args.appId, privateKeyPem: args.privateKeyPem, installationId: args.installationId },
209586
+ deps
209587
+ ) : null;
209588
+ minting?.catch(() => {
209589
+ });
209574
209590
  await put3("GITHUB_APP_ID", args.appId, false);
209575
209591
  await put3("GITHUB_APP_PRIVATE_KEY", args.privateKeyPem, true);
209576
209592
  await put3("GITHUB_APP_INSTALLATION_ID", args.installationId, false);
209577
209593
  if (args.slug) await put3("GITHUB_APP_SLUG", args.slug, false);
209578
- if (!args.actorId) {
209594
+ if (!minting) {
209579
209595
  return { account: args.slug ? `${args.slug}[bot]` : args.appId };
209580
209596
  }
209581
- const minted = await mintInstallationToken(
209582
- { appId: args.appId, privateKeyPem: args.privateKeyPem, installationId: args.installationId },
209583
- deps
209584
- );
209597
+ const minted = await minting;
209585
209598
  await put3("GITHUB_APP_TOKEN", minted.token, true);
209586
209599
  await put3("GITHUB_APP_TOKEN_EXPIRES_AT", String(minted.expiresAtMs), false);
209587
209600
  if (args.slug) {
209588
- const bot = await fetchAppBotIdentity(args.slug, { token: minted.token }, deps);
209601
+ const bot = args.knownBot ?? await fetchAppBotIdentity(args.slug, { token: minted.token }, deps);
209589
209602
  await put3("GITHUB_APP_BOT_EMAIL", bot?.email ?? "", false);
209590
209603
  await put3("GITHUB_APP_BOT_NAME", bot?.login ?? `${args.slug}[bot]`, false);
209591
209604
  }
@@ -209606,13 +209619,40 @@ function randomAppNameSuffix() {
209606
209619
  const alphabet = "abcdefghijklmnopqrstuvwxyz0123456789";
209607
209620
  return Array.from((0, import_node_crypto42.randomBytes)(4), (b2) => alphabet[b2 % alphabet.length]).join("");
209608
209621
  }
209622
+ async function githubAppNameTaken(name, fetchImpl = fetch, timeoutMs = 3e3) {
209623
+ const probe = async (url, headers = {}) => {
209624
+ const res = await fetchImpl(url, { method: "GET", redirect: "manual", headers, signal: AbortSignal.timeout(timeoutMs) });
209625
+ if (res.status === 404) return false;
209626
+ if (res.status === 200) return true;
209627
+ throw new Error(`unexpected ${res.status}`);
209628
+ };
209629
+ const n = encodeURIComponent(name);
209630
+ try {
209631
+ const [app, user] = await Promise.all([
209632
+ probe(`https://github.com/apps/${n}`),
209633
+ probe(`https://api.github.com/users/${n}`, { accept: "application/vnd.github+json", "user-agent": "oasis" })
209634
+ ]);
209635
+ return app || user;
209636
+ } catch {
209637
+ return true;
209638
+ }
209639
+ }
209640
+ async function chooseAppNameSuffix(args, isTaken) {
209641
+ const nameWith = (suffix2) => appName({ ...args, suffix: suffix2 });
209642
+ if (!await isTaken(nameWith(""))) return "";
209643
+ let suffix = randomAppNameSuffix();
209644
+ for (let i = 0; i < 4 && await isTaken(nameWith(suffix)); i++) suffix = randomAppNameSuffix();
209645
+ return suffix;
209646
+ }
209647
+ function appName(args) {
209648
+ const room = GITHUB_APP_NAME_MAX - args.namePrefix.length - 1 - (args.suffix ? args.suffix.length + 1 : 0);
209649
+ const middle = args.employeeSlug.slice(0, Math.max(0, room)).replace(/-+$/, "");
209650
+ return [args.namePrefix, middle, args.suffix].filter(Boolean).join("-");
209651
+ }
209609
209652
  function buildAppManifest(args) {
209610
209653
  const base = args.baseUrl.replace(/\/+$/, "");
209611
- const suffix = args.nameSuffix ?? randomAppNameSuffix();
209612
- const room = GITHUB_APP_NAME_MAX - args.namePrefix.length - suffix.length - 2;
209613
- const middle = args.employeeSlug.slice(0, Math.max(0, room)).replace(/-+$/, "");
209614
209654
  return {
209615
- name: [args.namePrefix, middle, suffix].filter(Boolean).join("-"),
209655
+ name: appName({ employeeSlug: args.employeeSlug, namePrefix: args.namePrefix, suffix: args.nameSuffix ?? "" }),
209616
209656
  url: base,
209617
209657
  redirect_url: `${base}${args.redirectPath}`,
209618
209658
  public: false,
@@ -210506,6 +210546,50 @@ var init_github_app_inventory = __esm({
210506
210546
  }
210507
210547
  });
210508
210548
 
210549
+ // ../server/src/github-app-timing.ts
210550
+ function startGithubAppTiming(res) {
210551
+ const t0 = performance.now();
210552
+ const entries = [];
210553
+ let n = 0;
210554
+ const record9 = async (name, desc, run) => {
210555
+ const s2 = performance.now();
210556
+ try {
210557
+ return await run();
210558
+ } finally {
210559
+ entries.push({ name: `${name}${++n}`, desc, startMs: s2 - t0, durMs: performance.now() - s2 });
210560
+ }
210561
+ };
210562
+ const writeHead = res.writeHead.bind(res);
210563
+ res.writeHead = (...args) => {
210564
+ const header = [
210565
+ ...entries.map((e) => `${e.name};desc="${e.desc} @${Math.round(e.startMs)}";dur=${e.durMs.toFixed(0)}`),
210566
+ `total;dur=${(performance.now() - t0).toFixed(0)}`
210567
+ ].join(", ");
210568
+ res.setHeader("server-timing", header);
210569
+ return writeHead(...args);
210570
+ };
210571
+ return {
210572
+ fetch: (input, init) => record9("gh", `${init?.method ?? "GET"} ${shape(String(input instanceof Request ? input.url : input))}`, () => globalThis.fetch(input, init)),
210573
+ step: record9
210574
+ };
210575
+ }
210576
+ function timedAdoptionStore(store, timing) {
210577
+ return { putVariable: (v2) => timing.step("db", `put ${v2.key}`, () => store.putVariable(v2)) };
210578
+ }
210579
+ var shape;
210580
+ var init_github_app_timing = __esm({
210581
+ "../server/src/github-app-timing.ts"() {
210582
+ "use strict";
210583
+ shape = (url) => {
210584
+ try {
210585
+ return new URL(url).pathname.replace(/\/\d+(?=\/|$)/g, "/:id").replace(/\/[^/]+%5Bbot%5D|\/[^/]+\[bot\]/g, "/:bot");
210586
+ } catch {
210587
+ return "?";
210588
+ }
210589
+ };
210590
+ }
210591
+ });
210592
+
210509
210593
  // ../server/src/server.ts
210510
210594
  function isBundleFrozen(err) {
210511
210595
  return typeof err === "object" && err !== null && err.code === "TENANT_BUNDLE_FROZEN";
@@ -216024,9 +216108,14 @@ ${composed}`;
216024
216108
  const { randomUUID: randomUUID48 } = await import("node:crypto");
216025
216109
  const state = randomUUID48();
216026
216110
  const nameSlug = (actorName || actorId?.split(":").pop() || owner?.login || "bot").toLowerCase().replace(/[^a-z0-9-]+/g, "-").replace(/^-|-$/g, "").slice(0, 24) || "bot";
216111
+ const nameSuffix = await chooseAppNameSuffix(
216112
+ { employeeSlug: nameSlug, namePrefix: "oasis" },
216113
+ (name) => (opts.githubAppNameTaken ?? githubAppNameTaken)(name)
216114
+ );
216027
216115
  const manifest = buildAppManifest({
216028
216116
  employeeSlug: nameSlug,
216029
216117
  namePrefix: "oasis",
216118
+ nameSuffix,
216030
216119
  baseUrl: base,
216031
216120
  redirectPath: "/api/connectors/github/app/manifest-callback",
216032
216121
  setupPath: "/api/connectors/github/app/setup-callback"
@@ -216182,9 +216271,10 @@ ${composed}`;
216182
216271
  res.writeHead(400, { "content-type": "application/json" }).end(JSON.stringify({ error: "\u7F3A\u5C11 appId" }));
216183
216272
  return;
216184
216273
  }
216274
+ const timing = startGithubAppTiming(res);
216185
216275
  const svc = await connectorActorsService();
216186
216276
  if (!svc) throw new Error("actors service unavailable");
216187
- const rows = await svc.listVariables();
216277
+ const rows = await timing.step("db", "list variables", () => svc.listVariables());
216188
216278
  const owners = rows.filter((r) => r.key === "GITHUB_APP_ID" && !r.encrypted && r.maskedValue === appId);
216189
216279
  const src = owners.find((r) => !r.actorId) ?? owners[0];
216190
216280
  if (!src) {
@@ -216192,27 +216282,30 @@ ${composed}`;
216192
216282
  return;
216193
216283
  }
216194
216284
  const srcActor = src.actorId;
216195
- const pem = await svc.revealVariable("GITHUB_APP_PRIVATE_KEY", srcActor);
216285
+ const pem = await timing.step("db", "reveal key", () => svc.revealVariable("GITHUB_APP_PRIVATE_KEY", srcActor));
216196
216286
  if (!pem) {
216197
216287
  res.writeHead(409, { "content-type": "application/json" }).end(JSON.stringify({ error: "\u8FD9\u4E2A App \u7684\u79C1\u94A5\u4E0D\u5728\u5E93\u91CC\uFF0C\u65E0\u6CD5\u7ED1\u5B9A" }));
216198
216288
  return;
216199
216289
  }
216200
216290
  const pick3 = (key2) => rows.find((r) => r.key === key2 && r.actorId === srcActor && !r.encrypted)?.maskedValue ?? "";
216201
216291
  const storedInstallationId = pick3("GITHUB_APP_INSTALLATION_ID");
216202
- const live = await listAppInstallations({ appId, privateKeyPem: pem }).catch(() => null);
216292
+ const live = await listAppInstallations({ appId, privateKeyPem: pem }, { fetch: timing.fetch }).catch(() => null);
216203
216293
  const installationId = live ? (live.find((i) => i.installationId === storedInstallationId) ?? live[0])?.installationId ?? "" : storedInstallationId;
216204
216294
  if (!installationId) {
216205
216295
  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", notInstalled: true }));
216206
216296
  return;
216207
216297
  }
216208
216298
  const slug6 = pick3("GITHUB_APP_SLUG");
216209
- const result = await adoptGithubApp(svc, {
216299
+ const botEmail = pick3("GITHUB_APP_BOT_EMAIL");
216300
+ const botLogin = pick3("GITHUB_APP_BOT_NAME");
216301
+ const result = await adoptGithubApp(timedAdoptionStore(svc, timing), {
216210
216302
  ...actorId ? { actorId } : {},
216211
216303
  appId,
216212
216304
  privateKeyPem: pem,
216213
216305
  installationId,
216214
- ...slug6 ? { slug: slug6 } : {}
216215
- });
216306
+ ...slug6 ? { slug: slug6 } : {},
216307
+ ...botEmail && botLogin ? { knownBot: { email: botEmail, login: botLogin } } : {}
216308
+ }, { fetch: timing.fetch });
216216
216309
  res.writeHead(200, { "content-type": "application/json" }).end(JSON.stringify({ ok: true, account: result.account }));
216217
216310
  } catch (e) {
216218
216311
  res.writeHead(500, { "content-type": "application/json" }).end(JSON.stringify({ error: String(e) }));
@@ -216231,11 +216324,15 @@ ${composed}`;
216231
216324
  bad(400, "\u8FD9\u4E0D\u50CF\u4E00\u4E2A\u79C1\u94A5\u6587\u4EF6\u2014\u2014\u8BF7\u4E0A\u4F20 GitHub \u4E0B\u8F7D\u7684 .pem\uFF08\u5185\u5BB9\u4EE5 -----BEGIN ... PRIVATE KEY----- \u5F00\u5934\uFF09");
216232
216325
  return;
216233
216326
  }
216327
+ const timing = startGithubAppTiming(res);
216234
216328
  const svc = await connectorActorsService();
216235
216329
  if (!svc) throw new Error("actors service unavailable");
216330
+ const listing = listAppInstallations({ appId, privateKeyPem }, { fetch: timing.fetch });
216331
+ listing.catch(() => {
216332
+ });
216236
216333
  let meta;
216237
216334
  try {
216238
- meta = await fetchAppSelfMetadata({ appId, privateKeyPem });
216335
+ meta = await fetchAppSelfMetadata({ appId, privateKeyPem }, { fetch: timing.fetch });
216239
216336
  } catch (e) {
216240
216337
  const msg = String(e);
216241
216338
  bad(
@@ -216253,7 +216350,7 @@ ${composed}`;
216253
216350
  );
216254
216351
  return;
216255
216352
  }
216256
- const installs = await listAppInstallations({ appId, privateKeyPem });
216353
+ const installs = await listing;
216257
216354
  if (installs.length === 0) {
216258
216355
  bad(409, "\u8FD9\u4E2A App \u8FD8\u6CA1\u6709\u88AB\u5B89\u88C5\u5230\u4EFB\u4F55\u8D26\u53F7\u2014\u2014\u5148\u5728 GitHub \u4E0A\u628A\u5B83\u88C5\u5230\u76EE\u6807\u4ED3\u5E93");
216259
216356
  return;
@@ -216267,14 +216364,14 @@ ${composed}`;
216267
216364
  }));
216268
216365
  return;
216269
216366
  }
216270
- const result = await adoptGithubApp(svc, {
216367
+ const result = await adoptGithubApp(timedAdoptionStore(svc, timing), {
216271
216368
  ...actorId ? { actorId } : {},
216272
216369
  appId,
216273
216370
  privateKeyPem,
216274
216371
  installationId: chosen.installationId,
216275
216372
  ...meta.slug ? { slug: meta.slug } : {}
216276
- });
216277
- const effective = await fetchInstallationPermissions({ appId, privateKeyPem, installationId: chosen.installationId }).catch(() => meta.permissions);
216373
+ }, { fetch: timing.fetch });
216374
+ const effective = Object.keys(chosen.permissions).length > 0 ? chosen.permissions : meta.permissions;
216278
216375
  res.writeHead(200, { "content-type": "application/json" }).end(JSON.stringify({
216279
216376
  ok: true,
216280
216377
  account: result.account,
@@ -216985,6 +217082,7 @@ var init_server3 = __esm({
216985
217082
  init_registry_audit_sink();
216986
217083
  init_src8();
216987
217084
  init_github_app_inventory();
217085
+ init_github_app_timing();
216988
217086
  init_continuation3();
216989
217087
  CONNECTOR_AUTH_SNAPSHOT_TTL_MS = 30 * 60 * 1e3;
216990
217088
  defaultResolveActor = (token) => token.startsWith("token:") ? token.slice("token:".length) : null;
@@ -237744,11 +237842,12 @@ function reviewLabel(r) {
237744
237842
  if (state === "retry") return "\u6B63\u5728\u91CD\u8BD5\u5BA1\u6838";
237745
237843
  return r.reviewerActorId.startsWith("actor:human:") ? "\u5F85\u5BA1\u6838" : "\u6B63\u5728\u5BA1\u6838";
237746
237844
  }
237747
- function reviewSummary(snap, reviews, events) {
237845
+ function reviewSummary(snap, reviews, events, versionNo) {
237748
237846
  const first = reviews[0];
237749
237847
  const work = snap.works.find((w2) => w2.id === first.targetWorkId);
237750
237848
  const node2 = snap.nodes.find((n) => n.id === first.nodeId);
237751
237849
  const name = node2?.title ?? first.nodeId;
237850
+ const ver = versionNo !== void 0 ? `\u7B2C ${versionNo} \u7248` : "";
237752
237851
  const latest = latestActivityReviews(reviews, events);
237753
237852
  const requirements = snap.requirements.filter((r) => r.nodeId === first.nodeId && r.reviewerActorId === first.reviewerActorId);
237754
237853
  for (const requirement of requirements) {
@@ -237760,21 +237859,21 @@ function reviewSummary(snap, reviews, events) {
237760
237859
  const judgement = judgeWork({ requirements: requirements.length ? requirements : latest, reviews: latest });
237761
237860
  const pending = latest.filter((r) => !r.cancelledAt && !r.verdict && reviewState(r) === "running");
237762
237861
  if (work?.acceptanceState === "accepted" || work?.acceptedAt || work?.acceptanceState !== "rejected" && judgement === "passed") {
237763
- return { phase: "done", status: `\u300A${name}\u300B\u5BA1\u6838\u901A\u8FC7\u3002`, latest, pending: [] };
237862
+ return { phase: "done", status: `\u300A${name}\u300B${ver}\u5BA1\u6838\u901A\u8FC7\u3002`, latest, pending: [] };
237764
237863
  }
237765
237864
  if (work?.acceptanceState === "rejected" || judgement === "rejected") {
237766
- return { phase: "done", status: `\u300A${name}\u300B\u5BA1\u6838\u4E0D\u901A\u8FC7\u3002`, latest, pending: [] };
237865
+ return { phase: "done", status: `\u300A${name}\u300B${ver}\u5BA1\u6838\u4E0D\u901A\u8FC7\u3002`, latest, pending: [] };
237767
237866
  }
237768
237867
  const endedWork = !!(work?.cancelledAt || work?.deadAt);
237769
237868
  if (endedWork && latest.every((r) => !!r.cancelledAt && reviewState(r) !== "retry")) {
237770
237869
  return { phase: "done", status: `\u300A${name}\u300B\u7684\u672C\u6B21\u5BA1\u6838\u5DF2\u53D6\u6D88\u3002`, latest, pending: [] };
237771
237870
  }
237772
237871
  const interrupted = latest.some((r) => ["failed", "dead"].includes(reviewState(r)));
237773
- if (interrupted) return { phase: "stuck", status: latest.length === 1 ? reviewState(latest[0]) === "dead" ? events.some((r) => r.event.kind === "review.kill" && r.event.reviewId === latest[0].id) ? `\u300A${name}\u300B\u7684\u8BC4\u5BA1\u8FDE\u7EED\u5931\u8D25\uFF0C\u5DF2\u505C\u6B62\u3002` : `\u300A${name}\u300B\u7684\u5BA1\u6838\u5DF2\u505C\u6B62\u3002` : `\u300A${name}\u300B\u7684\u5BA1\u6838\u672A\u5728\u65F6\u9650\u5185\u5B8C\u6210\u3002` : `\u300A${name}\u300B\u7684\u90E8\u5206\u5BA1\u6838\u6267\u884C\u4E2D\u65AD\uFF0C\u5C1A\u672A\u5B8C\u6210\u5168\u90E8\u5BA1\u6838\u3002`, latest, pending };
237872
+ if (interrupted) return { phase: "stuck", status: latest.length === 1 ? reviewState(latest[0]) === "dead" ? events.some((r) => r.event.kind === "review.kill" && r.event.reviewId === latest[0].id) ? `\u300A${name}\u300B\u7684\u5BA1\u6838\u8FDE\u7EED\u51E0\u6B21\u6CA1\u8DD1\u6210\uFF0C\u5DF2\u505C\u6B62\u3002` : `\u300A${name}\u300B\u7684\u5BA1\u6838\u5DF2\u505C\u6B62\u3002` : `\u300A${name}\u300B\u7684\u5BA1\u6838\u672A\u5728\u65F6\u9650\u5185\u5B8C\u6210\u3002` : `\u300A${name}\u300B\u6709\u5BA1\u6838\u4EBA\u7684\u5BA1\u6838\u4E2D\u65AD\u4E86\uFF0C\u5BA1\u6838\u8FD8\u6CA1\u5168\u90E8\u5B8C\u6210\u3002`, latest, pending };
237774
237873
  if (latest.some((r) => r.reviewerActorId.startsWith("actor:agent:") && ["running", "retry"].includes(reviewState(r)))) {
237775
- return { phase: "running", status: `\u6B63\u5728\u5BA1\u6838 \u300A${name}\u300B\u3002`, latest, pending };
237874
+ return { phase: "running", status: `\u6B63\u5728\u5BA1\u6838\u300A${name}\u300B${ver}\u3002`, latest, pending };
237776
237875
  }
237777
- return { phase: "not_started", status: `\u8BF7\u786E\u8BA4\u300A${name}\u300B\u662F\u5426\u5BA1\u6838\u901A\u8FC7\u3002`, latest, pending };
237876
+ return { phase: "not_started", status: `\u8BF7\u786E\u8BA4\u300A${name}\u300B${ver}\u662F\u5426\u5BA1\u6838\u901A\u8FC7\u3002`, latest, pending };
237778
237877
  }
237779
237878
  var REVIEW_CARD_PREFIX, REVIEW_TRACE_EVENT_KINDS;
237780
237879
  var init_review_activity = __esm({
@@ -237800,13 +237899,13 @@ function reviewRequirementActivity(snap, events, ref2) {
237800
237899
  const label2 = (r) => `${ref2(r.reviewerActorId).name || "\u672A\u547D\u540D\u5BA1\u6838\u4EBA"}${r.source === "closure" ? "\uFF08\u7ED3\u6848\u5BA1\u6838\uFF09" : ""}`;
237801
237900
  const names = (rows) => [...new Set(rows.map(label2))].join("\u3001");
237802
237901
  const submitter = (id) => ref2(id).name?.trim() || "\u672A\u547D\u540D\u6210\u5458";
237803
- const sentence = (id, nodeTitle, rows) => rows.length ? `${submitter(id)} \u66F4\u6539\u300A${nodeTitle}\u300B\u8282\u70B9\u7684\u5BA1\u6838\u4EBA\u4E3A ${names(rows)}\u3002` : `${submitter(id)} \u6E05\u7A7A\u4E86\u300A${nodeTitle}\u300B\u8282\u70B9\u7684\u5BA1\u6838\u4EBA\u3002`;
237902
+ const sentence = (id, nodeTitle, rows) => rows.length ? `${submitter(id)} \u628A\u300A${nodeTitle}\u300B\u7684\u5BA1\u6838\u4EBA\u6539\u4E3A ${names(rows)}\u3002` : `${submitter(id)} \u53D6\u6D88\u4E86\u300A${nodeTitle}\u300B\u7684\u5BA1\u6838\u4EBA\u3002`;
237804
237903
  for (const rec2 of [...events].sort((a, b2) => a.seq - b2.seq)) {
237805
237904
  if (rec2.status === "pending" || rec2.status === "failed") continue;
237806
237905
  const e = rec2.event;
237807
237906
  if (e.kind !== "plan.update_review_requirements") continue;
237808
237907
  if (!isAuthoredChange(rec2)) continue;
237809
- const nodeTitle = titles.get(e.nodeId) || "\u5DF2\u79FB\u9664\u7684\u8282\u70B9";
237908
+ const nodeTitle = titles.get(e.nodeId) || "\u5DF2\u79FB\u9664\u7684\u6B65\u9AA4";
237810
237909
  cards.push({
237811
237910
  id: `wo:${rec2.seq}`,
237812
237911
  seq: rec2.seq,
@@ -237834,6 +237933,186 @@ var init_review_requirement_activity = __esm({
237834
237933
  }
237835
237934
  });
237836
237935
 
237936
+ // ../server/src/domains/collab/activity-version.ts
237937
+ function countsAsVersion(w2) {
237938
+ if (w2.lane === "reply") return false;
237939
+ return !!w2.conclusion || w2.acceptanceState === "accepted";
237940
+ }
237941
+ function outputVersionNumbers(snap) {
237942
+ const byNode = /* @__PURE__ */ new Map();
237943
+ for (const w2 of snap.works) {
237944
+ if (!countsAsVersion(w2)) continue;
237945
+ const list2 = byNode.get(w2.nodeId) ?? [];
237946
+ list2.push(w2);
237947
+ byNode.set(w2.nodeId, list2);
237948
+ }
237949
+ const out = /* @__PURE__ */ new Map();
237950
+ for (const list2 of byNode.values()) {
237951
+ list2.sort((a, b2) => a.createdAt.localeCompare(b2.createdAt) || a.id.localeCompare(b2.id));
237952
+ list2.forEach((w2, i) => out.set(w2.id, i + 1));
237953
+ }
237954
+ return out;
237955
+ }
237956
+ function latestVersionAt(snap, versions, nodeId, at2) {
237957
+ let best;
237958
+ for (const w2 of snap.works) {
237959
+ if (w2.nodeId !== nodeId || w2.createdAt > at2) continue;
237960
+ const v2 = versions.get(w2.id);
237961
+ if (v2 !== void 0 && (best === void 0 || v2 > best)) best = v2;
237962
+ }
237963
+ return best;
237964
+ }
237965
+ function versionPhrase(n) {
237966
+ return n !== void 0 ? `\u7B2C ${n} \u7248` : "";
237967
+ }
237968
+ var init_activity_version = __esm({
237969
+ "../server/src/domains/collab/activity-version.ts"() {
237970
+ "use strict";
237971
+ }
237972
+ });
237973
+
237974
+ // ../server/src/domains/collab/stall-copy.ts
237975
+ function isNotDoneCode(code2) {
237976
+ return !!code2 && NOT_DONE_CODES.has(code2);
237977
+ }
237978
+ function notDoneCopy(code2, facts = {}) {
237979
+ switch (code2) {
237980
+ case "no-output":
237981
+ return { reason: "\u8FD9\u4E00\u8F6E\u6CA1\u6709\u4EA4\u51FA\u4EFB\u4F55\u6210\u679C", fix: "\u91CD\u65B0\u8FD0\u884C\uFF0C\u6216\u6539\u7528\u4E0A\u4E00\u7248\u6210\u679C" };
237982
+ case "work-dead":
237983
+ return { reason: facts.deadReason ? `\u8FD9\u4E00\u8F6E\u5DF2\u7ECF\u88AB\u505C\u6389\uFF08${facts.deadReason}\uFF09` : "\u8FD9\u4E00\u8F6E\u5DF2\u7ECF\u88AB\u505C\u6389", fix: "\u91CD\u65B0\u8FD0\u884C" };
237984
+ case "work-in-flight":
237985
+ return { reason: "\u8FD9\u4E00\u8F6E\u8FD8\u5728\u8FD0\u884C\uFF0C\u8FD8\u6CA1\u6709\u4EA4\u51FA\u6210\u679C", fix: "\u7B49\u5B83\u505A\u5B8C\uFF0C\u6216\u5148\u505C\u6389\u518D\u91CD\u65B0\u8FD0\u884C" };
237986
+ case "not-latest-work":
237987
+ return facts.selectedVersion !== void 0 && facts.latestVersion !== void 0 ? { reason: `\u9009\u4E2D\u7684\u662F\u7B2C ${facts.selectedVersion} \u7248\uFF0C\u6700\u65B0\u7684\u662F\u7B2C ${facts.latestVersion} \u7248`, fix: `\u6539\u7528\u7B2C ${facts.latestVersion} \u7248` } : { reason: "\u9009\u4E2D\u7684\u4E0D\u662F\u6700\u65B0\u7684\u4E00\u7248", fix: "\u6539\u7528\u6700\u65B0\u7684\u4E00\u7248" };
237988
+ case "handoff-undeclared":
237989
+ return { reason: "\u6267\u884C\u4EBA\u8BF4\u660E\u8FD9\u4E00\u8F6E\u6CA1\u6709\u505A\u5B8C", fix: "\u91CD\u65B0\u8FD0\u884C" };
237990
+ case "work-not-success":
237991
+ return { reason: "\u8FD9\u4E00\u7248\u662F\u8FD0\u884C\u5931\u8D25\u65F6\u7559\u4E0B\u7684", fix: "\u786E\u8BA4\u5185\u5BB9\u53EF\u7528\u5C31\u76F4\u63A5\u6309\u5F53\u524D\u6210\u679C\u7B97\u5B8C\u6210\uFF0C\u5426\u5219\u91CD\u65B0\u8FD0\u884C" };
237992
+ case "no-conclude":
237993
+ return { reason: "\u6267\u884C\u4EBA\u53EA\u63D0\u4EA4\u4E86\u9636\u6BB5\u6210\u679C\uFF0C\u6CA1\u6709\u8BF4\u660E\u5DF2\u7ECF\u505A\u5B8C\uFF0C\u6240\u4EE5\u6CA1\u6709\u8FDB\u5165\u5BA1\u6838", fix: "\u91CD\u65B0\u8FD0\u884C\u8BA9\u5B83\u505A\u5B8C\uFF0C\u6216\u76F4\u63A5\u6309\u5F53\u524D\u6210\u679C\u7B97\u5B8C\u6210" };
237994
+ case "no-reviewers":
237995
+ return { reason: "\u8FD9\u4E00\u6B65\u6CA1\u6709\u914D\u5230\u5BA1\u6838\u4EBA", fix: "\u7ED9\u8FD9\u4E00\u6B65\u6307\u5B9A\u5BA1\u6838\u4EBA" };
237996
+ case "already-accepted":
237997
+ return { reason: "\u8FD9\u4E00\u7248\u5DF2\u7ECF\u7B97\u5B8C\u6210\u4E86", fix: "\u4E0D\u7528\u518D\u5904\u7406" };
237998
+ case "work-missing":
237999
+ case "node-missing":
238000
+ return { reason: "\u7CFB\u7EDF\u627E\u4E0D\u5230\u8FD9\u4E00\u8F6E\u7684\u8BB0\u5F55", fix: "\u91CD\u65B0\u8FD0\u884C" };
238001
+ case "not-converged":
238002
+ return void 0;
238003
+ }
238004
+ }
238005
+ function stallCodeOfSource(source) {
238006
+ if (!source?.startsWith(STALL_PREFIX)) return void 0;
238007
+ const code2 = source.slice(STALL_PREFIX.length).split(":")[1];
238008
+ return code2 || void 0;
238009
+ }
238010
+ function legacyNotDoneCodeOf(body2) {
238011
+ const t = body2.replace(/\*\*/g, "");
238012
+ if (/这一版已结束但没有任何产出/.test(t)) return "no-output";
238013
+ if (/这一版已作废(/.test(t)) return "work-dead";
238014
+ if (/这一版还在跑、且还没有任何产出/.test(t)) return "work-in-flight";
238015
+ if (/不是本节点最新版(/.test(t)) return "not-latest-work";
238016
+ if (/执行人明确声明本轮未交付/.test(t)) return "handoff-undeclared";
238017
+ if (/这一版不是有效产出(/.test(t)) return "work-not-success";
238018
+ if (/已是验收态(/.test(t)) return "already-accepted";
238019
+ if (/^work 不在册:/m.test(t)) return "work-missing";
238020
+ if (/^节点不在册:/m.test(t)) return "node-missing";
238021
+ if (/已经交了稿,但没有收口/.test(t)) return "no-conclude";
238022
+ if (/没有配到任何评审人/.test(t)) return "no-reviewers";
238023
+ return void 0;
238024
+ }
238025
+ function parseStallBody(body2) {
238026
+ const t = body2.replace(/\*\*/g, "");
238027
+ const num5 = (...res) => {
238028
+ for (const re of res) {
238029
+ const m2 = re.exec(t)?.[1];
238030
+ if (m2 !== void 0) return Number(m2);
238031
+ }
238032
+ return void 0;
238033
+ };
238034
+ const rounds = num5(
238035
+ /连续被评审打回\s*(\d+)\s*轮/,
238036
+ /连续\s*(\d+)\s*次审核不通过/,
238037
+ // 打回到上限:旧 / 新
238038
+ /连续\s*(\d+)\s*轮没有新增交付内容/,
238039
+ /连续\s*(\d+)\s*轮提交的内容都没有变化/,
238040
+ // 无进展上限:旧 / 新
238041
+ /派出去\s*(\d+)\s*次都没等到票/,
238042
+ /审核发起了\s*(\d+)\s*次都没有等到结果/
238043
+ // 审核等不到结果:旧 / 新
238044
+ );
238045
+ const reviewer = /当前是\s*([^))\s]+)/.exec(t)?.[1];
238046
+ const dead = /已作废([^:)]*:([^)\n]+))/.exec(t)?.[1] ?? /已经被停掉(([^\n]+))/.exec(t)?.[1];
238047
+ const latestProposed = /最新有效产出:([^);\s]+)/.exec(t)?.[1];
238048
+ return {
238049
+ ...rounds !== void 0 ? { rounds } : {},
238050
+ ...reviewer ? { reviewerActorId: reviewer } : {},
238051
+ ...dead?.trim() ? { deadReason: dead.trim() } : {},
238052
+ ...latestProposed && latestProposed !== "\u65E0" ? { latestProposedWorkId: latestProposed } : {}
238053
+ };
238054
+ }
238055
+ function stallCardCopy(kind, source, body2, ctx) {
238056
+ const facts = parseStallBody(body2);
238057
+ const x2 = ctx.node;
238058
+ const notDone = (code2) => {
238059
+ const copy = code2 ? notDoneCopy(code2, {
238060
+ ...facts.deadReason ? { deadReason: facts.deadReason } : {},
238061
+ ...ctx.selectedVersion !== void 0 ? { selectedVersion: ctx.selectedVersion } : {},
238062
+ .../* @__PURE__ */ ((v2) => v2 !== void 0 ? { latestVersion: v2 } : {})(
238063
+ facts.latestProposedWorkId ? ctx.versionOfWork?.(facts.latestProposedWorkId) : void 0
238064
+ )
238065
+ }) : void 0;
238066
+ return copy ? { headline: `\u300A${x2}\u300B\u8FD9\u4E00\u8F6E\u7ED3\u675F\u4E86\uFF0C\u4F46\u8FD8\u4E0D\u80FD\u7B97\u5B8C\u6210\uFF1A${copy.reason}\u3002`, action: `${copy.fix}\u3002` } : { headline: `\u300A${x2}\u300B\u8FD9\u4E00\u8F6E\u7ED3\u675F\u4E86\uFF0C\u4F46\u8FD8\u4E0D\u80FD\u7B97\u5B8C\u6210\u3002`, action: "\u91CD\u65B0\u8FD0\u884C\uFF0C\u6216\u76F4\u63A5\u6309\u5F53\u524D\u6210\u679C\u7B97\u5B8C\u6210\u3002" };
238067
+ };
238068
+ const sourceCode = stallCodeOfSource(source);
238069
+ switch (kind) {
238070
+ case "stall_gate_rejections_exhausted":
238071
+ return {
238072
+ headline: ctx.latestVersion !== void 0 && facts.rounds !== void 0 ? `\u300A${x2}\u300B\u5DF2\u4EA4\u5230\u7B2C ${ctx.latestVersion} \u7248\uFF0C\u8FDE\u7EED ${facts.rounds} \u6B21\u5BA1\u6838\u4E0D\u901A\u8FC7\uFF0C\u7CFB\u7EDF\u5DF2\u505C\u6B62\u81EA\u52A8\u8FD4\u5DE5\u3002` : facts.rounds !== void 0 ? `\u300A${x2}\u300B\u8FDE\u7EED ${facts.rounds} \u6B21\u5BA1\u6838\u4E0D\u901A\u8FC7\uFF0C\u7CFB\u7EDF\u5DF2\u505C\u6B62\u81EA\u52A8\u8FD4\u5DE5\u3002` : `\u300A${x2}\u300B\u8FDE\u7EED\u591A\u6B21\u5BA1\u6838\u4E0D\u901A\u8FC7\uFF0C\u7CFB\u7EDF\u5DF2\u505C\u6B62\u81EA\u52A8\u8FD4\u5DE5\u3002`,
238073
+ action: "\u5148\u770B\u5BA1\u6838\u610F\u89C1\u548C\u6267\u884C\u4EBA\u6BCF\u6B21\u4EA4\u7684\u5185\u5BB9\uFF08\u591A\u534A\u662F\u5185\u5BB9\u6CA1\u6709\u5B9E\u8D28\u53D8\u5316\uFF09\uFF0C\u518D\u9009\u62E9\uFF1A\u91CD\u65B0\u8FD0\u884C\u3001\u6362\u6267\u884C\u4EBA\uFF0C\u6216\u76F4\u63A5\u6309\u5F53\u524D\u6210\u679C\u7B97\u5B8C\u6210\u3002"
238074
+ };
238075
+ case "stall_checkpoints_exhausted":
238076
+ return {
238077
+ headline: facts.rounds !== void 0 ? `\u300A${x2}\u300B\u8FDE\u7EED ${facts.rounds} \u8F6E\u63D0\u4EA4\u7684\u5185\u5BB9\u90FD\u6CA1\u6709\u53D8\u5316\uFF0C\u7CFB\u7EDF\u5DF2\u505C\u6B62\u81EA\u52A8\u7EE7\u7EED\u6267\u884C\u3002` : `\u300A${x2}\u300B\u8FDE\u7EED\u51E0\u8F6E\u63D0\u4EA4\u7684\u5185\u5BB9\u90FD\u6CA1\u6709\u53D8\u5316\uFF0C\u7CFB\u7EDF\u5DF2\u505C\u6B62\u81EA\u52A8\u7EE7\u7EED\u6267\u884C\u3002`,
238078
+ action: "\u5148\u770B\u6700\u8FD1\u63D0\u4EA4\u7684\u5185\u5BB9\u548C\u8FD8\u5269\u54EA\u4E9B\u6CA1\u505A\uFF0C\u8865\u5145\u8BF4\u660E\u540E\u91CD\u65B0\u8FD0\u884C\uFF1B\u5982\u679C\u5176\u5B9E\u5DF2\u7ECF\u505A\u5B8C\uFF0C\u76F4\u63A5\u6309\u5F53\u524D\u6210\u679C\u7B97\u5B8C\u6210\u3002"
238079
+ };
238080
+ case "stall_review_exhausted":
238081
+ return {
238082
+ headline: facts.rounds !== void 0 ? `\u300A${x2}\u300B\u7684\u5BA1\u6838\u53D1\u8D77\u4E86 ${facts.rounds} \u6B21\u90FD\u6CA1\u6709\u7B49\u5230\u7ED3\u679C\uFF0C\u7CFB\u7EDF\u5DF2\u505C\u6B62\u91CD\u65B0\u53D1\u8D77\u3002` : `\u300A${x2}\u300B\u7684\u5BA1\u6838\u53D1\u8D77\u4E86\u51E0\u6B21\u90FD\u6CA1\u6709\u7B49\u5230\u7ED3\u679C\uFF0C\u7CFB\u7EDF\u5DF2\u505C\u6B62\u91CD\u65B0\u53D1\u8D77\u3002`,
238083
+ action: facts.reviewerActorId ? `\u91CD\u65B0\u53D1\u8D77\u8FD9\u4E00\u8F6E\u5BA1\u6838\u3001\u6362\u4E00\u4E2A\u5BA1\u6838\u4EBA\uFF08\u5F53\u524D\u662F ${ctx.nameOf(facts.reviewerActorId)}\uFF09\uFF0C\u6216\u76F4\u63A5\u6309\u5F53\u524D\u6210\u679C\u7B97\u5B8C\u6210\u3002` : "\u91CD\u65B0\u53D1\u8D77\u8FD9\u4E00\u8F6E\u5BA1\u6838\u3001\u6362\u4E00\u4E2A\u5BA1\u6838\u4EBA\uFF0C\u6216\u76F4\u63A5\u6309\u5F53\u524D\u6210\u679C\u7B97\u5B8C\u6210\u3002"
238084
+ };
238085
+ case "stall_accept_blocked":
238086
+ return notDone(isNotDoneCode(sourceCode) ? sourceCode : legacyNotDoneCodeOf(body2));
238087
+ case "stall_idle_unexplained": {
238088
+ const code2 = isNotDoneCode(sourceCode) ? sourceCode : legacyNotDoneCodeOf(body2);
238089
+ if (code2 === "no-conclude" || code2 === "no-reviewers") return notDone(code2);
238090
+ return { headline: `\u300A${x2}\u300B\u505A\u5B8C\u4E4B\u540E\u957F\u65F6\u95F4\u6CA1\u6709\u52A8\u9759\uFF0C\u7CFB\u7EDF\u627E\u4E0D\u5230\u539F\u56E0\u3002`, action: "\u9700\u8981\u4F60\u770B\u4E00\u4E0B\u5B83\u5361\u5728\u54EA\u91CC\u3002" };
238091
+ }
238092
+ }
238093
+ }
238094
+ var NOT_DONE_CODES, STALL_PREFIX;
238095
+ var init_stall_copy = __esm({
238096
+ "../server/src/domains/collab/stall-copy.ts"() {
238097
+ "use strict";
238098
+ NOT_DONE_CODES = /* @__PURE__ */ new Set([
238099
+ "work-missing",
238100
+ "node-missing",
238101
+ "work-dead",
238102
+ "already-accepted",
238103
+ "work-in-flight",
238104
+ "no-output",
238105
+ "not-latest-work",
238106
+ "work-not-success",
238107
+ "handoff-undeclared",
238108
+ "not-converged",
238109
+ "no-conclude",
238110
+ "no-reviewers"
238111
+ ]);
238112
+ STALL_PREFIX = "sla:stall:";
238113
+ }
238114
+ });
238115
+
237837
238116
  // ../server/src/domains/collab/activity.ts
237838
238117
  function coordinatorCardGapId(cardId) {
237839
238118
  return cardId.startsWith(COORDINATOR_CARD_PREFIX) ? cardId.slice(COORDINATOR_CARD_PREFIX.length) : void 0;
@@ -237848,13 +238127,6 @@ function gapEvalsOf(body2) {
237848
238127
  const n = /评估了\s*(\d+)\s*次/.exec(body2)?.[1];
237849
238128
  return n !== void 0 ? Number(n) : void 0;
237850
238129
  }
237851
- function stallCardCopyOf(body2) {
237852
- const paras = body2.split(/\n{2,}/).map((p2) => p2.replace(/\*\*/g, "").trim()).filter(Boolean);
237853
- const headline = paras[0] && !paras[0].startsWith("\u9700\u8981\u4F60") ? paras[0] : void 0;
237854
- const actionPara = paras.slice(1).reverse().find((p2) => p2.startsWith("\u9700\u8981\u4F60"));
237855
- const action = actionPara?.replace(/^需要你处理[::]\s*/, "");
237856
- return { ...headline ? { headline } : {}, ...action ? { action } : {} };
237857
- }
237858
238130
  function parseStopLossFacts(kind, body2) {
237859
238131
  if (kind === "retry_exhausted") {
237860
238132
  const n2 = /连续\s*(\d+)\s*次执行失败/.exec(body2)?.[1];
@@ -237901,6 +238173,8 @@ function buildWorkorderActivity(input) {
237901
238173
  const nodeById = new Map(snap.nodes.map((n) => [n.id, n]));
237902
238174
  const workById = new Map(snap.works.map((w2) => [w2.id, w2]));
237903
238175
  const issueById = new Map(snap.issues.map((i) => [i.id, i]));
238176
+ const versionOf2 = outputVersionNumbers(snap);
238177
+ const versionOfWork = (workId) => workId ? versionOf2.get(workId) : void 0;
237904
238178
  const nodesWithInEdges = new Set(snap.edges.map((e) => e.toNodeId));
237905
238179
  const rootBriefNodeIds = new Set(
237906
238180
  snap.nodes.filter((n) => n.type === "brief" && !nodesWithInEdges.has(n.id)).map((n) => n.id)
@@ -238366,15 +238640,15 @@ function buildWorkorderActivity(input) {
238366
238640
  } else {
238367
238641
  const finalReviewers = node2?.isFinalOutput ? expectedReviewers(nodeId) : [];
238368
238642
  if (node2?.isFinalOutput && finalReviewers.length > 0) {
238369
- card2.status = ACTIVITY_COPY.finalDelivered(nameOf2(finalReviewers[0]));
238643
+ card2.status = ACTIVITY_COPY.finalDelivered(nameOf2(finalReviewers[0]), versionOfWork(w2.id));
238370
238644
  } else {
238371
238645
  const awaits = snap.reviews.some((r) => r.targetWorkId === w2.id) || nodesWithRequirement.has(nodeId);
238372
- card2.status = awaits ? ACTIVITY_COPY.doneAwaitingReview(name) : ACTIVITY_COPY.done(name);
238646
+ card2.status = awaits ? ACTIVITY_COPY.doneAwaitingReview(name, versionOfWork(w2.id)) : ACTIVITY_COPY.done(name, versionOfWork(w2.id));
238373
238647
  }
238374
238648
  }
238375
238649
  const bpRaisers = backpressureRaisersOf(w2.id);
238376
- if (bpRaisers && (card2.status === ACTIVITY_COPY.done(name) || card2.status === ACTIVITY_COPY.goalConfirmed || card2.status === ACTIVITY_COPY.goalRevised)) {
238377
- card2.status = ACTIVITY_COPY.backpressureAddressed(bpRaisers, backpressureTargetName(nodeId));
238650
+ if (bpRaisers && (card2.status === ACTIVITY_COPY.done(name, versionOfWork(w2.id)) || card2.status === ACTIVITY_COPY.goalConfirmed || card2.status === ACTIVITY_COPY.goalRevised)) {
238651
+ card2.status = ACTIVITY_COPY.backpressureAddressed(bpRaisers, backpressureTargetName(nodeId), versionOfWork(w2.id));
238378
238652
  }
238379
238653
  } else if (interrupt) {
238380
238654
  card2.phase = "stuck";
@@ -238400,7 +238674,7 @@ function buildWorkorderActivity(input) {
238400
238674
  card2.phase = "stuck";
238401
238675
  card2.segmentOpen = false;
238402
238676
  const editor = openerOfWork.get(supersededBy.workId);
238403
- card2.status = rootBriefNodeIds.has(supersededBy.upstreamNodeId) && editor && !isSystemActorId(editor) ? ACTIVITY_COPY.interruptedBySpecChange(nameOf2(editor), name) : ACTIVITY_COPY.interruptedByUpstreamVersion(nodeName(supersededBy.upstreamNodeId), name);
238677
+ card2.status = rootBriefNodeIds.has(supersededBy.upstreamNodeId) && editor && !isSystemActorId(editor) ? ACTIVITY_COPY.interruptedBySpecChange(nameOf2(editor), name) : ACTIVITY_COPY.interruptedByUpstreamVersion(nodeName(supersededBy.upstreamNodeId), name, versionOfWork(supersededBy.workId));
238404
238678
  } else if (removedHere || killedByPlan) {
238405
238679
  card2.phase = "stuck";
238406
238680
  card2.segmentOpen = false;
@@ -238419,7 +238693,7 @@ function buildWorkorderActivity(input) {
238419
238693
  fold2 = foldableFailure ? card2 : null;
238420
238694
  } else if (ackedRound) {
238421
238695
  card2.phase = "done";
238422
- card2.status = ACTIVITY_COPY.upstreamAcked(name);
238696
+ card2.status = ACTIVITY_COPY.upstreamAcked(name, versionOfWork(w2.upstreamAck?.acceptedWorkId));
238423
238697
  } else if (batchEnd) {
238424
238698
  card2.phase = "done";
238425
238699
  card2.segmentOpen = false;
@@ -238499,7 +238773,7 @@ function buildWorkorderActivity(input) {
238499
238773
  d.phase = "done";
238500
238774
  d.actions = [];
238501
238775
  d.executorId = subjectId;
238502
- d.status = addressed ? ACTIVITY_COPY.commentAddressed(nameOf2(subjectId), nameOf2(authorId), nodeName(d.nodeId)) : ACTIVITY_COPY.commentResolved(nameOf2(subjectId), nodeName(d.nodeId));
238776
+ d.status = addressed ? ACTIVITY_COPY.commentAddressed(nameOf2(subjectId), nameOf2(authorId), nodeName(d.nodeId), versionOfWork(drivenWork.id)) : ACTIVITY_COPY.commentResolved(nameOf2(subjectId), nodeName(d.nodeId));
238503
238777
  };
238504
238778
  for (const rec2 of events) {
238505
238779
  const ev = rec2.event;
@@ -238626,7 +238900,7 @@ function buildWorkorderActivity(input) {
238626
238900
  const bypassed = reviewers.length > 0 && isSystemAccept(by) && !voted;
238627
238901
  if (isSystemAccept(by) && !bypassed) break;
238628
238902
  const existing = acceptCards.get(workId);
238629
- const status = bypassed ? node2?.isFinalOutput ? ACTIVITY_COPY.autoAccepted(nameOf2(reviewers[0])) : ACTIVITY_COPY.autoAcceptedNode(nodeName(nodeId), nameOf2(reviewers[0])) : node2?.isFinalOutput ? ACTIVITY_COPY.accepted : ACTIVITY_COPY.acceptedNode(nodeName(nodeId));
238903
+ const status = bypassed ? node2?.isFinalOutput ? ACTIVITY_COPY.autoAccepted(nameOf2(reviewers[0])) : ACTIVITY_COPY.autoAcceptedNode(nodeName(nodeId), nameOf2(reviewers[0])) : node2?.isFinalOutput ? ACTIVITY_COPY.accepted(versionOfWork(workId)) : ACTIVITY_COPY.acceptedNode(nodeName(nodeId), versionOfWork(workId));
238630
238904
  if (existing) {
238631
238905
  touch(existing, rec2);
238632
238906
  existing.executorId = bypassed ? reviewers[0] : by;
@@ -238877,11 +239151,17 @@ function buildWorkorderActivity(input) {
238877
239151
  const ids2 = comments.map((c) => c.id);
238878
239152
  const closures = ids2.map(closureOf);
238879
239153
  const works = [...new Set(ids2.flatMap((id) => worksOfComment.get(id) ?? []))];
239154
+ let updatedVersion;
238880
239155
  const outcomeOf = (id, cl) => {
238881
239156
  if (!cl.wontfix) {
238882
239157
  const driven = cl.viaWorkId ? workById.get(cl.viaWorkId) : void 0;
238883
239158
  const drivers = driven ? [driven] : [...(worksOfComment.get(id) ?? []).filter((w2) => w2.createdAt <= cl.at), ...closingRoundsOf(first.nodeId, cl)];
238884
- if (drivers.some(deliveredNewVersion)) return 5;
239159
+ if (drivers.some(deliveredNewVersion)) {
239160
+ for (const v2 of drivers.map((w2) => versionOfWork(w2.id))) {
239161
+ if (v2 !== void 0 && (updatedVersion === void 0 || v2 > updatedVersion)) updatedVersion = v2;
239162
+ }
239163
+ return 5;
239164
+ }
238885
239165
  }
238886
239166
  if (cl.by && cl.by === first.authorId && cl.by !== executorId) return 1;
238887
239167
  if (!cl.by) return 2;
@@ -238893,7 +239173,7 @@ function buildWorkorderActivity(input) {
238893
239173
  phase = "done";
238894
239174
  const best = Math.max(...ids2.map((id, i) => outcomeOf(id, closures[i])));
238895
239175
  const nodeRemoved = !!(first.nodeId && nodeById.get(first.nodeId)?.cancelledAt);
238896
- status = best === 5 ? ACTIVITY_COPY.commentUpdated(author, node2) : best === 4 ? ACTIVITY_COPY.commentReplied(author) : best === 3 ? ACTIVITY_COPY.commentViewed(author) : best === 1 ? ACTIVITY_COPY.commentWithdrawn(author) : input.workorderTerminal ? ACTIVITY_COPY.closedNoAction(input.workorderTerminal) : nodeRemoved ? ACTIVITY_COPY.commentNodeRemoved(node2) : ACTIVITY_COPY.commentViewed(author);
239176
+ status = best === 5 ? ACTIVITY_COPY.commentUpdated(author, node2, updatedVersion) : best === 4 ? ACTIVITY_COPY.commentReplied(author) : best === 3 ? ACTIVITY_COPY.commentViewed(author) : best === 1 ? ACTIVITY_COPY.commentWithdrawn(author) : input.workorderTerminal ? ACTIVITY_COPY.closedNoAction(input.workorderTerminal) : nodeRemoved ? ACTIVITY_COPY.commentNodeRemoved(node2) : ACTIVITY_COPY.commentViewed(author);
238897
239177
  } else if (works.some((w2) => workRunning(w2) && ids2.some((id, i) => !closures[i] && worksOfComment.get(id)?.includes(w2)))) {
238898
239178
  phase = "running";
238899
239179
  status = ACTIVITY_COPY.commentInProgress(author);
@@ -238929,7 +239209,17 @@ function buildWorkorderActivity(input) {
238929
239209
  return escalationKindOf(x2.id, row?.authorActorId ?? x2.authorId, row?.source ?? x2.source ?? null);
238930
239210
  };
238931
239211
  const kind = kindOf3(e);
238932
- const stallCopy = isStallEscalation(kind) ? stallCardCopyOf(issueById.get(e.id)?.body ?? "") : void 0;
239212
+ const stallCopy = isStallEscalation(kind) && e.nodeId ? (() => {
239213
+ const row = issueById.get(e.id);
239214
+ const selected = [...allRoundsOfNode.get(e.nodeId) ?? []].filter((w2) => w2.createdAt <= e.at).at(-1);
239215
+ return stallCardCopy(kind, row?.source ?? e.source, row?.body ?? "", {
239216
+ node: nodeName(e.nodeId),
239217
+ .../* @__PURE__ */ ((v2) => v2 !== void 0 ? { latestVersion: v2 } : {})(latestVersionAt(snap, versionOf2, e.nodeId, e.at)),
239218
+ .../* @__PURE__ */ ((v2) => v2 !== void 0 ? { selectedVersion: v2 } : {})(versionOfWork(selected?.id)),
239219
+ versionOfWork: (id) => versionOf2.get(id),
239220
+ nameOf: (id) => nameOf2(id)
239221
+ });
239222
+ })() : void 0;
238933
239223
  const resolvedAt = (id) => issueById.get(id)?.resolvedAt ?? null;
238934
239224
  const closedAt = resolvedAt(e.id);
238935
239225
  const open3 = !closedAt;
@@ -238945,8 +239235,8 @@ function buildWorkorderActivity(input) {
238945
239235
  const gapId = issueById.get(id)?.gapId;
238946
239236
  return typeof gapId === "string" && !!issueById.get(gapId);
238947
239237
  };
238948
- const head = kind === "retry_exhausted" ? ACTIVITY_COPY.escalationHeadRetryExhausted(nodeName(e.nodeId)) : kind === "review_exhausted" ? ACTIVITY_COPY.escalationHeadReviewExhausted(nodeName(e.nodeId)) : kind === "output_stall" ? ACTIVITY_COPY.escalationHeadOutputStall(nodeName(e.nodeId)) : stallCopy ? stallCopy.headline ?? ACTIVITY_COPY.escalationHeadStall(nodeName(e.nodeId)) : kind === "sla_breaker" ? ACTIVITY_COPY.escalationHeadSlaBreaker(nodeName(e.nodeId)) : kind === "quota_starvation" ? ACTIVITY_COPY.escalationHeadQuotaStarvation(nodeName(e.nodeId)) : kind === "reviewer_unavailable" ? ACTIVITY_COPY.escalationHeadReviewerUnavailable(nodeName(e.nodeId)) : kind === "gap_evals_exhausted" ? ACTIVITY_COPY.escalationHeadGapEvalsExhausted(nodeName(e.nodeId), gapEvalsOf(issueById.get(e.id)?.body ?? "")) : kind === "dispatch_missing_node" ? ACTIVITY_COPY.escalationHeadDispatchMissingNode(nodeName(e.nodeId)) : gapBehind(e.id) ? ACTIVITY_COPY.escalationHeadGapEscalated(nameOf2(e.authorId), nodeName(e.nodeId)) : ACTIVITY_COPY.escalationHeadManual(nameOf2(stuckActor));
238949
- const howTo = kind === "retry_exhausted" ? ACTIVITY_COPY.escalationHowToRetryExhausted : kind === "review_exhausted" ? ACTIVITY_COPY.escalationHowToReviewExhausted : kind === "output_stall" ? ACTIVITY_COPY.escalationHowToOutputStall : stallCopy ? stallCopy.action : kind === "sla_breaker" ? ACTIVITY_COPY.escalationHowToSlaBreaker : kind === "quota_starvation" ? ACTIVITY_COPY.escalationHowToQuotaStarvation : kind === "reviewer_unavailable" ? ACTIVITY_COPY.escalationHowToReviewerUnavailable : kind === "gap_evals_exhausted" ? ACTIVITY_COPY.escalationHowToGapEvalsExhausted : kind === "dispatch_missing_node" ? ACTIVITY_COPY.escalationHowToDispatchMissingNode : void 0;
239238
+ const head = kind === "retry_exhausted" ? ACTIVITY_COPY.escalationHeadRetryExhausted(nodeName(e.nodeId)) : kind === "review_exhausted" ? ACTIVITY_COPY.escalationHeadReviewExhausted(nodeName(e.nodeId)) : kind === "output_stall" ? ACTIVITY_COPY.escalationHeadOutputStall(nodeName(e.nodeId)) : isStallEscalation(kind) ? stallCopy?.headline ?? ACTIVITY_COPY.escalationHeadStall(nodeName(e.nodeId)) : kind === "sla_breaker" ? ACTIVITY_COPY.escalationHeadSlaBreaker(nodeName(e.nodeId)) : kind === "quota_starvation" ? ACTIVITY_COPY.escalationHeadQuotaStarvation(nodeName(e.nodeId)) : kind === "reviewer_unavailable" ? ACTIVITY_COPY.escalationHeadReviewerUnavailable(nodeName(e.nodeId)) : kind === "gap_evals_exhausted" ? ACTIVITY_COPY.escalationHeadGapEvalsExhausted(nodeName(e.nodeId), gapEvalsOf(issueById.get(e.id)?.body ?? "")) : kind === "dispatch_missing_node" ? ACTIVITY_COPY.escalationHeadDispatchMissingNode(nodeName(e.nodeId)) : gapBehind(e.id) ? ACTIVITY_COPY.escalationHeadGapEscalated(nameOf2(e.authorId), nodeName(e.nodeId)) : ACTIVITY_COPY.escalationHeadManual(nameOf2(stuckActor));
239239
+ const howTo = kind === "retry_exhausted" ? ACTIVITY_COPY.escalationHowToRetryExhausted : kind === "review_exhausted" ? ACTIVITY_COPY.escalationHowToReviewExhausted : kind === "output_stall" ? ACTIVITY_COPY.escalationHowToOutputStall : isStallEscalation(kind) ? stallCopy?.action : kind === "sla_breaker" ? ACTIVITY_COPY.escalationHowToSlaBreaker : kind === "quota_starvation" ? ACTIVITY_COPY.escalationHowToQuotaStarvation : kind === "reviewer_unavailable" ? ACTIVITY_COPY.escalationHowToReviewerUnavailable : kind === "gap_evals_exhausted" ? ACTIVITY_COPY.escalationHowToGapEvalsExhausted : kind === "dispatch_missing_node" ? ACTIVITY_COPY.escalationHowToDispatchMissingNode : void 0;
238950
239240
  const closing = issueById.get(e.id);
238951
239241
  const closeEvent = plainResolveEvents.get(e.id);
238952
239242
  const closedBy = text5(closing?.resolvedBy) ?? closeEvent?.by ?? target;
@@ -239021,7 +239311,7 @@ function buildWorkorderActivity(input) {
239021
239311
  const pending = f2.decision === "pending" && !f2.acceptedAt && !removed;
239022
239312
  const paused = f2.decision === "denied" && !f2.releasedAt && !f2.acceptedAt && !removed;
239023
239313
  const open3 = pending || paused;
239024
- const status = f2.acceptedAt ? ACTIVITY_COPY.continuationOutputAccepted(nameOf2(signer), name) : f2.resumedAt ? ACTIVITY_COPY.continuationResumed(nameOf2(signer), f2.resumeRounds ?? f2.rounds) : f2.decision === "approved" ? ACTIVITY_COPY.continuationApproved(nameOf2(signer), f2.rounds) : removed ? ACTIVITY_COPY.continuationNodeRemoved : f2.decision === "completed" ? ACTIVITY_COPY.continuationSettled(name) : paused ? ACTIVITY_COPY.continuationDenied(nameOf2(signer), f2.reason) : ACTIVITY_COPY.continuationPending(name, f2.batchRounds, nameOf2(signer));
239314
+ const status = f2.acceptedAt ? ACTIVITY_COPY.continuationOutputAccepted(nameOf2(signer), name, versionOfWork(f2.acceptedWorkId)) : f2.resumedAt ? ACTIVITY_COPY.continuationResumed(nameOf2(signer), f2.resumeRounds ?? f2.rounds) : f2.decision === "approved" ? ACTIVITY_COPY.continuationApproved(nameOf2(signer), f2.rounds) : removed ? ACTIVITY_COPY.continuationNodeRemoved(name) : f2.decision === "completed" ? ACTIVITY_COPY.continuationSettled(name) : paused ? ACTIVITY_COPY.continuationDenied(nameOf2(signer), f2.reason) : ACTIVITY_COPY.continuationPending(name, f2.batchRounds, nameOf2(signer));
239025
239315
  continuationCards.push({
239026
239316
  id: f2.id,
239027
239317
  seq: f2.seq,
@@ -239036,7 +239326,7 @@ function buildWorkorderActivity(input) {
239036
239326
  status,
239037
239327
  detail: ACTIVITY_COPY.continuationRounds(f2.usedRounds, f2.authorizedRounds, f2.rounds),
239038
239328
  artifacts: batchOutputOf.get(f2.id) ?? [],
239039
- actions: !viewerMayDecide ? [] : pending ? [decide2(f2, "approve", "\u540C\u610F\u7EE7\u7EED", "primary"), decide2(f2, "accept_output", "\u63A5\u53D7\u5F53\u524D\u6210\u679C", "secondary"), decide2(f2, "pause", "\u5148\u6682\u505C", "quiet")] : paused ? [decide2(f2, "resume", "\u6062\u590D\u7EED\u8DD1", "primary"), decide2(f2, "accept_output", "\u63A5\u53D7\u5F53\u524D\u6210\u679C", "secondary")] : []
239329
+ actions: !viewerMayDecide ? [] : pending ? [decide2(f2, "approve", "\u540C\u610F\u7EE7\u7EED", "primary"), decide2(f2, "accept_output", "\u63A5\u53D7\u5F53\u524D\u6210\u679C", "secondary"), decide2(f2, "pause", "\u5148\u4E0D\u7EE7\u7EED", "quiet")] : paused ? [decide2(f2, "resume", "\u7EE7\u7EED\u6267\u884C", "primary"), decide2(f2, "accept_output", "\u63A5\u53D7\u5F53\u524D\u6210\u679C", "secondary")] : []
239040
239330
  });
239041
239331
  }
239042
239332
  }
@@ -239050,7 +239340,7 @@ function buildWorkorderActivity(input) {
239050
239340
  const name = nodeName(nodeId);
239051
239341
  const resolvedAt = gap.resolvedAt ?? null;
239052
239342
  const inWindow2 = (at2) => !!at2 && at2 >= gap.createdAt && (!resolvedAt || at2 <= resolvedAt);
239053
- const escalated = snap.issues.find((i) => i.kind === "escalation" && escalationKindOf(i.id, i.authorActorId, i.source) === "manual" && (i.gapId === gapId || (i.aboutNodeId ?? i.raisedByNodeId) === nodeId && inWindow2(i.createdAt)));
239343
+ const escalated = snap.issues.find((i) => i.kind === "escalation" && /* @__PURE__ */ ((k2) => k2 === "manual" || k2 === "gap_evals_exhausted")(escalationKindOf(i.id, i.authorActorId, i.source)) && (i.gapId === gapId || (i.aboutNodeId ?? i.raisedByNodeId) === nodeId && inWindow2(i.createdAt)));
239054
239344
  const run = coordinatorRunOfGap(
239055
239345
  { createdAt: gap.createdAt, resolvedAt },
239056
239346
  (input.coordinatorRuns ?? []).filter((r) => !!r.id && r.nodeId === nodeId)
@@ -239147,7 +239437,7 @@ function buildWorkorderActivity(input) {
239147
239437
  nodeId: w2.nodeId,
239148
239438
  executorId: acceptor,
239149
239439
  phase: "not_started",
239150
- status: ACTIVITY_COPY.acceptPending,
239440
+ status: ACTIVITY_COPY.acceptPending(versionOfWork(w2.id)),
239151
239441
  artifacts: artifactsByWork.get(w2.id) ?? [],
239152
239442
  actions: [
239153
239443
  { kind: "accept", label: BUTTON.acceptPass, target: node2.id, revisionId: w2.id, verdict: "approve" },
@@ -239169,7 +239459,7 @@ function buildWorkorderActivity(input) {
239169
239459
  reviewCards.set(cardId, group);
239170
239460
  }
239171
239461
  for (const [cardId, { workId, reviewerId, reviews }] of reviewCards) {
239172
- const summary = reviewSummary(snap, reviews, events);
239462
+ const summary = reviewSummary(snap, reviews, events, versionOfWork(workId));
239173
239463
  const first = reviews[0];
239174
239464
  const ids2 = new Set(reviews.map((r) => r.id));
239175
239465
  const created = events.filter((r) => r.event.kind === "review.create" && ids2.has(r.event.reviewId));
@@ -239184,7 +239474,7 @@ function buildWorkorderActivity(input) {
239184
239474
  return [{
239185
239475
  id: r.id,
239186
239476
  // 同一个人担多个评审组时才标组号——单组标了纯属噪声(卡上本来就只有这一个人)。
239187
- label: groups.length > 1 ? `\u8BC4\u5BA1\u7EC4 ${groups.indexOf(r.reviewGroup) + 1} \xB7 ${label2}` : label2,
239477
+ label: groups.length > 1 ? `\u5BA1\u6838\u7EC4 ${groups.indexOf(r.reviewGroup) + 1} \xB7 ${label2}` : label2,
239188
239478
  text: text7,
239189
239479
  tone: rejected ? "reject" : "note"
239190
239480
  }];
@@ -239207,15 +239497,17 @@ function buildWorkorderActivity(input) {
239207
239497
  /* 同一人担多个责任组时,展开区留一份**逐组结论**(只有判定词、没有意见正文)——小状态只有
239208
239498
  一句总判定,说不清哪个组过了哪个组没过。意见正文一律走下面的意见条。 */
239209
239499
  ...summary.latest.length > 1 ? {
239210
- detail: summary.latest.map((r) => `${nameOf2(r.reviewerActorId)}\uFF08\u8BC4\u5BA1\u7EC4 ${groups.indexOf(r.reviewGroup) + 1}\uFF09\uFF1A${reviewLabel(r)}`).join("\n")
239500
+ detail: summary.latest.map((r) => `${nameOf2(r.reviewerActorId)}\uFF08\u5BA1\u6838\u7EC4 ${groups.indexOf(r.reviewGroup) + 1}\uFF09\uFF1A${reviewLabel(r)}`).join("\n")
239211
239501
  } : {},
239212
239502
  ...reviewNotes.length ? { reviewNotes } : {},
239213
239503
  ...reviewLine ? { reviewThreadSessionId: reviewLine } : {},
239214
239504
  artifacts: artifactsByWork.get(workId) ?? [],
239505
+ /* 有审核线(有意见条)且没在等表态 ⇒ 给一颗「查看沟通」进审核线(奕璇 2026-09-29 审定清单第一节):
239506
+ 意见条改成卡内「展开 / 收起」之后,去审核沟通页的入口从意见条右下角的「展开 ›」挪到这颗按钮上,落点不变。 */
239215
239507
  actions: pending.length ? [
239216
239508
  { kind: "review", label: BUTTON.approve, target: first.nodeId, revisionId: workId, verdict: "approve" },
239217
239509
  { kind: "review", label: BUTTON.reject, target: first.nodeId, revisionId: workId, verdict: "request_changes" }
239218
- ] : []
239510
+ ] : reviewLine ? [{ kind: "open_thread", label: BUTTON.view, target: reviewLine, variant: "quiet" }] : []
239219
239511
  });
239220
239512
  }
239221
239513
  const openedSeq = events.find((rec2) => rec2.event.kind === "work.create")?.seq;
@@ -239337,6 +239629,8 @@ var init_activity2 = __esm({
239337
239629
  init_review_requirement_activity();
239338
239630
  init_workorder_manager();
239339
239631
  init_escalation_recipient();
239632
+ init_activity_version();
239633
+ init_stall_copy();
239340
239634
  ACTIVITY_EVENT_KINDS = [
239341
239635
  "plan.changed",
239342
239636
  "plan.update_review_requirements",
@@ -239382,9 +239676,9 @@ var init_activity2 = __esm({
239382
239676
  * 别复用 `goalChanged`(「{X} 修改了任务目标。」)——那句锚在 `workorder.meta_changed` 上,
239383
239677
  * 说的是「改了工单 meta 上的 goal 字段」,跟「brief 正文重新收口」不是同一动作。
239384
239678
  */
239385
- goalRevised: "\u5DF2\u4FEE\u8BA2\u76EE\u6807\uFF0C\u4EFB\u52A1\u5C06\u6309\u76EE\u6807\u5F00\u59CB\u6267\u884C\u3002",
239679
+ goalRevised: "\u76EE\u6807\u5DF2\u4FEE\u8BA2\uFF0C\u4EFB\u52A1\u6309\u65B0\u76EE\u6807\u5F00\u59CB\u6267\u884C\u3002",
239386
239680
  working: (node2) => `\u6B63\u5728\u5904\u7406\u300A${node2}\u300B\u3002`,
239387
- reworking: (node2) => `\u6B63\u5728\u6839\u636E\u5BA1\u6838\u4E0D\u901A\u8FC7\u539F\u56E0\u4FEE\u6539\u300A${node2}\u300B\u3002`,
239681
+ reworking: (node2) => `\u6B63\u5728\u6839\u636E\u5BA1\u6838\u610F\u89C1\u4FEE\u6539\u300A${node2}\u300B\u3002`,
239388
239682
  specChanged: (node2) => `\u6B63\u5728\u6839\u636E\u4EFB\u52A1\u8BF4\u660E\u7684\u66F4\u65B0\u8C03\u6574\u300A${node2}\u300B\u3002`,
239389
239683
  issueDriven: (node2) => `\u6B63\u5728\u6839\u636E\u53CD\u9988\u8C03\u6574\u300A${node2}\u300B\u3002`,
239390
239684
  /* `gapReworked`(「缺口已解决,{执行人} 带着答复重新开工《X》。」)**2026-09-21 删掉**:
@@ -239406,7 +239700,7 @@ var init_activity2 = __esm({
239406
239700
  也是引擎做的(`activate.ts` 的 `tryToRun`)——管理者根本不知道这两件事,此前却写成
239407
239701
  「{管理者}已主动介入」「{管理者}正在重试」,把系统干的事指名道姓按到人头上。
239408
239702
  「后来谁介入了」由 `plan.node_retry` 那张卡说,那里的主语是**真按下重试的人**,说的是事实。 */
239409
- timedOut: (executor) => `${executor} \u8FD0\u884C\u65F6\u8D85\u65F6\u3002`,
239703
+ timedOut: (executor) => `${executor} \u8FD0\u884C\u8D85\u65F6\u3002`,
239410
239704
  failed: (executor) => `${executor} \u6267\u884C\u5931\u8D25\u3002`,
239411
239705
  /**
239412
239706
  * **零产出轮**:会话正常退出,一件东西都没交(引擎判卷不是 success)。与 {@link failed} 分开说——
@@ -239414,8 +239708,8 @@ var init_activity2 = __esm({
239414
239708
  */
239415
239709
  endedWithoutOutput: (executor, node2) => `${executor} \u672C\u8F6E\u7ED3\u675F\uFF0C\u6CA1\u6709\u4EA4\u51FA\u300A${node2}\u300B\u7684\u4EFB\u4F55\u6210\u679C\u3002`,
239416
239710
  /** 人点了重试——只在 `plan.node_retry` 用,`by` 是真按下重试的那个人。 */
239417
- retriedAfterTimeout: (executor, by) => `${executor} \u8FD0\u884C\u65F6\u8D85\u65F6\uFF0C${by}\u5DF2\u4ECB\u5165\u91CD\u8BD5\u3002`,
239418
- retriedAfterFailure: (executor, by) => `${executor} \u6267\u884C\u5931\u8D25\uFF0C${by}\u5DF2\u4ECB\u5165\u91CD\u8BD5\u3002`,
239711
+ retriedAfterTimeout: (executor, by) => `${executor} \u8FD0\u884C\u8D85\u65F6\uFF0C${by} \u5DF2\u91CD\u65B0\u8FD0\u884C\u3002`,
239712
+ retriedAfterFailure: (executor, by) => `${executor} \u6267\u884C\u5931\u8D25\uFF0C${by} \u5DF2\u91CD\u65B0\u8FD0\u884C\u3002`,
239419
239713
  resumed: (executor, node2) => `${executor} \u5DF2\u6062\u590D\uFF0C\u7EE7\u7EED\u5904\u7406\u300A${node2}\u300B\u3002`,
239420
239714
  killed: (node2) => `\u300A${node2}\u300B\u672C\u8F6E\u6267\u884C\u5DF2\u7EC8\u6B62\u3002`,
239421
239715
  /**
@@ -239445,7 +239739,7 @@ var init_activity2 = __esm({
239445
239739
  /* `foldedFailuresRetried`(「{执行人} 连续 N 次没跑成《X》,{谁}已介入重试。」)**2026-09-22 删掉**
239446
239740
  (Bug 0209 / 用例 T-117):人介入现在是折叠的**断点**,断点前那张卡就此定稿,不该再被一句
239447
239741
  「已介入重试」回填;「又跑了」由断点之后另起的那张卡说,「谁重新运行了这一步」由升级卡说。 */
239448
- interruptedByIssue: (node2) => `\u300A${node2}\u300B\u672C\u8F6E\u6267\u884C\u56E0\u5361\u70B9\u4E2D\u65AD\u3002`,
239742
+ interruptedByIssue: (node2) => `\u300A${node2}\u300B\u672C\u8F6E\u6267\u884C\u9047\u5230\u95EE\u9898\uFF0C\u5DF2\u4E2D\u65AD\u3002`,
239449
239743
  /**
239450
239744
  * 同上,但这一轮是**被自己报的缺口**打断的:把「接下来等谁」并进结局句(F4,缺口卡删掉之后
239451
239745
  * 「该找谁」只剩这一个出口)。红卡三问里的「等谁」由这半句回答。
@@ -239455,7 +239749,7 @@ var init_activity2 = __esm({
239455
239749
  * 卡上。此前这里写过「已由 {协调者} 补齐缺口」——缺口也可能补不上、最后升级给人,那就是一句
239456
239750
  * 卡面兑现不了的断言(同「未经 X 确认」那一族)。
239457
239751
  */
239458
- interruptedByGap: (node2, helper) => `\u300A${node2}\u300B\u672C\u8F6E\u6267\u884C\u56E0\u5361\u70B9\u4E2D\u65AD\uFF0C\u7B49 ${helper} \u5904\u7406\u7F3A\u53E3\u3002`,
239752
+ interruptedByGap: (node2, helper) => `\u300A${node2}\u300B\u672C\u8F6E\u6267\u884C\u7F3A\u5C11\u5FC5\u8981\u4FE1\u606F\uFF0C\u5DF2\u53D1\u8D77\u6C42\u52A9\uFF0C\u7B49 ${helper} \u5904\u7406\u3002`,
239459
239753
  /* ── 两种系统止损,文案分开写(F6,奕璇 2026-09-21 拍板)──────────────────────────────
239460
239754
  改完之后两种止损的**执行者是一样的**(执行卡挂执行人、升级卡挂等处理的那个人),
239461
239755
  能不能分辨全落在这几句话上。投影侧怎么判是哪一种见 {@link escalationKindOf}。
@@ -239464,14 +239758,14 @@ var init_activity2 = __esm({
239464
239758
  上报正文是唯一事实源(奕璇 2026-09-22 定)。两头的口径都已经是**轮**:引擎那条本来就数
239465
239759
  work,保险丝那条 Bug 0211 起把同一轮里的协议续唤 run 折回去再数(老正文不回填,见
239466
239760
  {@link parseStopLossFacts})。 */
239467
- retryExhausted: (executor, node2, n, lastReason) => `${executor} \u8FDE\u7EED ${n} \u6B21\u6CA1\u8DD1\u6210\u300A${node2}\u300B\uFF0C\u91CD\u8BD5\u5DF2\u7528\u5C3D${lastReason ? `\uFF08\u672B\u6B21\uFF1A${lastReason}\uFF09` : ""}\u3002`,
239468
- outputStalled: (executor, node2, n, cost) => `${executor} \u8FDE\u7EED ${n} \u6B21\u6CA1\u4EA4\u51FA\u300A${node2}\u300B\u7684\u4EFB\u4F55\u6210\u679C${cost ? `\uFF08\u7EA6 ${cost}\uFF09` : ""}\uFF0C\u7CFB\u7EDF\u5DF2\u6B62\u635F\u3002`,
239761
+ retryExhausted: (executor, node2, n, lastReason) => `${executor} \u8FDE\u7EED ${n} \u6B21\u6CA1\u8DD1\u6210\u300A${node2}\u300B\uFF0C\u81EA\u52A8\u91CD\u8BD5\u6B21\u6570\u5DF2\u7528\u5B8C${lastReason ? `\uFF08\u6700\u540E\u4E00\u6B21\u62A5\u9519\uFF1A${lastReason}\uFF09` : ""}\u3002`,
239762
+ outputStalled: (executor, node2, n, cost) => `${executor} \u8FDE\u7EED ${n} \u6B21\u6CA1\u4EA4\u51FA\u300A${node2}\u300B\u7684\u4EFB\u4F55\u6210\u679C${cost ? `\uFF08\u7EA6 ${cost}\uFF09` : ""}\uFF0C\u7CFB\u7EDF\u5DF2\u6682\u505C\u8FD9\u4E00\u6B65\u3002`,
239469
239763
  /* ── 升级卡(上报)的整句 ───────────────────────────────────────────────────────────
239470
239764
  卡面只有一处小状态,所以「谁卡住了 / 等谁 / 你要做什么」三问连写成一句。
239471
239765
  开头那半句(`escalationHead*`)三族各一份,收口时也复用它——同一条上报前后两拍说的是
239472
239766
  同一件事,只换后半句(「等 @X 处理」→「X 已处理:…」)。 */
239473
- escalationHeadRetryExhausted: (node2) => `\u300A${node2}\u300B\u91CD\u8BD5\u5DF2\u7528\u5C3D\uFF0C`,
239474
- escalationHeadOutputStall: (node2) => `\u7CFB\u7EDF\u6B62\u635F\uFF1A\u300A${node2}\u300B\u505C\u4F4F\u4E86\uFF0C`,
239767
+ escalationHeadRetryExhausted: (node2) => `\u300A${node2}\u300B\u81EA\u52A8\u91CD\u8BD5\u6B21\u6570\u5DF2\u7528\u5B8C\uFF0C`,
239768
+ escalationHeadOutputStall: (node2) => `\u300A${node2}\u300B\u8FDE\u7EED\u591A\u6B21\u6CA1\u6709\u4EA4\u51FA\u6210\u679C\uFF0C\u7CFB\u7EDF\u5DF2\u6682\u505C\u8FD9\u4E00\u6B65\uFF0C`,
239475
239769
  /**
239476
239770
  * 评审连败熔断(Bug 0212):卡住的是**这一轮评审**,不是那一版活——话不能写成「执行异常」。
239477
239771
  *
@@ -239481,26 +239775,26 @@ var init_activity2 = __esm({
239481
239775
  * 这批行根本取不到。硬拼一句取不到值的模板只会永远不出现,不如不写。
239482
239776
  * 要补这半句,得先让评审派发把 `work_order_id` / `node_id` 记进 `agent_runs`(另开单)。
239483
239777
  */
239484
- escalationHeadReviewExhausted: (node2) => `\u300A${node2}\u300B\u7684\u8BC4\u5BA1\u8FDE\u7740\u6CA1\u8DD1\u6210\uFF0C\u8FD9\u4E00\u8F6E\u6536\u4E0D\u9F50\uFF0C`,
239778
+ escalationHeadReviewExhausted: (node2) => `\u300A${node2}\u300B\u7684\u5BA1\u6838\u8FDE\u7EED\u51E0\u6B21\u6CA1\u8DD1\u6210\uFF0C\u8FD9\u4E00\u8F6E\u5BA1\u6838\u5B8C\u6210\u4E0D\u4E86\uFF0C`,
239485
239779
  /** 人工上报里**关联着缺口**的那一档:协调者接了缺口、补不了,于是升级给人(四种停法的第二种)。 */
239486
- escalationHeadGapEscalated: (coordinator, node2) => `${coordinator} \u8865\u4E0D\u4E86\u300A${node2}\u300B\u7684\u7F3A\u53E3\uFF0C`,
239780
+ escalationHeadGapEscalated: (coordinator, node2) => `${coordinator} \u89E3\u51B3\u4E0D\u4E86\u300A${node2}\u300B\u7684\u6C42\u52A9\uFF0C`,
239487
239781
  /* ── 系统巡检那几路(Bug 0274):以前全被当成零产出保险丝、套「系统止损…空烧」那两句。
239488
- 巡检卡的标题与处理说明取正文里它自己那两段({@link stallCardCopyOf}),下面这句只在正文取不到时兜底。 */
239489
- escalationHeadStall: (node2) => `\u5DE1\u68C0\u53D1\u73B0\u300A${node2}\u300B\u505C\u4F4F\u4E86\uFF0C`,
239782
+ 巡检卡的标题与处理说明按类型现拼(`stall-copy.ts` 的 `stallCardCopy`),下面这句只在卡不挂节点时兜底。 */
239783
+ escalationHeadStall: (node2) => `\u7CFB\u7EDF\u53D1\u73B0\u300A${node2}\u300B\u505C\u4F4F\u4E86\uFF0C`,
239490
239784
  /** SLA 熔断:自动处理(或收尾审核)反复失败,系统已冻住派发(`escalateHold`)。 */
239491
- escalationHeadSlaBreaker: (node2) => `\u300A${node2}\u300B\u7684\u81EA\u52A8\u5904\u7406\u53CD\u590D\u5931\u8D25\uFF0C\u5DF2\u6682\u505C\u6D3E\u53D1\uFF0C`,
239785
+ escalationHeadSlaBreaker: (node2) => `\u300A${node2}\u300B\u53CD\u590D\u8FD0\u884C\u5931\u8D25\uFF0C\u7CFB\u7EDF\u5DF2\u6682\u505C\u8FD9\u4E00\u6B65\uFF0C`,
239492
239786
  /** 配额饥饿:运行时额度长期被限流,系统已冻住派发。 */
239493
- escalationHeadQuotaStarvation: (node2) => `\u300A${node2}\u300B\u56E0\u4E3A\u989D\u5EA6\u4E0D\u591F\u88AB\u9650\u6D41\u3001\u957F\u65F6\u95F4\u6CA1\u6062\u590D\uFF0C\u5DF2\u6682\u505C\u6D3E\u53D1\uFF0C`,
239787
+ escalationHeadQuotaStarvation: (node2) => `\u300A${node2}\u300B\u56E0\u4E3A\u989D\u5EA6\u4E0D\u591F\u4E00\u76F4\u8DD1\u4E0D\u8D77\u6765\uFF0C\u7CFB\u7EDF\u5DF2\u6682\u505C\u8FD9\u4E00\u6B65\uFF0C`,
239494
239788
  /* ── Bug 0275 的三路系统上报。🚨 都**不写「这一步会自动重试」**:关上报只会复活 latest_work 已死的节点
239495
239789
  (engine `issue.resolve` → `markNodeForRetry`),这三种停法里活多半没死,那句话兑现不了。 */
239496
239790
  /** 锁定审核人不可用(熔断 / 离岗 / 被移出角色):评审派不出去,要人改派。 */
239497
- escalationHeadReviewerUnavailable: (node2) => `\u300A${node2}\u300B\u9501\u5B9A\u7684\u5BA1\u6838\u4EBA\u73B0\u5728\u4E0D\u53EF\u7528\uFF0C\u8BC4\u5BA1\u6D3E\u4E0D\u51FA\u53BB\uFF0C`,
239791
+ escalationHeadReviewerUnavailable: (node2) => `\u300A${node2}\u300B\u6307\u5B9A\u7684\u5BA1\u6838\u4EBA\u73B0\u5728\u65E0\u6CD5\u5BA1\u6838\uFF0C`,
239498
239792
  /** 协调者对缺口评估到上限仍推进不了(系统兜底)。`n` 取正文里的评估次数,取不到就不说数。 */
239499
- escalationHeadGapEvalsExhausted: (node2, n) => `\u81EA\u52A8\u5206\u8BCA${n !== void 0 ? `\u8BC4\u4F30\u4E86 ${n} \u6B21` : "\u53CD\u590D\u8BC4\u4F30"}\u4ECD\u8865\u4E0D\u4E86\u300A${node2}\u300B\u7684\u7F3A\u53E3\uFF0C`,
239793
+ escalationHeadGapEvalsExhausted: (node2, n) => n !== void 0 ? `\u7CFB\u7EDF\u5C1D\u8BD5\u4E86 ${n} \u6B21\u4ECD\u89E3\u51B3\u4E0D\u4E86\u300A${node2}\u300B\u7684\u6C42\u52A9\uFF0C` : `\u7CFB\u7EDF\u591A\u6B21\u5C1D\u8BD5\u4ECD\u89E3\u51B3\u4E0D\u4E86\u300A${node2}\u300B\u7684\u6C42\u52A9\uFF0C`,
239500
239794
  /** 执行人的运行时绑定指向已删除 / 未注册的节点:这一步根本没开跑。 */
239501
- escalationHeadDispatchMissingNode: (node2) => `\u300A${node2}\u300B\u8FD8\u6CA1\u6709\u542F\u52A8\uFF1A\u6267\u884C\u4EBA\u7ED1\u5B9A\u7684\u8FD0\u884C\u8282\u70B9\u4E0D\u5B58\u5728\uFF0C`,
239795
+ escalationHeadDispatchMissingNode: (node2) => `\u300A${node2}\u300B\u8FD8\u6CA1\u6709\u5F00\u59CB\uFF1A\u6267\u884C\u4EBA\u7ED1\u5B9A\u7684\u673A\u5668\u4E0D\u5B58\u5728\uFF0C`,
239502
239796
  /** 其余人工上报:主语是卡住的那个执行人(异常是它的,不是上报作者的)。 */
239503
- escalationHeadManual: (stuckActor) => `${stuckActor} \u7684\u6267\u884C\u5F02\u5E38\u6301\u7EED\u672A\u89E3\u51B3\uFF0C`,
239797
+ escalationHeadManual: (stuckActor) => `${stuckActor} \u9047\u5230\u7684\u95EE\u9898\u4E00\u76F4\u6CA1\u6709\u89E3\u51B3\uFF0C`,
239504
239798
  /** 同一节点反复跳闸**并进同一张卡计数**,不每次新开一张(F6)。`n` ≥ 2 才拼这半句。 */
239505
239799
  stopLossAgain: (sentence, n) => `${sentence.replace(/。$/, "")}\uFF08\u7B2C ${n} \u6B21\uFF09\u3002`,
239506
239800
  /* 升级卡的整句 = 标题行 + 小状态(卡面只有一处小状态可用,两句连写)。
@@ -239510,15 +239804,15 @@ var init_activity2 = __esm({
239510
239804
  「已暂停」只有一个来源——整张工单被人点了暂停任务。两种止损都只停掉当前那一轮。 */
239511
239805
  escalationPendingWith: (head, target, howTo) => `${head}\u7B49 @${target} \u5904\u7406\u3002${howTo ?? ""}`,
239512
239806
  /** 「你要做什么」那半句——两种系统止损各一份(F6 的立论就在这里:执行者一样,只有话不一样)。 */
239513
- escalationHowToRetryExhausted: "\u5904\u7406\u5E76\u5173\u6389\u8FD9\u6761\u4E0A\u62A5\u540E\uFF0C\u8FD9\u4E00\u6B65\u4F1A\u81EA\u52A8\u91CD\u8BD5\uFF1B\u591A\u534A\u662F\u989D\u5EA6 / \u767B\u5F55\u8FC7\u671F / \u9650\u6D41\u8FD9\u7C7B\u73AF\u5883\u95EE\u9898\uFF0C\u5148\u770B\u8F68\u8FF9\u518D\u51B3\u5B9A\u91CD\u8BD5\u3002",
239514
- escalationHowToOutputStall: "\u5904\u7406\u5E76\u5173\u6389\u8FD9\u6761\u4E0A\u62A5\u540E\uFF0C\u8FD9\u4E00\u6B65\u4F1A\u81EA\u52A8\u91CD\u8BD5\uFF1B\u4E0D\u67E5\u6E05\u539F\u56E0\u5C31\u91CD\u8BD5\u4F1A\u7EE7\u7EED\u7A7A\u70E7\uFF0C\u5148\u8FDB\u8BA8\u8BBA\u623F\u3002",
239807
+ escalationHowToRetryExhausted: "\u591A\u534A\u662F\u989D\u5EA6\u4E0D\u591F\u3001\u767B\u5F55\u8FC7\u671F\u8FD9\u7C7B\u73AF\u5883\u95EE\u9898\uFF0C\u5148\u67E5\u770B\u6267\u884C\u8FC7\u7A0B\u786E\u8BA4\u539F\u56E0\u3002\u5904\u7406\u5B8C\u5173\u95ED\u8FD9\u6761\u5F85\u529E\uFF0C\u8FD9\u4E00\u6B65\u4F1A\u81EA\u52A8\u91CD\u8BD5\u3002",
239808
+ escalationHowToOutputStall: "\u5148\u70B9\u300C\u53BB\u5904\u7406\u300D\u67E5\u6E05\u539F\u56E0\uFF0C\u5426\u5219\u91CD\u8BD5\u8FD8\u662F\u4EA4\u4E0D\u51FA\u6210\u679C\u3002\u5904\u7406\u5B8C\u5173\u95ED\u8FD9\u6761\u5F85\u529E\uFF0C\u8FD9\u4E00\u6B65\u4F1A\u81EA\u52A8\u91CD\u8BD5\u3002",
239515
239809
  /** 评审熔断那一份:关掉之后重开的是**评审**,不是这一步的活(活已经交了,等的是审)。 */
239516
- escalationHowToReviewExhausted: "\u5904\u7406\u5E76\u5173\u6389\u8FD9\u6761\u4E0A\u62A5\u540E\uFF0C\u8FD9\u4E00\u8F6E\u8BC4\u5BA1\u4F1A\u81EA\u52A8\u91CD\u5F00\uFF1B\u591A\u534A\u662F\u8BC4\u5BA1\u5458\u7684\u8FD0\u884C\u73AF\u5883\u51FA\u4E86\u95EE\u9898\uFF08\u989D\u5EA6 / \u767B\u5F55\u8FC7\u671F / \u9650\u6D41\uFF09\uFF0C\u5148\u770B\u8F68\u8FF9\u518D\u51B3\u5B9A\u3002",
239517
- escalationHowToSlaBreaker: "\u5904\u7406\u5E76\u5173\u6389\u8FD9\u6761\u4E0A\u62A5\u540E\uFF0C\u8FD9\u4E00\u6B65\u4F1A\u81EA\u52A8\u91CD\u8BD5\uFF1B\u591A\u534A\u662F\u5E72\u6D3B\u7684\u673A\u5668\u8FDE\u4E0D\u4E0A\u3001\u5361\u4F4F\u6216\u7F3A\u914D\u7F6E\uFF0C\u5148\u8FDB\u8BA8\u8BBA\u623F\u770B\u6E05\u539F\u56E0\u3002",
239518
- escalationHowToQuotaStarvation: "\u5904\u7406\u5E76\u5173\u6389\u8FD9\u6761\u4E0A\u62A5\u540E\uFF0C\u8FD9\u4E00\u6B65\u4F1A\u81EA\u52A8\u91CD\u8BD5\uFF1B\u5148\u52A0\u989D\u5EA6\u3001\u6362\u8D26\u53F7\u6216\u51CF\u5C11\u540C\u65F6\u5728\u8DD1\u7684\u4EFB\u52A1\uFF0C\u5426\u5219\u91CD\u8BD5\u8FD8\u4F1A\u88AB\u9650\u6D41\u3002",
239519
- escalationHowToReviewerUnavailable: "\u9700\u8981\u4F60\u5B9A\u4E00\u4E0B\uFF1A\u6362\u4E00\u4E2A\u5BA1\u6838\u4EBA\uFF0C\u8FD8\u662F\u7B49\u539F\u5BA1\u6838\u4EBA\u6062\u590D\uFF1B\u5904\u7406\u5B8C\u5173\u6389\u8FD9\u6761\u4E0A\u62A5\uFF0C\u8FD9\u4E00\u6B65\u624D\u4F1A\u7EE7\u7EED\u3002",
239520
- escalationHowToGapEvalsExhausted: "\u9700\u8981\u4F60\u62FF\u4E2A\u4E3B\u610F\uFF1A\u8865\u4E0A\u7F3A\u7684\u4FE1\u606F\u6216\u6539\u4E00\u4E0B\u8FD9\u4E00\u6B65\u7684\u8BF4\u660E\uFF1B\u7F3A\u53E3\u89E3\u6389\u540E\u8FD9\u6761\u4E0A\u62A5\u4F1A\u4E00\u8D77\u5173\u6389\uFF0C\u8FD9\u4E00\u6B65\u968F\u5373\u7EE7\u7EED\u3002",
239521
- escalationHowToDispatchMissingNode: "\u6062\u590D\u90A3\u53F0\u8282\u70B9\uFF0C\u6216\u7ED9\u6267\u884C\u4EBA\u6539\u7ED1\u4E00\u53F0\u5728\u7EBF\u7684\u8282\u70B9\uFF1B\u4FEE\u597D\u540E\u5173\u6389\u8FD9\u6761\u4E0A\u62A5\uFF0C\u8FD9\u4E00\u6B65\u624D\u4F1A\u6D3E\u53D1\u3002",
239810
+ escalationHowToReviewExhausted: "\u591A\u534A\u662F\u5BA1\u6838\u4EBA\u7684\u8FD0\u884C\u73AF\u5883\u51FA\u4E86\u95EE\u9898\uFF08\u989D\u5EA6\u4E0D\u591F\u3001\u767B\u5F55\u8FC7\u671F\uFF09\uFF0C\u5148\u67E5\u770B\u6267\u884C\u8FC7\u7A0B\u786E\u8BA4\u539F\u56E0\u3002\u5904\u7406\u5B8C\u5173\u95ED\u8FD9\u6761\u5F85\u529E\uFF0C\u8FD9\u4E00\u8F6E\u5BA1\u6838\u4F1A\u81EA\u52A8\u91CD\u65B0\u5F00\u59CB\u3002",
239811
+ escalationHowToSlaBreaker: "\u591A\u534A\u662F\u6267\u884C\u7684\u673A\u5668\u8FDE\u4E0D\u4E0A\u3001\u5361\u4F4F\u6216\u7F3A\u5C11\u914D\u7F6E\uFF0C\u5148\u70B9\u300C\u53BB\u5904\u7406\u300D\u770B\u6E05\u539F\u56E0\u3002\u5904\u7406\u5B8C\u5173\u95ED\u8FD9\u6761\u5F85\u529E\uFF0C\u8FD9\u4E00\u6B65\u4F1A\u81EA\u52A8\u91CD\u8BD5\u3002",
239812
+ escalationHowToQuotaStarvation: "\u5148\u52A0\u989D\u5EA6\u3001\u6362\u8D26\u53F7\uFF0C\u6216\u51CF\u5C11\u540C\u65F6\u5728\u8DD1\u7684\u4EFB\u52A1\uFF0C\u5426\u5219\u91CD\u8BD5\u8FD8\u662F\u8DD1\u4E0D\u8D77\u6765\u3002\u5904\u7406\u5B8C\u5173\u95ED\u8FD9\u6761\u5F85\u529E\uFF0C\u8FD9\u4E00\u6B65\u4F1A\u81EA\u52A8\u91CD\u8BD5\u3002",
239813
+ escalationHowToReviewerUnavailable: "\u9700\u8981\u4F60\u51B3\u5B9A\uFF1A\u6362\u4E00\u4E2A\u5BA1\u6838\u4EBA\uFF0C\u8FD8\u662F\u7B49\u539F\u5BA1\u6838\u4EBA\u6062\u590D\u3002\u5904\u7406\u5B8C\u5173\u95ED\u8FD9\u6761\u5F85\u529E\uFF0C\u8FD9\u4E00\u6B65\u624D\u4F1A\u7EE7\u7EED\u3002",
239814
+ escalationHowToGapEvalsExhausted: "\u9700\u8981\u4F60\u51B3\u5B9A\uFF1A\u8865\u4E0A\u7F3A\u5C11\u7684\u4FE1\u606F\uFF0C\u6216\u4FEE\u6539\u8FD9\u4E00\u6B65\u7684\u8BF4\u660E\u3002\u6C42\u52A9\u89E3\u51B3\u540E\u8FD9\u6761\u5F85\u529E\u4F1A\u81EA\u52A8\u5173\u95ED\uFF0C\u8FD9\u4E00\u6B65\u63A5\u7740\u505A\u3002",
239815
+ escalationHowToDispatchMissingNode: "\u6062\u590D\u90A3\u53F0\u673A\u5668\uFF0C\u6216\u7ED9\u6267\u884C\u4EBA\u6362\u4E00\u53F0\u5728\u7EBF\u7684\u673A\u5668\u3002\u4FEE\u597D\u540E\u5173\u95ED\u8FD9\u6761\u5F85\u529E\uFF0C\u8FD9\u4E00\u6B65\u624D\u4F1A\u5F00\u59CB\u3002",
239522
239816
  /** 关掉了:接着开头那半句说「谁处理的、怎么处理的」。没写处置说明就只说「已处理」。
239523
239817
  * 处置说明常常是人自己写的一整段、末尾自带句号——**别再补一个**,否则卡面收在「。。」上。 */
239524
239818
  escalationHandled: (head, by, note) => `${head}${by} \u5DF2\u5904\u7406${note ? `\uFF1A${note}` : ""}${note && /[。!?.!?]$/.test(note) ? "" : "\u3002"}`,
@@ -239526,14 +239820,14 @@ var init_activity2 = __esm({
239526
239820
  * 从升级卡上点「重新运行」关掉的那一档(F7):这一步当场重开,所以不说「已处理:{说明}」
239527
239821
  * ——人并没有写说明,说明是这颗按钮替他写的。次数是**这个节点上第几次这么重试**。
239528
239822
  */
239529
- escalationRerun: (by, n) => `${by} \u91CD\u65B0\u8FD0\u884C\u4E86\u8FD9\u4E00\u6B65\uFF08\u7B2C ${n} \u6B21\u91CD\u8BD5\uFF09\u3002`,
239823
+ escalationRerun: (by, n) => `${by} \u91CD\u65B0\u8FD0\u884C\u4E86\u8FD9\u4E00\u6B65\uFF08\u7B2C ${n} \u6B21\uFF09\u3002`,
239530
239824
  /* ── 协调者卡(F2)──────────────────────────────────────────────────────────────────
239531
239825
  「协调者处理**某条缺口**的那一段」。三句话对应缺口的三种去向(已补齐 / 补不了升级给人 /
239532
239826
  还开着),判据全在缺口这条 issue 上,不看旁账那条 `coordinate:` 运行有没有留下结论
239533
239827
  (Bug 0213;前两句 T-104 / T-113 已验收,逐字不动)。 */
239534
- coordinatingGap: (coordinator, node2) => `${coordinator} \u6B63\u5728\u5904\u7406\u300A${node2}\u300B\u7684\u7F3A\u53E3\u3002`,
239535
- coordinatorFixedGap: (coordinator, executor) => `${coordinator} \u5DF2\u8865\u9F50\u7F3A\u53E3\uFF0C${executor} \u7EE7\u7EED\u6267\u884C\u3002`,
239536
- coordinatorEscalated: (coordinator, node2, target) => `${coordinator} \u8865\u4E0D\u4E86\u300A${node2}\u300B\u7684\u7F3A\u53E3\uFF0C\u5DF2\u5347\u7EA7\u7ED9 @${target} \u5904\u7406\u3002`,
239828
+ coordinatingGap: (coordinator, node2) => `${coordinator} \u6B63\u5728\u5904\u7406\u300A${node2}\u300B\u7684\u6C42\u52A9\u3002`,
239829
+ coordinatorFixedGap: (coordinator, executor) => `${coordinator} \u5DF2\u89E3\u51B3\u6C42\u52A9\uFF0C${executor} \u7EE7\u7EED\u6267\u884C\u3002`,
239830
+ coordinatorEscalated: (coordinator, node2, target) => `${coordinator} \u89E3\u51B3\u4E0D\u4E86\u300A${node2}\u300B\u7684\u6C42\u52A9\uFF0C\u5DF2\u8F6C\u7ED9 @${target} \u5904\u7406\u3002`,
239537
239831
  /**
239538
239832
  * 缺口还开着、也没升级给人,而这一段协调运行已经结束且没跑成(failed / orphaned / timeout)——
239539
239833
  * 如实说这一轮没跑成,别说「正在处理」(没人在处理),也别替它宣布一个没发生的结局。
@@ -239541,7 +239835,7 @@ var init_activity2 = __esm({
239541
239835
  * 🚨 兜底句「{协调者} 处理《X》的缺口,这一段没有结论」**已删**(Bug 0213):那句是拿 run 的结论
239542
239836
  * 拼卡时的产物——取不到结论就替这一段下一个空结论。缺口自己的状态永远答得出「补上了没有」。
239543
239837
  */
239544
- coordinatorFailed: (node2) => `\u5904\u7406\u300A${node2}\u300B\u7684\u7F3A\u53E3\u65F6\u8FD9\u4E00\u8F6E\u6CA1\u8DD1\u6210\u3002`,
239838
+ coordinatorFailed: (node2) => `\u5904\u7406\u300A${node2}\u300B\u7684\u6C42\u52A9\u65F6\u8FD9\u4E00\u8F6E\u6CA1\u8DD1\u6210\u3002`,
239545
239839
  /**
239546
239840
  * 这一轮**被计划变更打断**(P15)。与卡点中断同级:计划一动,这一轮就不再是「还在跑」,
239547
239841
  * 而是被引擎当场顶掉了——`plan.delete_node` 直接 `killLatestWork`;`plan.update_spec`(spec 真变)
@@ -239566,7 +239860,7 @@ var init_activity2 = __esm({
239566
239860
  * **上游出了新版本**,在跑的这一轮因此被引擎顶掉(Bug 0068)。上游是根节点任务说明、而且能认出是谁改的,
239567
239861
  * 走 {@link interruptedBySpecChange}(控制台「编辑任务说明」就是这条路径);其余上游换版走这一句。
239568
239862
  */
239569
- interruptedByUpstreamVersion: (upstream, node2) => `\u300A${upstream}\u300B\u51FA\u4E86\u65B0\u7248\u672C\uFF0C\u300A${node2}\u300B\u672C\u8F6E\u6267\u884C\u5230\u6B64\u4E2D\u65AD\u3002`,
239863
+ interruptedByUpstreamVersion: (upstream, node2, m2) => `\u300A${upstream}\u300B${m2 !== void 0 ? `\u51FA\u4E86\u7B2C ${m2} \u7248` : "\u6709\u65B0\u7248"}\uFF0C\u300A${node2}\u300B\u672C\u8F6E\u6267\u884C\u5230\u6B64\u4E2D\u65AD\u3002`,
239570
239864
  /** 换人那一档点破接手的人——卡到此为止,下一轮是新执行人的新卡。 */
239571
239865
  interruptedByReassign: (node2, next) => `\u300A${node2}\u300B\u672C\u8F6E\u6267\u884C\u56E0\u8BA1\u5212\u8C03\u6574\u4E2D\u65AD\uFF0C\u5DF2\u6539\u7531 ${next} \u63A5\u624B\u3002`,
239572
239866
  /** 节点被移出计划:这一轮再没有下文,也不会有新卡。 */
@@ -239577,7 +239871,7 @@ var init_activity2 = __esm({
239577
239871
  * 只剩**节点最新一轮、且查不到卡在哪**会落到这里(多半是引擎马上要续派 / 收口的那几秒);同节点已有
239578
239872
  * 下一轮接手的见 {@link stageHandedOver},交稿后卡在续跑审批 / 暂停 / 回压上的见 `heldBy*`。
239579
239873
  */
239580
- awaitingConclude: (node2) => `\u300A${node2}\u300B\u5DF2\u4EA4\u4ED8\uFF0C\u7B49\u5F85\u6536\u53E3\u3002`,
239874
+ awaitingConclude: (node2) => `\u300A${node2}\u300B\u5DF2\u505A\u5B8C\uFF0C\u7CFB\u7EDF\u6B63\u5728\u786E\u8BA4\u3002`,
239581
239875
  /**
239582
239876
  * **阶段交付已被接手**:这一轮交了稿、没收口,同节点已经派出下一轮接着做(分批 `propose --checkpoint
239583
239877
  * --continue`,或派活说明要求先别收口)。这一段到此已经交付——等收口的是接手的那一轮,不是它。
@@ -239586,14 +239880,14 @@ var init_activity2 = __esm({
239586
239880
  *
239587
239881
  * 不说「已完成」:节点并没有完成,说了会让人以为这一步做完、在等审核。
239588
239882
  */
239589
- stageHandedOver: (node2) => `\u300A${node2}\u300B\u5DF2\u63D0\u4EA4\u9636\u6BB5\u6210\u679C\uFF0C\u7531\u4E0B\u4E00\u8F6E\u7EE7\u7EED\u3002`,
239883
+ stageHandedOver: (node2) => `\u300A${node2}\u300B\u5DF2\u63D0\u4EA4\u9636\u6BB5\u6210\u679C\uFF0C\u4E0B\u4E00\u8F6E\u63A5\u7740\u505A\u3002`,
239590
239884
  /** 同一节点上紧挨着的阶段交付轮折成一张(与连败折叠同一套机制),批数写进句子。 */
239591
- foldedStageHandedOver: (node2, n) => `\u300A${node2}\u300B\u5DF2\u5206 ${n} \u6279\u63D0\u4EA4\u9636\u6BB5\u6210\u679C\uFF0C\u7531\u4E0B\u4E00\u8F6E\u7EE7\u7EED\u3002`,
239885
+ foldedStageHandedOver: (node2, n) => `\u300A${node2}\u300B\u5DF2\u5206 ${n} \u6B21\u63D0\u4EA4\u9636\u6BB5\u6210\u679C\uFF0C\u4E0B\u4E00\u8F6E\u63A5\u7740\u505A\u3002`,
239592
239886
  /**
239593
239887
  * 这一批的续跑额度用完、引擎落了续跑申请(ADR 0549):执行卡这一段到此结束(灰、无按钮)。
239594
239888
  * 同一批里紧挨着的阶段交付轮照旧折进来,句子只说到这一批为止;下一批是新卡,排在续跑申请卡后面。
239595
239889
  */
239596
- stageBatchExhausted: (node2, batch, rounds) => `\u300A${node2}\u300B\u5DF2\u4EA4\u4ED8\u7B2C ${batch} \u6279\u9636\u6BB5\u6210\u679C\uFF08\u672C\u6279 ${rounds} \u8F6E\u5DF2\u7528\u5B8C\uFF09\u3002`,
239890
+ stageBatchExhausted: (node2, batch, rounds) => `\u300A${node2}\u300B\u5DF2\u63D0\u4EA4\u7B2C ${batch} \u6279\u9636\u6BB5\u6210\u679C\uFF08\u672C\u6279 ${rounds} \u8F6E\u5DF2\u7528\u5B8C\uFF09\u3002`,
239597
239891
  /* ── 续跑申请卡(奕璇 2026-09-28 定稿,附件《续跑申请卡方案》)────────────────────────────────────
239598
239892
  署名工单真人负责人;等决定 / 暂停中砖红,决定按钮直接在卡上;同意 / 恢复 / 接受 / 取消之后灰、无按钮。 */
239599
239893
  continuationPending: (node2, rounds, owner) => `\u300A${node2}\u300B\u672C\u6279 ${rounds} \u8F6E\u5DF2\u7528\u5B8C\uFF0C\u9700\u8981 @${owner} \u51B3\u5B9A\uFF1A\u518D\u8DD1\u4E00\u6279\uFF0C\u6216\u63A5\u53D7\u5F53\u524D\u6210\u679C\u3002`,
@@ -239602,14 +239896,14 @@ var init_activity2 = __esm({
239602
239896
  * 暂停中(仍砖红,按钮是「恢复续跑 / 接受当前成果」)。理由是负责人自己写的,照铺(与 Bug 0236 的判据一致:
239603
239897
  * 人写的照铺、agent 写的不上卡);没填就去掉冒号那半句。
239604
239898
  */
239605
- continuationDenied: (owner, reason) => `@${owner} \u6682\u505C\u4E86\u7EED\u8DD1${reason ? `\uFF1A${reason.replace(/[。..]+$/, "")}` : ""}\u3002\u9700\u8981\u65F6\u5728\u8FD9\u91CC\u7EE7\u7EED\u6216\u63A5\u53D7\u5F53\u524D\u6210\u679C\u3002`,
239606
- continuationResumed: (owner, rounds) => `@${owner} \u6682\u505C\u540E\u6062\u590D\u7EED\u8DD1\uFF0C\u8FFD\u52A0 ${rounds} \u8F6E\u3002`,
239607
- continuationOutputAccepted: (owner, node2) => `@${owner} \u63A5\u53D7\u4E86\u5F53\u524D\u6210\u679C\uFF0C\u300A${node2}\u300B\u5DF2\u5B8C\u6210\u3002`,
239899
+ continuationDenied: (owner, reason) => `@${owner} \u9009\u62E9\u5148\u4E0D\u7EE7\u7EED${reason ? `\uFF1A${reason.replace(/[。..]+$/, "")}` : ""}\u3002\u4E4B\u540E\u53EF\u4EE5\u5728\u8FD9\u91CC\u7EE7\u7EED\u6267\u884C\uFF0C\u6216\u63A5\u53D7\u5F53\u524D\u6210\u679C\u3002`,
239900
+ continuationResumed: (owner, rounds) => `@${owner} \u51B3\u5B9A\u7EE7\u7EED\u6267\u884C\uFF0C\u8FFD\u52A0 ${rounds} \u8F6E\u3002`,
239901
+ continuationOutputAccepted: (owner, node2, v2) => `@${owner} \u63A5\u53D7\u4E86\u5F53\u524D\u6210\u679C\uFF0C\u300A${node2}\u300B${versionPhrase(v2)}\u5DF2\u5B8C\u6210\u3002`,
239608
239902
  /** 还没决定,节点的真实交付就被验收了(ADR 0549 D4 关闭遗留额度卡):这次申请不用再决定。 */
239609
- continuationSettled: (node2) => `\u300A${node2}\u300B\u5DF2\u4EA4\u4ED8\uFF0C\u8FD9\u6B21\u7EED\u8DD1\u7533\u8BF7\u65E0\u9700\u518D\u51B3\u5B9A\u3002`,
239610
- continuationNodeRemoved: "\u8282\u70B9\u5DF2\u53D6\u6D88\uFF0C\u8FD9\u6B21\u7EED\u8DD1\u7533\u8BF7\u4E0D\u518D\u9700\u8981\u5904\u7406\u3002",
239903
+ continuationSettled: (node2) => `\u300A${node2}\u300B\u5DF2\u5B8C\u6210\uFF0C\u8FD9\u6B21\u7533\u8BF7\u4E0D\u7528\u518D\u5904\u7406\u3002`,
239904
+ continuationNodeRemoved: (node2) => `\u300A${node2}\u300B\u5DF2\u53D6\u6D88\uFF0C\u8FD9\u6B21\u7533\u8BF7\u4E0D\u7528\u518D\u5904\u7406\u3002`,
239611
239905
  /** 续跑申请卡正文(展开区):申请那一刻的轮数账。执行人的进展说明不上卡。 */
239612
- continuationRounds: (used, authorized, next) => `\u5DF2\u8FD0\u884C ${used} \u8F6E / \u5DF2\u6388\u6743 ${authorized} \u8F6E \xB7 \u4E0B\u4E00\u6279 ${next} \u8F6E`,
239906
+ continuationRounds: (used, authorized, next) => `\u5DF2\u8DD1 ${used} \u8F6E / \u5DF2\u6279\u51C6 ${authorized} \u8F6E \xB7 \u4E0B\u4E00\u6279 ${next} \u8F6E`,
239613
239907
  /* ── 交了稿没收口、**卡在别处**(Bug 0053 D2,ADR 0588)────────────────────────────
239614
239908
  这一轮结束到下一轮开工之间(没有下一轮就是「到现在」)查得到明确原因 ⇒ 执行中断,小状态说清卡在哪、
239615
239909
  等谁。此前一律「已交付,等待收口」+ 进行中:生产 9/17 最后两张进行中卡就是这一档,一张续跑被拒、
@@ -239617,14 +239911,14 @@ var init_activity2 = __esm({
239617
239911
  /* 续跑额度那两句(「等待批准继续」「节点已暂停,等待放行」)**2026-09-28 删掉**(奕璇拍板续跑申请纳入卡片规范):
239618
239912
  等人决定这件事由负责人那张续跑申请卡说(砖红 + 「去处理」),执行卡只说自己这一段交了什么——
239619
239913
  「等待放行」还让人一直等一个并不存在的按钮。执行卡这一档见 {@link ACTIVITY_COPY.stageBatchExhausted}。 */
239620
- heldByWorkorderPause: (node2) => `\u300A${node2}\u300B\u5DF2\u4EA4\u4ED8\u9636\u6BB5\u6210\u679C\uFF0C\u4EFB\u52A1\u5DF2\u6682\u505C\uFF0C\u7B49\u5F85\u6062\u590D\u3002`,
239914
+ heldByWorkorderPause: (node2) => `\u300A${node2}\u300B\u5DF2\u63D0\u4EA4\u9636\u6BB5\u6210\u679C\uFF0C\u4EFB\u52A1\u5DF2\u6682\u505C\uFF0C\u7B49\u5F85\u6062\u590D\u3002`,
239621
239915
  /** 只剩「交完稿、这一轮结束之后才补提」的那一档;同一轮里提的见 {@link ACTIVITY_COPY.backpressureAfterStage}(ADR 0623 补章)。 */
239622
- 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`,
239916
+ heldByBackpressure: (node2, upstreams) => `\u300A${node2}\u300B\u5DF2\u505A\u5B8C\uFF0C\u7B49${upstreams.map((u) => `\u300A${u}\u300B`).join("\u3001")}\u6309\u4FEE\u6539\u8981\u6C42\u6539\u597D\u540E\u624D\u7B97\u5B8C\u6210\u3002`,
239623
239917
  /** `works.status=retry`:这一轮没跑成,等引擎重派。谁按的重试由 `plan.node_retry` 补进这句。 */
239624
- awaitingRedispatch: (executor, node2) => `${executor} \u8FD9\u4E00\u8F6E\u6CA1\u8DD1\u6210\u300A${node2}\u300B\uFF0C\u7B49\u5F85\u91CD\u6D3E\u3002`,
239625
- doneAwaitingReview: (name) => `\u300A${name}\u300B\u5DF2\u5B8C\u6210\uFF0C\u7B49\u5F85\u5BA1\u6838\u3002`,
239626
- done: (name) => `\u300A${name}\u300B\u5DF2\u5B8C\u6210\u3002`,
239627
- finalDelivered: (acceptor) => `\u6700\u7EC8\u4EA4\u4ED8\u5DF2\u4E0A\u4F20\uFF0C\u7B49\u5F85 ${acceptor} \u9A8C\u6536\u3002`,
239918
+ awaitingRedispatch: (executor, node2) => `${executor} \u8FD9\u4E00\u8F6E\u6CA1\u8DD1\u6210\u300A${node2}\u300B\uFF0C\u7CFB\u7EDF\u7A0D\u540E\u81EA\u52A8\u91CD\u8BD5\u3002`,
239919
+ doneAwaitingReview: (name, v2) => `\u300A${name}\u300B${versionPhrase(v2)}\u5DF2\u505A\u5B8C\uFF0C\u7B49\u5F85\u5BA1\u6838\u3002`,
239920
+ done: (name, v2) => `\u300A${name}\u300B${versionPhrase(v2)}\u5DF2\u5B8C\u6210\u3002`,
239921
+ finalDelivered: (acceptor, v2) => `\u6700\u7EC8\u4EA4\u4ED8${versionPhrase(v2)}\u5DF2\u4E0A\u4F20\uFF0C\u7B49\u5F85 ${acceptor} \u9A8C\u6536\u3002`,
239628
239922
  /* 🚨 审核卡那一族文案(待审 / 正在审 / 通过 / 不通过 / 超时 / 熔断)**不在这里**:
239629
239923
  审核卡改成按「被审交付 × 评审人」合卡之后,它的小状态由 `review-activity.ts` 的
239630
239924
  `reviewSummary()` 按该评审人的**有效判定**整句给出,不是逐事件套模板。这里此前留着一份
@@ -239632,9 +239926,9 @@ var init_activity2 = __esm({
239632
239926
  死副本上,页面纹丝不动。删掉,别再留第二个事实源。
239633
239927
  留在这里的只有下面这一句——它不是小状态,是**审核意见条**上那个判定词,由本文件拼卡时用。 */
239634
239928
  reviewNoteLabel: (rejected) => rejected ? "\u5BA1\u6838\u4E0D\u901A\u8FC7" : "\u5BA1\u6838\u610F\u89C1",
239635
- acceptPending: "\u8BF7\u9A8C\u6536\u6700\u7EC8\u4EA4\u4ED8\u3002",
239636
- accepted: "\u9A8C\u6536\u6700\u7EC8\u4EA4\u4ED8\u5DF2\u5B8C\u6210\u9A8C\u6536\u3002",
239637
- acceptedNode: (name) => `\u300A${name}\u300B\u5DF2\u9A8C\u6536\u3002`,
239929
+ acceptPending: (v2) => v2 !== void 0 ? `\u8BF7\u9A8C\u6536\u6700\u7EC8\u4EA4\u4ED8\uFF08\u7B2C ${v2} \u7248\uFF09\u3002` : "\u8BF7\u9A8C\u6536\u6700\u7EC8\u4EA4\u4ED8\u3002",
239930
+ accepted: (v2) => `\u6700\u7EC8\u4EA4\u4ED8${versionPhrase(v2)}\u5DF2\u9A8C\u6536\u3002`,
239931
+ acceptedNode: (name, v2) => `\u300A${name}\u300B${versionPhrase(v2)}\u5DF2\u9A8C\u6536\u3002`,
239638
239932
  /**
239639
239933
  * **有评审人、却没经过他** —— 引擎跳过评审自己盖了章。
239640
239934
  *
@@ -239646,8 +239940,8 @@ var init_activity2 = __esm({
239646
239940
  * 此前动态把这一拍渲染成「验收最终交付已完成验收。」、执行者是翻不出名字的「—」——
239647
239941
  * **看起来就像她批过了**。这两句的存在就是为了不让那件事再被盖住:谁该点头、事实上没点。
239648
239942
  */
239649
- autoAccepted: (reviewer) => `\u672A\u7ECF ${reviewer} \u786E\u8BA4\uFF0C\u7CFB\u7EDF\u5DF2\u81EA\u52A8\u9A8C\u6536\u3002`,
239650
- autoAcceptedNode: (name, reviewer) => `\u300A${name}\u300B\u672A\u7ECF ${reviewer} \u786E\u8BA4\uFF0C\u7CFB\u7EDF\u5DF2\u81EA\u52A8\u9A8C\u6536\u3002`,
239943
+ autoAccepted: (reviewer) => `${reviewer} \u6CA1\u6709\u5728\u65F6\u9650\u5185\u786E\u8BA4\uFF0C\u7CFB\u7EDF\u5DF2\u81EA\u52A8\u9A8C\u6536\u3002`,
239944
+ autoAcceptedNode: (name, reviewer) => `\u300A${name}\u300B\uFF1A${reviewer} \u6CA1\u6709\u5728\u65F6\u9650\u5185\u786E\u8BA4\uFF0C\u7CFB\u7EDF\u5DF2\u81EA\u52A8\u9A8C\u6536\u3002`,
239651
239945
  paused: (actor) => `${actor}\u5DF2\u6682\u505C\u4EFB\u52A1\u3002`,
239652
239946
  resumedWorkorder: (actor) => `${actor}\u5DF2\u6062\u590D\u4EFB\u52A1\u3002`,
239653
239947
  goalChanged: (actor, manager) => `${actor}\u4FEE\u6539\u4E86\u4EFB\u52A1\u76EE\u6807\uFF0C\u9700\u8981 ${manager}\u91CD\u65B0\u8BC4\u4F30\u3002`,
@@ -239691,7 +239985,7 @@ var init_activity2 = __esm({
239691
239985
  * 被要求方按修改要求交出了新一版(agent / 真人同一句)。取代「《X》已完成」「已修订目标」——
239692
239986
  * 此前同一件事还另有一张回压卡收口成「{X} 已基于 {Z} 的留言更新了一版」,一件事三张卡。
239693
239987
  */
239694
- backpressureAddressed: (raisers, node2) => `\u5DF2\u6309 ${raisers} \u7684\u4FEE\u6539\u8981\u6C42\u66F4\u65B0\u300A${node2}\u300B\u3002`,
239988
+ backpressureAddressed: (raisers, node2, v2) => v2 !== void 0 ? `\u5DF2\u6309 ${raisers} \u7684\u4FEE\u6539\u8981\u6C42\u628A\u300A${node2}\u300B\u66F4\u65B0\u5230\u7B2C ${v2} \u7248\u3002` : `\u5DF2\u6309 ${raisers} \u7684\u4FEE\u6539\u8981\u6C42\u66F4\u65B0\u300A${node2}\u300B\u3002`,
239695
239989
  /**
239696
239990
  * 被回压节点的负责人是**真人**时,系统为他开的那一轮(奕璇 2026-09-22 定):这一轮不会自己跑,
239697
239991
  * 是在等他动手——不能再说「正在根据反馈调整」(`issueDriven`),那句会让一张没人在动的卡挂成「进行中」。
@@ -239709,11 +240003,11 @@ var init_activity2 = __esm({
239709
240003
  卡左边是执行者(被留言节点的负责人),这几句**不再重复执行者的名字**;留言人只出现在句子里。
239710
240004
  上面 `commentRaised` / `commentHandling` 与下面 `commentResolved` 那套旧句子只剩缺口唤醒信的
239711
240005
  第二种来源(`ann:recovery:`,D8 未定)在用;`commentAddressed` / `commentResolved` 另给修改要求卡收口用。 */
239712
- commentWaiting: (author, node2, n) => n > 1 ? `${author} \u5728\u300A${node2}\u300B\u7559\u4E86 ${n} \u6761\u8A00\uFF0C\u7B49\u5F85\u67E5\u770B\u3002` : `${author} \u5728\u300A${node2}\u300B\u7559\u8A00\uFF0C\u7B49\u5F85\u67E5\u770B\u3002`,
240006
+ commentWaiting: (author, node2, n) => n > 1 ? `${author} \u5728\u300A${node2}\u300B\u7559\u4E86 ${n} \u6761\u7559\u8A00\uFF0C\u7B49\u5F85\u67E5\u770B\u3002` : `${author} \u5728\u300A${node2}\u300B\u7559\u8A00\uFF0C\u7B49\u5F85\u67E5\u770B\u3002`,
239713
240007
  commentInProgress: (author) => `\u6B63\u5728\u5904\u7406 ${author} \u7684\u7559\u8A00\u3002`,
239714
- commentUpdated: (author, node2) => `\u5DF2\u6309 ${author} \u7684\u7559\u8A00\u66F4\u65B0\u4E86\u4E00\u7248\u300A${node2}\u300B\u3002`,
240008
+ commentUpdated: (author, node2, v2) => v2 !== void 0 ? `\u5DF2\u6309 ${author} \u7684\u7559\u8A00\u628A\u300A${node2}\u300B\u66F4\u65B0\u5230\u7B2C ${v2} \u7248\u3002` : `\u5DF2\u6309 ${author} \u7684\u7559\u8A00\u66F4\u65B0\u4E86\u300A${node2}\u300B\u3002`,
239715
240009
  commentReplied: (author) => `\u5DF2\u56DE\u590D ${author} \u7684\u7559\u8A00\uFF0C\u672C\u6B21\u4E0D\u6539\u3002`,
239716
- commentViewed: (author) => `\u770B\u8FC7 ${author} \u7684\u7559\u8A00\uFF0C\u65E0\u9700\u4FEE\u6539\u3002`,
240010
+ commentViewed: (author) => `\u5DF2\u67E5\u770B ${author} \u7684\u7559\u8A00\uFF0C\u65E0\u9700\u4FEE\u6539\u3002`,
239717
240011
  /**
239718
240012
  * 确认沿用(ADR 0607):上游换版,执行人确认不影响已接受的交付。
239719
240013
  *
@@ -239721,7 +240015,7 @@ var init_activity2 = __esm({
239721
240015
  * 自由写的,除了长度截断没有任何语域约束,产品负责人 2026-09-23 在真页面上看到的是
239722
240016
  * 「任务1:…沿用 `wk:artifact:qa:…:1`,不产新版。」这种任务书编号腔 + 裸 work id。
239723
240017
  */
239724
- upstreamAcked: (node2) => `\u4E0A\u6E38\u6709\u65B0\u7248\uFF0C\u786E\u8BA4\u4E0D\u5F71\u54CD\u300A${node2}\u300B\uFF0C\u6CBF\u7528\u4E0A\u4E00\u7248\u4EA4\u4ED8\u3002`,
240018
+ upstreamAcked: (node2, v2) => `\u4E0A\u6E38\u6709\u65B0\u7248\uFF0C\u786E\u8BA4\u4E0D\u5F71\u54CD\u300A${node2}\u300B\uFF0C${v2 !== void 0 ? `\u7EE7\u7EED\u7528\u7B2C ${v2} \u7248\u6210\u679C` : "\u7EE7\u7EED\u7528\u539F\u6765\u7684\u6210\u679C"}\u3002`,
239725
240019
  commentWithdrawn: (author) => `${author} \u64A4\u56DE\u4E86\u7559\u8A00\uFF0C\u65E0\u9700\u5904\u7406\u3002`,
239726
240020
  /** 节点被移出计划(任务还没到头):引擎把挂在上面的留言一并按「不改」关掉(`plan.delete_node`)。 */
239727
240021
  commentNodeRemoved: (node2) => `\u300A${node2}\u300B\u5DF2\u4ECE\u8BA1\u5212\u4E2D\u79FB\u9664\uFF0C\u7559\u8A00\u968F\u4E4B\u5173\u95ED\u3002`,
@@ -239731,13 +240025,13 @@ var init_activity2 = __esm({
239731
240025
  * 全库实测 `ws:wo-56bba3c1` 那条收口后小状态仍是 **8096 字**。全文去留言线看。
239732
240026
  */
239733
240027
  /** 有人接手这条留言、开工了——由 `work.create` 的 `reasons` 引用该 issueId 判定。 */
239734
- commentHandling: (executor, author) => `${executor} \u6B63\u5728\u5904\u7406\u6765\u81EA ${author} \u7684\u7559\u8A00\u3002`,
240028
+ commentHandling: (executor, author) => `${executor} \u6B63\u5728\u5904\u7406 ${author} \u7684\u7559\u8A00\u3002`,
239735
240029
  /**
239736
240030
  * 处理完**并且真出了新一版**。
239737
240031
  * ⚠ 只在那一轮真有 conclusion 时才说「更新了一版」——resolve 也可能是「看了但不改」,
239738
240032
  * 那种情况说出了新版就是一句卡面兑现不了的断言(同「未经 X 确认」那一族)。
239739
240033
  */
239740
- commentAddressed: (executor, author, node2) => `${executor} \u5DF2\u57FA\u4E8E ${author} \u7684\u7559\u8A00\u66F4\u65B0\u4E86\u4E00\u7248\u300A${node2}\u300B\u3002`,
240034
+ commentAddressed: (executor, author, node2, v2) => v2 !== void 0 ? `${executor} \u5DF2\u6309 ${author} \u7684\u7559\u8A00\u628A\u300A${node2}\u300B\u66F4\u65B0\u5230\u7B2C ${v2} \u7248\u3002` : `${executor} \u5DF2\u6309 ${author} \u7684\u7559\u8A00\u66F4\u65B0\u4E86\u300A${node2}\u300B\u3002`,
239741
240035
  commentResolved: (by, node2) => `${by} \u5DF2\u5904\u7406\u300A${node2}\u300B\u7684\u7559\u8A00\u3002`,
239742
240036
  issueResolved: (note) => note
239743
240037
  };
@@ -239813,7 +240107,7 @@ function buildWorkorderActivityTrace(input) {
239813
240107
  const gap2 = snap.issues.find((i) => i.id === coordinatorGapId && i.kind === "gap");
239814
240108
  const gapNodeId = gap2 ? gap2.aboutNodeId ?? gap2.raisedByNodeId : null;
239815
240109
  const run = gap2 && gapNodeId ? coordinatorRunOfGap(gap2, (input.coordinatorRuns ?? []).filter((r) => r.nodeId === gapNodeId)) : void 0;
239816
- if (!run) return { ...empty2, notice: "\u534F\u8C03\u8005\u5904\u7406\u8FD9\u6761\u7F3A\u53E3\u7684\u90A3\u4E00\u6BB5\u6CA1\u6709\u7559\u4E0B\u8FD0\u884C\u8BB0\u5F55\uFF0C\u6682\u65F6\u65E0\u6CD5\u786E\u8BA4\u8FD0\u884C\u8F68\u8FF9\u3002" };
240110
+ if (!run) return { ...empty2, notice: "\u534F\u8C03\u8005\u5904\u7406\u8FD9\u6761\u6C42\u52A9\u65F6\u6CA1\u6709\u7559\u4E0B\u8FD0\u884C\u8BB0\u5F55\uFF0C\u6682\u65F6\u770B\u4E0D\u5230\u6267\u884C\u8FC7\u7A0B\u3002" };
239817
240111
  const node2 = snap.nodes.find((n) => n.id === run.nodeId);
239818
240112
  const spec = node2?.spec?.trim();
239819
240113
  return {
@@ -239838,7 +240132,7 @@ function buildWorkorderActivityTrace(input) {
239838
240132
  if (gap && !card2?.traceWorkIds?.length) return {
239839
240133
  ...empty2,
239840
240134
  works: [],
239841
- notice: "\u7F3A\u53E3\u8BB0\u5F55\u6CA1\u6709\u660E\u786E\u5173\u8054\u5230\u67D0\u6B21\u6267\u884C\uFF0C\u6682\u65F6\u65E0\u6CD5\u786E\u8BA4\u8FD0\u884C\u8F68\u8FF9\u3002"
240135
+ notice: "\u8FD9\u6761\u6C42\u52A9\u6CA1\u6709\u5BF9\u5E94\u5230\u5177\u4F53\u54EA\u4E00\u6B21\u6267\u884C\uFF0C\u6682\u65F6\u770B\u4E0D\u5230\u6267\u884C\u8FC7\u7A0B\u3002"
239842
240136
  };
239843
240137
  const workIds = card2?.traceWorkIds ?? (cardId.startsWith("work:") ? [cardId.slice(5)] : []);
239844
240138
  if (workIds.length > 0) {
@@ -239891,7 +240185,7 @@ function buildWorkorderActivityTrace(input) {
239891
240185
  cardId,
239892
240186
  briefing: firstBriefing,
239893
240187
  works,
239894
- ...gap && !gap.aboutWorkId ? { notice: "\u6839\u636E\u7F3A\u53E3\u63D0\u51FA\u65F6\u7684\u8BB0\u5F55\uFF0C\u5173\u8054\u5230\u8BE5\u8282\u70B9\u3001\u8BE5\u6267\u884C\u4EBA\u5F53\u65F6\u552F\u4E00\u7684\u6267\u884C\u3002" } : {},
240188
+ ...gap && !gap.aboutWorkId ? { notice: "\u6309\u6C42\u52A9\u63D0\u51FA\u65F6\u7684\u8BB0\u5F55\uFF0C\u5BF9\u5E94\u5230\u8FD9\u4E00\u6B65\u5F53\u65F6\u552F\u4E00\u7684\u4E00\u6B21\u6267\u884C\u3002" } : {},
239895
240189
  attempts: works.flatMap((w2) => w2.attempts).sort((a, b2) => a.at.localeCompare(b2.at) || a.runId.localeCompare(b2.runId))
239896
240190
  };
239897
240191
  }
@@ -240043,13 +240337,13 @@ function buildReviewHistory(input, targetWorkId, reviewerActorId, allReviews) {
240043
240337
  let detail;
240044
240338
  switch (e.kind) {
240045
240339
  case "review.create":
240046
- label2 = "\u521B\u5EFA\u5BA1\u6838";
240340
+ label2 = "\u53D1\u8D77\u5BA1\u6838";
240047
240341
  break;
240048
240342
  case "review.started":
240049
240343
  label2 = "\u5F00\u59CB\u5BA1\u6838";
240050
240344
  break;
240051
240345
  case "review.timeout":
240052
- label2 = "\u5BA1\u6838\u6267\u884C\u8D85\u65F6 / \u5931\u8D25";
240346
+ label2 = "\u5BA1\u6838\u8D85\u65F6 / \u5931\u8D25";
240053
240347
  break;
240054
240348
  case "review.kill":
240055
240349
  label2 = "\u5BA1\u6838\u5DF2\u505C\u6B62";
@@ -240060,7 +240354,7 @@ function buildReviewHistory(input, targetWorkId, reviewerActorId, allReviews) {
240060
240354
  detail = e.note ?? void 0;
240061
240355
  break;
240062
240356
  case "plan.node_retry":
240063
- label2 = "\u4ECB\u5165\u91CD\u8BD5";
240357
+ label2 = "\u91CD\u65B0\u8FD0\u884C";
240064
240358
  break;
240065
240359
  default:
240066
240360
  continue;
@@ -240098,6 +240392,8 @@ function buildReviewHistory(input, targetWorkId, reviewerActorId, allReviews) {
240098
240392
  targetWorkId,
240099
240393
  nodeTitle: snap.nodes.find((n) => n.id === nodeId)?.title ?? nodeId,
240100
240394
  ...work?.outputVersionNo != null ? { outputVersionNo: work.outputVersionNo } : {},
240395
+ /* 侧栏上给人看的「第 {N} 版」:与卡片同一张表(activity-version.ts),不是引擎那个只在验收时发号的 outputVersionNo。 */
240396
+ .../* @__PURE__ */ ((v2) => v2 !== void 0 ? { versionNo: v2 } : {})(outputVersionNumbers(snap).get(targetWorkId)),
240101
240397
  artifacts: work ? artifactsOfWork(work, input) : [],
240102
240398
  reviews,
240103
240399
  events,
@@ -240113,6 +240409,7 @@ var init_activity_trace = __esm({
240113
240409
  init_src();
240114
240410
  init_activity2();
240115
240411
  init_workorder_manager();
240412
+ init_activity_version();
240116
240413
  init_src7();
240117
240414
  }
240118
240415
  });
@@ -240122,14 +240419,14 @@ function commandCardLine(command2) {
240122
240419
  return command2.displayLabel?.trim() || stripInternalIds(command2.effectLabel ?? "");
240123
240420
  }
240124
240421
  function changeConfirmSummary(r) {
240125
- if (r.continuation) return `\u81EA\u52A8\u7EED\u8DD1\u5DF2\u5230\u9650\uFF0C\u7B49\u5F85\u4F60\u5BA1\u6838\uFF1A${r.plan.reason}`;
240422
+ if (r.continuation) return `\u672C\u6279\u8F6E\u6570\u5DF2\u7528\u5B8C\uFF0C\u7B49\u4F60\u51B3\u5B9A\u662F\u5426\u7EE7\u7EED\uFF1A${r.plan.reason}`;
240126
240423
  const why = r.plan.reason.slice(0, 100);
240127
240424
  if (r.kind === "command" && r.command) {
240128
240425
  return `\u7BA1\u7406\u8005\u8BF7\u6C42\u6267\u884C\u4E00\u4E2A\u9AD8\u5371\u64CD\u4F5C\uFF0C\u7B49\u4F60\u6388\u6743\uFF1A${commandCardLine(r.command)}`;
240129
240426
  }
240130
240427
  const ops = r.plan.ops ?? [];
240131
240428
  if (ops.length > 0 && ops.every((o) => o.action === "resolveEscalation")) {
240132
- return `\u7BA1\u7406\u8005\u8BA4\u4E3A\u8FD9\u6761\u4E0A\u62A5\u53EF\u4EE5\u5173\u4E86\uFF0C\u7B49\u4F60\u786E\u8BA4\uFF1A${why}`;
240429
+ return `\u534F\u8C03\u8005\u8BA4\u4E3A\u8FD9\u6761\u5F85\u529E\u53EF\u4EE5\u5173\u95ED\u4E86\uFF0C\u7B49\u4F60\u786E\u8BA4\uFF1A${why}`;
240133
240430
  }
240134
240431
  const what = r.nonMonotonicOps.map((a) => C_ACTION_LABEL[a] ?? a).join("\u3001");
240135
240432
  return `\u7BA1\u7406\u8005\u63D0\u4E86\u4E00\u4EFD\u9700\u8981\u4F60\u786E\u8BA4\u7684\u6539\u52A8\uFF08${what || "\u9500\u6BC1\u6027\u6539\u56FE"}\uFF09\uFF1A${why}`;
@@ -240240,7 +240537,7 @@ function buildInbox(model, me, leadOf, resolveActor, markers = [], slaMs, nowIso
240240
240537
  // 这里只补通用结构位:actor(== reporter)、impact(重复上报次数)、overdue(要决策恒真)。
240241
240538
  title: nodeLabel(a),
240242
240539
  actor: actorRef2(gap.by),
240243
- ...gapReportCount > 1 ? { impact: `\u5DF2\u91CD\u590D\u4E0A\u62A5 ${gapReportCount} \u6B21` } : {},
240540
+ ...gapReportCount > 1 ? { impact: `\u5DF2\u7ECF\u7B2C ${gapReportCount} \u6B21\u6C42\u52A9` } : {},
240244
240541
  overdue: exactEscalation ? true : overdueSince(gapSince) ?? false,
240245
240542
  // ADR 0079 D1:把挂在这条 gap 上的那条上报 id 带到**顶层**(前端「沟通聊聊」据它绑定会话 → 关上报可省 id)。
240246
240543
  ...exactEscalation?.escalationId ? { escalationId: exactEscalation.escalationId } : {},
@@ -240274,7 +240571,7 @@ function buildInbox(model, me, leadOf, resolveActor, markers = [], slaMs, nowIso
240274
240571
  // ADR-0108 D10:**不再截到 100 字**。熔断升级卡走的正是这条「无 gap」分支,而 D10 刚
240275
240572
  // 往里放进了真正有用的东西(末次失败原文、哪台机器死了多久、去哪改绑)——一刀切到 100 字
240276
240573
  // 会把它们全砍掉,只剩「连败 N 次」这种人早就知道的部分。前端按卡片排版自己收,不在这里截。
240277
- summary: `\u300C${nodeLabel(a)}\u300D\u88AB\u4E0A\u62A5\u9700\u4F60\u4ECB\u5165${who}\uFF1A${e.reason}`
240574
+ summary: `\u300C${nodeLabel(a)}\u300D\u9700\u8981\u4F60\u5904\u7406${who}\uFF1A${e.reason}`
240278
240575
  });
240279
240576
  }
240280
240577
  }
@@ -240321,7 +240618,7 @@ function buildInbox(model, me, leadOf, resolveActor, markers = [], slaMs, nowIso
240321
240618
  workspace: a.workspace,
240322
240619
  since: pause.at,
240323
240620
  actionRequired: true,
240324
- summary: "\u7EED\u8DD1\u5DF2\u6682\u505C\uFF0C\u5F85\u4F60\u51B3\u5B9A",
240621
+ summary: "\u5DF2\u9009\u62E9\u5148\u4E0D\u7EE7\u7EED\uFF0C\u4E4B\u540E\u7531\u4F60\u51B3\u5B9A\u662F\u5426\u7EE7\u7EED\u6267\u884C",
240325
240622
  draftId: pause.requestId,
240326
240623
  activityCardId: pause.requestId,
240327
240624
  title: nodeLabel(a),
@@ -240565,10 +240862,10 @@ var init_inbox = __esm({
240565
240862
  unlink: "\u65AD\u5F00\u4F9D\u8D56\u8FB9",
240566
240863
  pin: "\u6539\u9489\u7248\u672C",
240567
240864
  assign: "\u6539\u6D3E\u8D1F\u8D23\u4EBA",
240568
- seal: "\u7EC8\u7ED3\u8282\u70B9",
240569
- reopen: "\u89E3\u5C01\u8282\u70B9",
240865
+ seal: "\u7EC8\u6B62\u8FD9\u4E00\u6B65",
240866
+ reopen: "\u6062\u590D\u8FD9\u4E00\u6B65",
240570
240867
  cancelPart: "\u64A4\u6389\u90E8\u4EF6",
240571
- resolveEscalation: "\u5173\u95ED\u4E0A\u62A5",
240868
+ resolveEscalation: "\u5173\u95ED\u5F85\u529E",
240572
240869
  "edit:reviewers": "\u6539\u5BA1\u6838\u4EBA"
240573
240870
  };
240574
240871
  }
@@ -246013,13 +246310,21 @@ var init_sender = __esm({
246013
246310
  });
246014
246311
 
246015
246312
  // ../server/src/domains/runtime-allocation/faults.ts
246313
+ function isProviderAuthFailure(errorMessage2) {
246314
+ if (!errorMessage2) return false;
246315
+ return PROVIDER_AUTH_FAILURE_PATTERNS.some((p2) => p2.test(errorMessage2));
246316
+ }
246016
246317
  function faultReasonOfExit(reason, errorMessage2) {
246017
246318
  if (reason === "server-unreachable") return "node-error";
246018
246319
  if (reason === "auth-error") return "node-error";
246019
246320
  if (reason === "rate-limit") return "quota-exhausted";
246020
246321
  if (reason === "error" && errorMessage2?.startsWith("\u8FD9\u4E00\u8F6E\u6CA1\u80FD\u5728\u8282\u70B9\u4E0A\u542F\u52A8\uFF1A")) return "startup-failed";
246322
+ if (reason === "error" && isProviderAuthFailure(errorMessage2)) return "unauthenticated";
246021
246323
  return null;
246022
246324
  }
246325
+ function faultScopeOf(fault) {
246326
+ return RUNTIME_FAULT_SCOPE[fault.reason];
246327
+ }
246023
246328
  function probeIntervalMs(attempt) {
246024
246329
  const raw = RUNTIME_FAULT_PROBE_INITIAL_MS * 2 ** Math.max(0, attempt - 1);
246025
246330
  return Math.min(raw, RUNTIME_FAULT_PROBE_MAX_INTERVAL_MS);
@@ -246047,7 +246352,7 @@ function faultAfterFailure(previous3, input, now) {
246047
246352
  return { ...base, nextProbeAt: new Date(now + probeIntervalMs(attempts)).toISOString() };
246048
246353
  }
246049
246354
  function quotaClearsFault(fault, quotaStatus) {
246050
- if (RUNTIME_FAULT_RECOVERY[fault.reason] !== "readonly-probe" && fault.reason !== "unauthenticated") return false;
246355
+ if (RUNTIME_FAULT_RECOVERY[fault.reason] !== "readonly-probe") return false;
246051
246356
  return quotaStatus === "ok";
246052
246357
  }
246053
246358
  function describeFault(fault, now) {
@@ -246058,10 +246363,22 @@ function describeFault(fault, now) {
246058
246363
  const mins = Math.max(0, Math.round(waitMs / 6e4));
246059
246364
  return `${head}\uFF0C\u5DF2\u8BD5 ${fault.attempts} \u6B21\uFF0C${mins} \u5206\u949F\u540E\u653E\u4E00\u6761\u65B0\u4F1A\u8BDD\u8FC7\u53BB\u8BD5`;
246060
246365
  }
246366
+ var PROVIDER_AUTH_FAILURE_PATTERNS;
246061
246367
  var init_faults = __esm({
246062
246368
  "../server/src/domains/runtime-allocation/faults.ts"() {
246063
246369
  "use strict";
246064
246370
  init_src();
246371
+ PROVIDER_AUTH_FAILURE_PATTERNS = [
246372
+ /Identity verification is required/i,
246373
+ /API Error:\s*40[13]\b/i,
246374
+ /Failed to authenticate/i,
246375
+ /OAuth (?:session|token) (?:has )?expired/i,
246376
+ /invalid (?:x-)?api[ _-]?key/i,
246377
+ /authentication_error/i,
246378
+ /Please run \/login/i,
246379
+ /\bNot logged in\b/i,
246380
+ /\b401 Unauthorized\b/i
246381
+ ];
246065
246382
  }
246066
246383
  });
246067
246384
 
@@ -246795,9 +247112,13 @@ function nodesDomain(deps) {
246795
247112
  const QUOTA_CAPABLE_KINDS = /* @__PURE__ */ new Set(["codex", "claude-code"]);
246796
247113
  const UPDATE_TIMEOUT_MS = 15e4;
246797
247114
  const nodeUpdates = /* @__PURE__ */ new Map();
247115
+ function nodeScopedFaultOf(nodeId) {
247116
+ for (const f2 of faultByTarget.values()) if (f2.nodeId === nodeId && faultScopeOf(f2) === "node") return f2;
247117
+ return void 0;
247118
+ }
246798
247119
  function runtimeInfoOf(r) {
246799
247120
  const quota = quotaByNode.get(r.nodeId)?.find((q2) => q2.kind === r.kind);
246800
- const fault = faultByTarget.get(faultKey(r.nodeId, r.kind));
247121
+ const fault = faultByTarget.get(faultKey(r.nodeId, r.kind)) ?? nodeScopedFaultOf(r.nodeId);
246801
247122
  const activeRunCount = deps.activeRunCountOf?.(r.nodeId, r.kind) ?? 0;
246802
247123
  const activeRuns = deps.activeRunsOf?.(r.nodeId, r.kind);
246803
247124
  return {
@@ -246827,6 +247148,61 @@ function nodesDomain(deps) {
246827
247148
  }
246828
247149
  daemon?.close();
246829
247150
  }
247151
+ const emptySince = /* @__PURE__ */ new Map();
247152
+ const restartEmptyClock = (nodeId) => {
247153
+ emptySince.set(nodeId, Date.now());
247154
+ };
247155
+ async function removeNode(nodeId, releaseAs) {
247156
+ const kinds = [...new Set((await deps.nodeStore.listRuntimes(nodeId)).map((r) => r.kind))];
247157
+ revokeNodeAccess(nodeId);
247158
+ await deps.nodeStore.deleteNode(nodeId);
247159
+ emptySince.delete(nodeId);
247160
+ const released = [];
247161
+ if (releaseAs) {
247162
+ for (const runtimeKind of kinds) {
247163
+ released.push(...await deps.releaseBindingsOnRuntime?.(releaseAs.companyId, { nodeId, runtimeKind }, releaseAs.actor) ?? []);
247164
+ }
247165
+ }
247166
+ return released;
247167
+ }
247168
+ async function sweepEmptyNodes(graceMs) {
247169
+ const nowMs = Date.now();
247170
+ const nodes = await deps.nodeStore.listNodes();
247171
+ const withRuntime = new Set((await deps.nodeStore.listRuntimes()).map((r) => r.nodeId));
247172
+ const live = new Set(nodes.map((n) => n.id));
247173
+ for (const id of [...emptySince.keys()]) if (!live.has(id) || withRuntime.has(id)) emptySince.delete(id);
247174
+ const removed = [];
247175
+ for (const n of nodes) {
247176
+ if (withRuntime.has(n.id)) continue;
247177
+ const since = emptySince.get(n.id);
247178
+ if (since === void 0) {
247179
+ emptySince.set(n.id, nowMs);
247180
+ continue;
247181
+ }
247182
+ if (nowMs - since < graceMs) continue;
247183
+ if ((await deps.nodeStore.listRuntimes(n.id)).length > 0) {
247184
+ emptySince.delete(n.id);
247185
+ continue;
247186
+ }
247187
+ await removeNode(n.id);
247188
+ removed.push(n.id);
247189
+ console.log(`[nodes] \u6E05\u7406\u6CA1\u6709\u7ED1\u5B9A\u8FD0\u884C\u65F6\u7684\u8282\u70B9\uFF1A${n.name || n.hostname}\uFF08${n.id}\uFF09\uFF0C\u5DF2\u7A7A\u7740\u8D85\u8FC7 ${Math.round(graceMs / 6e4)} \u5206\u949F`);
247190
+ }
247191
+ if (removed.length > 0) deps.onStatusChange?.();
247192
+ return removed;
247193
+ }
247194
+ if (deps.emptyNodeSweep) {
247195
+ const { graceMs, intervalMs } = deps.emptyNodeSweep;
247196
+ let sweeping = false;
247197
+ const timer = setInterval(() => {
247198
+ if (sweeping) return;
247199
+ sweeping = true;
247200
+ void sweepEmptyNodes(graceMs).catch((err) => console.warn(`[nodes] \u7A7A\u8282\u70B9\u6E05\u626B\u5931\u8D25\uFF08\u4E0B\u4E00\u62CD\u518D\u8BD5\uFF09\uFF1A${err instanceof Error ? err.message : String(err)}`)).finally(() => {
247201
+ sweeping = false;
247202
+ });
247203
+ }, intervalMs);
247204
+ timer.unref?.();
247205
+ }
246830
247206
  const publicUpdateStatus = (entry, activeRunCount) => ({
246831
247207
  state: entry.state,
246832
247208
  requestedAt: new Date(entry.requestedAtMs).toISOString(),
@@ -247046,6 +247422,7 @@ function nodesDomain(deps) {
247046
247422
  // ——那一路在 /api/nodes/exchange 里按入网令牌写名字,不经过这里。
247047
247423
  ...existing?.name ? { name: existing.name } : d.meta.nodeName ? { name: d.meta.nodeName } : {}
247048
247424
  });
247425
+ restartEmptyClock(daemonId);
247049
247426
  await nodeStore.alignRuntimeCompanies(daemonId);
247050
247427
  await nodeStore.setNodeRuntimesOnline(daemonId, true);
247051
247428
  } else {
@@ -247345,6 +247722,7 @@ function nodesDomain(deps) {
247345
247722
  } else if (entry.companyId) {
247346
247723
  await deps.nodeStore.setNodeCompany?.(nodeId, entry.companyId);
247347
247724
  }
247725
+ restartEmptyClock(nodeId);
247348
247726
  deps.enrollTokens.consume(enrollToken);
247349
247727
  if (entry.name) await deps.nodeStore.updateNodeName(nodeId, entry.name);
247350
247728
  deps.nodeTokens.clearRevocations(nodeId);
@@ -247355,8 +247733,6 @@ function nodesDomain(deps) {
247355
247733
  router.patch("/api/nodes/:nodeId", async (req) => {
247356
247734
  const nodeId = req.params["nodeId"];
247357
247735
  if (!nodeId) return { status: 400, body: { error: "missing nodeId" } };
247358
- const forbidden = await forbidUnlessOrgManager(req, "\u91CD\u547D\u540D\u8282\u70B9");
247359
- if (forbidden) return forbidden;
247360
247736
  const scope2 = req.auth.companyId !== void 0 ? { companyId: req.auth.companyId } : void 0;
247361
247737
  const node2 = (await deps.nodeStore.listNodes(scope2)).find((n) => n.id === nodeId);
247362
247738
  if (!node2) return { status: 404, body: { error: "NODE_NOT_FOUND", message: "\u8282\u70B9\u4E0D\u5B58\u5728\u6216\u4E0D\u5C5E\u4E8E\u5F53\u524D\u7EC4\u7EC7" } };
@@ -247397,21 +247773,26 @@ function nodesDomain(deps) {
247397
247773
  const scope2 = req.auth.companyId !== void 0 ? { companyId: req.auth.companyId } : void 0;
247398
247774
  const node2 = (await deps.nodeStore.listNodes(scope2)).find((n) => n.id === nodeId);
247399
247775
  if (!node2) return { status: 404, body: { error: "NODE_NOT_FOUND", message: "\u8282\u70B9\u4E0D\u5B58\u5728\u6216\u4E0D\u5C5E\u4E8E\u5F53\u524D\u7EC4\u7EC7" } };
247400
- const cleared = await deps.nodeStore.clearRuntimeFault(nodeId, runtimeKind);
247776
+ let cleared = await deps.nodeStore.clearRuntimeFault(nodeId, runtimeKind);
247401
247777
  faultByTarget.delete(faultKey(nodeId, runtimeKind));
247778
+ for (const f2 of [...faultByTarget.values()]) {
247779
+ if (f2.nodeId !== nodeId || faultScopeOf(f2) !== "node") continue;
247780
+ cleared = await deps.nodeStore.clearRuntimeFault(nodeId, f2.runtimeKind) || cleared;
247781
+ faultByTarget.delete(faultKey(nodeId, f2.runtimeKind));
247782
+ }
247402
247783
  deps.onStatusChange?.();
247403
247784
  return { status: 200, body: { ok: true, cleared } };
247404
247785
  });
247405
247786
  router.delete("/api/nodes/:nodeId", async (req) => {
247406
247787
  const nodeId = req.params["nodeId"];
247407
247788
  if (!nodeId) return { status: 400, body: { error: "missing nodeId" } };
247408
- const kinds = [...new Set((await deps.nodeStore.listRuntimes(nodeId)).map((r) => r.kind))];
247409
- revokeNodeAccess(nodeId);
247410
- await deps.nodeStore.deleteNode(nodeId);
247411
- const released = [];
247412
- for (const runtimeKind of kinds) {
247413
- released.push(...await deps.releaseBindingsOnRuntime?.(req.auth.companyId, { nodeId, runtimeKind }, req.auth.actor) ?? []);
247789
+ const ifNoRuntimes = req.query.get("ifNoRuntimes");
247790
+ if (ifNoRuntimes === "1" || ifNoRuntimes === "true") {
247791
+ if ((await deps.nodeStore.listRuntimes(nodeId)).length > 0) {
247792
+ return { status: 409, body: { error: "NODE_HAS_RUNTIMES", message: "\u8FD9\u53F0\u673A\u5668\u5DF2\u7ECF\u7ED1\u5B9A\u4E86\u8FD0\u884C\u65F6\uFF0C\u4FDD\u7559\u4E0D\u5220" } };
247793
+ }
247414
247794
  }
247795
+ const released = await removeNode(nodeId, { companyId: req.auth.companyId, actor: req.auth.actor });
247415
247796
  deps.onStatusChange?.();
247416
247797
  return { status: 200, body: { ok: true, releasedActorIds: released } };
247417
247798
  });
@@ -247646,7 +248027,7 @@ function normalizeGatewayUrl(raw) {
247646
248027
  function newNodeId() {
247647
248028
  return "node-" + (0, import_node_crypto63.randomUUID)().replace(/-/g, "").slice(0, 12);
247648
248029
  }
247649
- var import_node_crypto63, import_node_fs18, import_node_url6, import_node_path22, NODE_NAME_MAX_LENGTH;
248030
+ var import_node_crypto63, import_node_fs18, import_node_url6, import_node_path22, NODE_NAME_MAX_LENGTH, EMPTY_NODE_GRACE_MS, EMPTY_NODE_SWEEP_INTERVAL_MS;
247650
248031
  var init_routes7 = __esm({
247651
248032
  "../server/src/domains/nodes/routes.ts"() {
247652
248033
  "use strict";
@@ -247664,6 +248045,8 @@ var init_routes7 = __esm({
247664
248045
  init_usage_read();
247665
248046
  init_node_health();
247666
248047
  NODE_NAME_MAX_LENGTH = 64;
248048
+ EMPTY_NODE_GRACE_MS = 30 * 60 * 1e3;
248049
+ EMPTY_NODE_SWEEP_INTERVAL_MS = 60 * 1e3;
247667
248050
  }
247668
248051
  });
247669
248052
 
@@ -247681,12 +248064,15 @@ var init_coordinator_runs = __esm({
247681
248064
  function isRuntimeDrifted(rt) {
247682
248065
  return rt?.presentOnMachine === false;
247683
248066
  }
247684
- function runtimeDriftBlock(runtimes, nodeId, runtimeKind) {
248067
+ function runtimeDriftBlock(runtimes, nodeId, runtimeKind, nodeName) {
247685
248068
  const rt = runtimes.find((r) => r.kind === runtimeKind);
247686
248069
  if (!isRuntimeDrifted(rt)) return null;
247687
248070
  return {
247688
248071
  reason: "runtime-not-on-machine",
247689
- errorMessage: `\u8282\u70B9 ${nodeId} \u4E0A\u5DF2\u63A2\u6D4B\u4E0D\u5230\u8FD0\u884C\u65F6 ${runtimeKind}\u2014\u2014\u767B\u8BB0\u8868\u91CC\u8FD8\u7559\u7740\u8FD9\u884C\uFF0C\u4F46\u673A\u5668\u6700\u8FD1\u4E00\u6B21\u4E0A\u62A5\u7684\u8FD0\u884C\u65F6\u91CC\u6CA1\u6709\u5B83\uFF08\u591A\u534A\u662F\u88AB\u5378\u8F7D\u4E86\u6216 PATH \u53D8\u4E86\uFF09\u3002\u7CFB\u7EDF\u6309\u300C\u53EA\u589E\u4E0D\u51CF\u300D\u4E0D\u4F1A\u81EA\u52A8\u5220\u8FD9\u884C\uFF0C\u6240\u4EE5\u6CA1\u6709\u76F4\u63A5\u6D3E\u8FC7\u53BB\uFF08\u5426\u5219\u4F1A\u4EE5\u4E00\u4E2A\u6307\u5411\u522B\u5904\u7684\u6CDB\u5316\u9519\u8BEF\u5931\u8D25\uFF09\u3002\u8BF7\u5728\u8FD0\u884C\u65F6\u9875\u786E\u8BA4\u540E\u6E05\u7406\u8FD9\u884C\uFF0C\u6216\u5728\u8BE5\u673A\u5668\u4E0A\u91CD\u65B0\u5B89\u88C5 ${runtimeKind} \u540E\u518D\u8BD5\u3002`
248072
+ errorMessage: (
248073
+ /* 奕璇 2026-09-29 审定(清单第十一节):这句上的是任务执行失败原因,给人看的,不讲登记表 / 只增不减这些内部机制。 */
248074
+ `\u673A\u5668\u300C${nodeName?.trim() || nodeId}\u300D\u4E0A\u627E\u4E0D\u5230 ${runtimeKind} \u4E86\uFF0C\u53EF\u80FD\u5DF2\u88AB\u5378\u8F7D\u3002\u8FD9\u4E00\u6B65\u6CA1\u6709\u6D3E\u7ED9\u5B83\u3002\u8BF7\u5728\u8FD0\u884C\u65F6\u9875\u628A\u8FD9\u4E00\u9879\u5220\u6389\uFF0C\u6216\u5728\u8FD9\u53F0\u673A\u5668\u4E0A\u91CD\u65B0\u5B89\u88C5 ${runtimeKind} \u540E\u518D\u8BD5\u3002`
248075
+ )
247690
248076
  };
247691
248077
  }
247692
248078
  var init_runtime_drift = __esm({
@@ -247741,6 +248127,14 @@ function faultIsBlocking(fault, now) {
247741
248127
  if (!fault.nextProbeAt) return true;
247742
248128
  return Date.parse(fault.nextProbeAt) > now;
247743
248129
  }
248130
+ function blockingNodeFaults(faults, now) {
248131
+ const out = /* @__PURE__ */ new Map();
248132
+ for (const fault of faults?.values() ?? []) {
248133
+ if (RUNTIME_FAULT_SCOPE[fault.reason] !== "node" || !faultIsBlocking(fault, now)) continue;
248134
+ if (!out.has(fault.nodeId)) out.set(fault.nodeId, fault);
248135
+ }
248136
+ return out;
248137
+ }
247744
248138
  function failureReason(rejections) {
247745
248139
  if (rejections.length === 0) return "no-candidates";
247746
248140
  const causes = new Set(rejections.map((r) => r.cause));
@@ -247757,6 +248151,7 @@ function failureReason(rejections) {
247757
248151
  function allocateRuntime(input, now = Date.now()) {
247758
248152
  const rejections = [];
247759
248153
  const eligible = [];
248154
+ const nodeFaults = blockingNodeFaults(input.faults, now);
247760
248155
  for (const candidate of input.candidates) {
247761
248156
  const key2 = runtimeTargetKey(candidate);
247762
248157
  const node2 = input.nodes.get(candidate.nodeId);
@@ -247783,6 +248178,11 @@ function allocateRuntime(input, now = Date.now()) {
247783
248178
  reject("faulted", `${fault.reason}${fault.detail ? `\uFF1A${fault.detail}` : ""}${fault.awaitingManual ? "\uFF08\u7B49\u4EBA\u5DE5\u6062\u590D\uFF09" : ""}`);
247784
248179
  continue;
247785
248180
  }
248181
+ const nodeFault = nodeFaults.get(candidate.nodeId);
248182
+ if (nodeFault) {
248183
+ reject("faulted", `\u6574\u53F0\u673A\u5668\u6545\u969C\uFF08\u5728 ${nodeFault.runtimeKind} \u4E0A\u53D1\u73B0\uFF0C${nodeFault.reason}${nodeFault.detail ? `\uFF1A${nodeFault.detail}` : ""}\uFF09${nodeFault.awaitingManual ? "\uFF08\u7B49\u4EBA\u5DE5\u6062\u590D\uFF09" : ""}`);
248184
+ continue;
248185
+ }
247786
248186
  if (atCapacity(node2)) {
247787
248187
  reject("at-capacity", `\u5728\u8DD1 ${node2.activeRunCount} / \u4E0A\u9650 ${node2.maxConcurrent}`);
247788
248188
  continue;
@@ -247890,9 +248290,7 @@ var init_errors6 = __esm({
247890
248290
 
247891
248291
  // ../server/src/domains/runtime-allocation/service.ts
247892
248292
  function isRuntimeClassFailure(reason, errorMessage2) {
247893
- if (reason === "server-unreachable" || reason === "rate-limit" || reason === "auth-error") return true;
247894
- if (reason === "error" && errorMessage2?.startsWith("\u8FD9\u4E00\u8F6E\u6CA1\u80FD\u5728\u8282\u70B9\u4E0A\u542F\u52A8\uFF1A")) return true;
247895
- return false;
248293
+ return faultReasonOfExit(reason, errorMessage2) !== null;
247896
248294
  }
247897
248295
  function createRuntimeAllocator(ports) {
247898
248296
  const units = /* @__PURE__ */ new Map();
@@ -248058,6 +248456,8 @@ function createRuntimeAllocator(ports) {
248058
248456
  if (!resolved || !ports.clearFault) return;
248059
248457
  try {
248060
248458
  await ports.clearFault(resolved);
248459
+ const siblings = (await ports.faults().catch(() => [])).filter((f2) => f2.nodeId === resolved.nodeId && f2.runtimeKind !== resolved.runtimeKind && faultScopeOf(f2) === "node");
248460
+ for (const f2 of siblings) await ports.clearFault({ nodeId: f2.nodeId, runtimeKind: f2.runtimeKind });
248061
248461
  } catch (err) {
248062
248462
  log3(`[runtime-alloc] \u6545\u969C\u89E3\u9664\u5931\u8D25 ${resolved.nodeId}\xB7${resolved.runtimeKind}\uFF1A${String(err)}`);
248063
248463
  }
@@ -258078,8 +258478,9 @@ var init_daemon_adapter = __esm({
258078
258478
  hub.addDisconnectListener((daemonId) => this.onNodeDown(daemonId));
258079
258479
  hub.addConnectListener((daemon) => this.onNodeUp(daemon));
258080
258480
  hub.addSessionConnectListener?.((i) => this.onSessionUp(i.dispatchId));
258081
- hub.registerSessionClaimCheck?.(({ dispatchId }) => {
258481
+ hub.registerSessionClaimCheck?.(({ dispatchId, nodeId }) => {
258082
258482
  const e = this.pending.get(dispatchId);
258483
+ if (e && nodeId && e.nodeId !== nodeId) return "settled";
258083
258484
  if (e) return e.exited ? "settled" : "live";
258084
258485
  return this.settledDispatchIds.has(dispatchId) ? "settled" : "unknown";
258085
258486
  });
@@ -258515,6 +258916,7 @@ var init_daemon_adapter = __esm({
258515
258916
  appendWaiters: []
258516
258917
  };
258517
258918
  this.pending.set(dispatchId, entry);
258919
+ this.settledDispatchIds.delete(dispatchId);
258518
258920
  const sessionAuth = this.sessionAuth ? { ...this.sessionAuth.issue({ nodeId, dispatchId }), gatewayUrl: this.sessionAuth.gatewayUrl } : void 0;
258519
258921
  let sent;
258520
258922
  try {
@@ -280700,6 +281102,8 @@ __export(src_exports2, {
280700
281102
  DirBlobStore: () => DirBlobStore,
280701
281103
  DisabledSkillMarketClient: () => DisabledSkillMarketClient,
280702
281104
  EMPTY_ACTOR_NAMES: () => EMPTY_ACTOR_NAMES,
281105
+ EMPTY_NODE_GRACE_MS: () => EMPTY_NODE_GRACE_MS,
281106
+ EMPTY_NODE_SWEEP_INTERVAL_MS: () => EMPTY_NODE_SWEEP_INTERVAL_MS,
280703
281107
  EscalationDeliveryWorker: () => EscalationDeliveryWorker,
280704
281108
  ExecutionContinuityService: () => ExecutionContinuityService,
280705
281109
  FakeSkillMarketClient: () => FakeSkillMarketClient,
@@ -284089,10 +284493,14 @@ var CompanyDispatchBridges = class {
284089
284493
  // ../cli/src/review-stall.ts
284090
284494
  init_src6();
284091
284495
  init_src5();
284496
+ init_stall_copy();
284092
284497
  var STALL_SOURCE_PREFIX = "sla:stall:";
284093
284498
  var REVIEW_STALL_SOURCE = "sla:review-stall";
284094
- function stallSourceFor(kind) {
284095
- return `${STALL_SOURCE_PREFIX}${kind}`;
284499
+ function stallSourceFor(kind, code2) {
284500
+ return `${STALL_SOURCE_PREFIX}${kind}${code2 ? `:${code2}` : ""}`;
284501
+ }
284502
+ function reasonCodeOf(reason) {
284503
+ return reason.kind === "accept-blocked" || reason.kind === "idle-unexplained" ? reason.code : void 0;
284096
284504
  }
284097
284505
  function isStallSource(source) {
284098
284506
  return !!source && (source === REVIEW_STALL_SOURCE || source.startsWith(STALL_SOURCE_PREFIX));
@@ -284104,22 +284512,23 @@ function stallBackoffMsFor(priorCards, ladder = DEFAULT_STALL_BACKOFF_MS) {
284104
284512
  }
284105
284513
  var isOpenReview = (r) => !!r && !r.verdict && !r.endedAt && !r.cancelledAt;
284106
284514
  function describeReviewStall(reqs, reviews, conclusion) {
284107
- if (reqs.length === 0) {
284108
- if (conclusion === null) {
284109
- return "\u8FD9\u4E00\u6B65**\u5DF2\u7ECF\u4EA4\u4E86\u7A3F\uFF0C\u4F46\u6CA1\u6709\u6536\u53E3**\uFF08\u6CA1\u8D70 conclude\uFF09\uFF0C\u5F15\u64CE\u56E0\u6B64\u8FDE\u8BC4\u5BA1/\u9A8C\u6536\u90FD\u8FDB\u4E0D\u53BB\u3002";
284110
- }
284111
- return "\u8FD9\u4E00\u6B65**\u6CA1\u6709\u914D\u5230\u4EFB\u4F55\u8BC4\u5BA1\u4EBA**\uFF08\u672C\u7C7B\u578B\u6CA1\u6709\u8BC4\u5BA1\u95F8\uFF0C\u6216\u5EFA\u5355\u65F6\u8BC4\u5BA1\u4EBA\u6CA1\u5C55\u5F00\u51FA\u6765\uFF09\u3002";
284112
- }
284515
+ const code2 = reviewStallFactsCode(reqs, conclusion);
284516
+ if (code2 === "no-conclude") return "\u8FD9\u4E00\u6B65\u4EA4\u4E86\u6210\u679C\uFF0C\u4F46\u6267\u884C\u4EBA\u6CA1\u6709\u8BF4\u660E\u5DF2\u7ECF\u505A\u5B8C\uFF0C\u6240\u4EE5\u6CA1\u6709\u8FDB\u5165\u5BA1\u6838\u3002";
284517
+ if (code2 === "no-reviewers") return "\u8FD9\u4E00\u6B65\u6CA1\u6709\u914D\u5230\u5BA1\u6838\u4EBA\u3002";
284113
284518
  const lines = reqs.map((r) => {
284114
284519
  const lr = r.latestReviewId ? reviews.find((x2) => x2.id === r.latestReviewId) : void 0;
284115
- if (!lr) return `\u300C${r.reviewGroup}\u300D\uFF1A\u8FD8\u6CA1\u5F00\u51FA\u8BC4\u5BA1\u2014\u2014\u8BC4\u5BA1\u6D3E\u4E0D\u51FA\u53BB`;
284116
- if (lr.verdict) return `\u300C${r.reviewGroup}\u300D\uFF1A\u5DF2\u6295 ${lr.verdict}`;
284117
- if (lr.cancelledAt || lr.endedAt) return `\u300C${r.reviewGroup}\u300D\uFF1A\u8BC4\u5BA1\u5DF2\u8D85\u65F6\u5931\u8D25\u3001\u4E0D\u4F1A\u518D\u91CD\u5F00`;
284118
- return `\u300C${r.reviewGroup}\u300D\uFF1A\u5728\u7B49 ${r.reviewerActorId} \u6295\u7968`;
284520
+ if (!lr) return `\u300C${r.reviewGroup}\u300D\uFF1A\u8FD8\u6CA1\u53D1\u8D77\u5BA1\u6838`;
284521
+ if (lr.verdict) return `\u300C${r.reviewGroup}\u300D\uFF1A\u5DF2\u7ED9\u51FA\u7ED3\u8BBA\uFF08${lr.verdict === "approve" ? "\u5BA1\u6838\u901A\u8FC7" : "\u5BA1\u6838\u4E0D\u901A\u8FC7"}\uFF09`;
284522
+ if (lr.cancelledAt || lr.endedAt) return `\u300C${r.reviewGroup}\u300D\uFF1A\u5BA1\u6838\u8D85\u65F6\u6CA1\u6709\u5B8C\u6210\uFF0C\u4E0D\u4F1A\u518D\u91CD\u65B0\u53D1\u8D77`;
284523
+ return `\u300C${r.reviewGroup}\u300D\uFF1A\u5728\u7B49 ${r.reviewerActorId} \u5BA1\u6838`;
284119
284524
  });
284120
- return `\u5404\u8BC4\u5BA1\u7EC4\u7684\u72B6\u6001\uFF1A
284525
+ return `\u5404\u5BA1\u6838\u7EC4\u7684\u72B6\u6001\uFF1A
284121
284526
  - ${lines.join("\n- ")}`;
284122
284527
  }
284528
+ function reviewStallFactsCode(reqs, conclusion) {
284529
+ if (reqs.length > 0) return void 0;
284530
+ return conclusion === null ? "no-conclude" : "no-reviewers";
284531
+ }
284123
284532
  function lastActivityAt(snap, nodeId) {
284124
284533
  let max = 0;
284125
284534
  const bump = (v2) => {
@@ -284179,34 +284588,34 @@ function renderStallReason(reason, facts) {
284179
284588
  switch (reason.kind) {
284180
284589
  case "checkpoints-exhausted":
284181
284590
  return {
284182
- headline: `\u8FD9\u4E00\u6B65**\u8FDE\u7EED ${reason.rounds} \u8F6E\u6CA1\u6709\u65B0\u589E\u4EA4\u4ED8\u5185\u5BB9**\uFF0C\u91CD\u590D\u63D0\u4EA4\u5DF2\u6709\u5FEB\u7167\u4E14\u672A\u6536\u53E3\uFF08\u65E0\u8FDB\u5C55\u4E0A\u9650 ${reason.limit}\uFF09\u2014\u2014\u5F15\u64CE\u5DF2\u6682\u505C\u81EA\u52A8\u7EED\u8DD1\uFF0C\u7B49\u5F85\u4EBA\u5DE5\u5904\u7406\u3002`,
284591
+ headline: `\u8FD9\u4E00\u6B65\u8FDE\u7EED ${reason.rounds} \u8F6E\u63D0\u4EA4\u7684\u5185\u5BB9\u90FD\u6CA1\u6709\u53D8\u5316\uFF0C\u7CFB\u7EDF\u5DF2\u505C\u6B62\u81EA\u52A8\u7EE7\u7EED\u6267\u884C\u3002`,
284183
284592
  detail: facts,
284184
- action: "\u9700\u8981\u4F60\u5904\u7406\uFF1A\u68C0\u67E5\u6700\u8FD1\u5FEB\u7167\u548C\u5269\u4F59\u5DE5\u4F5C\uFF0C\u8865\u5145\u6267\u884C\u65B9\u5411\u540E\u91CD\u8DD1\u672C\u8282\u70B9\uFF08\u91CD\u7F6E\u65E0\u8FDB\u5C55\u8BA1\u6570\uFF09\uFF1B\u5982\u679C\u5DF2\u7ECF\u5B8C\u6210\uFF0C\u8BA9\u6267\u884C\u4EBA\u63D0\u4EA4\u5B8C\u6574\u5FEB\u7167\u5E76 conclude\u3002"
284593
+ action: "\u9700\u8981\u4F60\u5904\u7406\uFF1A\u5148\u770B\u6700\u8FD1\u63D0\u4EA4\u7684\u5185\u5BB9\u548C\u8FD8\u5269\u54EA\u4E9B\u6CA1\u505A\uFF0C\u8865\u5145\u8BF4\u660E\u540E\u91CD\u65B0\u8FD0\u884C\uFF1B\u5982\u679C\u5176\u5B9E\u5DF2\u7ECF\u505A\u5B8C\uFF0C\u76F4\u63A5\u6309\u5F53\u524D\u6210\u679C\u7B97\u5B8C\u6210\u3002"
284185
284594
  };
284186
284595
  case "gate-rejections-exhausted":
284187
284596
  return {
284188
- headline: `\u8FD9\u4E00\u6B65**\u8FDE\u7EED\u88AB\u8BC4\u5BA1\u6253\u56DE ${reason.rounds} \u8F6E**\uFF08\u4E0A\u9650 ${reason.limit}\uFF09\u2014\u2014\u5F15\u64CE\u5DF2\u7ECF\u505C\u4E0B\uFF0C\u4E0D\u518D\u81EA\u52A8\u5F00\u8FD4\u5DE5\u8F6E\u3002`,
284597
+ headline: `\u8FD9\u4E00\u6B65\u8FDE\u7EED ${reason.rounds} \u6B21\u5BA1\u6838\u4E0D\u901A\u8FC7\uFF0C\u7CFB\u7EDF\u5DF2\u505C\u6B62\u81EA\u52A8\u8FD4\u5DE5\u3002`,
284189
284598
  detail: facts,
284190
- action: "\u9700\u8981\u4F60\u5904\u7406\uFF1A\u770B\u4E00\u773C\u8BC4\u5BA1\u610F\u89C1\u548C\u6267\u884C\u4EBA\u5230\u5E95\u5361\u5728\u54EA\uFF08\u591A\u534A\u662F\u5B83\u6BCF\u8F6E\u4EA4\u7684\u4E1C\u897F\u6CA1\u6709\u771F\u7684\u53D8\uFF09\uFF0C\u7136\u540E\u91CD\u8DD1\u672C\u8282\u70B9\uFF08\u91CD\u8DD1\u4F1A\u91CD\u7F6E\u8FD9\u4E2A\u8BA1\u6570\uFF09\u3001\u6362\u6267\u884C\u4EBA\uFF0C\u6216\u8005\u76F4\u63A5\u5F3A\u5236\u6536\u53E3\u3002"
284599
+ action: "\u9700\u8981\u4F60\u5904\u7406\uFF1A\u5148\u770B\u5BA1\u6838\u610F\u89C1\u548C\u6267\u884C\u4EBA\u6BCF\u6B21\u4EA4\u7684\u5185\u5BB9\uFF08\u591A\u534A\u662F\u5185\u5BB9\u6CA1\u6709\u5B9E\u8D28\u53D8\u5316\uFF09\uFF0C\u518D\u9009\u62E9\uFF1A\u91CD\u65B0\u8FD0\u884C\u3001\u6362\u6267\u884C\u4EBA\uFF0C\u6216\u76F4\u63A5\u6309\u5F53\u524D\u6210\u679C\u7B97\u5B8C\u6210\u3002"
284191
284600
  };
284192
284601
  case "review-exhausted":
284193
284602
  return {
284194
- headline: `\u300C${reason.group}\u300D\u7684\u8BC4\u5BA1**\u6D3E\u51FA\u53BB ${reason.rounds} \u6B21\u90FD\u6CA1\u7B49\u5230\u7968**\uFF08\u4E0A\u9650 ${reason.limit}\uFF09\uFF0C\u5F15\u64CE\u5DF2\u7ECF\u4E0D\u518D\u91CD\u5F00\u8FD9\u4E00\u8F6E\u3002`,
284603
+ headline: `\u300C${reason.group}\u300D\u7684\u5BA1\u6838\u53D1\u8D77\u4E86 ${reason.rounds} \u6B21\u90FD\u6CA1\u6709\u7B49\u5230\u7ED3\u679C\uFF0C\u7CFB\u7EDF\u5DF2\u505C\u6B62\u91CD\u65B0\u53D1\u8D77\u3002`,
284195
284604
  detail: facts,
284196
- action: `\u9700\u8981\u4F60\u5904\u7406\uFF1A\u91CD\u5F00\u8FD9\u4E00\u8F6E\u8BC4\u5BA1\u3001\u6362\u4E00\u4E2A\u8BC4\u5BA1\u4EBA\uFF08\u5F53\u524D\u662F ${reason.reviewerActorId}\uFF09\uFF0C\u6216\u8005\u76F4\u63A5\u5F3A\u5236\u6536\u53E3\u3002`
284605
+ action: `\u9700\u8981\u4F60\u5904\u7406\uFF1A\u91CD\u65B0\u53D1\u8D77\u8FD9\u4E00\u8F6E\u5BA1\u6838\u3001\u6362\u4E00\u4E2A\u5BA1\u6838\u4EBA\uFF08\u5F53\u524D\u662F ${reason.reviewerActorId}\uFF09\uFF0C\u6216\u76F4\u63A5\u6309\u5F53\u524D\u6210\u679C\u7B97\u5B8C\u6210\u3002`
284197
284606
  };
284198
284607
  case "accept-blocked":
284199
- return {
284200
- headline: "\u8FD9\u4E00\u6B65**\u4EA4\u4ED8\u5B8C\u6210\u4E86\uFF0C\u4F46\u6536\u53E3\u88AB\u6321\u4F4F**\u2014\u2014\u5F15\u64CE\u4E0D\u4F1A\u81EA\u5DF1\u5F80\u4E0B\u8D70\u3002",
284201
- detail: facts,
284202
- action: "\u9700\u8981\u4F60\u5904\u7406\uFF1A\u6309\u4E0A\u9762\u7684\u539F\u56E0\u4FEE\u6389\u6321\u4F4F\u6536\u53E3\u7684\u4E1C\u897F\uFF0C\u6216\u91CD\u8DD1\u672C\u8282\u70B9\u62FF\u4E00\u4E2A\u53EF\u6536\u53E3\u7684\u65B0\u7248\u672C\u3002"
284203
- };
284204
- case "idle-unexplained":
284205
- return {
284206
- headline: "\u8FD9\u4E2A\u8282\u70B9\u4EA4\u4ED8\u5B8C\u6210\u4E4B\u540E**\u957F\u65F6\u95F4\u6CA1\u6709\u4EFB\u4F55\u52A8\u9759\uFF0C\u800C\u4E14\u6CA1\u6709\u4EFB\u4F55\u5DF2\u77E5\u539F\u56E0\u80FD\u89E3\u91CA**\u3002",
284207
- detail: facts,
284208
- action: "\u9700\u8981\u4F60\u770B\u4E00\u773C\u5B83\u5230\u5E95\u5361\u5728\u54EA\u2014\u2014\u8FD9\u4E00\u6863\u662F\u515C\u5E95\u7F51\uFF0C\u8BF4\u660E\u5B83\u4E0D\u5C5E\u4E8E\u6211\u4EEC\u5DF2\u77E5\u7684\u4EFB\u4F55\u4E00\u79CD\u5361\u6CD5\u3002"
284209
- };
284608
+ case "idle-unexplained": {
284609
+ const copy = reason.code ? notDoneCopy(reason.code, reason.kind === "accept-blocked" && reason.deadReason ? { deadReason: reason.deadReason } : {}) : void 0;
284610
+ if (copy) {
284611
+ return {
284612
+ headline: "\u8FD9\u4E00\u8F6E\u7ED3\u675F\u4E86\uFF0C\u4F46\u8FD8\u4E0D\u80FD\u7B97\u5B8C\u6210\u3002",
284613
+ detail: reason.kind === "accept-blocked" ? `\u539F\u56E0\uFF1A${copy.reason}\u3002` : facts,
284614
+ action: `\u9700\u8981\u4F60\u5904\u7406\uFF1A${copy.fix}\u3002`
284615
+ };
284616
+ }
284617
+ return reason.kind === "accept-blocked" ? { headline: "\u8FD9\u4E00\u8F6E\u7ED3\u675F\u4E86\uFF0C\u4F46\u8FD8\u4E0D\u80FD\u7B97\u5B8C\u6210\u3002", detail: facts, action: "\u9700\u8981\u4F60\u5904\u7406\uFF1A\u91CD\u65B0\u8FD0\u884C\uFF0C\u6216\u76F4\u63A5\u6309\u5F53\u524D\u6210\u679C\u7B97\u5B8C\u6210\u3002" } : { headline: "\u8FD9\u4E00\u6B65\u505A\u5B8C\u4E4B\u540E\u957F\u65F6\u95F4\u6CA1\u6709\u52A8\u9759\uFF0C\u7CFB\u7EDF\u627E\u4E0D\u5230\u539F\u56E0\u3002", detail: facts, action: "\u9700\u8981\u4F60\u770B\u4E00\u4E0B\u5B83\u5361\u5728\u54EA\u91CC\u3002" };
284618
+ }
284210
284619
  }
284211
284620
  }
284212
284621
  function collectReviewStallSignals(snap, now, opts) {
@@ -284237,7 +284646,7 @@ function collectReviewStallSignals(snap, now, opts) {
284237
284646
  artifactId: node2.id,
284238
284647
  sinceMs,
284239
284648
  reason,
284240
- source: stallSourceFor(reason.kind),
284649
+ source: stallSourceFor(reason.kind, reasonCodeOf(reason)),
284241
284650
  ...renderStallReason(reason, facts),
284242
284651
  priorCards: priorCards.length,
284243
284652
  ...sinceLastReportMs !== void 0 ? { sinceLastReportMs } : {}
@@ -284258,13 +284667,19 @@ function collectReviewStallSignals(snap, now, opts) {
284258
284667
  if (hasUnexemptedVeto(scanBlockersForNode(state, node2, lw), "autoConclude")) continue;
284259
284668
  if (reqs.some((r) => isOpenReview(r.latestReviewId ? snap.reviews.find((x2) => x2.id === r.latestReviewId) : void 0))) continue;
284260
284669
  const blocked = acceptBlock(state, lw.id);
284670
+ if (blocked?.code === "no-output" && node2.latestAcceptId) continue;
284261
284671
  if (blocked) {
284672
+ const reason = {
284673
+ kind: "accept-blocked",
284674
+ code: blocked.code,
284675
+ ...blocked.code === "work-dead" && lw.rejectedReason ? { deadReason: lw.rejectedReason } : {}
284676
+ };
284262
284677
  out.push({
284263
284678
  artifactId: node2.id,
284264
284679
  sinceMs,
284265
- reason: { kind: "accept-blocked", code: blocked.code },
284266
- source: stallSourceFor("accept-blocked"),
284267
- ...renderStallReason({ kind: "accept-blocked", code: blocked.code }, blocked.detail),
284680
+ reason,
284681
+ source: stallSourceFor("accept-blocked", blocked.code),
284682
+ ...renderStallReason(reason, blocked.detail),
284268
284683
  priorCards: priorCards.length,
284269
284684
  ...sinceLastReportMs !== void 0 ? { sinceLastReportMs } : {}
284270
284685
  });
@@ -284273,7 +284688,8 @@ function collectReviewStallSignals(snap, now, opts) {
284273
284688
  if (sinceMs <= opts.idleFallbackMs) continue;
284274
284689
  scanTouched ??= nodesScanWillTouch(snap, new Date(now).toISOString(), tripCtx);
284275
284690
  if (scanTouched.has(node2.id)) continue;
284276
- emit({ kind: "idle-unexplained" });
284691
+ const factsCode = reviewStallFactsCode(reqs, lw.conclusion ?? null);
284692
+ emit({ kind: "idle-unexplained", ...factsCode ? { code: factsCode } : {} });
284277
284693
  }
284278
284694
  return out;
284279
284695
  }
@@ -284291,7 +284707,7 @@ function renderStallCard(st, now, fmtSince) {
284291
284707
  ` : "";
284292
284708
  const repeatNote = st.priorCards > 0 && st.sinceLastReportMs !== void 0 ? `
284293
284709
 
284294
- \uFF08\u8FD9\u662F\u540C\u4E00\u4E2A\u95EE\u9898\u7684\u7B2C ${st.priorCards + 1} \u6B21\u63D0\u9192\uFF0C\u4E0A\u4E00\u6B21\u662F ${fmtSince(new Date(now - st.sinceLastReportMs).toISOString(), now)}\u3002\u8BF7\u5904\u7406\u540E\u518D\u5173\u95ED\u5347\u7EA7\uFF1B\u82E5\u8282\u70B9\u4E4B\u540E\u4ECD\u6EDE\u7559\uFF0C\u7CFB\u7EDF\u4F1A\u6309 1 \u5C0F\u65F6 \u2192 4 \u5C0F\u65F6 \u2192 12 \u5C0F\u65F6\u7684\u95F4\u9694\u91CD\u65B0\u63D0\u9192\u3002\uFF09` : "";
284710
+ \uFF08\u8FD9\u662F\u540C\u4E00\u4E2A\u95EE\u9898\u7684\u7B2C ${st.priorCards + 1} \u6B21\u63D0\u9192\uFF0C\u4E0A\u4E00\u6B21\u662F ${fmtSince(new Date(now - st.sinceLastReportMs).toISOString(), now)}\u3002\u8BF7\u5904\u7406\u540E\u518D\u5173\u95ED\u8FD9\u6761\u5F85\u529E\uFF1B\u5982\u679C\u8FD9\u4E00\u6B65\u4E4B\u540E\u4ECD\u7136\u505C\u7740\uFF0C\u7CFB\u7EDF\u4F1A\u6309 1 \u5C0F\u65F6 \u2192 4 \u5C0F\u65F6 \u2192 12 \u5C0F\u65F6\u7684\u95F4\u9694\u91CD\u65B0\u63D0\u9192\u3002\uFF09` : "";
284295
284711
  return {
284296
284712
  // ★ 通知型(提案 3c):只在等待面陈列,不挡派发/收口/自动验收——以前它与人提的上报共用
284297
284713
  // 三轴 veto,于是**告警在制造它要报告的那个现象**。
@@ -284718,6 +285134,10 @@ function makeResumeRetryProxy(first, spawnFallback, runtimeRetry) {
284718
285134
  failed = true;
284719
285135
  }
284720
285136
  if (sawOutput) {
285137
+ const exit4 = runtimeRetry ? await exitOf(failure2) : {};
285138
+ if (runtimeRetry && exit4.reason === "server-unreachable" && runtimeRetry.shouldRetry(exit4)) {
285139
+ return retryOnce(() => runtimeRetry.spawn(exit4));
285140
+ }
284721
285141
  if (failed) throw failure2;
284722
285142
  return;
284723
285143
  }
@@ -287639,6 +288059,9 @@ async function startServe(opts) {
287639
288059
  nodeTokens,
287640
288060
  enrollTokens,
287641
288061
  nodeStore,
288062
+ // ADR 0629 修订(2026-09-29,方申):一个运行时都没绑的节点不该存在。前端关掉接入第 3 步时当场删;
288063
+ // 人直接关浏览器、离开 onboarding 时由这里兜底——连续空着 30 分钟(= 入网令牌有效期)就删。
288064
+ emptyNodeSweep: { graceMs: EMPTY_NODE_GRACE_MS, intervalMs: EMPTY_NODE_SWEEP_INTERVAL_MS },
287642
288065
  registry: registryStore,
287643
288066
  gatewayUrl: opts.gatewayUrl ?? `ws://127.0.0.1:${opts.port ?? 7320}/node-gateway`,
287644
288067
  repoRemote: opts.nodeRepoRemote ?? "git@github.com:open-friday/oasis-core.git",
@@ -288453,7 +288876,7 @@ async function startServe(opts) {
288453
288876
  escalateDiscussRetain: new FileSideMap(path31.join(opts.dir, "escalate-discuss-retain.json")),
288454
288877
  // 切片 2b:代署 escalate 发起会话保留(落盘)
288455
288878
  dispatchChat: automationDispatchChat = async (chatReq) => {
288456
- const dispatchChatImpl = async ({ actorId, message, dispatchId: requestDispatchId, admitDispatch, executionProfile, knowledgeAttemptKind, requiredConnectorSlugs, env: requestEnv, sessionId, chatSessionId, chatTurnId, attachments, workspace, companyId, model: sessionModelOverride, parentConversationSnapshot, extraSystemPrompt, delegatedChildTurn, workspaceBundle }) => {
288879
+ const dispatchChatImpl = async ({ actorId, message, dispatchId: requestDispatchId, admitDispatch, executionProfile, knowledgeAttemptKind, requiredConnectorSlugs, env: requestEnv, sessionId, chatSessionId, chatTurnId, attachments, workspace, companyId, model: sessionModelOverride, parentConversationSnapshot, extraSystemPrompt, delegatedChildTurn, workspaceBundle, retargetClaimedDispatch }) => {
288457
288880
  const isolatedKnowledgeTurn = executionProfile === "knowledge-single-turn";
288458
288881
  const discussionBackground = executionProfile === "discussion-background";
288459
288882
  const isolatedBackground = isolatedKnowledgeTurn || discussionBackground;
@@ -288479,7 +288902,8 @@ async function startServe(opts) {
288479
288902
  const traceRunId = requestDispatchId ? stableChatTraceRunId(requestDispatchId) : `chat-run:${(0, import_node_crypto91.randomUUID)()}`;
288480
288903
  const artifactId = requestDispatchId ? `artifact:chat:${deterministicUuid(`chat-artifact:${requestDispatchId}`)}` : `artifact:chat:${(0, import_node_crypto91.randomUUID)()}`;
288481
288904
  let stableDispatch = requestDispatchId ? await resolveStableChatDispatch({ store: await traceStoreFor(runCompanyId), runId: traceRunId }) : null;
288482
- if (stableDispatch && !stableDispatch.shouldDispatch) {
288905
+ const retargetClaim = Boolean(retargetClaimedDispatch && stableDispatch && allocatedTurn);
288906
+ if (stableDispatch && !stableDispatch.shouldDispatch && !retargetClaim) {
288483
288907
  if (isHumanSubmitDispatchId(requestDispatchId)) {
288484
288908
  throw markChatDispatchStarted(new ApiError(409, CHAT_SUBMISSION_EXISTS, "\u8FD9\u6B21\u63D0\u4EA4\u4E4B\u524D\u5DF2\u7ECF\u6D3E\u53D1\u8FC7\uFF0C\u7ED3\u679C\u4EE5\u8BB0\u5F55\u4E3A\u51C6"));
288485
288909
  }
@@ -288497,7 +288921,7 @@ async function startServe(opts) {
288497
288921
  let binding;
288498
288922
  let runtimeTargetSource = "actor_binding";
288499
288923
  let allocatedModelOverride;
288500
- if (stableDispatch) {
288924
+ if (stableDispatch && !retargetClaim) {
288501
288925
  binding = { actorId, ...stableDispatch.target, status: "active" };
288502
288926
  } else {
288503
288927
  const currentBinding = await resolveCompanyBinding(runCompanyId, actorId);
@@ -288677,6 +289101,29 @@ async function startServe(opts) {
288677
289101
  }
288678
289102
  }
288679
289103
  const stableTraceWasPresent = Boolean(stableDispatch);
289104
+ if (retargetClaim && stableDispatch && (stableDispatch.target.nodeId !== binding.nodeId || stableDispatch.target.runtimeKind !== binding.runtimeKind)) {
289105
+ const store = await traceStoreFor(runCompanyId);
289106
+ const prior = stableDispatch.run;
289107
+ const run = await store.updateRun(traceRunId, {
289108
+ runtimeKind: traceRuntimeKind(binding.runtimeKind),
289109
+ runtimeInstanceId: `runtime:${binding.nodeId}:${binding.runtimeKind}`,
289110
+ status: "running",
289111
+ endedAt: null,
289112
+ /* 上一次在旧机器上的退出原因与报错原文一并清掉:这一行接下来记的是新目标上这一次的结果。
289113
+ 不清的话,新机器上跑成了,记录却是「成功 + 一句旧机器的报错」(v2 run chat-run:538d40f8)。
289114
+ 旧机器那次失败的原文仍留在事件流里,也记进了故障标记与失败链。 */
289115
+ exitReason: null,
289116
+ errorMessage: null,
289117
+ metadata: {
289118
+ ...prior.metadata ?? {},
289119
+ nodeId: binding.nodeId,
289120
+ runtimeKind: binding.runtimeKind,
289121
+ runtimeTargetSource: "session_override"
289122
+ }
289123
+ });
289124
+ stableDispatch = { run, target: { nodeId: binding.nodeId, runtimeKind: binding.runtimeKind }, shouldDispatch: true };
289125
+ console.log(`[runtime-alloc] ${allocationUnitOfWork} \u6362\u5019\u9009\u91CD\u8BD5\uFF1A\u8BA4\u9886\u4ECE ${prior.runtimeInstanceId ?? "?"} \u6539\u5230 runtime:${binding.nodeId}:${binding.runtimeKind}`);
289126
+ }
288680
289127
  if (requestDispatchId && !stableDispatch) {
288681
289128
  stableDispatch = await resolveStableChatDispatch({
288682
289129
  store: await traceStoreFor(runCompanyId),
@@ -289156,8 +289603,17 @@ async function startServe(opts) {
289156
289603
  ...exit3.detail ? { errorMessage: exit3.detail } : {}
289157
289604
  });
289158
289605
  }
289159
- console.log(`[runtime-alloc] ${unitOfWork} \u56E0 ${exit3.reason ?? "runtime \u6545\u969C"} \u5931\u8D25\u4E14\u672A\u4EA7\u51FA\u4EFB\u4F55\u8F93\u51FA \u2192 \u6362\u5019\u9009\u91CD\u8D77\u8FD9\u4E00\u8F6E`);
289160
- return dispatchChatImpl(chatReq);
289606
+ console.log(`[runtime-alloc] ${unitOfWork} \u56E0 ${exit3.reason ?? "runtime \u6545\u969C"} \u5931\u8D25 \u2192 \u6362\u5019\u9009\u91CD\u8D77\u8FD9\u4E00\u8F6E`);
289607
+ const { admitDispatch: _alreadyAdmitted, ...retryReq } = chatReq;
289608
+ if (!chatReq.sessionId) return dispatchChatImpl({ ...retryReq, retargetClaimedDispatch: true });
289609
+ const message = chatReq.fallbackMessage ?? await buildResumeFallbackMessage(chatReq) ?? chatReq.message;
289610
+ return dispatchChatImpl({
289611
+ ...retryReq,
289612
+ sessionId: void 0,
289613
+ fallbackMessage: void 0,
289614
+ message,
289615
+ retargetClaimedDispatch: true
289616
+ });
289161
289617
  }
289162
289618
  };
289163
289619
  })() : void 0;
@@ -290339,9 +290795,10 @@ async function startServe(opts) {
290339
290795
  checkDispatchable: async ({ actor, binding }) => {
290340
290796
  try {
290341
290797
  if (!binding.nodeId) return null;
290342
- const foreign = foreignNodeBlock(await nodeStore.getNode(binding.nodeId).catch(() => null), companyId);
290798
+ const boundNode = await nodeStore.getNode(binding.nodeId).catch(() => null);
290799
+ const foreign = foreignNodeBlock(boundNode, companyId);
290343
290800
  if (foreign) return { reason: "node-not-in-company", errorMessage: foreign };
290344
- const drift = runtimeDriftBlock(await nodeStore.listRuntimes(binding.nodeId), binding.nodeId, binding.runtimeKind);
290801
+ const drift = runtimeDriftBlock(await nodeStore.listRuntimes(binding.nodeId), binding.nodeId, binding.runtimeKind, boundNode?.name);
290345
290802
  if (drift) return drift;
290346
290803
  const health = healthOfNode(binding.nodeId);
290347
290804
  if (health.healthy) return null;
@@ -297131,7 +297588,7 @@ async function waitForCommandAuthorization(base, headers, draftId, fallback) {
297131
297588
  }
297132
297589
  }
297133
297590
  }
297134
- var CLI_VERSION = "2.2.92" ? "2.2.92" : "dev";
297591
+ var CLI_VERSION = "2.2.94" ? "2.2.94" : "dev";
297135
297592
  function apiHeaders(token, company) {
297136
297593
  const headers = {
297137
297594
  authorization: `Bearer ${token}`,
@@ -301102,7 +301559,7 @@ function shimScript() {
301102
301559
  }
301103
301560
 
301104
301561
  // src/index.ts
301105
- var PKG_VERSION = true ? "2.2.92" : "dev";
301562
+ var PKG_VERSION = true ? "2.2.94" : "dev";
301106
301563
  var LOCAL_BIN = localBin();
301107
301564
  var NPM_PREFIX = npmPrefix();
301108
301565
  var INSTANCE = DEFAULT_INSTANCE;