oasis_test 0.1.83 → 0.1.85

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 +660 -128
  2. package/package.json +2 -2
package/dist/index.js CHANGED
@@ -2399,6 +2399,15 @@ function validateUpsertEvaluationRubricInput(value2) {
2399
2399
  if (input.status !== void 0 && input.status !== "active" && input.status !== "archived") {
2400
2400
  throw new Error("status \u53EA\u80FD\u662F active \u6216 archived");
2401
2401
  }
2402
+ if (input.scope !== void 0 && input.scope !== "shared" && input.scope !== "case") {
2403
+ throw new Error("scope \u53EA\u80FD\u662F shared \u6216 case");
2404
+ }
2405
+ if (input.scope === "case" && !input.ownerCaseId?.trim()) {
2406
+ throw new Error("Case \u7EA7\u6807\u51C6\u5FC5\u987B\u6307\u660E\u5F52\u5C5E\u7684 caseId");
2407
+ }
2408
+ if (input.scope !== "case" && input.ownerCaseId) {
2409
+ throw new Error("\u5171\u4EAB\u6807\u51C6\u4E0D\u80FD\u6307\u5B9A ownerCaseId");
2410
+ }
2402
2411
  return {
2403
2412
  ...typeof input.rubricId === "string" && input.rubricId ? { rubricId: input.rubricId } : {},
2404
2413
  name: input.name.trim(),
@@ -2406,13 +2415,25 @@ function validateUpsertEvaluationRubricInput(value2) {
2406
2415
  ...typeof input.description === "string" && input.description.trim() ? { description: input.description.trim() } : {},
2407
2416
  promptSection: input.promptSection,
2408
2417
  outputSpec: validateEvaluationRubricOutputSpec(input.outputSpec),
2409
- ...input.status ? { status: input.status } : {}
2418
+ ...input.status ? { status: input.status } : {},
2419
+ ...input.scope ? { scope: input.scope } : {},
2420
+ ...input.ownerCaseId?.trim() ? { ownerCaseId: input.ownerCaseId.trim() } : {}
2410
2421
  };
2411
2422
  }
2412
2423
  function rubricVersionOf(rubrics, fallback) {
2413
2424
  if (!rubrics.length) return fallback;
2414
2425
  return rubrics.map((rubric) => `${rubric.rubricId}@${rubric.version}`).join("+");
2415
2426
  }
2427
+ function rubricRefsOf(rubrics) {
2428
+ return rubrics.map((rubric) => ({
2429
+ rubricId: rubric.rubricId,
2430
+ version: rubric.version,
2431
+ name: rubric.name,
2432
+ kind: rubric.kind,
2433
+ scope: rubric.scope,
2434
+ sectionKey: rubric.outputSpec.sectionKey
2435
+ }));
2436
+ }
2416
2437
  var DIMENSION_ID;
2417
2438
  var init_evaluation_rubric = __esm({
2418
2439
  "../contract/src/evaluation-rubric.ts"() {
@@ -4828,7 +4849,9 @@ function memoryHowToLines() {
4828
4849
  `- **\u7528\u6237\u660E\u663E\u4E0D\u6EE1 / \u5931\u671B / \u53CD\u590D\u50AC**\u2192 \u6211\u54EA\u91CC\u6CA1\u8FBE\u5230\u9884\u671F\u3001\u4E0B\u6B21\u600E\u4E48\u907F\u5F00\uFF1F`,
4829
4850
  `- **\u88AB\u8981\u6C42\u8FD4\u5DE5 / \u6253\u56DE**\u2192 \u8FD9\u662F\u300C\u9879\u76EE\u7279\u6709\u7684\u5751\u300D\uFF0C\u8FD8\u662F\u300C\u6211\u8FD9\u7C7B\u6D3B\u513F\u53CD\u590D\u72AF\u7684\u6BDB\u75C5\u300D\uFF1F`,
4830
4851
  `\u5224\u636E\u4E00\u81F4\uFF1A\u628A\u9879\u76EE\u540D\u53BB\u6389\u8FD9\u53E5\u8BDD\u8FD8\u6210\u7ACB\u5417\u2014\u2014\u6210\u7ACB \`--scope actor\`\uFF0C\u53EA\u5728\u672C\u9879\u76EE\u6210\u7ACB \`--scope project\`\u3002`,
4831
- `\u5199**\u7ED3\u8BBA**\uFF08"\u4E0B\u6B21\u8BE5\u600E\u4E48\u505A\uFF0C\u4E00\u53E5\u8BDD"\uFF09\uFF0C\u4E0D\u5199\u7ECF\u8FC7\u3002`
4852
+ `\u5199**\u7ED3\u8BBA**\uFF08"\u4E0B\u6B21\u8BE5\u600E\u4E48\u505A\uFF0C\u4E00\u53E5\u8BDD"\uFF09\uFF0C\u4E0D\u5199\u7ECF\u8FC7\u3002`,
4853
+ `**\u987A\u5E8F**\uFF1A"\u6536\u5DE5\u524D"\uFF1D**\u7ED9\u51FA\u6700\u7EC8\u7B54\u590D\u4E4B\u524D**\uFF0C\u4E0D\u662F\u4E4B\u540E\u3002\`mem-write\` \u505A\u5B8C\u518D\u5199\u6B63\u6587\uFF0C`,
4854
+ `\u522B\u628A\u300C\u8BB0\u5FC6\u5DF2\u4FDD\u5B58\u300D\u8FD9\u7C7B\u786E\u8BA4\u7559\u6210\u4F60\u7684\u6700\u540E\u4E00\u53E5\u2014\u2014\u5C55\u793A\u9762\u53EA\u628A\u6700\u540E\u4E00\u6BB5\u6B63\u6587\u5F53\u56DE\u590D\uFF0C\u6536\u5C3E\u786E\u8BA4\u4F1A\u9876\u6389\u771F\u6B63\u7684\u7B54\u590D\u3002`
4832
4855
  ];
4833
4856
  }
4834
4857
  function memoryReflectionEscalation(opts) {
@@ -4951,7 +4974,7 @@ function commandReference(opts) {
4951
4974
  `**\u5199**`,
4952
4975
  opts.handler.submitInstruction({ artifactId: id, isPart: true, action: "produce" }),
4953
4976
  `- \`oasis gap ${id} --desc "<\u7F3A\u4EC0\u4E48>"\` \u2014\u2014 **\u505A\u4E0D\u4E0B\u53BB\u65F6\u62A5**\uFF1A\u7F3A\u8F93\u5165 / \u7F3A\u4FE1\u606F / \u8981\u4EBA\u51B3\u7B56 / \u7F3A\u80FD\u529B\uFF08\u5DE5\u5177\u88C5\u4E0D\u4E0A\u7B49\uFF09\u90FD\u7B97\u3002\`--desc\` \u8BF4\u6E05\u7F3A\u4EC0\u4E48\u3001\u4E3A\u4EC0\u4E48\u5361\u4F4F\uFF08**\u4F60\u5728\u54EA\u4E2A\u90E8\u4EF6\u7CFB\u7EDF\u5DF2\u77E5\u9053\uFF0C\u4E0D\u7528\u4F60\u586B**\uFF09\u3002\u62A5\u5B8C**\u6536\u5DE5**\uFF1A\u8FD9\u6761\u90E8\u4EF6\u4F1A\u6682\u505C\uFF0C\u7BA1\u7406\u8005\u5206\u8BCA\uFF08\u56FE\u4E0A\u8865 / \u627E\u4EBA\uFF09\uFF1B\u9700\u6C42\u6EE1\u8DB3\u540E\u7CFB\u7EDF\u4F1A\u5E26\u7740\u89E3\u6CD5\u8BF4\u660E\u91CD\u65B0\u5524\u8D77\u4F60\uFF1B\u671F\u95F4\u522B propose\u3002`,
4954
- `- \`oasis annotate <\u4E0A\u6E38id> --from ${id} --part <\u4F60\u7684\u90E8\u4EF6> --body "<\u7591\u95EE>"\` \u2014\u2014 \u5BF9\u4E0A\u6E38\u63D0\u7591\u95EE/\u5F02\u8BAE\uFF08\u56DE\u538B\uFF09\u3002\`<\u4E0A\u6E38id>\`=\u88AB\u95EE\u7684\u4E0A\u6E38\uFF1B\`--from\`=\u672C\u4EA7\u7269\uFF1B\`--part\`=\u4F60\u8FD9\u4E2A\u90E8\u4EF6\uFF1B\`--body\`=\u5177\u4F53\u95EE\u9898\u3002\u63D0\u5B8C**\u6536\u5DE5**\u7B49\u7B54\u590D\uFF1B\u63D0\u95EE\u4E4B\u540E\u522B propose\uFF08\u591A\u4EFB\u52A1\u65F6\u540C\u6837**\u5148\u4EA4\u4ED8\u540E\u62A5\u969C**\uFF09\u3002`,
4977
+ `- \`oasis annotate <\u76EE\u6807\u4EA7\u7269id> --body "<\u4F60\u8981\u8BF4\u7684>"\` \u2014\u2014 **\u5728\u522B\u4EBA\u7684\u4EA7\u7269\u4E0A\u5199\u4E00\u6761\u8BC4\u8BBA**\u3002\u8BF4\u4EC0\u4E48\u7531\u4F60\u5B9A\uFF1A\u7591\u95EE\u3001\u5F02\u8BAE\u3001\u4FEE\u6539\u610F\u89C1\u3001\u8981\u6C42\u8FD4\u5DE5\u3001\u5224\u5B9A\u672A\u8FBE\u6807\u3001\u6216\u53EA\u662F\u63D0\u9192\u2014\u2014\u4F5C\u7528\u53D6\u51B3\u4E8E\u4F60\u5199\u7684\u5185\u5BB9\uFF0C\u547D\u4EE4\u672C\u8EAB\u4E0D\u533A\u5206\u3002\u5199\u7ED9**\u4F60\u7684\u4E0A\u6E38**\u65F6\uFF0C\u8FD9\u6761\u8BC4\u8BBA\u81EA\u52A8\u5E26\u4E0A\u963B\u585E\u529B\uFF1A**\u4E0A\u6E38\u7B54\u590D\u5B83\u4E4B\u524D\u4E0D\u80FD\u6536\u5C3E**\uFF0C\u4E14\u5B83\u7B54\u590D\u540E\u7CFB\u7EDF\u4F1A\u91CD\u65B0\u5524\u8D77\u4F60\uFF08\u4F60\u5728\u54EA\u4E2A\u90E8\u4EF6\u3001\u4EE3\u8868\u54EA\u4E2A\u4EA7\u7269\uFF0C\u7CFB\u7EDF\u90FD\u5DF2\u77E5\u9053\uFF0C\u4E0D\u7528\u4F60\u586B\uFF09\u3002`,
4955
4978
  ``,
4956
4979
  `**\u8BFB**\uFF08\u4F60\u662F\u53F6\u5B50\u5DE5\u4EBA\uFF0C\u53EA\u770B\u4F60\u4F9D\u8D56\u7684\u4E1C\u897F\uFF0C\u4E0D\u770B\u5168\u5C40\uFF09`,
4957
4980
  `- \`oasis status <id>\` / \`oasis content <id|revisionId> [path]\` \u2014\u2014 \u770B\u4F60\u4F9D\u8D56\u7684\u5144\u5F1F\u90E8\u4EF6 / \u4E0A\u6E38\u8F93\u5165\u7684\u72B6\u6001\u7D22\u5F15\u4E0E\u5B9E\u9645\u5185\u5BB9\uFF08\`status\` \u7ED9\u5168\u90E8\u7248\u672C\u7684\u5B8C\u6574 id + \u8BC4\u5BA1\u7968 + \u6279\u6CE8 id\uFF1B\`content\` \u4F20 \`revision:\u2026\` \u53D6\u6307\u5B9A\u7248\u672C\u6B63\u6587\u3001\u4F20 \`annotation:\u2026\`/\`review:\u2026\` \u53D6\u6279\u6CE8\u6216\u8BC4\u5BA1\u7968\u5168\u6587\uFF09\u3002`
@@ -4971,8 +4994,8 @@ function commandReference(opts) {
4971
4994
  write.push(
4972
4995
  `- \`oasis conclude ${id}\` \u2014\u2014 \u5BA3\u5E03\u4EA7\u7269**\u5B8C\u6574\u3001\u53EF\u88AB\u4E0B\u6E38\u6D88\u8D39**\uFF0C\u76D6\u4E00\u4E2A\u91CC\u7A0B\u7891\u3002\u524D\u63D0\uFF1A\u961F\u5217\u5DF2\u6E05\u7A7A\u3001\u4E14\u65E0\u672A\u89E3\u6790 gap / \u672A\u7B54\u590D\u56DE\u538B\uFF08\u5426\u5219\u88AB\u62D2\uFF09\uFF1B\u6709\u672A\u5904\u7F6E\u6765\u4FE1\u5148\u5904\u7F6E\u5B8C\uFF08\u56DE\u590D\u6216\u6539\u540E\u56DE\u590D\uFF09\u2014\u2014\u7CFB\u7EDF\u4E0D\u4F1A\u5728\u6709\u6765\u4FE1\u65F6\u66FF\u4F60\u81EA\u52A8\u6536\u5C3E\u3002\u76D6\u5B8C\u4E0B\u6E38\u6536\u5230\u901A\u77E5\u3002`,
4973
4996
  `- \`oasis add-revision ${id} --desc "<\u8FD9\u90E8\u4EF6\u505A\u4EC0\u4E48>" [--name <\u90E8\u4EF6\u540D>] [--role <\u89D2\u8272>]\` \u2014\u2014 \u628A\u672C\u4EA7\u7269\u62C6\u51FA\u4E00\u4E2A**\u53EF\u5E76\u884C\u4EA7\u51FA\u7684\u5B50\u90E8\u4EF6**\u6D3E\u51FA\u53BB\u3002\`--desc\`\uFF08**\u5FC5\u586B**\uFF09= \u8FD9\u4E2A\u5B50\u90E8\u4EF6\u8981\u5B8C\u6210\u4EC0\u4E48\uFF1B\`--name\` = \u7ED9\u5B83\u8D77\u7684\u90E8\u4EF6\u540D\uFF08\u53EF\u9009\uFF0C\u7F3A\u7701\u81EA\u52A8\u751F\u6210\uFF09\uFF1B\`--role\` = \u8C01\u6765\u505A\uFF08\u53EF\u9009\uFF0C\u7F3A\u7701=\u4F60\u81EA\u5DF1\uFF09\u3002${opts.handler.mergeMode === "external-ff" ? "\u5404\u90E8\u4EF6\u628A\u6539\u52A8\u5E76\u5165\u5DE5\u5355\u5206\u652F\u540E\uFF0C\u4F60 `oasis propose` \u6536\u5168 feat \u6536\u53E3\uFF08git-native\uFF0C\u522B\u7528 integrate\uFF09\u3002" : "\u5404\u90E8\u4EF6\u4EA7\u5B8C\u7531\u4F60 `oasis integrate` \u6536\u53E3\u3002"}`,
4974
- `- \`oasis gap ${id} --desc "<\u7F3A\u4EC0\u4E48>"\` \u2014\u2014 **\u505A\u4E0D\u4E0B\u53BB\u65F6\u62A5**\uFF1A\u7F3A\u8F93\u5165 / \u7F3A\u4FE1\u606F / \u8981\u4EBA\u51B3\u7B56 / \u7F3A\u80FD\u529B\uFF08\u5DE5\u5177\u88C5\u4E0D\u4E0A\u7B49\uFF09\u90FD\u7B97\u3002\`--desc\` \u8BF4\u6E05\u7F3A\u4EC0\u4E48\u3001\u4E3A\u4EC0\u4E48\u5361\u4F4F\uFF08\u7BA1\u7406\u8005\u636E\u5B83\u5206\u8BCA\uFF1A\u56FE\u4E0A\u8865 / \u627E\u4EBA\uFF09\u3002\u62A5\u5B8C**\u6536\u5DE5**\uFF1A\u8FD9\u6761\u4F1A\u6682\u505C\uFF0C\u9700\u6C42\u6EE1\u8DB3\u540E\u7CFB\u7EDF\u4F1A\u5E26\u7740\u89E3\u6CD5\u8BF4\u660E\u91CD\u65B0\u5524\u8D77\u4F60\uFF1B\u62A5\u969C\u4E4B\u540E\u522B propose\uFF08**\u5148\u4EA4\u4ED8\u540E\u62A5\u969C**\uFF1A\u672C\u8F6E\u82E5\u8FD8\u6709\u5DF2\u5B8C\u6210\u7684\u4EFB\u52A1\uFF0C\u5148 propose \u518D\u62A5\u2014\u2014\u62A5\u4E86\u8FD9\u6761\u7EBF\u5C31\u4EA4\u4E0D\u51FA\u7A3F\u4E86\uFF09\u3002`,
4975
- `- \`oasis annotate <\u4E0A\u6E38id> --from ${id} --body "<\u7591\u95EE>"\` \u2014\u2014 \u5BF9\u67D0\u4E0A\u6E38\u63D0\u7591\u95EE/\u5F02\u8BAE\uFF08\u56DE\u538B\uFF09\u3002\`<\u4E0A\u6E38id>\`=\u88AB\u95EE\u7684\u4E0A\u6E38\uFF1B\`--from\`=\u672C\u4EA7\u7269\uFF08\u7CFB\u7EDF\u636E\u6B64\u5728\u4E0A\u6E38\u7B54\u590D\u540E\u91CD\u6D3E\u4F60\uFF09\uFF1B\`--body\`=\u5177\u4F53\u95EE\u9898\u3002\u63D0\u5B8C**\u6536\u5DE5**\u7B49\u7B54\u590D\uFF1B\u63D0\u95EE\u4E4B\u540E\u522B propose\uFF08\u591A\u4EFB\u52A1\u65F6\u540C\u6837**\u5148\u4EA4\u4ED8\u540E\u62A5\u969C**\uFF09\u3002`,
4997
+ `- \`oasis gap ${id} --desc "<\u7F3A\u4EC0\u4E48>"\` \u2014\u2014 **\u505A\u4E0D\u4E0B\u53BB\u65F6\u62A5**\uFF1A\u7F3A\u8F93\u5165 / \u7F3A\u4FE1\u606F / \u8981\u4EBA\u51B3\u7B56 / \u7F3A\u80FD\u529B\uFF08\u5DE5\u5177\u88C5\u4E0D\u4E0A\u7B49\uFF09\u90FD\u7B97\u3002\`--desc\` \u8BF4\u6E05\u7F3A\u4EC0\u4E48\u3001\u4E3A\u4EC0\u4E48\u5361\u4F4F\uFF08\u7BA1\u7406\u8005\u636E\u5B83\u5206\u8BCA\uFF1A\u56FE\u4E0A\u8865 / \u627E\u4EBA\uFF09\u3002\u62A5\u5B8C**\u6536\u5DE5**\uFF1A\u8FD9\u6761\u4F1A\u6682\u505C\uFF0C\u9700\u6C42\u6EE1\u8DB3\u540E\u7CFB\u7EDF\u4F1A\u5E26\u7740\u89E3\u6CD5\u8BF4\u660E\u91CD\u65B0\u5524\u8D77\u4F60\u3002`,
4998
+ `- \`oasis annotate <\u76EE\u6807\u4EA7\u7269id> --body "<\u4F60\u8981\u8BF4\u7684>"\` \u2014\u2014 **\u5728\u522B\u4EBA\u7684\u4EA7\u7269\u4E0A\u5199\u4E00\u6761\u8BC4\u8BBA**\u3002\u8BF4\u4EC0\u4E48\u7531\u4F60\u5B9A\uFF1A\u7591\u95EE\u3001\u5F02\u8BAE\u3001\u4FEE\u6539\u610F\u89C1\u3001\u8981\u6C42\u8FD4\u5DE5\u3001\u5224\u5B9A\u672A\u8FBE\u6807\u3001\u6216\u53EA\u662F\u63D0\u9192\u2014\u2014\u4F5C\u7528\u53D6\u51B3\u4E8E\u4F60\u5199\u7684\u5185\u5BB9\uFF0C\u547D\u4EE4\u672C\u8EAB\u4E0D\u533A\u5206\u3002\u5199\u7ED9**\u4F60\u7684\u4E0A\u6E38**\u65F6\uFF0C\u8FD9\u6761\u8BC4\u8BBA\u81EA\u52A8\u5E26\u4E0A\u963B\u585E\u529B\uFF1A**\u4E0A\u6E38\u7B54\u590D\u5B83\u4E4B\u524D\u4E0D\u80FD\u6536\u5C3E**\uFF0C\u4E14\u5B83\u7B54\u590D\u540E\u7CFB\u7EDF\u4F1A\u91CD\u65B0\u5524\u8D77\u4F60\uFF08\u4EE3\u8868\u54EA\u4E2A\u4EA7\u7269\u63D0\u7684\uFF0C\u7CFB\u7EDF\u5DF2\u77E5\u9053\uFF0C\u4E0D\u7528\u4F60\u586B\uFF09\u3002`,
4976
4999
  `- \`oasis project content-location add <projectId> (--system <s> --config '<json>' | --file <path>)\` \u2014\u2014 \u7ED9\u9879\u76EE\u767B\u8BB0\u4E00\u5904\u5185\u5BB9\u4F4D\u7F6E\uFF1A\u5916\u90E8 home\uFF08\u5982\u4F60\u5EFA\u597D\u7684 git \u4ED3\uFF0C\`--system\`+\`--config\`\uFF1B**\u53EA\u767B\u8BB0\u6307\u9488\u3001\u4E0D\u521B\u5EFA\u8D44\u6E90\u672C\u8EAB**\u2014\u2014\u5EFA\u4ED3\u7528 gh \u7B49\u6258\u7BA1\u65B9 CLI\uFF09\u6216\u5185\u90E8\u53C2\u8003\u6587\u4EF6\uFF08\`--file\`\uFF09\u3002config \u5F62\u72B6\u968F system\uFF0C\u7528\`content-location systems <s>\` \u67E5\u5B57\u6BB5\u3002`
4977
5000
  );
4978
5001
  if (opts.hasOpenAnnotations) {
@@ -5298,7 +5321,7 @@ ${manifestDiff(baseC, newC)}
5298
5321
  whyLines.push(``, `\uFF08\u5DF2\u5173\u95ED\u7684\u5386\u53F2\u8BC4\u8BBA\u4E0D\u5217\u5165\u6E05\u5355\u2014\u2014\u9700\u8981\u65F6\u7528 \`oasis show ${artifactId}\` \u67E5\u3002\uFF09`);
5299
5322
  }
5300
5323
  whatLines.push(
5301
- queueTaskDue ? `**\u987A\u5E8F**\uFF1A\u2460 \u7EAF\u56DE\u590D\u7C7B\u7684\u4FE1\u5148\u56DE\u6389\uFF08\`oasis resolve\`/\`oasis reply\`\u2014\u2014**\u56DE\u590D\u6C38\u4E0D\u963B\u585E**\uFF09\u2192 \u2461 \u628A\u961F\u5217\u91CC\u90A3\u4E9B\u7248\u672C\u7684\u610F\u56FE**\u8FDE\u540C\u672C\u8F6E\u5176\u5B83\u4EFB\u52A1\u7684\u6539\u52A8\u5408\u6210\u4E00\u7248**\uFF0C\u7528 \`oasis integrate\` \u4EA4\uFF08**\u522B\u7528 \`oasis propose\`**\u2014\u2014\u90A3\u53EA\u4F1A\u518D\u52A0\u4E00\u6761\u6392\u961F\u7248\u672C\uFF0C\u961F\u5217\u66F4\u957F\uFF09\u2192 \u2462 \u5361\u4F4F\u7684\u4EFB\u52A1\u6B64\u65F6\u624D \`oasis gap\` / \u5411\u4E0A\u6E38 \`oasis annotate --from\`\uFF08**\u5148\u4EA4\u4ED8\u540E\u62A5\u969C**\uFF09\u2192 \u2463 \u6536\u5DE5\u3002**\u5168\u90E8\u4EFB\u52A1\u5904\u7406\u5B8C\u624D\u6536\u5DE5\u3002**` : `**\u987A\u5E8F**\uFF1A\u2460 \u7EAF\u56DE\u590D\u7C7B\u7684\u4FE1\u5148\u56DE\u6389\uFF08\`oasis resolve\`/\`oasis reply\`\u2014\u2014**\u56DE\u590D\u6C38\u4E0D\u963B\u585E**\uFF0C\u5148\u56DE\u514D\u5F97\u63D0\u4EA4\u88AB\u65E0\u5173\u7684\u4FE1\u62BC\u7740\uFF09\u2192 \u2461 \u9700\u8981\u6539\u5185\u5BB9\u7684\u4EFB\u52A1\u6539\u52A8**\u5408\u6210\u4E00\u7248** propose\uFF08\`--reason\` **\u6309\u4EFB\u52A1\u9010\u6761**\u5199\u6E05\uFF1A"\u4EFB\u52A11\uFF1A\u2026\uFF1B\u4EFB\u52A12\uFF1A\u2026"\uFF09\u2192 \u2462 \u5361\u4F4F\u7684\u4EFB\u52A1\u6B64\u65F6\u624D \`oasis gap\` / \u5411\u4E0A\u6E38 \`oasis annotate --from\`\uFF08**\u5148\u4EA4\u4ED8\u540E\u62A5\u969C**\u2014\u2014\u53CD\u8FC7\u6765\u8FD9\u6761\u7EBF\u5C31\u4E0D\u80FD propose \u4E86\uFF0C\u4F60\u5DF2\u5B8C\u6210\u7684\u5DE5\u4F5C\u4EA4\u4E0D\u51FA\u53BB\uFF09\u2192 \u2463 \u6536\u5DE5\u3002**\u5168\u90E8\u4EFB\u52A1\u5904\u7406\u5B8C\u624D\u6536\u5DE5\u3002**`
5324
+ queueTaskDue ? `**\u987A\u5E8F**\uFF1A\u2460 \u7EAF\u56DE\u590D\u7C7B\u7684\u4FE1\u5148\u56DE\u6389\uFF08\`oasis resolve\`/\`oasis reply\`\u2014\u2014**\u56DE\u590D\u6C38\u4E0D\u963B\u585E**\uFF09\u2192 \u2461 \u628A\u961F\u5217\u91CC\u90A3\u4E9B\u7248\u672C\u7684\u610F\u56FE**\u8FDE\u540C\u672C\u8F6E\u5176\u5B83\u4EFB\u52A1\u7684\u6539\u52A8\u5408\u6210\u4E00\u7248**\uFF0C\u7528 \`oasis integrate\` \u4EA4\uFF08**\u522B\u7528 \`oasis propose\`**\u2014\u2014\u90A3\u53EA\u4F1A\u518D\u52A0\u4E00\u6761\u6392\u961F\u7248\u672C\uFF0C\u961F\u5217\u66F4\u957F\uFF09\u2192 \u2462 \u9700\u8981\u4E0A\u6E38\u8FD4\u5DE5\u7684\u5411\u4E0A\u6E38 \`oasis annotate\`\u3001\u9700\u8981\u4EBA\u4ECB\u5165\u7684 \`oasis gap\` \u2192 \u2463 \u6536\u5DE5\u3002**\u5168\u90E8\u4EFB\u52A1\u5904\u7406\u5B8C\u624D\u6536\u5DE5\u3002**` : `**\u987A\u5E8F**\uFF1A\u2460 \u7EAF\u56DE\u590D\u7C7B\u7684\u4FE1\u5148\u56DE\u6389\uFF08\`oasis resolve\`/\`oasis reply\`\u2014\u2014**\u56DE\u590D\u6C38\u4E0D\u963B\u585E**\uFF0C\u5148\u56DE\u514D\u5F97\u63D0\u4EA4\u88AB\u65E0\u5173\u7684\u4FE1\u62BC\u7740\uFF09\u2192 \u2461 \u9700\u8981\u6539\u5185\u5BB9\u7684\u4EFB\u52A1\u6539\u52A8**\u5408\u6210\u4E00\u7248** propose\uFF08\`--reason\` **\u6309\u4EFB\u52A1\u9010\u6761**\u5199\u6E05\uFF1A"\u4EFB\u52A11\uFF1A\u2026\uFF1B\u4EFB\u52A12\uFF1A\u2026"\uFF09\u2192 \u2462 \u9700\u8981\u4E0A\u6E38\u8FD4\u5DE5\u7684\u5411\u4E0A\u6E38 \`oasis annotate\`\u3001\u9700\u8981\u4EBA\u4ECB\u5165\u7684 \`oasis gap\` \u2192 \u2463 \u6536\u5DE5\u3002**\u5168\u90E8\u4EFB\u52A1\u5904\u7406\u5B8C\u624D\u6536\u5DE5\u3002**`
5302
5325
  );
5303
5326
  } else if (integrating) {
5304
5327
  whyLines.push(`**\u5404\u90E8\u4EF6\u5DF2\u4EA7\u9F50\uFF0C\u5F85\u4F60\u96C6\u6210**\uFF1A\u4F60\u5148\u524D\u6D3E\u51FA\u53BB\u7684\u5B50\u90E8\u4EF6\u90FD\u4EA7\u51FA\u4E86\uFF0C\u73B0\u5728\u7531\u4F60 owner \u6536\u53E3\u6210\u4E00\u7248\u5B8C\u6574\u3001\u81EA\u6D3D\u7684 R\\*\u3002`);
@@ -5662,8 +5685,11 @@ ${m2.spec.split("\n").map((l) => ` > ${l}`).join("\n")}`);
5662
5685
  ``,
5663
5686
  `## \u751F\u4EA7\u8005\u5B88\u5219\uFF08\u56FA\u5B9A\uFF0C\u6BCF\u4E2A\u751F\u4EA7\u8005\u90FD\u4E00\u6837\uFF09`,
5664
5687
  `- \u4F60\u53EA\u4EA7**\u8FD9\u4E00\u4E2A**\u4EA7\u7269\uFF0C\u522B\u66FF\u522B\u7684\u4EA7\u7269\u62CD\u677F\u3001\u522B\u8D8A\u754C\u6539\u522B\u4EBA\u7684\u4E1C\u897F\uFF1B**\u4E0D\u5F52\u4F60\u5B9A\u7684\u51B3\u7B56\u522B\u5728\u5047\u8BBE\u4E0A\u786C\u7F16**\uFF08\u4F1A\u88AB\u6253\u56DE\uFF09\u3002`,
5665
- `- \u7F3A\u4E00\u4E2A**\u8FD8\u4E0D\u5B58\u5728\u7684\u524D\u7F6E**\u505A\u4E0D\u4E0B\u53BB \u2192 \`oasis gap\` \u62A5\u7F3A\u53E3\u540E**\u6536\u5DE5**\uFF0C\u522B\u786C\u7F16\uFF1B\u63D0\u4E86 gap \u5C31\u522B\u518D propose\uFF08\u8FD9\u6761\u4F1A\u6682\u505C\uFF0C\u524D\u7F6E\u5C31\u7EEA\u540E\u7CFB\u7EDF\u91CD\u65B0\u5524\u8D77\u4F60\uFF09\u3002`,
5666
- `- \u5BF9**\u4E0A\u6E38**\u6709\u7591\u95EE/\u5F02\u8BAE \u2192 \`oasis annotate\` \u63D0\u56DE\u538B\u540E**\u6536\u5DE5**\uFF0C\u540C\u6837\u522B propose\uFF1B\u4E0A\u6E38\u7B54\u590D\u540E\u7CFB\u7EDF\u91CD\u6D3E\u4F60\u3002`,
5688
+ // 这两条**不再预告"提了就别 propose"**:那条闸只对并行部件会话成立(kernel.ts:532/541
5689
+ // `proposingPart !== null` 为前提),整体版 propose 不设防。部件会话仍在自己的命令清单里看到真话
5690
+ // (本文件 :273)。失败条件一律交给内核报错讲——它自带理由与出路,且永不与实现漂移。
5691
+ `- \u7F3A\u4E00\u4E2A**\u8FD8\u4E0D\u5B58\u5728\u7684\u524D\u7F6E**\u505A\u4E0D\u4E0B\u53BB \u2192 \`oasis gap\` \u62A5\u7F3A\u53E3\u540E**\u6536\u5DE5**\uFF0C\u522B\u786C\u7F16\uFF08\u8FD9\u6761\u4F1A\u6682\u505C\uFF0C\u524D\u7F6E\u5C31\u7EEA\u540E\u7CFB\u7EDF\u91CD\u65B0\u5524\u8D77\u4F60\uFF09\u3002`,
5692
+ `- \u5BF9**\u4E0A\u6E38**\u6709\u610F\u89C1\u2014\u2014\u6709\u7591\u95EE\u3001\u8981\u5B83\u6539\u3001\u5224\u5B83\u6CA1\u8FBE\u6807 \u2192 \`oasis annotate <\u4E0A\u6E38id> --body "<\u8BF4\u6E05\u695A>"\` \u540E**\u6536\u5DE5**\u3002\u5199\u7ED9\u4E0A\u6E38\u7684\u8BC4\u8BBA\u81EA\u52A8\u5E26\u963B\u585E\u529B\uFF1A**\u5B83\u7B54\u590D\u524D\u4E0D\u80FD\u6536\u5C3E**\uFF0C\u7B54\u590D\u540E\u7CFB\u7EDF\u91CD\u6D3E\u4F60\u3002`,
5667
5693
  ...args.part === void 0 ? [`- \u6D3B\u592A\u5927 / \u53EF\u5E76\u884C \u2192 \`oasis add-revision\` \u62C6\u6210\u5B50\u90E8\u4EF6\u6D3E\u51FA\u53BB\uFF0C\u522B\u4E00\u4EBA\u4E00\u4F1A\u8BDD\u786C\u625B\u3002`] : [],
5668
5694
  ...batchedContinuation && args.part === void 0 ? [`- \u957F\u4EFB\u52A1\u5DF2\u5141\u8BB8\u5206\u6279\u7EED\u8DD1\uFF1A\u4E00\u6279\u6709\u8FDB\u5C55\u4F46\u6574\u4F53\u672A\u5B8C\u6210\u65F6\uFF0C\u6309\u300C\u600E\u4E48\u63D0\u4EA4\u300D\u91CC\u7684 propose \u547D\u4EE4\u8FFD\u52A0 \`--checkpoint --continue\`\uFF0Creason \u5199\u6E05\u672C\u6279\u5B8C\u6210\u4E86\u4EC0\u4E48\u3001\u5269\u4EC0\u4E48\u3001\u4E0B\u6279\u4ECE\u54EA\u7EE7\u7EED\uFF0C\u7136\u540E\u6536\u5DE5\uFF1B\u7CFB\u7EDF\u4F1A\u6D3E\u53D1\u4E0B\u4E00\u8F6E\u6B63\u5F0F session\u3002\u5168\u90E8\u5B8C\u6210\u65F6\u7167\u5E38 propose \u540E \`oasis conclude ${artifactId}\`\u3002`] : [],
5669
5695
  `- \u5199\u5165\u53EA\u8D70 \`oasis propose\`\uFF0C\u771F\u6B63\u5B8C\u6574\u4E86\u624D \`oasis conclude\`\uFF1B\u5361\u6CA1\u5361\u4F4F\u3001\u8FDB\u5EA6\u90FD\u662F\u7CFB\u7EDF\u7B97\u7684\uFF0C\u522B\u81EA\u5DF1\u7EF4\u62A4\u3002`,
@@ -7663,36 +7689,57 @@ var init_store = __esm({
7663
7689
  });
7664
7690
 
7665
7691
  // ../core/src/evaluation/rubric-store.ts
7666
- var import_node_crypto4, bindingKey, MemoryEvaluationRubricStore;
7692
+ var import_node_crypto4, bindingKey, versionKey, snapshotOf, MemoryEvaluationRubricStore;
7667
7693
  var init_rubric_store = __esm({
7668
7694
  "../core/src/evaluation/rubric-store.ts"() {
7669
7695
  "use strict";
7670
7696
  import_node_crypto4 = require("node:crypto");
7671
7697
  bindingKey = (companyId, caseId) => JSON.stringify([companyId, caseId]);
7698
+ versionKey = (rubricId, version2) => JSON.stringify([rubricId, version2]);
7699
+ snapshotOf = (rubric) => ({
7700
+ rubricId: rubric.rubricId,
7701
+ version: rubric.version,
7702
+ name: rubric.name,
7703
+ kind: rubric.kind,
7704
+ scope: rubric.scope,
7705
+ ...rubric.ownerCaseId ? { ownerCaseId: rubric.ownerCaseId } : {},
7706
+ ...rubric.description ? { description: rubric.description } : {},
7707
+ promptSection: rubric.promptSection,
7708
+ outputSpec: structuredClone(rubric.outputSpec),
7709
+ createdBy: rubric.updatedBy,
7710
+ createdAt: rubric.updatedAt
7711
+ });
7672
7712
  MemoryEvaluationRubricStore = class {
7673
7713
  rubrics = /* @__PURE__ */ new Map();
7714
+ versions = /* @__PURE__ */ new Map();
7674
7715
  bindings = /* @__PURE__ */ new Map();
7675
- async listRubrics(companyId) {
7676
- return [...this.rubrics.values()].filter((rubric) => rubric.companyId === companyId).sort((left, right) => left.name.localeCompare(right.name)).map((rubric) => structuredClone(rubric));
7716
+ async listRubrics(companyId, scope = "shared") {
7717
+ return [...this.rubrics.values()].filter((rubric) => rubric.companyId === companyId && rubric.scope === scope).sort((left, right) => left.name.localeCompare(right.name)).map((rubric) => structuredClone(rubric));
7677
7718
  }
7678
7719
  async getRubric(rubricId) {
7679
7720
  const rubric = this.rubrics.get(rubricId);
7680
7721
  return rubric ? structuredClone(rubric) : null;
7681
7722
  }
7723
+ async getCaseRubric(companyId, caseId) {
7724
+ const found = [...this.rubrics.values()].find((rubric) => rubric.companyId === companyId && rubric.scope === "case" && rubric.ownerCaseId === caseId);
7725
+ return found ? structuredClone(found) : null;
7726
+ }
7682
7727
  async upsertRubric(input, at) {
7683
7728
  const existing = input.rubricId ? this.rubrics.get(input.rubricId) : void 0;
7684
7729
  if (existing && existing.companyId !== input.companyId) {
7685
7730
  throw new Error(`\u8BC4\u5206\u6807\u51C6 ${input.rubricId} \u4E0D\u5C5E\u4E8E\u5F53\u524D\u516C\u53F8`);
7686
7731
  }
7732
+ const scope = input.scope ?? existing?.scope ?? "shared";
7687
7733
  const rubric = {
7688
7734
  rubricId: existing?.rubricId ?? input.rubricId ?? (0, import_node_crypto4.randomUUID)(),
7689
7735
  companyId: input.companyId,
7690
7736
  name: input.name,
7691
7737
  kind: input.kind,
7692
7738
  ...input.description ? { description: input.description } : {},
7739
+ scope,
7740
+ ...scope === "case" && (input.ownerCaseId ?? existing?.ownerCaseId) ? { ownerCaseId: input.ownerCaseId ?? existing?.ownerCaseId } : {},
7693
7741
  promptSection: input.promptSection,
7694
7742
  outputSpec: structuredClone(input.outputSpec),
7695
- // 版本随每次保存自增:判分记录靠 rubricId@version 复现「当时用的是哪一版标准」。
7696
7743
  version: (existing?.version ?? 0) + 1,
7697
7744
  status: input.status ?? existing?.status ?? "active",
7698
7745
  createdBy: existing?.createdBy ?? input.actor,
@@ -7701,6 +7748,7 @@ var init_rubric_store = __esm({
7701
7748
  updatedAt: at
7702
7749
  };
7703
7750
  this.rubrics.set(rubric.rubricId, rubric);
7751
+ this.versions.set(versionKey(rubric.rubricId, rubric.version), snapshotOf(rubric));
7704
7752
  return structuredClone(rubric);
7705
7753
  }
7706
7754
  async deleteRubric(rubricId, companyId) {
@@ -7715,6 +7763,13 @@ var init_rubric_store = __esm({
7715
7763
  });
7716
7764
  }
7717
7765
  }
7766
+ async listRubricVersions(rubricId) {
7767
+ return [...this.versions.values()].filter((version2) => version2.rubricId === rubricId).sort((left, right) => right.version - left.version).map((version2) => structuredClone(version2));
7768
+ }
7769
+ async getRubricVersion(rubricId, version2) {
7770
+ const found = this.versions.get(versionKey(rubricId, version2));
7771
+ return found ? structuredClone(found) : null;
7772
+ }
7718
7773
  async listBindings(companyId) {
7719
7774
  return [...this.bindings.values()].filter((binding) => binding.companyId === companyId).map((binding) => structuredClone(binding));
7720
7775
  }
@@ -138699,6 +138754,16 @@ function nodeLabel(a) {
138699
138754
  const tail = a.id.split(":").slice(2).join(":");
138700
138755
  return tail ? `\u672A\u547D\u540D ${a.type}\uFF08${tail}\uFF09` : `\u672A\u547D\u540D ${a.type}`;
138701
138756
  }
138757
+ function workspaceTitleMap(model) {
138758
+ const titleOf = /* @__PURE__ */ new Map();
138759
+ for (const a of model.artifacts.values()) {
138760
+ if (a.type === "brief") {
138761
+ const title = a.title ?? a.description;
138762
+ if (title) titleOf.set(a.workspace, title);
138763
+ }
138764
+ }
138765
+ return titleOf;
138766
+ }
138702
138767
  function isCurrentHeadConcluded2(model, a) {
138703
138768
  return a.currentRev !== null && concludedHead(model, a.id) === a.currentRev;
138704
138769
  }
@@ -140987,6 +141052,56 @@ var init_session_creds = __esm({
140987
141052
  }
140988
141053
  });
140989
141054
 
141055
+ // ../connectors/src/_base/refresh.ts
141056
+ function isExpiringSoon(expiresAtMs, nowMs, skewMs = CREDENTIAL_REFRESH_SKEW_MS) {
141057
+ if (!expiresAtMs) return true;
141058
+ return nowMs >= expiresAtMs - skewMs;
141059
+ }
141060
+ async function refreshCredentialsIfNeeded(connector, credentials, opts = {}) {
141061
+ if (!connector.refreshCredentials) {
141062
+ return { status: "unsupported", detail: `${connector.config.slug}: \u51ED\u636E\u4E0D\u8FC7\u671F\uFF0C\u65E0\u9700\u7EED\u671F` };
141063
+ }
141064
+ const nowMs = opts.nowMs ?? Date.now();
141065
+ const skewMs = opts.skewMs ?? CREDENTIAL_REFRESH_SKEW_MS;
141066
+ const expiresAt = readEpochMs(credentials, connector.expiresAtCredentialKey);
141067
+ if (!isExpiringSoon(expiresAt, nowMs, skewMs)) {
141068
+ return { status: "fresh" };
141069
+ }
141070
+ const materialExpiresAt = readEpochMs(credentials, connector.refreshMaterialExpiresAtCredentialKey);
141071
+ if (materialExpiresAt && nowMs >= materialExpiresAt) {
141072
+ return { status: "expired", detail: `${connector.config.slug}: \u7EED\u671F\u6750\u6599\u5DF2\u8FC7\u671F\uFF0C\u9700\u91CD\u65B0\u6388\u6743` };
141073
+ }
141074
+ try {
141075
+ const result = await connector.refreshCredentials(credentials);
141076
+ if (!result) return { status: "not-refreshable", detail: `${connector.config.slug}: \u65E0\u53EF\u7528\u7EED\u671F\u6750\u6599` };
141077
+ return {
141078
+ status: "refreshed",
141079
+ credentials: result.credentials,
141080
+ ...result.expiresAt ? { expiresAt: result.expiresAt } : {}
141081
+ };
141082
+ } catch (err) {
141083
+ return { status: "failed", detail: summarize(err) };
141084
+ }
141085
+ }
141086
+ function readEpochMs(credentials, key) {
141087
+ if (!key) return void 0;
141088
+ const raw = credentials[key];
141089
+ if (!raw) return void 0;
141090
+ const n = Number(raw);
141091
+ return Number.isFinite(n) && n > 0 ? n : void 0;
141092
+ }
141093
+ function summarize(err) {
141094
+ const e = err;
141095
+ return (typeof e?.message === "string" ? e.message : String(err)).slice(0, 300);
141096
+ }
141097
+ var CREDENTIAL_REFRESH_SKEW_MS;
141098
+ var init_refresh = __esm({
141099
+ "../connectors/src/_base/refresh.ts"() {
141100
+ "use strict";
141101
+ CREDENTIAL_REFRESH_SKEW_MS = 5 * 60 * 1e3;
141102
+ }
141103
+ });
141104
+
140990
141105
  // ../connectors/src/_base/cli-connector.ts
140991
141106
  function summarizeError(err) {
140992
141107
  const e = err;
@@ -141129,13 +141244,32 @@ var init_base = __esm({
141129
141244
  init_tool_home();
141130
141245
  init_wrapper_assets();
141131
141246
  init_session_creds();
141247
+ init_refresh();
141132
141248
  init_cli_connector();
141133
141249
  init_mcp_connector();
141134
141250
  }
141135
141251
  });
141136
141252
 
141137
141253
  // ../connectors/src/feishu/index.ts
141138
- var import_node_child_process7, import_node_util4, import_node_path6, import_node_url2, import_promises3, execFileAsync2, __dirname2, FEISHU_WELL_KNOWN_SKILLS_BASE, FeishuConnector;
141254
+ function parseFeishuTokenResponse(data, nowMs) {
141255
+ const accessToken = String(data["access_token"] ?? "");
141256
+ const refreshToken = String(data["refresh_token"] ?? "");
141257
+ const expiresInSec = Number(data["expires_in"] ?? 7200);
141258
+ const refreshExpiresInSec = Number(
141259
+ data["refresh_token_expires_in"] ?? data["refresh_expires_in"] ?? 30 * 24 * 3600
141260
+ );
141261
+ const expiresAtMs = nowMs + expiresInSec * 1e3;
141262
+ return {
141263
+ credentials: {
141264
+ ...accessToken ? { user_access_token: accessToken } : {},
141265
+ ...refreshToken ? { refresh_token: refreshToken } : {},
141266
+ user_token_expires_at: String(expiresAtMs),
141267
+ refresh_token_expires_at: String(nowMs + refreshExpiresInSec * 1e3)
141268
+ },
141269
+ expiresAt: new Date(expiresAtMs)
141270
+ };
141271
+ }
141272
+ var import_node_child_process7, import_node_util4, import_node_path6, import_node_url2, import_promises3, execFileAsync2, __dirname2, FEISHU_WELL_KNOWN_SKILLS_BASE, FEISHU_TOKEN_URL, FeishuConnector;
141139
141273
  var init_feishu = __esm({
141140
141274
  "../connectors/src/feishu/index.ts"() {
141141
141275
  "use strict";
@@ -141148,6 +141282,7 @@ var init_feishu = __esm({
141148
141282
  execFileAsync2 = (0, import_node_util4.promisify)(import_node_child_process7.execFile);
141149
141283
  __dirname2 = (0, import_node_path6.dirname)((0, import_node_url2.fileURLToPath)(__esm_import_meta_url));
141150
141284
  FEISHU_WELL_KNOWN_SKILLS_BASE = "https://open.feishu.cn/.well-known/skills";
141285
+ FEISHU_TOKEN_URL = "https://open.feishu.cn/open-apis/authen/v2/oauth/token";
141151
141286
  FeishuConnector = class extends CliConnector {
141152
141287
  config = {
141153
141288
  slug: "feishu",
@@ -141165,12 +141300,28 @@ var init_feishu = __esm({
141165
141300
  commandName = "lark-cli";
141166
141301
  requiredCommands = ["lark-cli"];
141167
141302
  envPrefix = "FEISHU_";
141168
- /** server 侧从 actor 变量表组装凭据的声明(见 provision.ts)。 */
141303
+ /** server 侧从 actor 变量表组装凭据的声明(见 provision.ts)。这些会随 job 下发到节点。 */
141169
141304
  credentialSources = [
141170
141305
  { key: "app_id", from: "FEISHU_APP_ID", required: true },
141171
141306
  { key: "app_secret", from: "FEISHU_APP_SECRET", required: true },
141172
141307
  { key: "user_access_token", from: "FEISHU_USER_ACCESS_TOKEN", required: false }
141173
141308
  ];
141309
+ /**
141310
+ * 续期材料——**只在 server 侧读,不下发节点**。refresh_token 能换出新的 user token,
141311
+ * 属于"长期材料",没有任何理由让它出现在 agent 所在的机器上。
141312
+ */
141313
+ refreshCredentialSources = [
141314
+ { key: "app_id", from: "FEISHU_APP_ID", required: true },
141315
+ { key: "app_secret", from: "FEISHU_APP_SECRET", required: true },
141316
+ { key: "refresh_token", from: "FEISHU_REFRESH_TOKEN", required: true },
141317
+ { key: "user_token_expires_at", from: "FEISHU_USER_TOKEN_EXPIRES_AT", required: false },
141318
+ { key: "refresh_token_expires_at", from: "FEISHU_REFRESH_TOKEN_EXPIRES_AT", required: false },
141319
+ { key: "user_access_token", from: "FEISHU_USER_ACCESS_TOKEN", required: false }
141320
+ ];
141321
+ /** 过期判定只认这个键——必须与注入时消费的 token 同源(见接口注释里的历史坑)。 */
141322
+ expiresAtCredentialKey = "user_token_expires_at";
141323
+ /** refresh_token 自身 ~30d 过期,过了只能重新授权。 */
141324
+ refreshMaterialExpiresAtCredentialKey = "refresh_token_expires_at";
141174
141325
  /**
141175
141326
  * 绝不允许进入 agent 进程环境的原始变量。
141176
141327
  * 含 APP_ID 与几个过期时间戳:它们本身不算密钥,但同属飞书凭据材料,一并挡在 agent 之外,
@@ -141261,6 +141412,36 @@ var init_feishu = __esm({
141261
141412
  sessionEnv.set("FEISHU_RUN_TMPDIR", creds.dir);
141262
141413
  sessionEnv.set("LARKSUITE_CLI_CONFIG_DIR", configDir);
141263
141414
  }
141415
+ /**
141416
+ * 用 refresh_token 换一对新的 user token(飞书 v2 OAuth)。
141417
+ *
141418
+ * **纯换取,不落库**——持久化由调用方做(server 侧 `oauth.ts`)。这样平台知识
141419
+ * (端点、字段名、~2h/~30d 的寿命)全部收在连接器里,与 GitHub App 的续期共用
141420
+ * `_base/refresh.ts` 那套判定。
141421
+ *
141422
+ * 返回 null=没有可用的续期材料(未完成授权)。平台报错则抛,由上层收敛成 failed。
141423
+ */
141424
+ async refreshCredentials(credentials) {
141425
+ const appId = credentials["app_id"];
141426
+ const appSecret = credentials["app_secret"];
141427
+ const refreshToken = credentials["refresh_token"];
141428
+ if (!appId || !appSecret || !refreshToken) return null;
141429
+ const resp = await fetch(FEISHU_TOKEN_URL, {
141430
+ method: "POST",
141431
+ headers: { "Content-Type": "application/x-www-form-urlencoded" },
141432
+ body: new URLSearchParams({
141433
+ grant_type: "refresh_token",
141434
+ refresh_token: refreshToken,
141435
+ client_id: appId,
141436
+ client_secret: appSecret
141437
+ }).toString()
141438
+ });
141439
+ const data = await resp.json();
141440
+ if (!resp.ok || data["error"]) {
141441
+ throw new Error(`feishu refresh failed: HTTP ${resp.status} ${String(data["error"] ?? "")}`.trim());
141442
+ }
141443
+ return parseFeishuTokenResponse(data, Date.now());
141444
+ }
141264
141445
  async initNewApp() {
141265
141446
  await new Promise((resolve9, reject) => {
141266
141447
  const proc = (0, import_node_child_process7.spawn)(this.larkBin, ["config", "init", "--new"], { stdio: "inherit" });
@@ -141471,52 +141652,54 @@ var init_scopes = __esm({
141471
141652
 
141472
141653
  // ../connectors/src/feishu/oauth.ts
141473
141654
  async function persistFeishuUserTokens(store, actorId, data, nowMs) {
141474
- const accessToken = String(data["access_token"] ?? "");
141475
- const refreshToken = String(data["refresh_token"] ?? "");
141476
- const expiresInSec = Number(data["expires_in"] ?? 7200);
141477
- const refreshExpiresInSec = Number(data["refresh_token_expires_in"] ?? data["refresh_expires_in"] ?? 30 * 24 * 3600);
141478
- const put = (key, value2, encrypted) => store.putVariable({ key, value: value2, scope: "personal", actorId, connectorId: "feishu", encrypted });
141479
- if (accessToken) await put("FEISHU_USER_ACCESS_TOKEN", accessToken, true);
141480
- if (refreshToken) await put("FEISHU_REFRESH_TOKEN", refreshToken, true);
141481
- await put("FEISHU_USER_TOKEN_EXPIRES_AT", String(nowMs + expiresInSec * 1e3), false);
141482
- await put("FEISHU_REFRESH_TOKEN_EXPIRES_AT", String(nowMs + refreshExpiresInSec * 1e3), false);
141655
+ const { credentials } = parseFeishuTokenResponse(data, nowMs);
141656
+ await persistRefreshedCredentials(store, actorId, credentials);
141657
+ }
141658
+ async function persistRefreshedCredentials(store, actorId, credentials) {
141659
+ const connector = new FeishuConnector();
141660
+ const secretKeys = new Set(connector.config.schema.filter((f2) => f2.secret).map((f2) => f2.key));
141661
+ for (const src of connector.refreshCredentialSources ?? []) {
141662
+ const value2 = credentials[src.key];
141663
+ if (value2 === void 0) continue;
141664
+ await store.putVariable({
141665
+ key: src.from,
141666
+ value: value2,
141667
+ scope: "personal",
141668
+ actorId,
141669
+ connectorId: "feishu",
141670
+ encrypted: secretKeys.has(src.key)
141671
+ });
141672
+ }
141483
141673
  }
141484
141674
  async function refreshFeishuUserTokenIfNeeded(store, actorId) {
141485
- const expiresAt = Number(await store.revealVariable("FEISHU_USER_TOKEN_EXPIRES_AT", actorId) ?? 0);
141486
- const nowMs = Date.now();
141487
- if (expiresAt && nowMs < expiresAt - REFRESH_SKEW_MS) return;
141488
- const refreshToken = await store.revealVariable("FEISHU_REFRESH_TOKEN", actorId);
141489
- const appId = await store.revealResolvedVariable("FEISHU_APP_ID", actorId);
141490
- const appSecret = await store.revealResolvedVariable("FEISHU_APP_SECRET", actorId);
141491
- if (!refreshToken || !appId || !appSecret) return;
141492
- const refreshExpiresAt = Number(await store.revealVariable("FEISHU_REFRESH_TOKEN_EXPIRES_AT", actorId) ?? 0);
141493
- if (refreshExpiresAt && nowMs >= refreshExpiresAt) {
141494
- await store.deleteVariable("FEISHU_USER_ACCESS_TOKEN", actorId);
141495
- await store.deleteVariable("FEISHU_REFRESH_TOKEN", actorId);
141496
- await store.deleteVariable("FEISHU_USER_TOKEN_EXPIRES_AT", actorId);
141497
- await store.deleteVariable("FEISHU_REFRESH_TOKEN_EXPIRES_AT", actorId);
141675
+ const connector = new FeishuConnector();
141676
+ const credentials = {};
141677
+ for (const src of connector.refreshCredentialSources ?? []) {
141678
+ const v2 = src.key === "app_id" || src.key === "app_secret" ? await store.revealResolvedVariable(src.from, actorId) : await store.revealVariable(src.from, actorId);
141679
+ if (v2) credentials[src.key] = v2;
141680
+ }
141681
+ const outcome = await refreshCredentialsIfNeeded(connector, credentials);
141682
+ if (outcome.status === "refreshed" && outcome.credentials) {
141683
+ await persistRefreshedCredentials(store, actorId, outcome.credentials);
141684
+ return;
141685
+ }
141686
+ if (outcome.status === "expired") {
141687
+ for (const key of [
141688
+ "FEISHU_USER_ACCESS_TOKEN",
141689
+ "FEISHU_REFRESH_TOKEN",
141690
+ "FEISHU_USER_TOKEN_EXPIRES_AT",
141691
+ "FEISHU_REFRESH_TOKEN_EXPIRES_AT"
141692
+ ]) {
141693
+ await store.deleteVariable(key, actorId);
141694
+ }
141498
141695
  return;
141499
141696
  }
141500
- const resp = await fetch(FEISHU_TOKEN_URL, {
141501
- method: "POST",
141502
- headers: { "Content-Type": "application/x-www-form-urlencoded" },
141503
- body: new URLSearchParams({
141504
- grant_type: "refresh_token",
141505
- refresh_token: refreshToken,
141506
- client_id: appId,
141507
- client_secret: appSecret
141508
- }).toString()
141509
- });
141510
- const data = await resp.json();
141511
- if (!resp.ok || data["error"]) return;
141512
- await persistFeishuUserTokens(store, actorId, data, nowMs);
141513
141697
  }
141514
- var FEISHU_TOKEN_URL, REFRESH_SKEW_MS;
141515
141698
  var init_oauth = __esm({
141516
141699
  "../connectors/src/feishu/oauth.ts"() {
141517
141700
  "use strict";
141518
- FEISHU_TOKEN_URL = "https://open.feishu.cn/open-apis/authen/v2/oauth/token";
141519
- REFRESH_SKEW_MS = 5 * 60 * 1e3;
141701
+ init_refresh();
141702
+ init_feishu();
141520
141703
  }
141521
141704
  });
141522
141705
 
@@ -146463,6 +146646,7 @@ var init_src5 = __esm({
146463
146646
  init_base();
146464
146647
  init_base();
146465
146648
  init_base();
146649
+ init_base();
146466
146650
  init_feishu();
146467
146651
  init_scopes();
146468
146652
  init_oauth();
@@ -147331,14 +147515,17 @@ ${acceptanceCriteria.map((c) => `- ${c}`).join("\n")}`] : []
147331
147515
  return { message: revisionArg !== void 0 ? `\u9A8C\u6536\u7968\u5DF2\u8BB0\u5F55\uFF08${status === "pending" ? "\u7B49\u5176\u4F59\u4F5C\u8005/\u5408\u5165" : status}\uFF09` : `review \u5DF2\u8BB0\u5F55\uFF1Bgate \u2192 ${status}` };
147332
147516
  }
147333
147517
  case "annotate": {
147334
- const raisedFrom = optStr(args, "raisedFrom");
147518
+ const targetId = str(args, "artifactId");
147519
+ const sessionNode = ctx.sessionArtifactId;
147520
+ const autoRaisedFrom = optStr(args, "raisedFrom") === void 0 && sessionNode !== void 0 && sessionNode !== targetId && (kernel.model.artifacts.get(sessionNode)?.inputs ?? []).some((e) => e.to === targetId) ? sessionNode : void 0;
147521
+ const raisedFrom = optStr(args, "raisedFrom") ?? autoRaisedFrom;
147335
147522
  const attachments = attachmentArgs(args);
147336
147523
  const mentions = Array.isArray(args["mentions"]) ? args["mentions"].filter((x2) => typeof x2 === "string" && x2.startsWith("actor:")) : void 0;
147337
147524
  if (mentions !== void 0 && mentions.length > 0 && isAgent(actor)) {
147338
147525
  throw new Error("@ \u6743\u53EA\u5C5E\u4E8E\u4EBA\uFF1Aagent \u627E agent \u7528\u56DE\u538B\uFF08annotate --from\uFF09\u6216\u5199\u4FE1\u5230\u5B83\u7684\u4EA7\u7269\uFF1B\u66FF\u4EBA @ \u9700\u5728 1:1 \u4F1A\u8BDD\u4E2D\uFF08\u7F72\u540D\u4EE3\u7F72\uFF09\u3002");
147339
147526
  }
147340
147527
  const annotation = await kernel.annotate({
147341
- artifactId: str(args, "artifactId"),
147528
+ artifactId: targetId,
147342
147529
  actor,
147343
147530
  body: str(args, "body"),
147344
147531
  ...hand !== void 0 ? { hand } : {},
@@ -148133,6 +148320,8 @@ async function startOasisServer(opts) {
148133
148320
  }
148134
148321
  const result = await runCommand(engine.kernel, engine.blobs, engine.oplog, actor, body.command, body.args ?? {}, {
148135
148322
  ...tokenClaims?.dispatchSeq !== void 0 ? { dispatchSeq: tokenClaims.dispatchSeq } : {},
148323
+ // annotate 自动补 raisedFrom 的依据:本会话在做哪个节点(同源同解析器,见 ctx.sessionArtifactId)
148324
+ ...tokenClaims?.artifactId !== void 0 ? { sessionArtifactId: tokenClaims.artifactId } : {},
148136
148325
  ...actingForHuman !== void 0 ? { actingForHuman } : {},
148137
148326
  ...chatOrigin !== void 0 ? { chatOrigin } : {},
148138
148327
  ...opts.projectState ? { projectState: opts.projectState } : {},
@@ -150963,6 +151152,7 @@ var init_memory_control_plane_store = __esm({
150963
151152
  else c.avatar = patch.avatar;
150964
151153
  }
150965
151154
  if (patch.status !== void 0) c.status = patch.status;
151155
+ if (patch.onboardingVariant !== void 0) c.onboardingVariant = patch.onboardingVariant;
150966
151156
  this.companies.set(id, c);
150967
151157
  }
150968
151158
  // —— 成员 ——
@@ -157744,7 +157934,7 @@ function clip(text2) {
157744
157934
  const s2 = typeof text2 === "string" ? text2.trim() : "";
157745
157935
  return s2.length > CLIP ? `${s2.slice(0, CLIP)}\u2026` : s2;
157746
157936
  }
157747
- function summarize(kind, payload) {
157937
+ function summarize2(kind, payload) {
157748
157938
  const p2 = payload;
157749
157939
  switch (kind) {
157750
157940
  case "report_gap":
@@ -157793,7 +157983,7 @@ async function actorActivity(oplog, actorId, limit = 50) {
157793
157983
  for await (const { op } of oplog.readAll()) {
157794
157984
  if (op.actor !== actorId) continue;
157795
157985
  total++;
157796
- matched.push({ at: op.timestamp, kind: op.kind, target: op.target, ...summarize(op.kind, op.payload) });
157986
+ matched.push({ at: op.timestamp, kind: op.kind, target: op.target, ...summarize2(op.kind, op.payload) });
157797
157987
  if (matched.length > capped) matched.shift();
157798
157988
  }
157799
157989
  matched.reverse();
@@ -159008,7 +159198,11 @@ function toApiError(e) {
159008
159198
  const code = e.code;
159009
159199
  const message = e instanceof Error ? e.message : String(e);
159010
159200
  if (code === "FORBIDDEN") throw new ApiError(403, code, message);
159011
- if (code === "ASSISTANT_LAZY_CREATE_FAILED") throw new ApiError(409, code, message);
159201
+ if (code === "ASSISTANT_LAZY_CREATE_FAILED") {
159202
+ const step = e.step;
159203
+ if (step === "no-runtime" || step === "probe-failed") throw new ApiError(503, "ASSISTANT_SERVICE_UNAVAILABLE", message);
159204
+ throw new ApiError(409, code, message);
159205
+ }
159012
159206
  if (code === "AUTO_ASSIGN_OFF") throw new ApiError(409, code, message);
159013
159207
  if (code === "ASSISTANT_ARCHIVED_CANNOT_REACTIVATE") throw new ApiError(409, code, message);
159014
159208
  if (code === "LEGACY_AGENT_NOT_ELIGIBLE_FOR_REBIND") throw new ApiError(409, code, message);
@@ -160076,6 +160270,31 @@ var init_service4 = __esm({
160076
160270
  async get(id) {
160077
160271
  return this.store.getCompany(id);
160078
160272
  }
160273
+ /**
160274
+ * 读取公司的登录后助理引导变体(首个 per-company 配置字段)。
160275
+ * 缺省/未设一律视为 "A"(默认);**读取失败绝不抛给调用方**——降级到 "A",
160276
+ * 保证登录后引导流程永远有一个确定的默认路径,不因控制面读故障而阻塞。
160277
+ */
160278
+ async getOnboardingVariant(companyId) {
160279
+ try {
160280
+ const c = await this.store.getCompany(companyId);
160281
+ return c?.onboardingVariant ?? "A";
160282
+ } catch {
160283
+ return "A";
160284
+ }
160285
+ }
160286
+ /**
160287
+ * 设置公司的登录后助理引导变体("A" | "B")。公司不存在则抛 404,非法值抛 400。
160288
+ * 权限(owner/admin)由路由层强制。
160289
+ */
160290
+ async setOnboardingVariant(companyId, variant) {
160291
+ if (variant !== "A" && variant !== "B") {
160292
+ throw new CompanyError(400, "BAD_REQUEST", `onboardingVariant \u987B\u4E3A "A" | "B"`);
160293
+ }
160294
+ const company = await this.requireCompany(companyId);
160295
+ await this.store.updateCompany(companyId, { onboardingVariant: variant });
160296
+ return { ...company, onboardingVariant: variant };
160297
+ }
160079
160298
  /** 设置/清除公司头像引用("blob:<哈希>";null = 回到默认首字母标识)。 */
160080
160299
  async setAvatar(id, ref2) {
160081
160300
  const company = await this.requireCompany(id);
@@ -160412,6 +160631,13 @@ function companiesDomain(opts) {
160412
160631
  throw e;
160413
160632
  };
160414
160633
  const whoami = (req) => currentUser(req.auth, req.raw.headers);
160634
+ const requireOwnerOrAdmin = async (req, companyId) => {
160635
+ const me = await whoami(req);
160636
+ const member = await service.getMember(companyId, me.accountId);
160637
+ if (!member || member.role !== "owner" && member.role !== "admin") {
160638
+ throw new ApiError(403, "FORBIDDEN", "\u9700\u8981\u8BE5\u516C\u53F8\u7684 owner/admin \u6743\u9650");
160639
+ }
160640
+ };
160415
160641
  return (router) => {
160416
160642
  router.get("/api/companies", async (req) => {
160417
160643
  const me = await whoami(req);
@@ -160423,6 +160649,19 @@ function companiesDomain(opts) {
160423
160649
  if (!company) throw new ApiError(404, "NOT_FOUND", `\u6CA1\u6709\u8FD9\u4E2A\u516C\u53F8\uFF1A${req.params.id}`);
160424
160650
  return { status: 200, body: company };
160425
160651
  });
160652
+ router.patch("/api/companies/:id", async (req) => {
160653
+ const cid = req.params.id;
160654
+ const b2 = req.body ?? {};
160655
+ if (b2.onboardingVariant === void 0) {
160656
+ throw new ApiError(400, "BAD_REQUEST", "\u7F3A\u5C11\u53EF\u66F4\u65B0\u5B57\u6BB5\uFF08onboardingVariant\uFF09");
160657
+ }
160658
+ if (b2.onboardingVariant !== "A" && b2.onboardingVariant !== "B") {
160659
+ throw new ApiError(400, "BAD_REQUEST", `onboardingVariant \u987B\u4E3A "A" | "B"`);
160660
+ }
160661
+ await requireOwnerOrAdmin(req, cid);
160662
+ const company = await service.setOnboardingVariant(cid, b2.onboardingVariant).catch(mapErr);
160663
+ return { status: 200, body: company };
160664
+ });
160426
160665
  router.post("/api/companies", async (req) => {
160427
160666
  const b2 = req.body ?? {};
160428
160667
  const me = await whoami(req);
@@ -161987,14 +162226,10 @@ function buildInbox(model, me, leadOf, resolveActor, markers = [], slaMs, nowIso
161987
162226
  const downstreamCount = (id) => consumersOf(model, id).length;
161988
162227
  const firstSentence = (s2, max = 80) => (s2 ?? "").replace(/\s+/g, " ").trim().slice(0, max);
161989
162228
  const creatorOf = /* @__PURE__ */ new Map();
161990
- const titleOf = /* @__PURE__ */ new Map();
161991
162229
  for (const a of model.artifacts.values()) {
161992
- if (a.type === "brief") {
161993
- creatorOf.set(a.workspace, a.owner);
161994
- const title = a.title ?? a.description;
161995
- if (title) titleOf.set(a.workspace, title);
161996
- }
162230
+ if (a.type === "brief") creatorOf.set(a.workspace, a.owner);
161997
162231
  }
162232
+ const titleOf = workspaceTitleMap(model);
161998
162233
  const staffingSeen = /* @__PURE__ */ new Set();
161999
162234
  for (const a of model.artifacts.values()) {
162000
162235
  if (!isPendingOwner(a.owner) || creatorOf.get(a.workspace) !== me) continue;
@@ -162385,8 +162620,20 @@ function toAuditEntry(op) {
162385
162620
  summary: `${KIND_LABEL[op.kind] ?? op.kind} \xB7 ${op.artifactId}`
162386
162621
  };
162387
162622
  }
162388
- async function buildAudit(oplog, q = {}) {
162623
+ function enrichAudit(entry, model, titleOf) {
162624
+ const a = model.artifacts.get(entry.artifactId);
162625
+ if (!a) return { ...entry, visibility: "deleted" };
162626
+ const workspaceTitle = titleOf.get(a.workspace);
162627
+ return {
162628
+ ...entry,
162629
+ nodeName: nodeLabel(a),
162630
+ ...workspaceTitle ? { workspaceTitle } : {},
162631
+ visibility: "ok"
162632
+ };
162633
+ }
162634
+ async function buildAudit(oplog, q = {}, model) {
162389
162635
  const limit = Math.min(Math.max(q.limit ?? 100, 1), 500);
162636
+ const titleOf = model ? workspaceTitleMap(model) : void 0;
162390
162637
  const items = [];
162391
162638
  let lastCursor = null;
162392
162639
  let truncated = false;
@@ -162396,7 +162643,8 @@ async function buildAudit(oplog, q = {}) {
162396
162643
  if (q.kind && op.kind !== q.kind) continue;
162397
162644
  if (q.from && op.timestamp < q.from) continue;
162398
162645
  if (q.to && op.timestamp > q.to) continue;
162399
- items.push(toAuditEntry(op));
162646
+ const base = toAuditEntry(op);
162647
+ items.push(model && titleOf ? enrichAudit(base, model, titleOf) : base);
162400
162648
  lastCursor = cursor;
162401
162649
  if (items.length >= limit) {
162402
162650
  truncated = true;
@@ -162409,6 +162657,7 @@ var KIND_LABEL;
162409
162657
  var init_audit = __esm({
162410
162658
  "../server/src/domains/collab/audit.ts"() {
162411
162659
  "use strict";
162660
+ init_node_state();
162412
162661
  KIND_LABEL = {
162413
162662
  spawn_artifact: "\u5EFA\u4EA7\u7269",
162414
162663
  propose_revision: "\u63D0\u4FEE\u8BA2",
@@ -163397,7 +163646,7 @@ function collabDomain(opts) {
163397
163646
  return { status: 200, body: { ok: true } };
163398
163647
  });
163399
163648
  router.get("/api/audit", async (req) => {
163400
- const { oplog } = await resolveCtx(req.auth.companyId);
163649
+ const { oplog, kernel } = await resolveCtx(req.auth.companyId);
163401
163650
  const q = req.query;
163402
163651
  const audit = {};
163403
163652
  const actor = q.get("actor");
@@ -163412,7 +163661,7 @@ function collabDomain(opts) {
163412
163661
  if (cursor) audit.cursor = cursor;
163413
163662
  const limit = q.get("limit");
163414
163663
  if (limit) audit.limit = Number(limit);
163415
- return { status: 200, body: await buildAudit(oplog, audit) };
163664
+ return { status: 200, body: await buildAudit(oplog, audit, kernel.model) };
163416
163665
  });
163417
163666
  router.get("/api/dashboard", async (req) => {
163418
163667
  const { kernel, oplog, registry: registry2 } = await resolveCtx(req.auth.companyId);
@@ -165583,7 +165832,7 @@ function validateTriggers(raw) {
165583
165832
  }
165584
165833
  function automationsDomain(opts) {
165585
165834
  const { service } = opts;
165586
- const summarize2 = async (id) => {
165835
+ const summarize3 = async (id) => {
165587
165836
  const automation = await service.getAutomation(id);
165588
165837
  if (!automation) return null;
165589
165838
  const [triggers, recent] = await Promise.all([
@@ -165608,7 +165857,7 @@ function automationsDomain(opts) {
165608
165857
  return { status: 200, body };
165609
165858
  });
165610
165859
  router.get("/api/automations/:id", async (req) => {
165611
- const summary = await summarize2(req.params.id);
165860
+ const summary = await summarize3(req.params.id);
165612
165861
  if (!summary) throw new ApiError(404, "NOT_FOUND", `\u6CA1\u6709\u8FD9\u4E2A\u81EA\u52A8\u5316\uFF1A${req.params.id}`);
165613
165862
  return { status: 200, body: summary };
165614
165863
  });
@@ -165638,7 +165887,7 @@ function automationsDomain(opts) {
165638
165887
  ...b2.createdBy !== void 0 ? { createdBy: b2.createdBy } : {},
165639
165888
  ...triggers !== void 0 ? { triggers } : {}
165640
165889
  });
165641
- const summary = await summarize2(created.id);
165890
+ const summary = await summarize3(created.id);
165642
165891
  return { status: 201, body: summary };
165643
165892
  });
165644
165893
  router.patch("/api/automations/:id", async (req) => {
@@ -165657,7 +165906,7 @@ function automationsDomain(opts) {
165657
165906
  ...b2.enabled !== void 0 ? { enabled: b2.enabled } : {},
165658
165907
  ...triggers !== void 0 ? { triggers } : {}
165659
165908
  });
165660
- const summary = await summarize2(id);
165909
+ const summary = await summarize3(id);
165661
165910
  return { status: 200, body: summary };
165662
165911
  });
165663
165912
  router.post("/api/automations/:id/enabled", async (req) => {
@@ -165666,7 +165915,7 @@ function automationsDomain(opts) {
165666
165915
  const b2 = req.body ?? {};
165667
165916
  if (typeof b2.enabled !== "boolean") throw new ApiError(400, "BAD_REQUEST", "\u7F3A\u5C11 enabled\uFF08boolean\uFF09");
165668
165917
  await service.setEnabled(id, b2.enabled);
165669
- const summary = await summarize2(id);
165918
+ const summary = await summarize3(id);
165670
165919
  return { status: 200, body: summary };
165671
165920
  });
165672
165921
  router.post("/api/automations/:id/run-now", async (req) => {
@@ -165799,6 +166048,9 @@ function builtinRubricsForCase(evaluationCase) {
165799
166048
  const base = {
165800
166049
  companyId: "",
165801
166050
  version: 0,
166051
+ // 回落标准不是登记表里的记录,标成 shared 只是为了满足类型;它们的 rubricId 是 builtin:*,
166052
+ // 不会被写进挂载,也不会出现在「评分标准」页。
166053
+ scope: "shared",
165802
166054
  status: "active",
165803
166055
  createdBy: "system",
165804
166056
  createdAt: at,
@@ -165950,11 +166202,36 @@ var init_scoring = __esm({
165950
166202
  at() {
165951
166203
  return (this.options.now ?? (() => /* @__PURE__ */ new Date()))().toISOString();
165952
166204
  }
166205
+ /** 「评分标准」登记表——只列共享标准。Case 级的走 getCaseScoring。 */
165953
166206
  listRubrics(companyId) {
165954
- return this.options.store.listRubrics(companyId);
166207
+ return this.options.store.listRubrics(companyId, "shared");
166208
+ }
166209
+ listRubricVersions(rubricId) {
166210
+ return this.options.store.listRubricVersions(rubricId);
165955
166211
  }
165956
- async upsertRubric(body, companyId, actor) {
165957
- const raw = body ?? {};
166212
+ async getRubricVersion(rubricId, version2) {
166213
+ const found = await this.options.store.getRubricVersion(rubricId, version2);
166214
+ if (!found) {
166215
+ throw new ScoringConfigError(404, "RUBRIC_VERSION_NOT_FOUND", `\u8BC4\u5206\u6807\u51C6 ${rubricId}@${version2} \u4E0D\u5B58\u5728`);
166216
+ }
166217
+ return found;
166218
+ }
166219
+ /** 保存共享标准(登记表)。每次保存版本号 +1 并落一条不可变快照。 */
166220
+ upsertRubric(body, companyId, actor) {
166221
+ return this.save({ ...body, scope: "shared" }, companyId, actor);
166222
+ }
166223
+ /** 保存某个 Case 自己的标准。同一个 Case 只有一条,重复保存就是出新版本。 */
166224
+ async saveCaseRubric(caseId, body, companyId, actor) {
166225
+ const existing = await this.options.store.getCaseRubric(companyId, caseId);
166226
+ return this.save({
166227
+ ...body,
166228
+ scope: "case",
166229
+ ownerCaseId: caseId,
166230
+ // 认已有那条的 id:Case 级标准一对一,不带 id 再存一次会变成两条抢同一个 Case。
166231
+ ...existing ? { rubricId: existing.rubricId } : {}
166232
+ }, companyId, actor);
166233
+ }
166234
+ async save(raw, companyId, actor) {
165958
166235
  const rubricId = typeof raw["rubricId"] === "string" && raw["rubricId"] ? raw["rubricId"] : void 0;
165959
166236
  let existing = null;
165960
166237
  if (rubricId) {
@@ -165962,6 +166239,13 @@ var init_scoring = __esm({
165962
166239
  if (existing && existing.companyId !== companyId) {
165963
166240
  throw new ScoringConfigError(404, "RUBRIC_NOT_FOUND", `\u8BC4\u5206\u6807\u51C6 ${rubricId} \u4E0D\u5B58\u5728`);
165964
166241
  }
166242
+ if (existing && raw["scope"] && existing.scope !== raw["scope"]) {
166243
+ throw new ScoringConfigError(
166244
+ 409,
166245
+ "RUBRIC_SCOPE_IMMUTABLE",
166246
+ `\u8BC4\u5206\u6807\u51C6 ${rubricId} \u5DF2\u662F ${existing.scope === "shared" ? "\u5171\u4EAB" : "Case \u7EA7"}\u6807\u51C6\uFF0C\u4E0D\u80FD\u6539\u4F5C\u7528\u57DF`
166247
+ );
166248
+ }
165965
166249
  }
165966
166250
  const merged = existing ? {
165967
166251
  rubricId,
@@ -165970,7 +166254,9 @@ var init_scoring = __esm({
165970
166254
  description: raw["description"] ?? existing.description,
165971
166255
  promptSection: raw["promptSection"] ?? existing.promptSection,
165972
166256
  outputSpec: raw["outputSpec"] ?? existing.outputSpec,
165973
- status: raw["status"] ?? existing.status
166257
+ status: raw["status"] ?? existing.status,
166258
+ scope: existing.scope,
166259
+ ...existing.ownerCaseId ? { ownerCaseId: existing.ownerCaseId } : {}
165974
166260
  } : raw;
165975
166261
  let input;
165976
166262
  try {
@@ -165990,6 +166276,7 @@ var init_scoring = __esm({
165990
166276
  listBindings(companyId) {
165991
166277
  return this.options.store.listBindings(companyId);
165992
166278
  }
166279
+ /** 改 Case 挂的共享标准 / 判分员工。Case 自己那条标准不走这里(走 saveCaseRubric)。 */
165993
166280
  async putBinding(caseId, body, companyId, actor) {
165994
166281
  const input = body ?? {};
165995
166282
  const rubricIds = Array.isArray(input.rubricIds) ? input.rubricIds.filter((id) => typeof id === "string" && id.length > 0) : [];
@@ -166002,19 +166289,20 @@ var init_scoring = __esm({
166002
166289
  if (!rubric || rubric.companyId !== companyId) {
166003
166290
  throw new ScoringConfigError(400, "RUBRIC_NOT_FOUND", `\u8BC4\u5206\u6807\u51C6 ${rubricId} \u4E0D\u5B58\u5728`);
166004
166291
  }
166292
+ if (rubric.scope !== "shared") {
166293
+ throw new ScoringConfigError(
166294
+ 400,
166295
+ "RUBRIC_NOT_SHARED",
166296
+ `\u300C${rubric.name}\u300D\u662F Case \u7EA7\u6807\u51C6\uFF0C\u53EA\u80FD\u5728\u5B83\u81EA\u5DF1\u7684 Case \u91CC\u7F16\u8F91\uFF0C\u4E0D\u80FD\u6302\u8F7D`
166297
+ );
166298
+ }
166005
166299
  if (rubric.status !== "active") {
166006
166300
  throw new ScoringConfigError(400, "RUBRIC_ARCHIVED", `\u8BC4\u5206\u6807\u51C6\u300C${rubric.name}\u300D\u5DF2\u5F52\u6863\uFF0C\u4E0D\u80FD\u6302\u8F7D`);
166007
166301
  }
166008
166302
  rubrics.push(rubric);
166009
166303
  }
166010
- const sections = rubrics.map((rubric) => rubric.outputSpec.sectionKey);
166011
- if (new Set(sections).size !== sections.length) {
166012
- throw new ScoringConfigError(
166013
- 400,
166014
- "RUBRIC_SECTION_CONFLICT",
166015
- `\u6302\u8F7D\u7684\u6807\u51C6\u8F93\u51FA\u5230\u4E86\u540C\u4E00\u4E2A\u7ED3\u679C\u6BB5\uFF08${sections.join("\u3001")}\uFF09\uFF0C\u8BF7\u6539\u5176\u4E2D\u4E00\u6761\u7684 sectionKey`
166016
- );
166017
- }
166304
+ const caseRubric = await this.options.store.getCaseRubric(companyId, caseId);
166305
+ this.assertNoSectionConflict(rubrics, caseRubric);
166018
166306
  const judgeActorId = typeof input.judgeActorId === "string" && input.judgeActorId.trim() ? input.judgeActorId.trim() : null;
166019
166307
  if (judgeActorId && this.options.verifyJudgeActor && !await this.options.verifyJudgeActor(judgeActorId, companyId)) {
166020
166308
  throw new ScoringConfigError(400, "JUDGE_ACTOR_INVALID", `${judgeActorId} \u4E0D\u662F\u53EF\u7528\u7684\u5224\u5206\u5458\u5DE5`);
@@ -166025,26 +166313,46 @@ var init_scoring = __esm({
166025
166313
  );
166026
166314
  return {
166027
166315
  caseId,
166028
- rubrics,
166316
+ sharedRubrics: rubrics,
166317
+ caseRubric,
166029
166318
  judgeActorId: binding.judgeActorId,
166030
- usesBuiltinFallback: rubrics.length === 0
166319
+ usesBuiltinFallback: rubrics.length === 0 && !caseRubric
166031
166320
  };
166032
166321
  }
166033
- /** Case 当前装配的对外投影(挂载展开 + 是否还在吃回落)。 */
166322
+ /** 两条标准写进同一个结果段 = 后写的覆盖前一条,判分结果里会静默少一套标准。挡在入口。 */
166323
+ assertNoSectionConflict(shared, caseRubric) {
166324
+ const all = caseRubric ? [...shared, caseRubric] : shared;
166325
+ const sections = all.map((rubric) => rubric.outputSpec.sectionKey);
166326
+ if (new Set(sections).size !== sections.length) {
166327
+ throw new ScoringConfigError(
166328
+ 400,
166329
+ "RUBRIC_SECTION_CONFLICT",
166330
+ `\u751F\u6548\u7684\u6807\u51C6\u5199\u5230\u4E86\u540C\u4E00\u4E2A\u7ED3\u679C\u6BB5\uFF08${sections.join("\u3001")}\uFF09\uFF0C\u8BF7\u6539\u5176\u4E2D\u4E00\u6761\u7684 sectionKey`
166331
+ );
166332
+ }
166333
+ }
166334
+ /** Case 当前装配的对外投影(共享挂载 + 自己的标准 + 是否还在吃回落)。 */
166034
166335
  async getCaseScoring(caseId, companyId) {
166035
166336
  const binding = await this.options.store.getBinding(companyId, caseId);
166036
- const rubrics = await this.mountedRubrics(binding, companyId);
166337
+ const [sharedRubrics, caseRubric] = await Promise.all([
166338
+ this.mountedRubrics(binding, companyId),
166339
+ this.options.store.getCaseRubric(companyId, caseId)
166340
+ ]);
166037
166341
  return {
166038
166342
  caseId,
166039
- rubrics,
166343
+ sharedRubrics,
166344
+ caseRubric,
166040
166345
  judgeActorId: binding?.judgeActorId ?? null,
166041
- usesBuiltinFallback: rubrics.length === 0
166346
+ usesBuiltinFallback: sharedRubrics.length === 0 && !caseRubric
166042
166347
  };
166043
166348
  }
166044
166349
  /** 判分前的最终解析:拿到本次真正要用的标准与判分员工。 */
166045
166350
  async resolveForRun(evaluationCase, companyId, requestedJudgeActorId) {
166046
166351
  const binding = await this.options.store.getBinding(companyId, evaluationCase.id);
166047
- const mounted = await this.mountedRubrics(binding, companyId);
166352
+ const [mounted, caseRubric] = await Promise.all([
166353
+ this.mountedRubrics(binding, companyId),
166354
+ this.options.store.getCaseRubric(companyId, evaluationCase.id)
166355
+ ]);
166048
166356
  const judgeActorId = requestedJudgeActorId?.trim() || binding?.judgeActorId || this.options.defaultJudgeActorId;
166049
166357
  if (!judgeActorId) {
166050
166358
  throw new ScoringConfigError(
@@ -166053,10 +166361,20 @@ var init_scoring = __esm({
166053
166361
  "\u6CA1\u6709\u53EF\u7528\u7684\u5224\u5206\u5458\u5DE5\uFF1A\u8BF7\u5728 Case \u8BC4\u5206\u88C5\u914D\u91CC\u6307\u5B9A\uFF0C\u6216\u914D\u7F6E OASIS_EVALUATION_SCORING_ACTOR_ID"
166054
166362
  );
166055
166363
  }
166364
+ const configured = caseRubric ? [...mounted, caseRubric] : mounted;
166365
+ if (configured.length) {
166366
+ const covered = new Set(configured.map((rubric) => rubric.outputSpec.sectionKey));
166367
+ const filled = builtinRubricsForCase(evaluationCase).filter((rubric) => !covered.has(rubric.outputSpec.sectionKey));
166368
+ return {
166369
+ rubrics: [...configured, ...filled],
166370
+ judgeActorId,
166371
+ usesBuiltinFallback: false
166372
+ };
166373
+ }
166056
166374
  return {
166057
- rubrics: mounted.length ? mounted : builtinRubricsForCase(evaluationCase),
166375
+ rubrics: builtinRubricsForCase(evaluationCase),
166058
166376
  judgeActorId,
166059
- usesBuiltinFallback: mounted.length === 0
166377
+ usesBuiltinFallback: true
166060
166378
  };
166061
166379
  }
166062
166380
  async mountedRubrics(binding, companyId) {
@@ -166337,6 +166655,8 @@ var init_service7 = __esm({
166337
166655
  judgeActorId: resolved.judgeActorId,
166338
166656
  judgeVersion: this.options.judgeVersion,
166339
166657
  rubricVersion: rubricVersionOf(resolved.rubrics, evaluationCase.rubricVersion),
166658
+ // 结构化引用:Case 的标准会变,这里钉死这一次用的是哪几条、各是第几版。
166659
+ rubricRefs: rubricRefsOf(resolved.rubrics),
166340
166660
  evaluatorVersion: evaluationCase.evaluatorVersion,
166341
166661
  createdBy: actor,
166342
166662
  createdAt: now
@@ -166441,6 +166761,20 @@ function evaluationDomain(options) {
166441
166761
  return mapError(error2);
166442
166762
  }
166443
166763
  });
166764
+ router.get("/api/evaluations/rubrics/:rubricId/versions", async (req) => ({
166765
+ status: 200,
166766
+ body: { items: await scoring.listRubricVersions(req.params.rubricId) }
166767
+ }));
166768
+ router.get("/api/evaluations/rubrics/:rubricId/versions/:version", async (req) => {
166769
+ try {
166770
+ return {
166771
+ status: 200,
166772
+ body: await scoring.getRubricVersion(req.params.rubricId, Number(req.params.version))
166773
+ };
166774
+ } catch (error2) {
166775
+ return mapError(error2);
166776
+ }
166777
+ });
166444
166778
  router.get("/api/evaluations/case-scoring", async (req) => ({
166445
166779
  status: 200,
166446
166780
  body: { items: await scoring.listBindings(req.auth.companyId ?? "default") }
@@ -166464,6 +166798,21 @@ function evaluationDomain(options) {
166464
166798
  return mapError(error2);
166465
166799
  }
166466
166800
  });
166801
+ router.put("/api/evaluations/cases/:caseId/rubric", async (req) => {
166802
+ try {
166803
+ return {
166804
+ status: 200,
166805
+ body: await scoring.saveCaseRubric(
166806
+ req.params.caseId,
166807
+ req.body,
166808
+ req.auth.companyId ?? "default",
166809
+ req.auth.actor
166810
+ )
166811
+ };
166812
+ } catch (error2) {
166813
+ return mapError(error2);
166814
+ }
166815
+ });
166467
166816
  router.get("/api/evaluations/judge-candidates", async (req) => ({
166468
166817
  status: 200,
166469
166818
  body: {
@@ -174735,7 +175084,9 @@ var init_registry3 = __esm({
174735
175084
  dependsOn: ["diagnosis:\u6839\u56E0\u5206\u6790"],
174736
175085
  role: "\u5F00\u53D1",
174737
175086
  ownerRole: "dev",
174738
- brief: () => `\u6309\u300C\u6839\u56E0\u5206\u6790\u300D\u7ED9\u51FA\u7684\u65B9\u6848\u4FEE\u590D\u5E76\u4EA4\u4ED8\u4EE3\u7801\u3002\u6539\u52A8\u805A\u7126\u6839\u56E0\u3001\u907F\u514D\u8FC7\u5EA6\u6269\u6563\uFF1B\u81EA\u6D4B\u8986\u76D6\u5DE5\u5355\u63CF\u8FF0\u91CC\u6240\u62A5\u7684\u90A3\u6761\u8DEF\u5F84\u3002`
175087
+ brief: () => `**\u4EA7\u51FA**\uFF1A\u6309\u300C\u6839\u56E0\u5206\u6790\u300D\u7ED9\u51FA\u7684\u65B9\u6848\u4FEE\u590D\u5E76\u4EA4\u4ED8\u4EE3\u7801\u3002
175088
+ **\u8D1F\u8D23\u5230\u54EA**\uFF1A\u53EA\u6539\u6839\u56E0\u6D89\u53CA\u7684\u90E8\u5206\uFF0C\u907F\u514D\u8FC7\u5EA6\u6269\u6563\uFF1B\u4E0A\u7EBF\u4E0E\u9A8C\u8BC1\u5F52\u4E0B\u6E38\u3002
175089
+ **\u4EA4\u4ED8\u524D\u5FC5\u987B\u80FD\u56DE\u7B54**\uFF1A\u2460 \u5DE5\u5355\u63CF\u8FF0\u91CC\u6240\u62A5\u7684\u90A3\u6761\u8DEF\u5F84\uFF0C\u81EA\u6D4B\u8DD1\u901A\u4E86\u5417\u3001\u600E\u4E48\u8DD1\u7684\uFF1F\u2461 \u672C\u6B21\u65B0\u5F15\u5165\u6216\u6539\u5199\u7684\u5224\u65AD\uFF0C\u5728\u5B83\u7684\u6BCF\u4E00\u79CD\u72B6\u6001\u4E0B\u7ED3\u8BBA\u5206\u522B\u662F\u4EC0\u4E48\uFF1F\u2462 \u8BCA\u65AD\u91CC\u7684\u65B9\u6848\u6709\u6CA1\u6709\u54EA\u6761\u4F60\u6CA1\u7167\u505A\u3001\u4E3A\u4EC0\u4E48\uFF1F`
174739
175090
  },
174740
175091
  {
174741
175092
  // 修复与冒烟之间必须隔一道上线核验:dev 收尾的**同一刻**下游就解除阻塞,而那时合并才刚发生、
@@ -174746,7 +175097,9 @@ var init_registry3 = __esm({
174746
175097
  dependsOn: ["dev:\u4FEE\u590D\u5B9E\u73B0"],
174747
175098
  role: "\u8FD0\u7EF4\u90E8\u7F72",
174748
175099
  ownerRole: "devops",
174749
- brief: () => `\u786E\u8BA4\u300C\u4FEE\u590D\u5B9E\u73B0\u300D\u4EA4\u4ED8\u7684\u90A3\u4E2A commit **\u771F\u7684\u8DD1\u5230\u4E86\u76EE\u6807\u73AF\u5883\u4E0A**\uFF0C\u7136\u540E\u653E\u884C\u4E0B\u6E38\u5192\u70DF\u3002
175100
+ brief: () => `**\u4EA7\u51FA**\uFF1A\u4E00\u4EFD\u90E8\u7F72\u8BB0\u5F55\uFF0C\u786E\u8BA4\u300C\u4FEE\u590D\u5B9E\u73B0\u300D\u4EA4\u4ED8\u7684\u90A3\u4E2A commit **\u771F\u7684\u8DD1\u5230\u4E86\u76EE\u6807\u73AF\u5883\u4E0A**\uFF0C\u7136\u540E\u653E\u884C\u4E0B\u6E38\u5192\u70DF\u3002
175101
+ **\u8D1F\u8D23\u5230\u54EA**\uFF1A\u53EA\u786E\u8BA4\u300C\u4E0A\u6CA1\u4E0A\u53BB\u300D\uFF0C\u4E0D\u5224\u65AD\u529F\u80FD\u5BF9\u9519\u2014\u2014\u90A3\u662F\u5192\u70DF\u7684\u4E8B\u3002
175102
+ **\u4EA4\u4ED8\u524D\u5FC5\u987B\u80FD\u56DE\u7B54**\uFF1A\u2460 \u4E0A\u7EBF\u7684\u662F\u54EA\u4E2A commit\uFF1F\u2461 \u4E0B\u6E38\u8981\u9A8C\u7684\u662F\u54EA\u4E2A\u73AF\u5883\u5730\u5740\uFF08\u5192\u70DF\u5168\u9760\u8FD9\u6761\u77E5\u9053\u53BB\u54EA\u6D4B\uFF09\uFF1F\u2462 \u63A2\u9488\u5B9E\u9645\u8FD4\u56DE\u4E86\u4EC0\u4E48\uFF1F
174750
175103
 
174751
175104
  **\u5224\u636E\u4E24\u6761\uFF0C\u7F3A\u4E00\u4E0D\u53EF**\uFF1A
174752
175105
  \u2460 \u90E8\u7F72\u6D41\u6C34\u7EBF\u5728\u8BE5 commit \u4E0A\u7684\u8FD0\u884C\u7ED3\u8BBA\u4E3A success\uFF1B
@@ -174756,8 +175109,6 @@ var init_registry3 = __esm({
174756
175109
 
174757
175110
  **\u4F60\u63A5\u5230\u8FD9\u4E2A\u4EFB\u52A1\u65F6\uFF0C\u90E8\u7F72\u516B\u6210\u8FD8\u6CA1\u5B8C\u6210**\uFF08\u5408\u5E76\u5230\u4E0A\u7EBF\u8981\u51E0\u5206\u949F\uFF09\u3002\u505A\u6CD5\uFF1A**\u5728\u540C\u4E00\u4E2A\u56DE\u5408\u91CC\u8F6E\u8BE2\u7740\u7B49**\u2014\u2014\u67E5\u4E00\u6B21\u3001\`sleep\` \u4E00\u4F1A\u513F\u3001\u518D\u67E5\u3002\u6BCF\u8F6E\u628A\u89C2\u5BDF\u5230\u7684\u5199\u4E0B\u6765\uFF08\u7B2C\u51E0\u6B21\u67E5\u3001\u6D41\u6C34\u7EBF\u4EC0\u4E48\u72B6\u6001\u3001\u63A2\u9488\u62A5\u7684\u662F\u54EA\u4E2A sha\uFF09\uFF0C\u8BA9\u4EBA\u5728\u8F68\u8FF9\u91CC\u770B\u5F97\u89C1\u4F60\u5728\u7B49\u4EC0\u4E48\u3002**\u7EDD\u4E0D\u8981\u7ED3\u675F\u56DE\u5408\u53BB\u7B49\u5524\u9192**\uFF08\u90A3\u7C7B\u5DE5\u5177\u5728\u8FD9\u91CC\u5FC5\u6B7B\uFF0C\u4F1A\u53D8\u6210\u96F6\u4EA7\u51FA\u4F1A\u8BDD\uFF09\u3002\u7B49\u5230\u4E0A\u9650\uFF08\u5EFA\u8BAE 10 \u5206\u949F\uFF09\u4ECD\u4E0D\u5230\u4F4D\uFF0C\u5C31**\u5982\u5B9E\u4E0A\u62A5\u7B49\u4E0D\u5230\u3001\u8BF4\u660E\u5361\u5728\u54EA\u4E00\u6B65**\uFF0C\u4E0D\u8BB8\u5047\u88C5\u901A\u8FC7\u3002
174758
175111
 
174759
- **\u4EA7\u51FA\u90E8\u7F72\u8BB0\u5F55**\uFF0C\u5FC5\u987B\u5305\u542B\uFF1A\u4E0A\u7EBF\u7684\u662F\u54EA\u4E2A commit\u3001**\u4E0B\u6E38\u8981\u9A8C\u7684\u662F\u54EA\u4E2A\u73AF\u5883\u5730\u5740**\uFF08\u5192\u70DF\u5168\u9760\u8FD9\u6761\u77E5\u9053\u53BB\u54EA\u6D4B\uFF09\u3001\u63A2\u9488\u5B9E\u9645\u8FD4\u56DE\u4E86\u4EC0\u4E48\u3002
174760
-
174761
175112
  **\u4F60\u8BF4\u901A\u8FC7\u4E0D\u7B97\u6570**\u2014\u2014\u6536\u5C3E\u65F6\u673A\u5668\u4F1A\u62FF\u540C\u6837\u7684\u5224\u636E\u5B9E\u6D4B\u590D\u6838\u4E00\u904D\uFF0C\u5BF9\u4E0D\u4E0A\u4F1A\u6253\u56DE\u3002\u6240\u4EE5\u522B\u53EA\u8BFB\u4EE3\u7801\u5C31\u5224\u5B9A\uFF0C\u4E5F\u522B\u5728\u63A2\u9488\u6253\u4E0D\u901A\u65F6\u731C\u300C\u5E94\u8BE5\u597D\u4E86\u300D\u3002\u82E5\u67E5\u660E\u662F\u73AF\u5883/\u6D41\u6C34\u7EBF\u95EE\u9898\u800C\u4E0D\u662F\u4EE3\u7801\u95EE\u9898\uFF0C\u5982\u5B9E\u4E0A\u62A5\u5347\u7EA7\uFF0C**\u4E0D\u8981\u6253\u56DE\u4E0A\u6E38\u7684\u300C\u4FEE\u590D\u5B9E\u73B0\u300D**\u2014\u2014\u8BA9\u5F00\u53D1\u91CD\u5199\u4EE3\u7801\u89E3\u51B3\u4E0D\u4E86\u90E8\u7F72\u73AF\u5883\u7684\u95EE\u9898\u3002`
174762
175113
  },
174763
175114
  {
@@ -174768,7 +175119,15 @@ var init_registry3 = __esm({
174768
175119
  dependsOn: ["cd:\u4E0A\u7EBF\u6838\u9A8C"],
174769
175120
  role: "\u5192\u70DF\u6D4B\u8BD5",
174770
175121
  ownerRole: "smoke",
174771
- brief: () => `\u7167\u5DE5\u5355\u63CF\u8FF0\u91CC\u6240\u62A5\u7684\u73B0\u8C61\u5728\u771F\u5B9E\u73AF\u5883\u8D70\u4E00\u904D\uFF0C\u786E\u8BA4\u5B83\u786E\u5B9E\u4E0D\u518D\u51FA\u73B0\uFF1B\u987A\u5E26\u786E\u8BA4\u6539\u52A8\u6CA1\u6709\u660E\u663E\u6253\u574F\u76F4\u63A5\u76F8\u90BB\u7684\u8DEF\u5F84\u3002**\u4E0D\u505A\u5168\u91CF\u56DE\u5F52\u3001\u4E0D\u5199\u6D4B\u8BD5\u8BA1\u5212**\u3002\u4EA7\u51FA\u9A8C\u8BC1\u8BB0\u5F55\uFF1A\u6267\u884C\u4E86\u54EA\u4E9B\u547D\u4EE4\u3001\u770B\u5230\u4EC0\u4E48\u8F93\u51FA\u3001\u636E\u6B64\u5224\u5B9A\u901A\u8FC7\u4E0E\u5426\u3002\u5224\u636E\u6765\u81EA\u5DE5\u5355\u63CF\u8FF0\u672C\u8EAB\u2014\u2014\u6CA1\u6709\u771F\u5B9E\u6267\u884C\u8FC7\u4E0D\u7B97\u9A8C\u8BC1\u8FC7\u3002**\u63CF\u8FF0\u91CC\u6CA1\u6709\u53EF\u6267\u884C\u7684\u590D\u73B0\u65B9\u5F0F\u65F6\uFF0C\u5148\u5982\u5B9E\u8BF4\u660E\u8FD9\u4E00\u70B9\uFF0C\u518D\u7ED9\u51FA\u4F60\u5B9E\u9645\u91C7\u7528\u7684\u9A8C\u8BC1\u65B9\u5F0F**\uFF0C\u4E0D\u8981\u56E0\u4E3A\u300C\u4EE3\u7801\u770B\u8D77\u6765\u5BF9\u300D\u5C31\u5224\u901A\u8FC7\u3002\u9A8C\u4E0D\u8FC7\u5982\u5B9E\u6253\u56DE\u300C\u4FEE\u590D\u5B9E\u73B0\u300D\u5E76\u5199\u6E05\u5361\u5728\u54EA\u4E00\u6B65\u3001\u5B9E\u9645\u770B\u5230\u4EC0\u4E48\u3002`,
175122
+ brief: () => `**\u4EA7\u51FA**\uFF1A\u4E00\u4EFD\u9A8C\u8BC1\u8BB0\u5F55\u2014\u2014\u7167\u5DE5\u5355\u63CF\u8FF0\u91CC\u6240\u62A5\u7684\u73B0\u8C61\u5728\u771F\u5B9E\u73AF\u5883\u8D70\u4E00\u904D\uFF0C\u786E\u8BA4\u5B83\u786E\u5B9E\u4E0D\u518D\u51FA\u73B0\u3002
175123
+ **\u8D1F\u8D23\u5230\u54EA**\uFF1A\u53EA\u9A8C\u6240\u62A5\u73B0\u8C61 + \u76F4\u63A5\u76F8\u90BB\u7684\u8DEF\u5F84\u6709\u6CA1\u6709\u88AB\u6253\u574F\uFF1B**\u4E0D\u505A\u5168\u91CF\u56DE\u5F52\u3001\u4E0D\u5199\u6D4B\u8BD5\u8BA1\u5212**\u3002
175124
+ **\u4EA4\u4ED8\u524D\u5FC5\u987B\u80FD\u56DE\u7B54**\uFF1A\u2460 \u6BCF\u6761\u9A8C\u6536\u6807\u51C6\u4F60\u5B9E\u9645\u6267\u884C\u4E86\u4EC0\u4E48\u547D\u4EE4/\u64CD\u4F5C\u3001\u770B\u5230\u4E86\u4EC0\u4E48\u8F93\u51FA\uFF1F\u2461 \u6709\u6CA1\u6709\u54EA\u4E00\u6761\u4F60\u6CA1\u80FD\u5B9E\u6D4B\uFF1F\u6CA1\u80FD\u5B9E\u6D4B\u7684\uFF0C\u662F**\u88AB\u6D4B\u7269\u6709\u7F3A\u9677**\u3001\u8FD8\u662F**\u9700\u6C42\u672C\u8EAB\u8C41\u514D**\u3001\u8FD8\u662F**\u4F60\u5361\u4F4F\u4E86\u9700\u8981\u4EBA\u5E2E\u5FD9**\u2014\u2014\u9010\u6761\u8BF4\u6E05\u662F\u54EA\u4E00\u79CD\u3002\u2462 \u76F8\u90BB\u4E09\u533A/\u76F8\u90BB\u8DEF\u5F84\u6539\u52A8\u524D\u540E\u884C\u4E3A\u4E00\u81F4\u5417\uFF1F
175125
+
175126
+ \u5224\u636E\u6765\u81EA\u5DE5\u5355\u63CF\u8FF0\u672C\u8EAB\u2014\u2014**\u6CA1\u6709\u771F\u5B9E\u6267\u884C\u8FC7\u4E0D\u7B97\u9A8C\u8BC1\u8FC7**\u3002\u63CF\u8FF0\u91CC\u6CA1\u6709\u53EF\u6267\u884C\u7684\u590D\u73B0\u65B9\u5F0F\u65F6\uFF0C\u5148\u5982\u5B9E\u8BF4\u660E\u8FD9\u4E00\u70B9\uFF0C\u518D\u7ED9\u51FA\u4F60\u5B9E\u9645\u91C7\u7528\u7684\u9A8C\u8BC1\u65B9\u5F0F\uFF0C\u4E0D\u8981\u56E0\u4E3A\u300C\u4EE3\u7801\u770B\u8D77\u6765\u5BF9\u300D\u5C31\u5224\u901A\u8FC7\u3002
175127
+
175128
+ **\u300C\u76F8\u90BB\u8DEF\u5F84\u6CA1\u6253\u574F\u300D\u2260\u300C\u65E2\u6709\u6D4B\u8BD5\u5168\u7EFF\u300D**\uFF1A\u672C\u6B21\u65B0\u5F15\u5165\u6216\u6539\u5199\u7684\u5224\u65AD\uFF0C\u5728\u5B83\u7684\u6BCF\u4E00\u79CD\u72B6\u6001\u4E0B\u90FD\u8981\u6709\u5BF9\u5E94\u8BC1\u636E\u3002\u65E7\u5957\u4EF6\u91CC\u672C\u6765\u5C31\u6CA1\u6709\u8FD9\u4E9B\u72B6\u6001\u7684\u7528\u4F8B\uFF0C\u5168\u7EFF\u53EA\u8BC1\u660E\u300C\u6CA1\u6253\u574F\u65E7\u884C\u4E3A\u300D\u3002
175129
+
175130
+ \u9A8C\u4E0D\u8FC7\u5982\u5B9E\u6253\u56DE\u300C\u4FEE\u590D\u5B9E\u73B0\u300D\u5E76\u5199\u6E05\u5361\u5728\u54EA\u4E00\u6B65\u3001\u5B9E\u9645\u770B\u5230\u4EC0\u4E48\u3002`,
174772
175131
  isFinalOutput: true
174773
175132
  }
174774
175133
  ];
@@ -184546,8 +184905,10 @@ var init_postgres_control_plane = __esm({
184546
184905
  name text NOT NULL,
184547
184906
  avatar text, -- "blob:<hash>"\uFF0C\u7A7A=\u9ED8\u8BA4
184548
184907
  status text NOT NULL, -- active | suspended
184549
- created_at timestamptz NOT NULL
184908
+ created_at timestamptz NOT NULL,
184909
+ onboarding_variant text -- \u767B\u5F55\u540E\u52A9\u7406\u5F15\u5BFC\u53D8\u4F53 A|B\uFF1BNULL=\u7F3A\u7701\u89C6\u4E3A A
184550
184910
  )`);
184911
+ await pool.query(`ALTER TABLE "${s2}".companies ADD COLUMN IF NOT EXISTS onboarding_variant text`);
184551
184912
  await pool.query(`
184552
184913
  CREATE TABLE IF NOT EXISTS "${s2}".company_members (
184553
184914
  company_id text NOT NULL,
@@ -184592,8 +184953,8 @@ var init_postgres_control_plane = __esm({
184592
184953
  // —— 公司 ——
184593
184954
  async createCompany(c) {
184594
184955
  await this.pool.query(
184595
- `INSERT INTO ${this.s}.companies (id, slug, name, avatar, status, created_at) VALUES ($1,$2,$3,$4,$5,$6)`,
184596
- [c.id, c.slug, c.name, c.avatar ?? null, c.status, c.createdAt]
184956
+ `INSERT INTO ${this.s}.companies (id, slug, name, avatar, status, created_at, onboarding_variant) VALUES ($1,$2,$3,$4,$5,$6,$7)`,
184957
+ [c.id, c.slug, c.name, c.avatar ?? null, c.status, c.createdAt, c.onboardingVariant ?? null]
184597
184958
  );
184598
184959
  }
184599
184960
  async getCompany(id) {
@@ -184623,6 +184984,10 @@ var init_postgres_control_plane = __esm({
184623
184984
  args.push(patch.status);
184624
184985
  sets.push(`status = $${args.length}`);
184625
184986
  }
184987
+ if (patch.onboardingVariant !== void 0) {
184988
+ args.push(patch.onboardingVariant);
184989
+ sets.push(`onboarding_variant = $${args.length}`);
184990
+ }
184626
184991
  args.push(id);
184627
184992
  const sql = sets.length ? `UPDATE ${this.s}.companies SET ${sets.join(", ")} WHERE id = $${args.length}` : `SELECT 1 FROM ${this.s}.companies WHERE id = $${args.length}`;
184628
184993
  const r = await this.pool.query(sql, args);
@@ -184886,7 +185251,8 @@ var init_postgres_control_plane = __esm({
184886
185251
  name: row.name,
184887
185252
  ...row.avatar !== null && row.avatar !== void 0 ? { avatar: row.avatar } : {},
184888
185253
  status: row.status,
184889
- createdAt: new Date(row.created_at).toISOString()
185254
+ createdAt: new Date(row.created_at).toISOString(),
185255
+ ...row.onboarding_variant ? { onboardingVariant: row.onboarding_variant } : {}
184890
185256
  });
184891
185257
  rowToMember = (row) => ({
184892
185258
  companyId: row.company_id,
@@ -187223,6 +187589,20 @@ function benchmarkSchemaSql(schema) {
187223
187589
  created_by text NOT NULL, created_at timestamptz NOT NULL,
187224
187590
  updated_by text NOT NULL, updated_at timestamptz NOT NULL
187225
187591
  );
187592
+ ALTER TABLE ${s2}.benchmark_rubrics ADD COLUMN IF NOT EXISTS scope text NOT NULL DEFAULT 'shared';
187593
+ ALTER TABLE ${s2}.benchmark_rubrics ADD COLUMN IF NOT EXISTS owner_case_id text;
187594
+ ALTER TABLE ${s2}.benchmark_evaluations ADD COLUMN IF NOT EXISTS rubric_refs jsonb;
187595
+ -- \u7248\u672C\u5FEB\u7167\u53EA\u589E\u4E0D\u6539\uFF1A\u5386\u53F2 Evaluation \u9760 (rubric_id, version) \u56DE\u67E5\u5F53\u65F6\u90A3\u4EFD\u6B63\u6587\u3002
187596
+ CREATE TABLE IF NOT EXISTS ${s2}.benchmark_rubric_versions (
187597
+ rubric_id text NOT NULL, version integer NOT NULL, name text NOT NULL, kind text NOT NULL,
187598
+ scope text NOT NULL, owner_case_id text, description text,
187599
+ prompt_section text NOT NULL, output_spec jsonb NOT NULL,
187600
+ created_by text NOT NULL, created_at timestamptz NOT NULL,
187601
+ PRIMARY KEY (rubric_id, version)
187602
+ );
187603
+ -- \u4E00\u4E2A Case \u53EA\u80FD\u6709\u4E00\u6761\u81EA\u5DF1\u7684\u6807\u51C6\uFF1B\u9760\u552F\u4E00\u7D22\u5F15\u515C\u4F4F\u5E76\u53D1\u91CD\u590D\u5199\u3002
187604
+ CREATE UNIQUE INDEX IF NOT EXISTS benchmark_rubrics_owner_case_idx
187605
+ ON ${s2}.benchmark_rubrics (company_id, owner_case_id) WHERE owner_case_id IS NOT NULL;
187226
187606
  CREATE TABLE IF NOT EXISTS ${s2}.benchmark_case_scoring (
187227
187607
  company_id text NOT NULL, case_id text NOT NULL, rubric_ids jsonb NOT NULL,
187228
187608
  judge_actor_id text, updated_by text NOT NULL, updated_at timestamptz NOT NULL,
@@ -187263,6 +187643,7 @@ function toEvaluation(row) {
187263
187643
  judgeRunId: nullableText(row.judge_run_id),
187264
187644
  judgeVersion: String(row.judge_version),
187265
187645
  rubricVersion: String(row.rubric_version),
187646
+ rubricRefs: row.rubric_refs == null ? null : structuredClone(row.rubric_refs),
187266
187647
  evaluatorVersion: String(row.evaluator_version),
187267
187648
  result: row.result == null ? null : structuredClone(row.result),
187268
187649
  errorCode: nullableText(row.error_code),
@@ -187280,6 +187661,8 @@ function toRubric(row) {
187280
187661
  name: String(row.name),
187281
187662
  kind: String(row.kind),
187282
187663
  ...row.description == null ? {} : { description: String(row.description) },
187664
+ scope: row.scope == null ? "shared" : String(row.scope),
187665
+ ...row.owner_case_id == null ? {} : { ownerCaseId: String(row.owner_case_id) },
187283
187666
  promptSection: String(row.prompt_section),
187284
187667
  outputSpec: structuredClone(row.output_spec),
187285
187668
  version: Number(row.version),
@@ -187300,6 +187683,21 @@ function toBinding(row) {
187300
187683
  updatedAt: iso2(row.updated_at)
187301
187684
  };
187302
187685
  }
187686
+ function toRubricVersion(row) {
187687
+ return {
187688
+ rubricId: String(row.rubric_id),
187689
+ version: Number(row.version),
187690
+ name: String(row.name),
187691
+ kind: String(row.kind),
187692
+ scope: String(row.scope),
187693
+ ...row.owner_case_id == null ? {} : { ownerCaseId: String(row.owner_case_id) },
187694
+ ...row.description == null ? {} : { description: String(row.description) },
187695
+ promptSection: String(row.prompt_section),
187696
+ outputSpec: structuredClone(row.output_spec),
187697
+ createdBy: String(row.created_by),
187698
+ createdAt: iso2(row.created_at)
187699
+ };
187700
+ }
187303
187701
  var import_node_crypto38, ident17, iso2, nullableIso, nullableText, PostgresEvaluationStore, PostgresEvaluationRubricStore;
187304
187702
  var init_postgres_evaluation = __esm({
187305
187703
  "../storage/src/postgres-evaluation.ts"() {
@@ -187425,8 +187823,8 @@ var init_postgres_evaluation = __esm({
187425
187823
  `INSERT INTO ${this.s}.benchmark_evaluations
187426
187824
  (evaluation_id,run_id,evaluation_no,status,judge_actor_id,judge_run_id,judge_version,
187427
187825
  rubric_version,evaluator_version,result,error_code,error_message,created_by,created_at,
187428
- started_at,completed_at)
187429
- VALUES ($1,$2,$3,$4,$5,$6,$7,$8,$9,$10::jsonb,$11,$12,$13,$14,$15,$16) RETURNING *`,
187826
+ started_at,completed_at,rubric_refs)
187827
+ VALUES ($1,$2,$3,$4,$5,$6,$7,$8,$9,$10::jsonb,$11,$12,$13,$14,$15,$16,$17::jsonb) RETURNING *`,
187430
187828
  [
187431
187829
  input.evaluationId,
187432
187830
  input.runId,
@@ -187443,7 +187841,8 @@ var init_postgres_evaluation = __esm({
187443
187841
  input.createdBy,
187444
187842
  input.createdAt,
187445
187843
  input.startedAt ?? null,
187446
- input.completedAt ?? null
187844
+ input.completedAt ?? null,
187845
+ input.rubricRefs ? JSON.stringify(input.rubricRefs) : null
187447
187846
  ]
187448
187847
  );
187449
187848
  await client.query("COMMIT");
@@ -187473,7 +187872,7 @@ var init_postgres_evaluation = __esm({
187473
187872
  const result = await this.pool.query(
187474
187873
  `UPDATE ${this.s}.benchmark_evaluations SET status=$2,judge_run_id=$3,judge_version=$4,
187475
187874
  rubric_version=$5,evaluator_version=$6,result=$7::jsonb,error_code=$8,error_message=$9,
187476
- started_at=$10,completed_at=$11 WHERE evaluation_id=$1`,
187875
+ started_at=$10,completed_at=$11,rubric_refs=$12::jsonb WHERE evaluation_id=$1`,
187477
187876
  [
187478
187877
  evaluation.evaluationId,
187479
187878
  evaluation.status,
@@ -187485,7 +187884,8 @@ var init_postgres_evaluation = __esm({
187485
187884
  evaluation.errorCode ?? null,
187486
187885
  evaluation.errorMessage ?? null,
187487
187886
  evaluation.startedAt ?? null,
187488
- evaluation.completedAt ?? null
187887
+ evaluation.completedAt ?? null,
187888
+ evaluation.rubricRefs ? JSON.stringify(evaluation.rubricRefs) : null
187489
187889
  ]
187490
187890
  );
187491
187891
  if (result.rowCount !== 1) throw new Error(`Evaluation ${evaluation.evaluationId} \u4E0D\u5B58\u5728`);
@@ -187503,13 +187903,35 @@ var init_postgres_evaluation = __esm({
187503
187903
  await pool.query(benchmarkSchemaSql(safe));
187504
187904
  return new _PostgresEvaluationRubricStore(pool, safe);
187505
187905
  }
187506
- async listRubrics(companyId) {
187906
+ async listRubrics(companyId, scope = "shared") {
187507
187907
  const result = await this.pool.query(
187508
- `SELECT * FROM ${this.s}.benchmark_rubrics WHERE company_id=$1 ORDER BY name ASC`,
187509
- [companyId]
187908
+ `SELECT * FROM ${this.s}.benchmark_rubrics WHERE company_id=$1 AND scope=$2 ORDER BY name ASC`,
187909
+ [companyId, scope]
187510
187910
  );
187511
187911
  return result.rows.map((row) => toRubric(row));
187512
187912
  }
187913
+ async getCaseRubric(companyId, caseId) {
187914
+ const result = await this.pool.query(
187915
+ `SELECT * FROM ${this.s}.benchmark_rubrics
187916
+ WHERE company_id=$1 AND scope='case' AND owner_case_id=$2`,
187917
+ [companyId, caseId]
187918
+ );
187919
+ return result.rows[0] ? toRubric(result.rows[0]) : null;
187920
+ }
187921
+ async listRubricVersions(rubricId) {
187922
+ const result = await this.pool.query(
187923
+ `SELECT * FROM ${this.s}.benchmark_rubric_versions WHERE rubric_id=$1 ORDER BY version DESC`,
187924
+ [rubricId]
187925
+ );
187926
+ return result.rows.map((row) => toRubricVersion(row));
187927
+ }
187928
+ async getRubricVersion(rubricId, version2) {
187929
+ const result = await this.pool.query(
187930
+ `SELECT * FROM ${this.s}.benchmark_rubric_versions WHERE rubric_id=$1 AND version=$2`,
187931
+ [rubricId, version2]
187932
+ );
187933
+ return result.rows[0] ? toRubricVersion(result.rows[0]) : null;
187934
+ }
187513
187935
  async getRubric(rubricId) {
187514
187936
  const result = await this.pool.query(
187515
187937
  `SELECT * FROM ${this.s}.benchmark_rubrics WHERE rubric_id=$1`,
@@ -187533,15 +187955,18 @@ var init_postgres_evaluation = __esm({
187533
187955
  const status = input.status ?? (existing ? String(existing.status) : "active");
187534
187956
  const createdBy = existing ? String(existing.created_by) : input.actor;
187535
187957
  const createdAt = existing ? iso2(existing.created_at) : at;
187958
+ const scope = input.scope ?? (existing ? String(existing.scope ?? "shared") : "shared");
187959
+ const ownerCaseId = scope === "case" ? input.ownerCaseId ?? (existing?.owner_case_id == null ? null : String(existing.owner_case_id)) : null;
187536
187960
  await client.query(
187537
187961
  `INSERT INTO ${this.s}.benchmark_rubrics
187538
187962
  (rubric_id,company_id,name,kind,description,prompt_section,output_spec,version,status,
187539
- created_by,created_at,updated_by,updated_at)
187540
- VALUES ($1,$2,$3,$4,$5,$6,$7::jsonb,$8,$9,$10,$11,$12,$13)
187963
+ created_by,created_at,updated_by,updated_at,scope,owner_case_id)
187964
+ VALUES ($1,$2,$3,$4,$5,$6,$7::jsonb,$8,$9,$10,$11,$12,$13,$14,$15)
187541
187965
  ON CONFLICT (rubric_id) DO UPDATE SET name=EXCLUDED.name,kind=EXCLUDED.kind,
187542
187966
  description=EXCLUDED.description,prompt_section=EXCLUDED.prompt_section,
187543
187967
  output_spec=EXCLUDED.output_spec,version=EXCLUDED.version,status=EXCLUDED.status,
187544
- updated_by=EXCLUDED.updated_by,updated_at=EXCLUDED.updated_at`,
187968
+ updated_by=EXCLUDED.updated_by,updated_at=EXCLUDED.updated_at,
187969
+ scope=EXCLUDED.scope,owner_case_id=EXCLUDED.owner_case_id`,
187545
187970
  [
187546
187971
  rubricId,
187547
187972
  input.companyId,
@@ -187555,6 +187980,28 @@ var init_postgres_evaluation = __esm({
187555
187980
  createdBy,
187556
187981
  createdAt,
187557
187982
  input.actor,
187983
+ at,
187984
+ scope,
187985
+ ownerCaseId
187986
+ ]
187987
+ );
187988
+ await client.query(
187989
+ `INSERT INTO ${this.s}.benchmark_rubric_versions
187990
+ (rubric_id,version,name,kind,scope,owner_case_id,description,prompt_section,output_spec,
187991
+ created_by,created_at)
187992
+ VALUES ($1,$2,$3,$4,$5,$6,$7,$8,$9::jsonb,$10,$11)
187993
+ ON CONFLICT (rubric_id,version) DO NOTHING`,
187994
+ [
187995
+ rubricId,
187996
+ version2,
187997
+ input.name,
187998
+ input.kind,
187999
+ scope,
188000
+ ownerCaseId,
188001
+ input.description ?? null,
188002
+ input.promptSection,
188003
+ JSON.stringify(input.outputSpec),
188004
+ input.actor,
187558
188005
  at
187559
188006
  ]
187560
188007
  );
@@ -188125,6 +188572,14 @@ async function buildChatWhoSection(service, rolesAdmin, humanActorId, selfActorI
188125
188572
  lines.push(`\u60F3\u8FDB\u4E00\u6B65\u4E86\u89E3 TA\u3001\u6216\u6838\u5B9E\u5BF9\u8BDD\u91CC\u63D0\u5230\u7684\u5176\u4ED6\u4EBA\u662F\u8C01\uFF1A\`oasis actor <actorId>\`\uFF08\u5355\u4EBA\u5B8C\u6574\u6863\u6848\uFF09\u3002`);
188126
188573
  return lines;
188127
188574
  }
188575
+ var CHAT_FINAL_REPLY_RULE = [
188576
+ "",
188577
+ "## \u6536\u5C3E\u987A\u5E8F\uFF08\u5C55\u793A\u9762\u5951\u7EA6\uFF09",
188578
+ "",
188579
+ "**\u4F60\u7684\u6700\u540E\u4E00\u6BB5\u6B63\u6587\u5C31\u662F\u7528\u6237\u770B\u5230\u7684\u56DE\u590D**\u2014\u2014\u5728\u5B83\u4E4B\u524D\u7684\u5185\u5BB9\uFF0C\u754C\u9762\u9ED8\u8BA4\u6298\u53E0\u6210\u300C\u601D\u8003\u8FC7\u7A0B\u300D\u3002",
188580
+ "\u6240\u4EE5\uFF1A\u6240\u6709\u6536\u5C3E\u526F\u4F5C\u7528\uFF08`oasis mem-write`\u3001\u6539\u56FE / \u5EFA\u5355\u3001\u53D1\u6D88\u606F\u3001\u5199\u6587\u4EF6\uFF09\u90FD\u5728\u7ED9\u51FA\u56DE\u590D**\u4E4B\u524D**\u505A\u5B8C\uFF1B",
188581
+ "\u505A\u5B8C\u4E4B\u540E\u522B\u518D\u8865\u300C\u5DF2\u4FDD\u5B58 / \u5DF2\u8BB0\u5F55 / \u597D\u7684\u300D\u8FD9\u7C7B\u786E\u8BA4\u53E5\u2014\u2014\u8981\u8BF4\u7684\u4E00\u5E76\u5199\u8FDB\u90A3\u6BB5\u56DE\u590D\u6B63\u6587\u91CC\u3002"
188582
+ ];
188128
188583
  function traceRuntimeKind(runtimeKind) {
188129
188584
  if (runtimeKind === "claude" || runtimeKind === "claude-code") return "claude-code";
188130
188585
  if (runtimeKind === "codex") return "codex";
@@ -189265,7 +189720,9 @@ async function startServe(opts) {
189265
189720
  "## \u7528\u6237\u6D88\u606F",
189266
189721
  "",
189267
189722
  message,
189268
- ...chatMemoryLines
189723
+ ...chatMemoryLines,
189724
+ // 压在最末:这条要盖住紧邻其上的「收工前想一下」,否则记忆写入就落到正文之后(见常量注释)。
189725
+ ...CHAT_FINAL_REPLY_RULE
189269
189726
  ].join("\n")
189270
189727
  };
189271
189728
  Object.assign(files, attachmentFiles);
@@ -191548,14 +192005,18 @@ var USAGE = `oasis \u2014\u2014 artifact-centric \u534F\u4F5C\u5185\u6838 CLI\uF
191548
192005
  evaluate <workOrderId> --case <caseId> [--judge <actorId>] [--case-version <v>] # \u5EFA Run \u5E76\u6392\u961F\u9996\u6B21\u8BC4\u5206
191549
192006
  rescore <runId> [--judge <actorId>] # \u53EA\u65B0\u589E\u4E00\u6B21\u8BC4\u5206\uFF0C\u4E0D\u52A8 Run \u5E8F\u53F7
191550
192007
  judges # \u53EF\u5F53\u5224\u5206\u5458\u5DE5\u7684 agent\uFF08\u53EA\u5217\u6709 active runtime \u7ED1\u5B9A\u7684\uFF09
191551
- rubrics [--kind common|specialty|case_special] # \u8BC4\u5206\u6807\u51C6\u6E05\u5355\uFF08\u4E09\u79CD\u6807\u51C6\u662F\u540C\u4E00\u79CD\u5B9E\u4F53\uFF09
192008
+ rubrics [--kind common|specialty] # **\u5171\u4EAB**\u8BC4\u5206\u6807\u51C6\u6E05\u5355\uFF08\u957F\u671F\u8D44\u4EA7\uFF0C\u53EF\u6302\u591A\u4E2A Case\uFF09
191552
192009
  rubric <rubricId> # \u770B\u4E00\u6761\u6807\u51C6\uFF1A\u8BF4\u660E + \u8F93\u51FA\u5951\u7EA6 + \u6CE8\u5165 prompt \u7684\u6B63\u6587\u5168\u6587
191553
192010
  rubric-save [--rubric-id <id>] --name <\u540D> --kind <k> (--prompt <\u6587\u672C>|--prompt-file <\u8DEF\u5F84>) [--spec-file <json>|--spec <json>] [--description \u2026] [--status active|archived]
191554
192011
  # \u4E0D\u5E26 --rubric-id \u662F\u65B0\u5EFA\uFF0C\u5E26\u4E0A\u662F\u6539\uFF08\u7248\u672C\u53F7\u81EA\u589E\uFF09\u3002--spec \u662F\u8F93\u51FA\u5951\u7EA6\uFF1A
191555
192012
  # { "sectionKey": "common", "aggregation": "none"|"mean", "strictIds": true, "dimensions": [{"id":"G1","label":"\u2026","min":1,"max":5,"nullable":false}] }
191556
192013
  # \u5B83\u540C\u65F6\u51B3\u5B9A\u300C\u5224\u5206\u5458\u5DE5\u8981\u8F93\u51FA\u4EC0\u4E48\u5F62\u72B6\u300D\u548C\u300C\u7ED3\u679C\u600E\u4E48\u6821\u9A8C\u300D\u2014\u2014\u4E0D\u518D\u5199\u6B7B G1-G7/\u4E94\u9879\u3002
191557
192014
  rubric-delete <rubricId> # \u5220\u6807\u51C6\uFF08\u6302\u8F7D\u4E86\u5B83\u7684 Case \u540C\u6B65\u6458\u5F15\u7528\uFF09
191558
- case-scoring <caseId> # \u770B\u67D0 Case \u6302\u4E86\u54EA\u51E0\u5957\u6807\u51C6\u3001\u7531\u8C01\u6253\u5206\u3001\u662F\u5426\u8FD8\u5728\u5403\u56DE\u843D
192015
+ rubric-versions <rubricId> [--version N] # \u7248\u672C\u5386\u53F2 / \u67D0\u4E00\u7248\u7684\u6B63\u6587\uFF08Run \u8BB0\u7684\u662F id@version\uFF0C\u9760\u5B83\u56DE\u67E5\uFF09
192016
+ case-rubric <caseId> # \u770B\u8FD9\u4E2A Case **\u81EA\u5DF1\u90A3\u6761**\u6807\u51C6\uFF08\u4E0D\u5728\u5171\u4EAB\u767B\u8BB0\u8868\u91CC\uFF09
192017
+ case-rubric-save <caseId> --name <\u540D> (--prompt <\u6587\u672C>|--prompt-file <\u8DEF\u5F84>) [--spec-file <json>] [--kind \u2026]
192018
+ # Case \u7EA7\u6807\u51C6\uFF1A\u53EA\u5C5E\u4E8E\u8FD9\u4E00\u4E2A Case\uFF0C\u6BCF\u5B58\u4E00\u6B21\u51FA\u65B0\u7248\u672C\uFF0C\u540C\u4E00 Case \u6052\u53EA\u6709\u4E00\u6761\u3002
192019
+ case-scoring <caseId> # \u770B\u67D0 Case \u6302\u4E86\u54EA\u51E0\u5957\u5171\u4EAB\u6807\u51C6\u3001Case \u7EA7\u6807\u51C6\u3001\u7531\u8C01\u6253\u5206
191559
192020
  case-scoring-set <caseId> [--rubrics id1,id2] [--judge <actorId>|none]
191560
192021
  # \u53EA\u7ED9\u5176\u4E2D\u4E00\u4E2A flag \u5C31\u53EA\u6539\u90A3\u4E00\u9879\uFF08\u53E6\u4E00\u9879\u6CBF\u7528\u73B0\u503C\uFF0C\u4E0D\u4F1A\u88AB\u6E05\u7A7A\uFF09\u3002--judge none = \u56DE\u5230\u90E8\u7F72\u9ED8\u8BA4\u3002
191561
192022
  # \u4E00\u6761\u6807\u51C6\u90FD\u4E0D\u6302 = \u56DE\u843D\u5230 oasis-bench \u81EA\u5E26\u7684\u901A\u7528/\u4E13\u9879/Case \u4E13\u9879\u4E09\u4EFD\u6750\u6599\uFF0C\u884C\u4E3A\u4E0E\u914D\u7F6E\u5316\u4E4B\u524D\u4E00\u81F4\u3002
@@ -192111,7 +192572,7 @@ async function runCli(argv, println = console.log) {
192111
192572
  return uploaded;
192112
192573
  };
192113
192574
  const STAGE_COMMANDS = /* @__PURE__ */ new Set(["link", "spawn", "unlink", "cancel", "freeze", "cancel-part", "edit", "assign", "reopen"]);
192114
- const GLOBAL_NON_FIELD_FLAGS = /* @__PURE__ */ new Set(["server", "token", "dir", "set-json", "clear"]);
192575
+ const GLOBAL_NON_FIELD_FLAGS = /* @__PURE__ */ new Set(["server", "token", "dir", "set-json", "clear", "workspace"]);
192115
192576
  const SPAWN_OWN_FLAGS = /* @__PURE__ */ new Set(["type", "owner", "workspace", "persistence", "title", "brief", "description", "input", "parts-file"]);
192116
192577
  const EDIT_OWN_FLAGS = /* @__PURE__ */ new Set(["title", "brief"]);
192117
192578
  const fieldsFromFlags = (ownFlags) => {
@@ -193538,7 +193999,7 @@ ${res.warning}`);
193538
193999
  const kind = flags.get("kind");
193539
194000
  const res = await api.request("GET", "/api/evaluations/rubrics");
193540
194001
  const items = kind ? res.items.filter((r) => r.kind === kind) : res.items;
193541
- println(`\u8BC4\u5206\u6807\u51C6(${items.length}${kind ? ` / kind=${kind}` : ""})\u2014\u2014\u6302\u5230 Case \u7528 \`oasis case-scoring-set\`:`);
194002
+ println(`\u5171\u4EAB\u8BC4\u5206\u6807\u51C6(${items.length}${kind ? ` / kind=${kind}` : ""})\u2014\u2014\u6302\u5230 Case \u7528 \`oasis case-scoring-set\`\uFF1BCase \u81EA\u5DF1\u7684\u6807\u51C6\u89C1 \`oasis case-rubric <caseId>\`:`);
193542
194003
  for (const r of items) {
193543
194004
  println(`- ${r.rubricId} ${r.name} [${r.kind}] v${r.version}${r.status === "archived" ? " [\u5DF2\u5F52\u6863]" : ""}`);
193544
194005
  println(` \u8F93\u51FA\u6BB5=${r.outputSpec.sectionKey} \u7EF4\u5EA6=${r.outputSpec.dimensions.length || "\u7531 Case \u5B9A\u4E49"}${r.description ? ` ${r.description}` : ""}`);
@@ -193590,12 +194051,14 @@ ${res.warning}`);
193590
194051
  );
193591
194052
  println(`Case ${res.caseId} \u8BC4\u5206\u88C5\u914D:`);
193592
194053
  println(` \u5224\u5206\u5458\u5DE5: ${res.judgeActorId ?? "\uFF08\u7528\u90E8\u7F72\u9ED8\u8BA4\uFF09"}`);
194054
+ println(` \u5171\u4EAB\u6807\u51C6(${res.sharedRubrics.length}):`);
194055
+ for (const r of res.sharedRubrics) {
194056
+ println(` - ${r.rubricId} ${r.name} [${r.kind}] v${r.version} \u2192 \u8F93\u51FA\u6BB5 ${r.outputSpec.sectionKey}`);
194057
+ }
194058
+ if (!res.sharedRubrics.length) println(" \uFF08\u65E0\uFF09");
194059
+ println(` Case \u7EA7\u6807\u51C6: ${res.caseRubric ? `${res.caseRubric.rubricId} ${res.caseRubric.name} v${res.caseRubric.version} \u2192 \u8F93\u51FA\u6BB5 ${res.caseRubric.outputSpec.sectionKey}` : "\uFF08\u672A\u5199\uFF0C\u8BE5\u6BB5\u56DE\u843D\u5230 bench \u91CC\u7684 case-special.yaml\uFF09"}`);
193593
194060
  if (res.usesBuiltinFallback) {
193594
- println(" \u8BC4\u5206\u6807\u51C6: \uFF08\u672A\u6302\u8F7D\uFF0C\u56DE\u843D\u5230 oasis-bench \u81EA\u5E26\u7684\u901A\u7528 / \u4E13\u9879 / Case \u4E13\u9879\u4E09\u4EFD\u6750\u6599\uFF09");
193595
- } else {
193596
- for (const r of res.rubrics) {
193597
- println(` - ${r.rubricId} ${r.name} [${r.kind}] v${r.version} \u2192 \u8F93\u51FA\u6BB5 ${r.outputSpec.sectionKey}`);
193598
- }
194061
+ println(" \u2192 \u4E24\u8005\u90FD\u7A7A\uFF1A\u6574\u6761\u56DE\u843D\u5230 oasis-bench \u81EA\u5E26\u7684\u4E09\u4EFD\u6750\u6599");
193599
194062
  }
193600
194063
  break;
193601
194064
  }
@@ -193605,7 +194068,7 @@ ${res.warning}`);
193605
194068
  "GET",
193606
194069
  `/api/evaluations/cases/${encodeURIComponent(caseId)}/scoring`
193607
194070
  );
193608
- const rubricIds = flags.get("rubrics") !== void 0 ? flags.get("rubrics").split(",").map((v2) => v2.trim()).filter(Boolean) : current.rubrics.map((r) => r.rubricId);
194071
+ const rubricIds = flags.get("rubrics") !== void 0 ? flags.get("rubrics").split(",").map((v2) => v2.trim()).filter(Boolean) : current.sharedRubrics.map((r) => r.rubricId);
193609
194072
  const judgeFlag = flags.get("judge");
193610
194073
  const judgeActorId = judgeFlag === void 0 ? current.judgeActorId : judgeFlag === "none" || judgeFlag === "" ? null : judgeFlag;
193611
194074
  const saved = await api.request(
@@ -193613,8 +194076,70 @@ ${res.warning}`);
193613
194076
  `/api/evaluations/cases/${encodeURIComponent(caseId)}/scoring`,
193614
194077
  { rubricIds, judgeActorId }
193615
194078
  );
193616
- println(`\u5DF2\u66F4\u65B0 Case ${saved.caseId} \u7684\u8BC4\u5206\u88C5\u914D\uFF1A\u6807\u51C6 ${saved.rubrics.length} \u5957\uFF0C\u5224\u5206\u5458\u5DE5 ${saved.judgeActorId ?? "\u90E8\u7F72\u9ED8\u8BA4"}`);
193617
- if (saved.usesBuiltinFallback) println("\uFF08\u4E00\u6761\u90FD\u6CA1\u6302 = \u4ECD\u8D70 oasis-bench \u56DE\u843D\uFF09");
194079
+ println(`\u5DF2\u66F4\u65B0 Case ${saved.caseId} \u7684\u8BC4\u5206\u88C5\u914D\uFF1A\u5171\u4EAB\u6807\u51C6 ${saved.sharedRubrics.length} \u5957\uFF0CCase \u7EA7\u6807\u51C6 ${saved.caseRubric ? "\u6709" : "\u65E0"}\uFF0C\u5224\u5206\u5458\u5DE5 ${saved.judgeActorId ?? "\u90E8\u7F72\u9ED8\u8BA4"}`);
194080
+ if (saved.usesBuiltinFallback) println("\uFF08\u5171\u4EAB\u4E0E Case \u7EA7\u90FD\u7A7A = \u4ECD\u8D70 oasis-bench \u56DE\u843D\uFF09");
194081
+ break;
194082
+ }
194083
+ case "case-rubric": {
194084
+ const caseId = needPos(positional, 0, "oasis case-rubric <caseId>");
194085
+ const res = await api.request(
194086
+ "GET",
194087
+ `/api/evaluations/cases/${encodeURIComponent(caseId)}/scoring`
194088
+ );
194089
+ if (!res.caseRubric) {
194090
+ println(`Case ${caseId} \u8FD8\u6CA1\u6709\u81EA\u5DF1\u7684\u8BC4\u5206\u6807\u51C6\uFF08\u8BE5\u6BB5\u56DE\u843D\u5230 bench \u91CC\u7684 case-special.yaml\uFF09\u3002`);
194091
+ println(`\u5199\u4E00\u6761\uFF1Aoasis case-rubric-save ${caseId} --name <\u540D> --prompt <\u6B63\u6587>`);
194092
+ break;
194093
+ }
194094
+ const r = res.caseRubric;
194095
+ println(`${r.name} [Case \u7EA7 / ${r.kind}] v${r.version} \u5F52\u5C5E ${r.ownerCaseId ?? caseId}`);
194096
+ if (r.description) println(`\u8BF4\u660E: ${r.description}`);
194097
+ println(`\u8F93\u51FA\u5951\u7EA6: ${JSON.stringify(r.outputSpec, null, 2)}`);
194098
+ println("--- \u6CE8\u5165\u5224\u5206 prompt \u7684\u6B63\u6587 ---");
194099
+ println(r.promptSection);
194100
+ break;
194101
+ }
194102
+ case "case-rubric-save": {
194103
+ const caseId = needPos(positional, 0, "oasis case-rubric-save <caseId> --name <\u540D> (--prompt <\u6587\u672C>|--prompt-file <\u8DEF\u5F84>)");
194104
+ const promptSection = flags.get("prompt-file") !== void 0 ? readFileArg(flags.get("prompt-file")) : flags.get("prompt");
194105
+ const outputSpec = flags.get("spec-file") !== void 0 ? JSON.parse(readFileArg(flags.get("spec-file"))) : flags.get("spec") !== void 0 ? JSON.parse(flags.get("spec")) : void 0;
194106
+ const body = {};
194107
+ if (promptSection !== void 0) body["promptSection"] = promptSection;
194108
+ if (flags.get("name") !== void 0) body["name"] = flags.get("name");
194109
+ if (flags.get("description") !== void 0) body["description"] = flags.get("description");
194110
+ body["kind"] = flags.get("kind") ?? "case_special";
194111
+ if (outputSpec !== void 0) body["outputSpec"] = outputSpec;
194112
+ else body["outputSpec"] = { sectionKey: "caseSpecial", aggregation: "none", dimensions: [] };
194113
+ const saved = await api.request(
194114
+ "PUT",
194115
+ `/api/evaluations/cases/${encodeURIComponent(caseId)}/rubric`,
194116
+ body
194117
+ );
194118
+ println(`\u5DF2\u4FDD\u5B58 Case ${caseId} \u7684\u8BC4\u5206\u6807\u51C6\uFF1A${saved.rubricId} ${saved.name} v${saved.version}`);
194119
+ break;
194120
+ }
194121
+ case "rubric-versions": {
194122
+ const rubricId = needPos(positional, 0, "oasis rubric-versions <rubricId> [--version N]");
194123
+ const one = flags.get("version");
194124
+ if (one) {
194125
+ const v2 = await api.request(
194126
+ "GET",
194127
+ `/api/evaluations/rubrics/${encodeURIComponent(rubricId)}/versions/${encodeURIComponent(one)}`
194128
+ );
194129
+ println(`${v2.name} [${v2.scope}/${v2.kind}] v${v2.version} \u7531 ${v2.createdBy} \u4E8E ${v2.createdAt}`);
194130
+ println(`\u8F93\u51FA\u5951\u7EA6: ${JSON.stringify(v2.outputSpec, null, 2)}`);
194131
+ println("--- \u8BE5\u7248\u672C\u7684\u6B63\u6587 ---");
194132
+ println(v2.promptSection);
194133
+ break;
194134
+ }
194135
+ const res = await api.request(
194136
+ "GET",
194137
+ `/api/evaluations/rubrics/${encodeURIComponent(rubricId)}/versions`
194138
+ );
194139
+ println(`${rubricId} \u7684\u7248\u672C\u5386\u53F2(${res.items.length}\uFF0C\u65B0\u2192\u65E7):`);
194140
+ for (const v2 of res.items) {
194141
+ println(`- v${v2.version} ${v2.name} \u7531 ${v2.createdBy} \u4E8E ${v2.createdAt}`);
194142
+ }
193618
194143
  break;
193619
194144
  }
193620
194145
  case "judges": {
@@ -193635,7 +194160,12 @@ ${res.warning}`);
193635
194160
  for (const item of items) {
193636
194161
  const e = item.latestEvaluation;
193637
194162
  println(`- ${item.run.runId} ${item.run.workOrderName} case=${item.run.caseId}#${item.run.attemptNo}`);
193638
- println(` \u8BC4\u5206=${e ? `${e.status}${e.errorMessage ? ` (${e.errorMessage})` : ""}` : "\u5C1A\u672A\u8BC4\u5206"} \u5224\u5206\u5458\u5DE5=${e?.judgeActorId ?? "-"} \u6807\u51C6\u7248\u672C=${e?.rubricVersion ?? "-"}`);
194163
+ println(` \u8BC4\u5206=${e ? `${e.status}${e.errorMessage ? ` (${e.errorMessage})` : ""}` : "\u5C1A\u672A\u8BC4\u5206"} \u5224\u5206\u5458\u5DE5=${e?.judgeActorId ?? "-"}`);
194164
+ if (e?.rubricRefs?.length) {
194165
+ println(` \u672C\u6B21\u6807\u51C6: ${e.rubricRefs.map((r) => `${r.name}@v${r.version}[${r.scope}]`).join(" ")}`);
194166
+ } else if (e) {
194167
+ println(` \u672C\u6B21\u6807\u51C6: ${e.rubricVersion}`);
194168
+ }
193639
194169
  }
193640
194170
  break;
193641
194171
  }
@@ -193882,7 +194412,7 @@ function syncRuntimeAssets(candidateRoots, binDir) {
193882
194412
  }
193883
194413
 
193884
194414
  // src/index.ts
193885
- var PKG_VERSION = true ? "0.1.83" : "dev";
194415
+ var PKG_VERSION = true ? "0.1.85" : "dev";
193886
194416
  var OASIS_DIR = path29.join(os9.homedir(), ".oasis");
193887
194417
  var CONFIG_FILE = path29.join(OASIS_DIR, "node-config.json");
193888
194418
  var PID_FILE = path29.join(OASIS_DIR, "node.pid");
@@ -193955,9 +194485,11 @@ exec node "${BIN_FILE}" "$@"
193955
194485
  function setupAutostart() {
193956
194486
  const cmd = `${process.execPath} "${BIN_FILE}" --_daemon`;
193957
194487
  const home = os9.homedir();
194488
+ const pnpmHome = process.env["PNPM_HOME"] || path29.join(home, ".local", "share", "pnpm");
193958
194489
  const extraBins = [
193959
194490
  path29.join(home, ".npm-global", "bin"),
193960
194491
  path29.join(home, ".local", "bin"),
194492
+ path29.join(pnpmHome, "bin"),
193961
194493
  "/usr/local/bin"
193962
194494
  ];
193963
194495
  const daemonPath = sanitizeDaemonPath([process.env["PATH"] ?? "", ...extraBins].filter(Boolean).join(":"));