create-windy 0.6.18 → 0.6.20

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 (33) hide show
  1. package/dist/cli.js +133 -54
  2. package/package.json +1 -1
  3. package/template/.windy-template.json +2 -2
  4. package/template/README.md +5 -2
  5. package/template/apps/agent-server/README.md +6 -0
  6. package/template/apps/agent-server/src/southwind_agent_server/app.py +23 -0
  7. package/template/apps/agent-server/src/southwind_agent_server/health_routes.py +9 -0
  8. package/template/apps/agent-server/src/southwind_agent_server/runtime_compatibility.py +89 -0
  9. package/template/apps/agent-server/tests/test_runtime_compatibility.py +119 -0
  10. package/template/apps/server/src/agent/remote-runtime-compatibility.test.ts +147 -0
  11. package/template/apps/server/src/agent/remote-runtime.ts +66 -4
  12. package/template/apps/server/src/agent/runtime-bootstrap.ts +1 -0
  13. package/template/apps/web/src/components/common/FormDialog.vue +1 -0
  14. package/template/apps/web/src/components/common/ModalLayout.webtest.ts +1 -0
  15. package/template/apps/web/src/components/ui/dialog/DialogContent.vue +1 -1
  16. package/template/apps/web/src/components/ui/dialog/DialogScrollContent.vue +1 -1
  17. package/template/apps/web/src/pages/system/components/SystemResourceEditor.vue +1 -0
  18. package/template/apps/web/src/pages/system/components/user-account/CreateDepartmentDialog.vue +182 -0
  19. package/template/apps/web/src/pages/system/components/user-account/CreateDepartmentDialog.webtest.ts +93 -0
  20. package/template/apps/web/src/pages/system/components/user-account/RolePermissionSummary.vue +59 -0
  21. package/template/apps/web/src/pages/system/components/user-account/UserAccountForm.vue +21 -71
  22. package/template/apps/web/src/pages/system/components/user-account/UserAccountForm.webtest.ts +71 -0
  23. package/template/apps/web/src/pages/system/components/user-account/UserRoleSelector.vue +48 -27
  24. package/template/apps/web/src/pages/system/components/user-account/UserRoleSelector.webtest.ts +30 -1
  25. package/template/docs/architecture/agent-runtime-compatibility.md +107 -0
  26. package/template/docs/architecture/ai-runtime.md +8 -15
  27. package/template/docs/platform/agent-runtime.md +5 -0
  28. package/template/packages/server-sdk/contracts/agent-runtime-compatibility/v1/offer.json +22 -0
  29. package/template/packages/server-sdk/index.ts +19 -0
  30. package/template/packages/server-sdk/package.json +2 -0
  31. package/template/packages/server-sdk/src/agent-runtime-compatibility.contract.test.ts +143 -0
  32. package/template/packages/server-sdk/src/agent-runtime-compatibility.public-api.test.ts +19 -0
  33. package/template/packages/server-sdk/src/agent-runtime-compatibility.ts +284 -0
package/dist/cli.js CHANGED
@@ -33,7 +33,7 @@ var __toESM = (mod, isNodeMode, target) => {
33
33
  var __commonJS = (cb, mod) => () => (mod || cb((mod = { exports: {} }).exports, mod), mod.exports);
34
34
  var __require = import.meta.require;
35
35
 
36
- // ../../node_modules/cli-width/index.js
36
+ // ../../node_modules/.bun/cli-width@4.1.0/node_modules/cli-width/index.js
37
37
  var require_cli_width = __commonJS((exports, module2) => {
38
38
  module2.exports = cliWidth;
39
39
  function normalizeOpts(options) {
@@ -73,7 +73,7 @@ var require_cli_width = __commonJS((exports, module2) => {
73
73
  }
74
74
  });
75
75
 
76
- // ../../node_modules/mute-stream/lib/index.js
76
+ // ../../node_modules/.bun/mute-stream@3.0.0/node_modules/mute-stream/lib/index.js
77
77
  var require_lib = __commonJS((exports, module2) => {
78
78
  var Stream = __require("stream");
79
79
 
@@ -372,6 +372,7 @@ var agentOptionalModule = {
372
372
  documentation: [
373
373
  "docs/adr/0003-agent-session-event-source.md",
374
374
  "docs/architecture/agent-harness.md",
375
+ "docs/architecture/agent-runtime-compatibility.md",
375
376
  "docs/architecture/agent-workflow.md",
376
377
  "docs/architecture/durable-jobs.md",
377
378
  "docs/architecture/ai-egress.md",
@@ -3921,6 +3922,8 @@ bun run build
3921
3922
  bun run windy doctor
3922
3923
  bun run windy update --check
3923
3924
  bun run windy update --dry-run
3925
+ bun run windy update
3926
+ # \u5982\u9700\u5728\u672C\u6B21\u66F4\u65B0\u4E2D\u663E\u5F0F\u6267\u884C\u9879\u76EE\u8D28\u91CF\u811A\u672C\uFF1Abun run windy update --verify
3924
3927
  bun run windy module list
3925
3928
  ${moduleActivationCommands}
3926
3929
  ${includeOxc ? `bun run lint
@@ -3931,11 +3934,12 @@ ${includeOxc ? "\u63D0\u4EA4\u65F6 Husky \u4F1A\u901A\u8FC7 lint-staged \u4E0E O
3931
3934
 
3932
3935
  \u66F4\u65B0\u524D\u65E0\u9700\u63D0\u4EA4\u3001\u6682\u5B58\u6216\u6E05\u7406\u4E1A\u52A1\u6539\u52A8\u3002\`--dry-run\` \u53EA\u751F\u6210\u8BA1\u5212\uFF0C\u4E0D\u5199\u9879\u76EE\uFF1B\u65E0\u5173 WIP
3933
3936
  \u4FDD\u6301\u539F\u6837\uFF0C\u547D\u4E2D\u540C\u4E00\u8DEF\u5F84\u65F6\u6309\u51BB\u7ED3\u5FEB\u7167\u4E0E\u4E09\u65B9\u5408\u5E76 fail closed\u3002\u6B63\u5F0F\u66F4\u65B0\u5148\u5907\u4EFD\uFF0C\u518D\u505A
3934
- package \u5B57\u6BB5\u7EA7\u5408\u5E76\u3001\u5171\u4EAB\u811A\u624B\u67B6\u4E09\u65B9\u5408\u5E76\u4E0E\u5B8C\u6574\u9A8C\u8BC1\u3002\u6574\u4E2A \`apps/web/src\` \u548C
3937
+ package \u5B57\u6BB5\u7EA7\u5408\u5E76\u3001\u5171\u4EAB\u811A\u624B\u67B6\u4E09\u65B9\u5408\u5E76\u4E0E\u5B89\u5168\u6536\u53E3\uFF1B\u9ED8\u8BA4\u4E0D\u6267\u884C\u9879\u76EE lint\u3001typecheck\u3001
3938
+ test \u6216 build\uFF0C\u4F20\u5165 \`--verify\` \u624D\u663E\u5F0F\u6267\u884C\u5E76\u8BB0\u5F55\u7ED3\u679C\u3002\u6574\u4E2A \`apps/web/src\` \u548C
3935
3939
  \`apps/server/src/app\` \u9ED8\u8BA4\u4FDD\u6301\u9879\u76EE\u7248\u672C\uFF1B\u5DF2\u767B\u8BB0\u7248\u672C recipe \u53EA\u4FEE\u6539\u5176\u58F0\u660E\u7684\u7A84\u951A\u70B9\u3002
3936
3940
  \u4EFB\u4F55\u51B2\u7A81\u90FD\u4F1A\u5728\u5199\u5165\u524D\u4E2D\u6B62\uFF0C\u6A21\u5757\u542F\u505C\u5728\u4EA4\u4E92
3937
3941
  \u7EC8\u7AEF\u786E\u8BA4\uFF1BCI\u3001Agent \u6216\u5176\u5B83\u975E TTY \u73AF\u5883\u5FC5\u987B\u663E\u5F0F\u4F20\u5165 \`--yes\`\u3002
3938
- \u65E0\u5E76\u53D1\u53D8\u5316\u7684\u9A8C\u8BC1\u5931\u8D25\u4F1A\u81EA\u52A8\u56DE\u6EDA\uFF1B\u4E2D\u65AD\u6062\u590D\u4F7F\u7528
3942
+ \u5B89\u5168\u68C0\u67E5\u5931\u8D25\u4F1A\u81EA\u52A8\u56DE\u6EDA\uFF1B\u9879\u76EE\u8D28\u91CF\u811A\u672C\u5931\u8D25\u672C\u8EAB\u4E0D\u56DE\u6EDA\u5DF2\u5B89\u5168\u5E94\u7528\u7684\u66F4\u65B0\u3002\u4E2D\u65AD\u6062\u590D\u4F7F\u7528
3939
3943
  \`bun run windy doctor --repair\`\u3002\u8BE5\u547D\u4EE4\u5728\u63D0\u4EA4\u70B9\u524D\u4FDD\u7559\u5E76\u53D1\u7F16\u8F91\u5E76\u56DE\u6EDA\u5176\u4F59\u8DEF\u5F84\uFF0C
3940
3944
  \u5728 committed \u63D0\u4EA4\u70B9\u540E\u53EA\u5B8C\u6210\u4E8B\u52A1\u72B6\u6001\u4E0E\u5907\u4EFD\u6E05\u7406\uFF0C\u4E0D\u56DE\u6EDA\u9879\u76EE\u6587\u4EF6\u3002
3941
3945
 
@@ -4089,6 +4093,7 @@ var templateDocumentationFiles = [
4089
4093
  "docs/adr/0003-agent-session-event-source.md",
4090
4094
  "docs/architecture/admin-surface-boundary.md",
4091
4095
  "docs/architecture/agent-harness.md",
4096
+ "docs/architecture/agent-runtime-compatibility.md",
4092
4097
  "docs/architecture/agent-workflow.md",
4093
4098
  "docs/architecture/ai-egress.md",
4094
4099
  "docs/architecture/ai-runtime.md",
@@ -4666,7 +4671,7 @@ function isMissing6(error) {
4666
4671
  import { createInterface as createInterface2 } from "readline/promises";
4667
4672
  import { stdin, stdout } from "process";
4668
4673
 
4669
- // ../../node_modules/@inquirer/core/dist/lib/key.js
4674
+ // ../../node_modules/.bun/@inquirer+core@11.2.1+7cb241fc07b679d9/node_modules/@inquirer/core/dist/lib/key.js
4670
4675
  var keybindings = ["emacs", "vim"];
4671
4676
  var keybindingLookup = new Set(keybindings);
4672
4677
  function isKeybinding(value) {
@@ -4683,7 +4688,7 @@ var isDownKey = (key, keybindings2 = []) => key.name === "down" || keybindings2.
4683
4688
  var isSpaceKey = (key) => key.name === "space";
4684
4689
  var isNumberKey = (key) => "1234567890".includes(key.name);
4685
4690
  var isEnterKey = (key) => key.name === "enter" || key.name === "return";
4686
- // ../../node_modules/@inquirer/core/dist/lib/errors.js
4691
+ // ../../node_modules/.bun/@inquirer+core@11.2.1+7cb241fc07b679d9/node_modules/@inquirer/core/dist/lib/errors.js
4687
4692
  class AbortPromptError extends Error {
4688
4693
  name = "AbortPromptError";
4689
4694
  message = "Prompt was aborted";
@@ -4709,10 +4714,10 @@ class HookError extends Error {
4709
4714
  class ValidationError extends Error {
4710
4715
  name = "ValidationError";
4711
4716
  }
4712
- // ../../node_modules/@inquirer/core/dist/lib/use-state.js
4717
+ // ../../node_modules/.bun/@inquirer+core@11.2.1+7cb241fc07b679d9/node_modules/@inquirer/core/dist/lib/use-state.js
4713
4718
  import { AsyncResource as AsyncResource2 } from "async_hooks";
4714
4719
 
4715
- // ../../node_modules/@inquirer/core/dist/lib/hook-engine.js
4720
+ // ../../node_modules/.bun/@inquirer+core@11.2.1+7cb241fc07b679d9/node_modules/@inquirer/core/dist/lib/hook-engine.js
4716
4721
  import { AsyncLocalStorage, AsyncResource } from "async_hooks";
4717
4722
  var hookStorage = new AsyncLocalStorage;
4718
4723
  function createStore(rl) {
@@ -4817,7 +4822,7 @@ var effectScheduler = {
4817
4822
  }
4818
4823
  };
4819
4824
 
4820
- // ../../node_modules/@inquirer/core/dist/lib/use-state.js
4825
+ // ../../node_modules/.bun/@inquirer+core@11.2.1+7cb241fc07b679d9/node_modules/@inquirer/core/dist/lib/use-state.js
4821
4826
  function isFactory(value) {
4822
4827
  return typeof value === "function";
4823
4828
  }
@@ -4838,7 +4843,7 @@ function useState(defaultValue) {
4838
4843
  });
4839
4844
  }
4840
4845
 
4841
- // ../../node_modules/@inquirer/core/dist/lib/use-effect.js
4846
+ // ../../node_modules/.bun/@inquirer+core@11.2.1+7cb241fc07b679d9/node_modules/@inquirer/core/dist/lib/use-effect.js
4842
4847
  function useEffect(cb, depArray) {
4843
4848
  withPointer((pointer) => {
4844
4849
  const oldDeps = pointer.get();
@@ -4850,10 +4855,10 @@ function useEffect(cb, depArray) {
4850
4855
  });
4851
4856
  }
4852
4857
 
4853
- // ../../node_modules/@inquirer/core/dist/lib/theme.js
4858
+ // ../../node_modules/.bun/@inquirer+core@11.2.1+7cb241fc07b679d9/node_modules/@inquirer/core/dist/lib/theme.js
4854
4859
  import { styleText } from "util";
4855
4860
 
4856
- // ../../node_modules/@inquirer/figures/dist/index.js
4861
+ // ../../node_modules/.bun/@inquirer+figures@2.0.7/node_modules/@inquirer/figures/dist/index.js
4857
4862
  import process2 from "process";
4858
4863
  function isUnicodeSupported() {
4859
4864
  if (!process2.platform.startsWith("win")) {
@@ -5142,7 +5147,7 @@ var figures = shouldUseMain ? mainSymbols : fallbackSymbols;
5142
5147
  var dist_default = figures;
5143
5148
  var replacements = Object.entries(specialMainSymbols);
5144
5149
 
5145
- // ../../node_modules/@inquirer/core/dist/lib/theme.js
5150
+ // ../../node_modules/.bun/@inquirer+core@11.2.1+7cb241fc07b679d9/node_modules/@inquirer/core/dist/lib/theme.js
5146
5151
  var defaultTheme = {
5147
5152
  prefix: {
5148
5153
  idle: styleText("blue", "?"),
@@ -5170,7 +5175,7 @@ function getDefaultTheme() {
5170
5175
  };
5171
5176
  }
5172
5177
 
5173
- // ../../node_modules/@inquirer/core/dist/lib/make-theme.js
5178
+ // ../../node_modules/.bun/@inquirer+core@11.2.1+7cb241fc07b679d9/node_modules/@inquirer/core/dist/lib/make-theme.js
5174
5179
  function isPlainObject(value) {
5175
5180
  if (typeof value !== "object" || value === null)
5176
5181
  return false;
@@ -5198,7 +5203,7 @@ function makeTheme(...themes) {
5198
5203
  return deepMerge(...themesToMerge);
5199
5204
  }
5200
5205
 
5201
- // ../../node_modules/@inquirer/core/dist/lib/use-prefix.js
5206
+ // ../../node_modules/.bun/@inquirer+core@11.2.1+7cb241fc07b679d9/node_modules/@inquirer/core/dist/lib/use-prefix.js
5202
5207
  function usePrefix({ status = "idle", theme }) {
5203
5208
  const [showLoader, setShowLoader] = useState(false);
5204
5209
  const [tick, setTick] = useState(0);
@@ -5228,7 +5233,7 @@ function usePrefix({ status = "idle", theme }) {
5228
5233
  const iconName = status === "loading" ? "idle" : status;
5229
5234
  return typeof prefix === "string" ? prefix : prefix[iconName] ?? prefix["idle"];
5230
5235
  }
5231
- // ../../node_modules/@inquirer/core/dist/lib/use-memo.js
5236
+ // ../../node_modules/.bun/@inquirer+core@11.2.1+7cb241fc07b679d9/node_modules/@inquirer/core/dist/lib/use-memo.js
5232
5237
  function useMemo(fn, dependencies) {
5233
5238
  return withPointer((pointer) => {
5234
5239
  const prev = pointer.get();
@@ -5240,12 +5245,12 @@ function useMemo(fn, dependencies) {
5240
5245
  return prev.value;
5241
5246
  });
5242
5247
  }
5243
- // ../../node_modules/@inquirer/core/dist/lib/use-ref.js
5248
+ // ../../node_modules/.bun/@inquirer+core@11.2.1+7cb241fc07b679d9/node_modules/@inquirer/core/dist/lib/use-ref.js
5244
5249
  function useRef(val) {
5245
5250
  return useState({ current: val })[0];
5246
5251
  }
5247
5252
 
5248
- // ../../node_modules/@inquirer/core/dist/lib/use-keypress.js
5253
+ // ../../node_modules/.bun/@inquirer+core@11.2.1+7cb241fc07b679d9/node_modules/@inquirer/core/dist/lib/use-keypress.js
5249
5254
  function useKeypress(userHandler) {
5250
5255
  const signal = useRef(userHandler);
5251
5256
  signal.current = userHandler;
@@ -5263,10 +5268,10 @@ function useKeypress(userHandler) {
5263
5268
  };
5264
5269
  }, []);
5265
5270
  }
5266
- // ../../node_modules/@inquirer/core/dist/lib/utils.js
5271
+ // ../../node_modules/.bun/@inquirer+core@11.2.1+7cb241fc07b679d9/node_modules/@inquirer/core/dist/lib/utils.js
5267
5272
  var import_cli_width = __toESM(require_cli_width(), 1);
5268
5273
 
5269
- // ../../node_modules/fast-string-truncated-width/dist/utils.js
5274
+ // ../../node_modules/.bun/fast-string-truncated-width@3.0.3/node_modules/fast-string-truncated-width/dist/utils.js
5270
5275
  var getCodePointsLength = (() => {
5271
5276
  const SURROGATE_PAIR_RE = /[\uD800-\uDBFF][\uDC00-\uDFFF]/g;
5272
5277
  return (input) => {
@@ -5285,7 +5290,7 @@ var isWideNotCJKTNotEmoji = (x) => {
5285
5290
  return x === 8987 || x === 9001 || x >= 12272 && x <= 12287 || x >= 12289 && x <= 12350 || x >= 12441 && x <= 12543 || x >= 12549 && x <= 12591 || x >= 12593 && x <= 12686 || x >= 12688 && x <= 12771 || x >= 12783 && x <= 12830 || x >= 12832 && x <= 12871 || x >= 12880 && x <= 19903 || x >= 65040 && x <= 65049 || x >= 65072 && x <= 65106 || x >= 65108 && x <= 65126 || x >= 65128 && x <= 65131 || x >= 127488 && x <= 127490 || x >= 127504 && x <= 127547 || x >= 127552 && x <= 127560 || x >= 131072 && x <= 196605 || x >= 196608 && x <= 262141;
5286
5291
  };
5287
5292
 
5288
- // ../../node_modules/fast-string-truncated-width/dist/index.js
5293
+ // ../../node_modules/.bun/fast-string-truncated-width@3.0.3/node_modules/fast-string-truncated-width/dist/index.js
5289
5294
  var ANSI_RE = /[\u001b\u009b][[()#;?]*(?:[0-9]{1,4}(?:;[0-9]{0,4})*)?[0-9A-ORZcf-nqry=><]|\u001b\]8;[^;]*;.*?(?:\u0007|\u001b\u005c)/y;
5290
5295
  var CONTROL_RE = /[\x00-\x08\x0A-\x1F\x7F-\x9F]{1,1000}/y;
5291
5296
  var CJKT_WIDE_RE = /(?:(?![\uFF61-\uFF9F\uFF00-\uFFEF])[\p{Script=Han}\p{Script=Hiragana}\p{Script=Katakana}\p{Script=Hangul}\p{Script=Tangut}]){1,1000}/yu;
@@ -5384,7 +5389,7 @@ var getStringTruncatedWidth = (input, truncationOptions = {}, widthOptions = {})
5384
5389
  };
5385
5390
  var dist_default2 = getStringTruncatedWidth;
5386
5391
 
5387
- // ../../node_modules/fast-string-width/dist/index.js
5392
+ // ../../node_modules/.bun/fast-string-width@3.0.2/node_modules/fast-string-width/dist/index.js
5388
5393
  var NO_TRUNCATION2 = {
5389
5394
  limit: Infinity,
5390
5395
  ellipsis: "",
@@ -5395,7 +5400,7 @@ var fastStringWidth = (input, options = {}) => {
5395
5400
  };
5396
5401
  var dist_default3 = fastStringWidth;
5397
5402
 
5398
- // ../../node_modules/fast-wrap-ansi/lib/main.js
5403
+ // ../../node_modules/.bun/fast-wrap-ansi@0.2.2/node_modules/fast-wrap-ansi/lib/main.js
5399
5404
  var ESC = "\x1B";
5400
5405
  var CSI = "\x9B";
5401
5406
  var END_CODE = 39;
@@ -5607,7 +5612,7 @@ function wrapAnsi(string, columns, options) {
5607
5612
  `);
5608
5613
  }
5609
5614
 
5610
- // ../../node_modules/@inquirer/core/dist/lib/utils.js
5615
+ // ../../node_modules/.bun/@inquirer+core@11.2.1+7cb241fc07b679d9/node_modules/@inquirer/core/dist/lib/utils.js
5611
5616
  function breakLines(content, width) {
5612
5617
  return content.split(`
5613
5618
  `).flatMap((line) => wrapAnsi(line, width, { trim: false, wordWrap: false }).split(`
@@ -5618,7 +5623,7 @@ function readlineWidth() {
5618
5623
  return import_cli_width.default({ defaultWidth: 80, output: readline().output });
5619
5624
  }
5620
5625
 
5621
- // ../../node_modules/@inquirer/core/dist/lib/pagination/use-pagination.js
5626
+ // ../../node_modules/.bun/@inquirer+core@11.2.1+7cb241fc07b679d9/node_modules/@inquirer/core/dist/lib/pagination/use-pagination.js
5622
5627
  function usePointerPosition({ active, renderedItems, pageSize, loop }) {
5623
5628
  const state = useRef({
5624
5629
  lastPointer: active,
@@ -5684,12 +5689,12 @@ function usePagination({ items, active, renderItem, pageSize, loop = true }) {
5684
5689
  return pageBuffer.filter((line) => typeof line === "string").join(`
5685
5690
  `);
5686
5691
  }
5687
- // ../../node_modules/@inquirer/core/dist/lib/create-prompt.js
5692
+ // ../../node_modules/.bun/@inquirer+core@11.2.1+7cb241fc07b679d9/node_modules/@inquirer/core/dist/lib/create-prompt.js
5688
5693
  var import_mute_stream = __toESM(require_lib(), 1);
5689
5694
  import * as readline2 from "readline";
5690
5695
  import { AsyncResource as AsyncResource3 } from "async_hooks";
5691
5696
 
5692
- // ../../node_modules/signal-exit/dist/mjs/signals.js
5697
+ // ../../node_modules/.bun/signal-exit@4.1.0/node_modules/signal-exit/dist/mjs/signals.js
5693
5698
  var signals = [];
5694
5699
  signals.push("SIGHUP", "SIGINT", "SIGTERM");
5695
5700
  if (process.platform !== "win32") {
@@ -5699,7 +5704,7 @@ if (process.platform === "linux") {
5699
5704
  signals.push("SIGIO", "SIGPOLL", "SIGPWR", "SIGSTKFLT");
5700
5705
  }
5701
5706
 
5702
- // ../../node_modules/signal-exit/dist/mjs/index.js
5707
+ // ../../node_modules/.bun/signal-exit@4.1.0/node_modules/signal-exit/dist/mjs/index.js
5703
5708
  var processOk = (process3) => !!process3 && typeof process3 === "object" && typeof process3.removeListener === "function" && typeof process3.emit === "function" && typeof process3.reallyExit === "function" && typeof process3.listeners === "function" && typeof process3.kill === "function" && typeof process3.pid === "number" && typeof process3.on === "function";
5704
5709
  var kExitEmitter = Symbol.for("signal-exit emitter");
5705
5710
  var global = globalThis;
@@ -5897,10 +5902,10 @@ var {
5897
5902
  unload
5898
5903
  } = signalExitWrap(processOk(process3) ? new SignalExit(process3) : new SignalExitFallback);
5899
5904
 
5900
- // ../../node_modules/@inquirer/core/dist/lib/screen-manager.js
5905
+ // ../../node_modules/.bun/@inquirer+core@11.2.1+7cb241fc07b679d9/node_modules/@inquirer/core/dist/lib/screen-manager.js
5901
5906
  import { stripVTControlCharacters } from "util";
5902
5907
 
5903
- // ../../node_modules/@inquirer/ansi/dist/index.js
5908
+ // ../../node_modules/.bun/@inquirer+ansi@2.0.7/node_modules/@inquirer/ansi/dist/index.js
5904
5909
  var ESC2 = "\x1B[";
5905
5910
  var cursorLeft = ESC2 + "G";
5906
5911
  var cursorHide = ESC2 + "?25l";
@@ -5916,7 +5921,7 @@ var cursorTo = (x, y) => {
5916
5921
  var eraseLine = ESC2 + "2K";
5917
5922
  var eraseLines = (lines) => lines > 0 ? (eraseLine + cursorUp(1)).repeat(lines - 1) + eraseLine + cursorLeft : "";
5918
5923
 
5919
- // ../../node_modules/@inquirer/core/dist/lib/screen-manager.js
5924
+ // ../../node_modules/.bun/@inquirer+core@11.2.1+7cb241fc07b679d9/node_modules/@inquirer/core/dist/lib/screen-manager.js
5920
5925
  var height = (content) => content.split(`
5921
5926
  `).length;
5922
5927
  var lastLine = (content) => content.split(`
@@ -5982,7 +5987,7 @@ class ScreenManager {
5982
5987
  }
5983
5988
  }
5984
5989
 
5985
- // ../../node_modules/@inquirer/core/dist/lib/promise-polyfill.js
5990
+ // ../../node_modules/.bun/@inquirer+core@11.2.1+7cb241fc07b679d9/node_modules/@inquirer/core/dist/lib/promise-polyfill.js
5986
5991
  class PromisePolyfill extends Promise {
5987
5992
  static withResolver() {
5988
5993
  let resolve6;
@@ -5995,7 +6000,7 @@ class PromisePolyfill extends Promise {
5995
6000
  }
5996
6001
  }
5997
6002
 
5998
- // ../../node_modules/@inquirer/core/dist/lib/create-prompt.js
6003
+ // ../../node_modules/.bun/@inquirer+core@11.2.1+7cb241fc07b679d9/node_modules/@inquirer/core/dist/lib/create-prompt.js
5999
6004
  import path from "path";
6000
6005
  var nativeSetImmediate = globalThis.setImmediate;
6001
6006
  function getCallSites() {
@@ -6106,7 +6111,7 @@ function createPrompt(view) {
6106
6111
  };
6107
6112
  return prompt;
6108
6113
  }
6109
- // ../../node_modules/@inquirer/core/dist/lib/Separator.js
6114
+ // ../../node_modules/.bun/@inquirer+core@11.2.1+7cb241fc07b679d9/node_modules/@inquirer/core/dist/lib/Separator.js
6110
6115
  import { styleText as styleText2 } from "util";
6111
6116
  class Separator {
6112
6117
  separator = styleText2("dim", Array.from({ length: 15 }).join(dist_default.line));
@@ -6120,7 +6125,7 @@ class Separator {
6120
6125
  return Boolean(choice && typeof choice === "object" && "type" in choice && choice.type === "separator");
6121
6126
  }
6122
6127
  }
6123
- // ../../node_modules/@inquirer/checkbox/dist/index.js
6128
+ // ../../node_modules/.bun/@inquirer+checkbox@5.2.1+7cb241fc07b679d9/node_modules/@inquirer/checkbox/dist/index.js
6124
6129
  import { styleText as styleText3 } from "util";
6125
6130
  var checkboxTheme = {
6126
6131
  icon: {
@@ -8492,6 +8497,16 @@ var recipes = [
8492
8497
  id: "starter-0.6.17-to-0.6.18",
8493
8498
  from: "0.6.17",
8494
8499
  to: "0.6.18"
8500
+ },
8501
+ {
8502
+ id: "starter-0.6.18-to-0.6.19",
8503
+ from: "0.6.18",
8504
+ to: "0.6.19"
8505
+ },
8506
+ {
8507
+ id: "starter-0.6.19-to-0.6.20",
8508
+ from: "0.6.19",
8509
+ to: "0.6.20"
8495
8510
  }
8496
8511
  ];
8497
8512
  function resolveRecipeChain(sourceVersion, targetVersion) {
@@ -8537,8 +8552,7 @@ function parseVersion2(version) {
8537
8552
  }
8538
8553
 
8539
8554
  // src/update/updater.ts
8540
- import { readFile as readFile24, rm as rm8 } from "fs/promises";
8541
- import { join as join29 } from "path";
8555
+ import { rm as rm8 } from "fs/promises";
8542
8556
 
8543
8557
  // src/update/artifacts.ts
8544
8558
  import { mkdtemp, readFile as readFile15 } from "fs/promises";
@@ -10859,12 +10873,48 @@ function safeTemporaryPath(root, path2) {
10859
10873
  return target;
10860
10874
  }
10861
10875
 
10876
+ // src/update/project-quality-verification.ts
10877
+ import { readFile as readFile24 } from "fs/promises";
10878
+ import { join as join29 } from "path";
10879
+ var qualityScriptNames = ["lint", "typecheck", "test", "build"];
10880
+ function skippedProjectQualityVerification() {
10881
+ return {
10882
+ status: "skipped",
10883
+ steps: [
10884
+ {
10885
+ name: "\u9879\u76EE\u8D28\u91CF\u9A8C\u8BC1\uFF08lint/typecheck/test/build\uFF09",
10886
+ status: "skipped"
10887
+ }
10888
+ ]
10889
+ };
10890
+ }
10891
+ async function runProjectQualityVerification(projectDirectory, execute) {
10892
+ const packageJson = JSON.parse(await readFile24(join29(projectDirectory, "package.json"), "utf8"));
10893
+ const steps = [];
10894
+ for (const name of qualityScriptNames) {
10895
+ const stepName = `bun run ${name}`;
10896
+ if (!packageJson.scripts?.[name]) {
10897
+ steps.push({ name: stepName, status: "skipped" });
10898
+ continue;
10899
+ }
10900
+ try {
10901
+ await execute("bun", ["run", name], projectDirectory);
10902
+ steps.push({ name: stepName, status: "passed" });
10903
+ } catch {
10904
+ steps.push({ name: stepName, status: "failed" });
10905
+ return { status: "failed", steps };
10906
+ }
10907
+ }
10908
+ return { status: "passed", steps };
10909
+ }
10910
+
10862
10911
  // src/update/updater-report.ts
10863
- async function writeUpdateReport(result2, steps, relativePath2, expected) {
10912
+ async function writeUpdateReport(result2, steps, projectQualityVerification, relativePath2, expected) {
10864
10913
  const lines = [
10865
10914
  `# Windy \u66F4\u65B0\u62A5\u544A ${result2.plan.id}`,
10866
10915
  "",
10867
- "- \u9A8C\u8BC1\u72B6\u6001\uFF1A\u5DF2\u901A\u8FC7\uFF1B\u4E8B\u52A1\u7ED3\u679C\u4EE5 update-state \u6700\u7EC8\u63D0\u4EA4\u4E3A\u51C6",
10916
+ "- \u5B89\u5168\u63D0\u4EA4\u72B6\u6001\uFF1A\u5DF2\u901A\u8FC7\uFF1B\u4E8B\u52A1\u7ED3\u679C\u4EE5 update-state \u6700\u7EC8\u63D0\u4EA4\u4E3A\u51C6",
10917
+ `- \u9879\u76EE\u8D28\u91CF\u9A8C\u8BC1\uFF1A${qualityStatusLabel(projectQualityVerification)}`,
10868
10918
  `- \u6765\u6E90\u7248\u672C\uFF1A${result2.plan.sourceVersion}`,
10869
10919
  `- \u76EE\u6807\u7248\u672C\uFF1A${result2.plan.targetVersion}`,
10870
10920
  `- Recipe\uFF1A${result2.plan.recipes.join("\u3001") || "\u65E0"}`,
@@ -10883,10 +10933,14 @@ async function writeUpdateReport(result2, steps, relativePath2, expected) {
10883
10933
  ...result2.plan.manualCleanup.map(({ message }) => `- ${message}`)
10884
10934
  ] : [],
10885
10935
  "",
10886
- "## \u9A8C\u8BC1",
10936
+ "## \u5B89\u5168\u68C0\u67E5\u4E0E\u53EF\u9009\u9A8C\u8BC1",
10887
10937
  "",
10888
10938
  ...steps.map(({ name, status }) => `- ${name}\uFF1A${status}`),
10889
10939
  "",
10940
+ ...projectQualityVerification === "skipped" ? [
10941
+ "\u672C\u6B21\u66F4\u65B0\u672A\u6267\u884C\u9879\u76EE\u7EA7 lint/typecheck/test/build\u3002\u9700\u8981\u5B8C\u6574\u8D28\u91CF\u9A8C\u8BC1\u65F6\uFF0C\u8BF7\u5728\u9879\u76EE\u6839\u76EE\u5F55\u6309\u5B9E\u9645\u5B58\u5728\u7684\u811A\u672C\u663E\u5F0F\u8FD0\u884C\u8FD9\u4E9B\u547D\u4EE4\u3002",
10942
+ ""
10943
+ ] : [],
10890
10944
  "\u4E8B\u52A1\u63D0\u4EA4\u540E\u81EA\u52A8\u6E05\u7406\u5907\u4EFD\uFF1B\u82E5\u63D0\u793A\u6E05\u7406\u5F85\u5B8C\u6210\uFF0C\u8BF7\u8FD0\u884C `bun run windy doctor --repair`\u3002",
10891
10945
  ""
10892
10946
  ];
@@ -10895,6 +10949,13 @@ async function writeUpdateReport(result2, steps, relativePath2, expected) {
10895
10949
  await writeOwnedTransactionFile(result2.plan.projectDirectory, relativePath2, content, expected);
10896
10950
  return expectationForContent(relativePath2, content);
10897
10951
  }
10952
+ function qualityStatusLabel(status) {
10953
+ if (status === "passed")
10954
+ return "\u5DF2\u901A\u8FC7\uFF08\u663E\u5F0F --verify\uFF09";
10955
+ if (status === "failed")
10956
+ return "\u672A\u901A\u8FC7\uFF1B\u5347\u7EA7\u4ECD\u6309\u5B89\u5168\u68C0\u67E5\u7ED3\u679C\u63D0\u4EA4";
10957
+ return "\u5DF2\u8DF3\u8FC7\uFF08\u9ED8\u8BA4\u4E0D\u6267\u884C lint/typecheck/test/build\uFF09";
10958
+ }
10898
10959
 
10899
10960
  // src/update/updater.ts
10900
10961
  class DefaultStarterUpdater {
@@ -10903,12 +10964,14 @@ class DefaultStarterUpdater {
10903
10964
  #beforeTransactionFinalize;
10904
10965
  #finalizeTransaction;
10905
10966
  #skipInstall;
10967
+ #verifyProject;
10906
10968
  constructor(dependencies = {}) {
10907
10969
  this.#artifacts = dependencies.artifacts ?? new PublishedArtifactProvider;
10908
10970
  this.#execute = dependencies.execute ?? runCommand;
10909
10971
  this.#beforeTransactionFinalize = dependencies.beforeTransactionFinalize;
10910
10972
  this.#finalizeTransaction = dependencies.finalizeTransaction ?? finalizeOwnedTransaction;
10911
10973
  this.#skipInstall = dependencies.skipInstall ?? false;
10974
+ this.#verifyProject = dependencies.verifyProject ?? false;
10912
10975
  }
10913
10976
  async plan(input) {
10914
10977
  return createUpdatePlan(input, this.#artifacts);
@@ -10979,18 +11042,11 @@ class DefaultStarterUpdater {
10979
11042
  const plannedPaths = new Set(result2.plan.files.map(({ path: path2 }) => path2));
10980
11043
  replaceExpectations(result2, verificationSnapshot.filter(({ path: path2 }) => !metadataPaths2.has(path2) && !plannedPaths.has(path2)));
10981
11044
  verificationExpectationsFrozen = true;
10982
- const packageJson = JSON.parse(await readFile24(join29(result2.plan.projectDirectory, "package.json"), "utf8"));
10983
- for (const name of ["lint", "typecheck", "test", "build"]) {
10984
- if (!packageJson.scripts?.[name]) {
10985
- steps.push({ name: `bun run ${name}`, status: "skipped" });
10986
- continue;
10987
- }
10988
- await this.#execute("bun", ["run", name], result2.plan.projectDirectory);
10989
- steps.push({ name: `bun run ${name}`, status: "passed" });
10990
- }
11045
+ const projectQualityVerification = this.#verifyProject ? await runProjectQualityVerification(result2.plan.projectDirectory, this.#execute) : skippedProjectQualityVerification();
11046
+ steps.push(...projectQualityVerification.steps);
10991
11047
  const metadataExpectations = await finalize(result2, stabilized);
10992
11048
  const reportPath = `.windy/reports/${result2.plan.id}.md`;
10993
- const reportExpectation = await writeUpdateReport(result2, steps, reportPath, expectedForPath(result2, reportPath));
11049
+ const reportExpectation = await writeUpdateReport(result2, steps, projectQualityVerification.status, reportPath, expectedForPath(result2, reportPath));
10994
11050
  replaceExpectations(result2, [...metadataExpectations, reportExpectation]);
10995
11051
  await rm8(result2.plan.temporaryRoot, { recursive: true, force: true });
10996
11052
  await this.#beforeTransactionFinalize?.({ result: result2, reportPath });
@@ -11000,7 +11056,13 @@ class DefaultStarterUpdater {
11000
11056
  expectations: result2.finalizationExpectations,
11001
11057
  workspaceMembershipExpectations: result2.plan.finalWorkspaceMembershipExpectations ?? result2.plan.workspaceMembershipExpectations
11002
11058
  });
11003
- return { updateId: result2.plan.id, status: "passed", steps, reportPath };
11059
+ return {
11060
+ updateId: result2.plan.id,
11061
+ status: "passed",
11062
+ projectQualityVerification: projectQualityVerification.status,
11063
+ steps,
11064
+ reportPath
11065
+ };
11004
11066
  } catch (error) {
11005
11067
  await rm8(result2.plan.temporaryRoot, { recursive: true, force: true });
11006
11068
  if (error instanceof TransactionCommittedCleanupPendingError) {
@@ -11013,7 +11075,7 @@ class DefaultStarterUpdater {
11013
11075
  if (verificationExpectationsFrozen) {
11014
11076
  const conflicts = await retainOwnedTransactionExpectationConflicts(result2.plan.projectDirectory, result2.transactionOwnerToken, result2.finalizationExpectations);
11015
11077
  if (conflicts.length) {
11016
- throw new Error(`\u66F4\u65B0\u9A8C\u8BC1\u671F\u95F4\u68C0\u6D4B\u5230\u5E76\u53D1\u4FEE\u6539\uFF0C\u4E8B\u52A1\u73B0\u573A\u5DF2\u4FDD\u7559\uFF1A${conflicts.join("\u3001")}`, { cause: error });
11078
+ throw new Error(`\u66F4\u65B0\u5B89\u5168\u6536\u53E3\u671F\u95F4\u68C0\u6D4B\u5230\u5E76\u53D1\u4FEE\u6539\uFF0C\u4E8B\u52A1\u73B0\u573A\u5DF2\u4FDD\u7559\uFF1A${conflicts.join("\u3001")}`, { cause: error });
11017
11079
  }
11018
11080
  }
11019
11081
  await restoreUpdateState(result2.plan.projectDirectory, {
@@ -11025,7 +11087,7 @@ class DefaultStarterUpdater {
11025
11087
  phase: "ready",
11026
11088
  manifestHash: result2.expectedManifestHash
11027
11089
  });
11028
- throw new Error(`\u66F4\u65B0\u9A8C\u8BC1\u5931\u8D25\uFF0C\u9879\u76EE\u5DF2\u56DE\u6EDA\u5230 ${result2.plan.sourceVersion}\uFF1A${error instanceof Error ? error.message : String(error)}`, { cause: error });
11090
+ throw new Error(`\u66F4\u65B0\u5B89\u5168\u68C0\u67E5\u5931\u8D25\uFF0C\u9879\u76EE\u5DF2\u56DE\u6EDA\u5230 ${result2.plan.sourceVersion}\uFF1A${error instanceof Error ? error.message : String(error)}`, { cause: error });
11029
11091
  }
11030
11092
  }
11031
11093
  }
@@ -11671,6 +11733,20 @@ function actionLabel(action) {
11671
11733
  return action === "add" ? "\u5B89\u88C5" : action === "update" ? "\u66F4\u65B0" : "\u5378\u8F7D";
11672
11734
  }
11673
11735
 
11736
+ // src/update/cli-quality-output.ts
11737
+ function printProjectQualityVerification(status) {
11738
+ if (status === "passed") {
11739
+ console.log("\u9879\u76EE\u8D28\u91CF\u9A8C\u8BC1\uFF1A\u5DF2\u901A\u8FC7\uFF08\u663E\u5F0F --verify\uFF09");
11740
+ return;
11741
+ }
11742
+ if (status === "failed") {
11743
+ console.error("\u9879\u76EE\u8D28\u91CF\u9A8C\u8BC1\uFF1A\u672A\u901A\u8FC7\uFF1B\u5347\u7EA7\u5DF2\u5B89\u5168\u63D0\u4EA4\uFF0C\u8BF7\u67E5\u770B\u4E0A\u65B9\u5931\u8D25\u547D\u4EE4\u548C\u66F4\u65B0\u62A5\u544A");
11744
+ process.exitCode = 1;
11745
+ return;
11746
+ }
11747
+ console.log("\u9879\u76EE\u8D28\u91CF\u9A8C\u8BC1\uFF1A\u5DF2\u8DF3\u8FC7\uFF1B\u53EF\u6309\u9700\u663E\u5F0F\u8FD0\u884C lint\u3001typecheck\u3001test \u548C build");
11748
+ }
11749
+
11674
11750
  // src/update/cli.ts
11675
11751
  async function runLifecycleCommand(args, dependencies = {}) {
11676
11752
  const command = args[0];
@@ -11745,6 +11821,7 @@ var updateHelpText = `\u7528\u6CD5\uFF1Acreate-windy update [\u9009\u9879]
11745
11821
  --to <\u7248\u672C> \u66F4\u65B0\u5230\u6307\u5B9A\u7248\u672C
11746
11822
  --cwd <\u76EE\u5F55> \u6307\u5B9A\u9879\u76EE\u76EE\u5F55
11747
11823
  --skip-install \u66F4\u65B0\u65F6\u4E0D\u6267\u884C bun install
11824
+ --verify \u663E\u5F0F\u6267\u884C\u9879\u76EE lint/typecheck/test/build
11748
11825
  --force \u5F3A\u5236\u91C7\u7528\u5E73\u53F0\u57FA\u7EBF\u53C2\u4E0E\u4E09\u65B9\u5408\u5E76
11749
11826
  -h, --help \u663E\u793A update \u5E2E\u52A9\u5E76\u9000\u51FA
11750
11827
  `;
@@ -11777,7 +11854,8 @@ async function runUpdate(args, projectDirectory) {
11777
11854
  return;
11778
11855
  }
11779
11856
  const updater = new DefaultStarterUpdater({
11780
- skipInstall: args.includes("--skip-install")
11857
+ skipInstall: args.includes("--skip-install"),
11858
+ verifyProject: args.includes("--verify")
11781
11859
  });
11782
11860
  const plan = await updater.plan({
11783
11861
  projectDirectory,
@@ -11800,6 +11878,7 @@ async function runUpdate(args, projectDirectory) {
11800
11878
  const environmentRepair = await repairLocalEnvironment(projectDirectory);
11801
11879
  console.log(`
11802
11880
  \u66F4\u65B0\u5B8C\u6210\uFF1A${plan.sourceVersion} \u2192 ${plan.targetVersion}`);
11881
+ printProjectQualityVerification(report.projectQualityVerification);
11803
11882
  console.log(`\u62A5\u544A\uFF1A${report.reportPath}`);
11804
11883
  printEnvironmentRepair(environmentRepair);
11805
11884
  if (environmentRepair.missingKeys.length || environmentRepair.duplicateKeys.length) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-windy",
3
- "version": "0.6.18",
3
+ "version": "0.6.20",
4
4
  "description": "创建单组织、单租户、私有部署优先的 Windy 企业 Dashboard Starter",
5
5
  "type": "module",
6
6
  "bin": {
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "schemaVersion": 1,
3
- "templateCommit": "517a96ca3489632c4083fe08921935bb3f4b1781",
4
- "generatorVersion": "0.6.18",
3
+ "templateCommit": "2b9bad011549abb0be1af6a4d0eefe60426f7247",
4
+ "generatorVersion": "0.6.20",
5
5
  "modules": [
6
6
  {
7
7
  "name": "system",
@@ -160,6 +160,8 @@ bun run build
160
160
  bun run windy doctor
161
161
  bun run windy update --check
162
162
  bun run windy update --dry-run
163
+ bun run windy update
164
+ # 如需在本次更新中显式执行项目质量脚本:bun run windy update --verify
163
165
  bun run windy module list
164
166
  # 当前组合没有可安全 runtime disable 的已安装可选模块
165
167
  bun run lint
@@ -170,11 +172,12 @@ bun run format
170
172
 
171
173
  更新前无需提交、暂存或清理业务改动。`--dry-run` 只生成计划,不写项目;无关 WIP
172
174
  保持原样,命中同一路径时按冻结快照与三方合并 fail closed。正式更新先备份,再做
173
- package 字段级合并、共享脚手架三方合并与完整验证。整个 `apps/web/src` 和
175
+ package 字段级合并、共享脚手架三方合并与安全收口;默认不执行项目 lint、typecheck、
176
+ test 或 build,传入 `--verify` 才显式执行并记录结果。整个 `apps/web/src` 和
174
177
  `apps/server/src/app` 默认保持项目版本;已登记版本 recipe 只修改其声明的窄锚点。
175
178
  任何冲突都会在写入前中止,模块启停在交互
176
179
  终端确认;CI、Agent 或其它非 TTY 环境必须显式传入 `--yes`。
177
- 无并发变化的验证失败会自动回滚;中断恢复使用
180
+ 安全检查失败会自动回滚;项目质量脚本失败本身不回滚已安全应用的更新。中断恢复使用
178
181
  `bun run windy doctor --repair`。该命令在提交点前保留并发编辑并回滚其余路径,
179
182
  在 committed 提交点后只完成事务状态与备份清理,不回滚项目文件。
180
183
 
@@ -15,6 +15,7 @@ uv run mypy
15
15
 
16
16
  HTTP 接口:
17
17
 
18
+ - `GET /api/agent/runtime/compatibility`(内部认证、V1 协议 Offer)
18
19
  - `POST /api/agent/runs`
19
20
  - `GET /api/agent/runs/:runId/events`
20
21
  - `GET /api/agent/runs/:runId/result`
@@ -24,6 +25,11 @@ Run 创建使用 `Idempotency-Key`。事件以 SSE 传输,支持 `Last-Event-I
24
25
  断线重放,并周期发送 comment heartbeat。Provider 或 Engine 异常只映射为 canonical
25
26
  安全错误,不把原始异常正文暴露给调用方。
26
27
 
28
+ 兼容 Offer 返回 Engine identity、支持的协议版本、传输能力、callback auth profile 及三层摘要。
29
+ 新 Windy Server 会在 Run 请求头固定所选版本和 Offer digest;新 Agent Server 仍接受完全不带 pin
30
+ 的旧调用方,但拒绝半套或不匹配 pin。它只用于同一受控部署,不能把内部 token 发给外部业务系统。
31
+ 跨系统限制见 [Remote Agent Runtime 兼容契约](../../docs/architecture/agent-runtime-compatibility.md)。
32
+
27
33
  配置以下仅服务端可见的环境变量后启用 Direct Engine:
28
34
 
29
35
  ```bash
@@ -34,6 +34,12 @@ from .http_helpers import (
34
34
  )
35
35
  from .report_routes import create_report_router
36
36
  from .report_store import AgentRuntimeReportStore
37
+ from .runtime_compatibility import (
38
+ OFFER_DIGEST_HEADER,
39
+ PROTOCOL_HEADER,
40
+ RuntimeCompatibilityError,
41
+ validate_runtime_compatibility_pin,
42
+ )
37
43
  from .runtime_factory import (
38
44
  default_engine,
39
45
  default_report_store,
@@ -191,11 +197,28 @@ def create_app(
191
197
  request: StartRunRequest,
192
198
  idempotency_key: Annotated[str, Header(alias="Idempotency-Key")],
193
199
  authorization: Annotated[str | None, Header()] = None,
200
+ runtime_protocol: Annotated[str | None, Header(alias=PROTOCOL_HEADER)] = None,
201
+ runtime_offer_digest: Annotated[
202
+ str | None, Header(alias=OFFER_DIGEST_HEADER)
203
+ ] = None,
194
204
  ) -> StartRunResponse | JSONResponse:
195
205
  if not authorized(authorization, internal_token):
196
206
  return safe_error(403, "FORBIDDEN", "内部调用认证失败", False)
197
207
  if not idempotency_key.strip():
198
208
  return safe_error(400, "INVALID_INPUT", "幂等键不能为空", False)
209
+ try:
210
+ validate_runtime_compatibility_pin(
211
+ runtime_protocol,
212
+ runtime_offer_digest,
213
+ runtime_engine.engine_id,
214
+ )
215
+ except RuntimeCompatibilityError:
216
+ return safe_error(
217
+ 409,
218
+ "RUNTIME_UNAVAILABLE",
219
+ "Agent Runtime compatibility pin 不匹配",
220
+ False,
221
+ )
199
222
  try:
200
223
  await require_vision_readiness(runtime_engine, request)
201
224
  except SafeEngineError as error:
@@ -7,6 +7,7 @@ from fastapi import FastAPI, Header
7
7
  from fastapi.responses import JSONResponse
8
8
 
9
9
  from .http_helpers import authorized, safe_error
10
+ from .runtime_compatibility import COMPATIBILITY_PATH, runtime_compatibility_offer
10
11
  from .service import AgentRunService
11
12
  from .vlm_readiness import unavailable_vlm_snapshot
12
13
 
@@ -82,3 +83,11 @@ def install_health_routes(
82
83
  "Agent Runtime Model coordinate 不可用",
83
84
  True,
84
85
  )
86
+
87
+ @application.get(COMPATIBILITY_PATH, response_model=None)
88
+ async def runtime_compatibility(
89
+ authorization: Annotated[str | None, Header()] = None,
90
+ ) -> dict[str, object] | JSONResponse:
91
+ if not authorized(authorization, internal_token):
92
+ return safe_error(403, "FORBIDDEN", "内部调用认证失败", False)
93
+ return runtime_compatibility_offer(runtime.engine.engine_id)