dsh-composer-ux 0.5.0 → 0.6.2

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.
package/lib/index.js CHANGED
@@ -17,6 +17,32 @@ function pick(source, keys, forced) {
17
17
  for (const key of keys) if (forced || source[key] !== void 0) result[key] = source[key];
18
18
  return result;
19
19
  }
20
+ var write = Symbol.for("cosmokit.volatile.write");
21
+ function snapshot(value, ancestors = /* @__PURE__ */ new Set()) {
22
+ if (typeof value === "function") throw new TypeError("volatile config cannot contain functions");
23
+ if (value === null || typeof value !== "object") return value;
24
+ if (ancestors.has(value)) throw new TypeError("volatile config cannot contain cycles");
25
+ ancestors.add(value);
26
+ try {
27
+ if (Array.isArray(value)) return Object.freeze(value.map((item) => snapshot(item, ancestors)));
28
+ if (Object.getPrototypeOf(value) !== Object.prototype && Object.getPrototypeOf(value) !== null) throw new TypeError("volatile config objects must be plain objects or arrays");
29
+ return Object.freeze(Object.fromEntries(Object.entries(value).map(([key, item]) => [key, snapshot(item, ancestors)])));
30
+ } finally {
31
+ ancestors.delete(value);
32
+ }
33
+ }
34
+ function createVolatile(value) {
35
+ let current = snapshot(value);
36
+ return Object.freeze({
37
+ get: () => current,
38
+ [write]: (value2) => {
39
+ current = value2;
40
+ }
41
+ });
42
+ }
43
+ function isVolatile(value) {
44
+ return typeof value === "object" && value !== null && write in value;
45
+ }
20
46
  function is(type, value) {
21
47
  if (arguments.length === 1) return (value2) => is(type, value2);
22
48
  return type in globalThis && value instanceof globalThis[type] || Object.prototype.toString.call(value).slice(8, -1) === type;
@@ -95,24 +121,37 @@ function clone(source, refs = /* @__PURE__ */ new Map()) {
95
121
  return result;
96
122
  }
97
123
  function deepEqual(a, b, strict) {
98
- if (a === b) return true;
99
- if (!strict && isNullable(a) && isNullable(b)) return true;
100
- if (typeof a !== typeof b) return false;
101
- if (typeof a !== "object") return false;
102
- if (!a || !b) return false;
103
- function check(test, then) {
104
- return test(a) ? test(b) ? then(a, b) : false : test(b) ? false : void 0;
105
- }
106
- return check(Array.isArray, (a2, b2) => a2.length === b2.length && a2.every((item, index) => deepEqual(item, b2[index]))) ?? check(is("Date"), (a2, b2) => a2.valueOf() === b2.valueOf()) ?? check(is("RegExp"), (a2, b2) => a2.source === b2.source && a2.flags === b2.flags) ?? check(isArrayBufferLike, (a2, b2) => {
107
- if (a2.byteLength !== b2.byteLength) return false;
108
- const viewA = new Uint8Array(a2);
109
- const viewB = new Uint8Array(b2);
110
- for (let i = 0; i < viewA.length; i++) if (viewA[i] !== viewB[i]) return false;
111
- return true;
112
- }) ?? Object.keys({
113
- ...a,
114
- ...b
115
- }).every((key) => deepEqual(a[key], b[key], strict));
124
+ const ancestors = /* @__PURE__ */ new Set();
125
+ function compare(a2, b2) {
126
+ if (a2 === b2) return true;
127
+ if (isVolatile(a2) || isVolatile(b2)) return isVolatile(a2) && isVolatile(b2);
128
+ if (!strict && isNullable(a2) && isNullable(b2)) return true;
129
+ if (typeof a2 !== typeof b2 || typeof a2 !== "object" || !a2 || !b2) return false;
130
+ if (ancestors.has(a2)) return false;
131
+ function check(test, then) {
132
+ return test(a2) ? test(b2) ? then(a2, b2) : false : test(b2) ? false : void 0;
133
+ }
134
+ ancestors.add(a2);
135
+ try {
136
+ return check(Array.isArray, (a3, b3) => {
137
+ if (a3.length !== b3.length) return false;
138
+ for (let index = 0; index < a3.length; index++) if (!compare(a3[index], b3[index])) return false;
139
+ return true;
140
+ }) ?? check(is("Date"), (a3, b3) => a3.valueOf() === b3.valueOf()) ?? check(is("URL"), (a3, b3) => a3.href === b3.href) ?? check(is("RegExp"), (a3, b3) => a3.source === b3.source && a3.flags === b3.flags) ?? check(isArrayBufferLike, (a3, b3) => {
141
+ if (a3.byteLength !== b3.byteLength) return false;
142
+ const viewA = new Uint8Array(a3);
143
+ const viewB = new Uint8Array(b3);
144
+ for (let i = 0; i < viewA.length; i++) if (viewA[i] !== viewB[i]) return false;
145
+ return true;
146
+ }) ?? ((!strict || [a2, b2].every((value) => Object.getPrototypeOf(value) === Object.prototype || Object.getPrototypeOf(value) === null)) && Object.keys({
147
+ ...a2,
148
+ ...b2
149
+ }).every((key) => compare(a2[key], b2[key])));
150
+ } finally {
151
+ ancestors.delete(a2);
152
+ }
153
+ }
154
+ return compare(a, b);
116
155
  }
117
156
  var Time;
118
157
  (function(Time2) {
@@ -361,6 +400,7 @@ Schema.prototype.pattern = function pattern(regexp) {
361
400
  return schema;
362
401
  };
363
402
  Schema.prototype.simplify = function simplify(value) {
403
+ if (isVolatile(value)) value = value.get();
364
404
  if (deepEqual(value, this.meta.default, this.type === "dict")) return null;
365
405
  if (isNullable(value)) return value;
366
406
  if (this.type === "object" || this.type === "dict") {
@@ -418,12 +458,49 @@ for (const key of [
418
458
  };
419
459
  return schema;
420
460
  } });
461
+ Schema.prototype.volatile = function volatile() {
462
+ if (this.meta.volatile) throw new TypeError("volatile schema is already wrapped");
463
+ return this.extra("volatile", true);
464
+ };
421
465
  var resolvers = {};
466
+ var checkedVolatile = Symbol("checked-volatile-schema");
467
+ function validateVolatileSchema(schema, path = [], blocked = false, seen = /* @__PURE__ */ new Map()) {
468
+ const states = seen.get(schema) ?? /* @__PURE__ */ new Set();
469
+ if (states.has(blocked)) return;
470
+ states.add(blocked);
471
+ seen.set(schema, states);
472
+ if (schema.meta?.volatile && blocked) throw new ValidationError("volatile fields require a fixed object path without an enclosing volatile field", { path });
473
+ const nested = blocked || !!schema.meta?.volatile;
474
+ if (schema.dict) for (const [key, child] of Object.entries(schema.dict)) validateVolatileSchema(child, [...path, key], nested, seen);
475
+ if (schema.sKey) validateVolatileSchema(schema.sKey, [...path, "<key>"], true, seen);
476
+ if (schema.inner && (schema.type !== "lazy" || schema.inner[kSchema])) validateVolatileSchema(schema.inner, [...path, "*"], true, seen);
477
+ if (schema.list) for (let index = 0; index < schema.list.length; index++) validateVolatileSchema(schema.list[index], [...path, String(index)], true, seen);
478
+ }
422
479
  Schema.extend = function extend(type, resolve3) {
423
480
  resolvers[type] = resolve3;
424
481
  };
425
482
  Schema.resolve = function resolve(data, schema, options = {}, strict = false) {
426
483
  if (!schema) return [data];
484
+ if (!options[checkedVolatile]) {
485
+ validateVolatileSchema(schema, options.path);
486
+ options = {
487
+ ...options,
488
+ [checkedVolatile]: true
489
+ };
490
+ }
491
+ if (schema.meta?.volatile) {
492
+ const inner = Schema(schema);
493
+ inner.meta = {
494
+ ...schema.meta,
495
+ volatile: false
496
+ };
497
+ const [value, adapted] = Schema.resolve(data, inner, options, strict);
498
+ try {
499
+ return [createVolatile(value), adapted];
500
+ } catch (error) {
501
+ throw new ValidationError(error instanceof Error ? error.message : String(error), options);
502
+ }
503
+ }
427
504
  if (options.ignore?.(data, schema)) return [data];
428
505
  if (isNullable(data) && schema.type !== "lazy") {
429
506
  if (schema.meta.required) throw new ValidationError(`missing required value`, options);
@@ -531,6 +608,7 @@ Schema.extend("lazy", (data, schema, options, strict) => {
531
608
  ...schema.meta,
532
609
  ...schema.inner.meta
533
610
  };
611
+ validateVolatileSchema(schema.inner, options.path, true);
534
612
  }
535
613
  return Schema.resolve(data, schema.inner, options, strict);
536
614
  });
@@ -630,7 +708,7 @@ function property(data, key, schema, options) {
630
708
  } catch (e) {
631
709
  if (!options?.autofix) throw e;
632
710
  delete data[key];
633
- return schema.meta.default;
711
+ return schema.meta.volatile ? createVolatile(schema.meta.default) : schema.meta.default;
634
712
  }
635
713
  }
636
714
  Schema.extend("array", (data, { inner, meta }, options) => {
@@ -790,7 +868,7 @@ defineMethod("transform", [
790
868
  import { spawn } from "node:child_process";
791
869
  import { existsSync as existsSync2, readFileSync } from "node:fs";
792
870
  import { tmpdir } from "node:os";
793
- import { dirname as dirname2, join as join2, resolve as resolve2 } from "node:path";
871
+ import { dirname as dirname3, join as join3, resolve as resolve2 } from "node:path";
794
872
  import inspector from "node:inspector";
795
873
 
796
874
  // src/terminal/discover.ts
@@ -1008,6 +1086,9 @@ function terminalStatusText(input) {
1008
1086
  if (input.mode === "pwsh") {
1009
1087
  return "\u4FDD\u6301 DSH \u9ED8\u8BA4\uFF08PowerShell\uFF09\u2014\u2014\u672C\u63D2\u4EF6\u4E0D\u4ECB\u5165\u7EC8\u7AEF\u5DE5\u5177\u9762";
1010
1088
  }
1089
+ if (input.probeFailed !== void 0 && input.probeFailed !== "") {
1090
+ return `\u5DF2\u542F\u7528\uFF0C\u4F46\u81EA\u68C0\u672A\u901A\u8FC7\uFF1A${input.probeFailed} \u2014\u2014 \u4E3A\u907F\u514D\u628A\u4F1A\u8BDD\u7684 shell \u6253\u6B7B\uFF0C\u6682\u4E0D\u63A5\u7BA1\uFF08\u4FDD\u6301 PowerShell\uFF09`;
1091
+ }
1011
1092
  if (input.effective === "bash") {
1012
1093
  const path = input.effectivePath ?? "";
1013
1094
  const candidate = input.candidates.find((item) => item.path === path);
@@ -1018,6 +1099,9 @@ function terminalStatusText(input) {
1018
1099
  const excluded = input.excludedCount > 0 ? `\uFF08\u5DF2\u6392\u9664 ${input.excludedCount} \u4E2A WSL \u7684 bash.exe\uFF09` : "";
1019
1100
  return `\u6CA1\u627E\u5230\u53EF\u7528\u7684 bash${excluded}\uFF0C\u6682\u65F6\u4FDD\u6301 PowerShell`;
1020
1101
  }
1102
+ if (input.deliveryFailed === true) {
1103
+ return "\u627E\u5230\u4E86 bash \u4F46\u6CA1\u80FD\u6362\u4E0A\uFF08\u5DF2\u56DE\u6EDA\uFF0Cpwsh \u7167\u65E7\u53EF\u7528\uFF09\uFF0C\u6682\u65F6\u4FDD\u6301 PowerShell";
1104
+ }
1021
1105
  return `\u5019\u9009\u4E0D\u53EF\u7528\uFF0C\u6682\u65F6\u4FDD\u6301 PowerShell`;
1022
1106
  })();
1023
1107
  const failure = input.failure === void 0 || input.failure === "" ? "" : `\uFF1B\u4E0B\u53D1\u5931\u8D25\uFF1A${input.failure}`;
@@ -1035,7 +1119,6 @@ var PANEL_ENABLED_FIELD = "panelEnabled";
1035
1119
  var TERMINAL_ENABLED_FIELD = "terminalEnabled";
1036
1120
  var SEND_KEY_FIELD = "sendKey";
1037
1121
  var NEWLINE_KEY_FIELD = "newlineKey";
1038
- var PANEL_SCROLL_FIELD = "panelScroll";
1039
1122
  var PANEL_RESIZE_FIELD = "panelResize";
1040
1123
  var PANEL_WIDTH_FIELD = "panelWidth";
1041
1124
  var PANEL_HEIGHT_FIELD = "panelHeight";
@@ -1069,7 +1152,21 @@ var QUICK_BOOK_VERSION = 2;
1069
1152
  var DEFAULT_CATEGORY_NAME = "\u9ED8\u8BA4";
1070
1153
  var OPTIMIZE_OUTPUT_MAX = 12e3;
1071
1154
  var OPTIMIZER_API_PATH = "/composer-ux/optimize";
1155
+ var OPTIMIZER_TIERS = [
1156
+ { id: "basic", label: "\u666E\u901A", hint: "\u53EA\u505A\u8BED\u8A00\u5C42\u4FEE\u590D\uFF1A\u75C5\u53E5\u3001\u9519\u522B\u5B57\u3001\u6307\u4EE3\u4E0E\u542B\u7CCA\u8BCD\uFF0C\u4E0D\u65B0\u589E\u4EFB\u4F55\u9700\u6C42\uFF0C\u7BC7\u5E45\u7EA6\u4E3A\u539F\u6587 1.4 \u500D\u3002" },
1157
+ { id: "advanced", label: "\u9AD8\u7EA7", hint: "\u5728\u4E0D\u52A8\u76EE\u6807\u7684\u524D\u63D0\u4E0B\uFF0C\u628A\u300C\u4F60\u663E\u7136\u60F3\u8981\u3001\u4F46\u6CA1\u8BF4\u51FA\u53E3\u300D\u7684\u5FC5\u8981\u8981\u6C42\u8865\u6210\u5BF9 AI \u7684\u8981\u6C42\uFF0C\u6BCF\u6761\u90FD\u8981\u6307\u56DE\u4F60\u539F\u8BDD\u91CC\u7684\u67D0\u4E00\u53E5\u3002" },
1158
+ { id: "extreme", label: "\u6781\u7AEF", hint: "\u6309\u590D\u6742\u4EFB\u52A1\u5904\u7406\uFF1A\u5728\u4E0A\u9762\u57FA\u7840\u4E0A\u518D\u52A0\u5206\u9636\u6BB5\u6267\u884C\u8BA1\u5212\u4E0E 2~4 \u79CD\u60C5\u51B5\u7684\u9884\u6848\u3002" }
1159
+ ];
1072
1160
  var DEFAULT_OPTIMIZER_TIER = "advanced";
1161
+ var OPTIMIZER_PROMPT_FIELDS = {
1162
+ basic: "optimizerPromptBasic",
1163
+ advanced: "optimizerPromptAdvanced",
1164
+ extreme: "optimizerPromptExtreme"
1165
+ };
1166
+ var OPTIMIZER_PROMPT_FIELD_LIST = OPTIMIZER_TIERS.map((item) => OPTIMIZER_PROMPT_FIELDS[item.id]);
1167
+ function optimizerPromptFieldOf(tier) {
1168
+ return OPTIMIZER_PROMPT_FIELDS[tier] ?? OPTIMIZER_PROMPT_FIELDS[DEFAULT_OPTIMIZER_TIER];
1169
+ }
1073
1170
  var DEFAULT_QUICK_PROMPTS = [
1074
1171
  { id: "builtin-1", label: "\u4E00\u95EE\u4E00\u7B54", prompt: "\u4F60\u4E0D\u61C2\u7684\u5C31\u95EE\u6211\uFF0C\u4E00\u95EE\u4E00\u7B54\uFF1B\u540C\u65F6\u8BF4\u6E05\u695A\u4F60\u4E3A\u4EC0\u4E48\u8981\u95EE\u8BE5\u95EE\u9898\uFF1B\u76F4\u5230\u4F60\u5BF9\u6211\u7684\u76EE\u6807\u6709\u660E\u786E\u8BA4\u77E5\u540E\u518D\u5F00\u59CB\u5E72\u6D3B\u3002", always: false, firstOnly: false },
1075
1172
  { id: "builtin-2", label: "\u4EA4\u63A5\u6587\u6863", prompt: "\u628A\u8FD9\u6B21\u4EFB\u52A1\u3001\u5DF2\u5B8C\u6210\u5185\u5BB9\u3001\u5F53\u524D\u5361\u70B9\u3001\u4E0B\u4E00\u6B65\u8BA1\u5212\u3001\u8E29\u8FC7\u7684\u5751\uFF0C\u6574\u7406\u6210\u4E00\u4EFD\u4EA4\u63A5\u6587\u6863\uFF0C\u5199\u7ED9\u65B0\u4F1A\u8BDD\u770B\u3002", always: false, firstOnly: false },
@@ -1214,7 +1311,6 @@ var DEFAULT_SETTINGS = {
1214
1311
  menuDelete: true,
1215
1312
  menuSelectAll: true,
1216
1313
  menuMode: "official",
1217
- panelScroll: true,
1218
1314
  panelResize: true,
1219
1315
  headerEnabled: false,
1220
1316
  headerName: DEFAULT_HEADER_NAME,
@@ -1225,6 +1321,10 @@ var DEFAULT_SETTINGS = {
1225
1321
  headerStatus: "",
1226
1322
  quickPrompts: DEFAULT_QUICK_PROMPTS,
1227
1323
  optimizerTier: DEFAULT_OPTIMIZER_TIER,
1324
+ // 空串 = 用内置提示词。默认必须是空串:它同时就是「恢复内置」要写回去的值。
1325
+ optimizerPromptBasic: "",
1326
+ optimizerPromptAdvanced: "",
1327
+ optimizerPromptExtreme: "",
1228
1328
  terminalMode: DEFAULT_TERMINAL_MODE,
1229
1329
  terminalBashPath: "",
1230
1330
  terminalCandidates: [],
@@ -1250,7 +1350,7 @@ var SECTION_SIGNALS = {
1250
1350
  // (`menuModeFrom` 对"两个键都没有"给的就是 'official',天然满足"缺省 = 默认"。)
1251
1351
  [MENU_ENABLED_FIELD]: (source) => menuModeFrom(source[MENU_MODE_FIELD], source[MENU_NATIVE_FIELD]) !== "official",
1252
1352
  [QUICK_ENABLED_FIELD]: (source) => Array.isArray(source[QUICK_PROMPTS_FIELD]) && source[QUICK_PROMPTS_FIELD].length !== DEFAULT_QUICK_PROMPTS.length || touched(source, OPTIMIZER_TIER_FIELD, DEFAULT_OPTIMIZER_TIER),
1253
- [PANEL_ENABLED_FIELD]: (source) => touched(source, PANEL_SCROLL_FIELD, true) || touched(source, PANEL_RESIZE_FIELD, true) || source[PANEL_WIDTH_FIELD] !== void 0 || source[PANEL_HEIGHT_FIELD] !== void 0,
1353
+ [PANEL_ENABLED_FIELD]: (source) => touched(source, PANEL_RESIZE_FIELD, true) || source[PANEL_WIDTH_FIELD] !== void 0 || source[PANEL_HEIGHT_FIELD] !== void 0,
1254
1354
  [TERMINAL_ENABLED_FIELD]: (source) => touched(source, TERMINAL_MODE_FIELD, DEFAULT_TERMINAL_MODE) || touched(source, TERMINAL_BASH_PATH_FIELD, "")
1255
1355
  };
1256
1356
  function touched(source, field, untouched) {
@@ -1506,6 +1606,7 @@ function createBashTool(deps, options) {
1506
1606
  const escalationModes = escalationModesOf(deps);
1507
1607
  const sep2 = options.sep ?? "\\";
1508
1608
  const isAbsolute2 = options.isAbsolute ?? ((path) => /^([a-zA-Z]:[\\/]|[\\/])/.test(path));
1609
+ const electron = options.electron ?? process.versions.electron !== void 0;
1509
1610
  const minTimeout = options.timeoutMs ?? DEFAULT_TIMEOUT_MS;
1510
1611
  const maxTimeout = options.maxTimeoutMs ?? MAX_TIMEOUT_MS;
1511
1612
  const spawnSpec = (argv, cwd, dshEnv, signal) => ({
@@ -1519,7 +1620,17 @@ function createBashTool(deps, options) {
1519
1620
  graceMs: GRACE_MS,
1520
1621
  ...signal === void 0 ? {} : { signal },
1521
1622
  // dshEnv 必须显式带上:subprocess 会先按凭据形状清洗父环境(含清掉所有 DSH_*)。
1522
- env: { ...ENV_OVERRIDES, ...dshEnv }
1623
+ env: {
1624
+ ...ENV_OVERRIDES,
1625
+ ...dshEnv,
1626
+ // ⚠️ 打包形态(Electron 桌面版)下必须补这一项。Windows 上受限模式的沙箱 runner
1627
+ // 的 argv[0] 是 `process.execPath`(`dsh-sandbox-local/lib/index.js:539`),桌面版里
1628
+ // 那就是 `DeepSeek Harness.exe`;而用 Electron 跑 JS 脚本**必须**带
1629
+ // `ELECTRON_RUN_AS_NODE=1`,否则它按 App 形态启动、原生初始化就失败
1630
+ // (2026-09-25 实测:零输出 / 0xC0000142)。官方 `sandbox-local` 只返回 argv、不带 env,
1631
+ // 所以这一项只能由 **spawn 方**补齐。对 `bash.exe` 本身无副作用(它不认这个变量)。
1632
+ ...electron ? { ELECTRON_RUN_AS_NODE: "1" } : {}
1633
+ }
1523
1634
  });
1524
1635
  const readFrame = (handle, offsets) => {
1525
1636
  const outRead = handle.collected.stdout?.readFrom(offsets.stdout);
@@ -1713,6 +1824,52 @@ ${raw.replace(/\n+$/, "")}
1713
1824
  }
1714
1825
  };
1715
1826
  }
1827
+ var PROBE_COMMAND = "printf %s dsh-composer-ux-probe-ok";
1828
+ var PROBE_MARKER = "dsh-composer-ux-probe-ok";
1829
+ var PROBE_TIMEOUT_MS = 8e3;
1830
+ function formatProbeExit(exitCode) {
1831
+ if (exitCode === null) return "null\uFF08\u88AB\u4FE1\u53F7\u7ED3\u675F\uFF09";
1832
+ if (exitCode < 0 || exitCode >= 2147483648) {
1833
+ const unsigned = exitCode < 0 ? exitCode + 4294967296 : exitCode;
1834
+ return `0x${unsigned.toString(16).toUpperCase()}\uFF08${unsigned}\uFF09`;
1835
+ }
1836
+ return String(exitCode);
1837
+ }
1838
+ async function probeBashExecution(deps, options, exec) {
1839
+ const probeDeps = deps.shellEnv === void 0 ? deps : {
1840
+ ...deps,
1841
+ shellEnv: {
1842
+ collect: (ctx) => {
1843
+ try {
1844
+ return deps.shellEnv?.collect(ctx) ?? {};
1845
+ } catch {
1846
+ return {};
1847
+ }
1848
+ }
1849
+ }
1850
+ };
1851
+ try {
1852
+ const tool = createBashTool(probeDeps, { ...options, timeoutMs: PROBE_TIMEOUT_MS, maxTimeoutMs: PROBE_TIMEOUT_MS });
1853
+ const result = await tool.execute(
1854
+ { command: PROBE_COMMAND, description: "composer-ux terminal self-check", timeoutMs: PROBE_TIMEOUT_MS },
1855
+ exec
1856
+ );
1857
+ if (result?.kind !== "foreground") return { ok: false, detail: "\u81EA\u68C0\u6CA1\u6709\u8D70\u524D\u53F0\u6267\u884C\u8DEF\u5F84" };
1858
+ if (result.timedOut === true) return { ok: false, detail: `\u81EA\u68C0\u8D85\u65F6\uFF08${PROBE_TIMEOUT_MS}ms\uFF09` };
1859
+ const text = result.stdout?.text ?? "";
1860
+ const exit = result.exitCode ?? 0;
1861
+ if (exit !== 0) {
1862
+ return {
1863
+ ok: false,
1864
+ detail: `\u81EA\u68C0\u547D\u4EE4\u9000\u51FA\u7801 ${formatProbeExit(exit)}${text.trim() === "" ? "\uFF08\u96F6\u8F93\u51FA\uFF09" : ""}`
1865
+ };
1866
+ }
1867
+ if (!text.includes(PROBE_MARKER)) return { ok: false, detail: "\u81EA\u68C0\u6CA1\u62FF\u5230\u9884\u671F\u8F93\u51FA\uFF08\u547D\u4EE4\u6CA1\u771F\u6B63\u8DD1\u8D77\u6765\uFF09" };
1868
+ return { ok: true, detail: "" };
1869
+ } catch (error) {
1870
+ return { ok: false, detail: error instanceof Error ? error.message : String(error) };
1871
+ }
1872
+ }
1716
1873
 
1717
1874
  // src/terminal/host.ts
1718
1875
  var TERMINAL_OFF_TEXT = "\u672A\u542F\u7528\uFF1A\u6253\u5F00\u8FD9\u4E00\u680F\u7684\u5F00\u5173\u540E\u624D\u4F1A\u63A5\u7BA1\uFF08\u73B0\u5728\u4FDD\u6301 PowerShell\uFF09";
@@ -1734,8 +1891,11 @@ function installTerminalPolicy(ctx, namespace, settings, readToolDeps, options =
1734
1891
  let lastEffective = "";
1735
1892
  let lastCandidates = "";
1736
1893
  let failure = "";
1894
+ let lastDeliveryFailure = "";
1737
1895
  let busy = false;
1738
1896
  let again = false;
1897
+ let probeKey = "";
1898
+ let probeOutcome;
1739
1899
  const discover = options.discover ?? ((explicitPath) => probeBash(
1740
1900
  explicitPath,
1741
1901
  process.env.PATH ?? "",
@@ -1755,12 +1915,14 @@ function installTerminalPolicy(ctx, namespace, settings, readToolDeps, options =
1755
1915
  const scoped = agent.ctx;
1756
1916
  const cleanups = [];
1757
1917
  const notes = [];
1918
+ const skips = [];
1758
1919
  try {
1759
1920
  const lift = scoped.tools?.restrict({ deny: ["pwsh"] });
1760
1921
  if (typeof lift === "function") cleanups.push(lift);
1761
1922
  } catch (error) {
1762
- notes.push(`restrict(pwsh) \u8DF3\u8FC7\uFF1A${errorText(error)}`);
1923
+ skips.push(`restrict(pwsh) \u8DF3\u8FC7\uFF1A${errorText(error)}`);
1763
1924
  }
1925
+ let registered = false;
1764
1926
  try {
1765
1927
  const deps = readToolDeps();
1766
1928
  if (deps === void 0) {
@@ -1768,7 +1930,12 @@ function installTerminalPolicy(ctx, namespace, settings, readToolDeps, options =
1768
1930
  } else {
1769
1931
  const definition = createBashTool(deps, { bashPath, sep, isAbsolute });
1770
1932
  const unregister = scoped.tools?.register(definition);
1771
- if (typeof unregister === "function") cleanups.push(unregister);
1933
+ if (typeof unregister === "function") {
1934
+ cleanups.push(unregister);
1935
+ registered = true;
1936
+ } else {
1937
+ notes.push("tools.register \u6CA1\u6709\u8FD4\u56DE disposer\uFF08\u8FD9\u4E00\u7248\u7684\u6CE8\u518C\u9762\u4E0D\u53EF\u7528\uFF09");
1938
+ }
1772
1939
  }
1773
1940
  } catch (error) {
1774
1941
  notes.push(`\u6CE8\u518C bash \u5931\u8D25\uFF1A${errorText(error)}`);
@@ -1788,6 +1955,16 @@ function installTerminalPolicy(ctx, namespace, settings, readToolDeps, options =
1788
1955
  } catch (error) {
1789
1956
  notes.push(`\u63D0\u793A\u8BCD\u6BB5\u5931\u8D25\uFF1A${errorText(error)}`);
1790
1957
  }
1958
+ if (!registered) {
1959
+ for (const cleanup of [...cleanups].reverse()) {
1960
+ try {
1961
+ cleanup();
1962
+ } catch {
1963
+ }
1964
+ }
1965
+ lastDeliveryFailure = notes.length === 0 ? "bash \u5DE5\u5177\u6CA1\u6709\u6CE8\u518C\u4E0A" : notes.join("\uFF1B");
1966
+ return void 0;
1967
+ }
1791
1968
  return {
1792
1969
  path: bashPath,
1793
1970
  dispose: () => {
@@ -1798,9 +1975,45 @@ function installTerminalPolicy(ctx, namespace, settings, readToolDeps, options =
1798
1975
  }
1799
1976
  }
1800
1977
  },
1801
- ...notes.length === 0 ? {} : { failure: notes.join("\uFF1B") }
1978
+ ...notes.length === 0 ? {} : { failure: notes.join("\uFF1B") },
1979
+ ...skips.length === 0 ? {} : { skipped: skips.join("\uFF1B") }
1802
1980
  };
1803
1981
  };
1982
+ const confinedModeOf = (deps, agent) => {
1983
+ if (deps?.sandbox === void 0 || deps.sandboxPolicy === void 0) return "";
1984
+ try {
1985
+ const session = agent?.session;
1986
+ const mode = deps.sandboxPolicy.resolve(session === void 0 ? {} : { session }).mode;
1987
+ return mode === "read-only" || mode === "workspace-write" ? mode : "";
1988
+ } catch {
1989
+ return "";
1990
+ }
1991
+ };
1992
+ const runProbe = async (deps, bashPath, mode, agent) => {
1993
+ const key = `${bashPath}|${mode}`;
1994
+ if (probeOutcome !== void 0 && probeKey === key) return probeOutcome;
1995
+ const controller = new AbortController();
1996
+ const timer = setTimeout(() => {
1997
+ controller.abort();
1998
+ }, PROBE_TIMEOUT_MS * 2);
1999
+ try {
2000
+ probeOutcome = await (options.probe ?? probeBashExecution)(
2001
+ deps,
2002
+ { bashPath, sep, isAbsolute },
2003
+ {
2004
+ callId: "composer-ux-self-check",
2005
+ signal: controller.signal,
2006
+ ...agent === void 0 ? {} : { agent }
2007
+ }
2008
+ );
2009
+ } catch (error) {
2010
+ probeOutcome = { ok: false, detail: error instanceof Error ? error.message : String(error) };
2011
+ } finally {
2012
+ clearTimeout(timer);
2013
+ }
2014
+ probeKey = key;
2015
+ return probeOutcome;
2016
+ };
1804
2017
  const reconcile = () => {
1805
2018
  if (busy) {
1806
2019
  again = true;
@@ -1838,8 +2051,24 @@ function installTerminalPolicy(ctx, namespace, settings, readToolDeps, options =
1838
2051
  const candidates = candidatesToStored(found.candidates);
1839
2052
  const bashPath = activeBashPath(mode, explicit, candidates, found.explicit);
1840
2053
  const usable = bashPath !== "" && existsFn(bashPath);
2054
+ let probeFailure = "";
1841
2055
  if (usable) {
2056
+ const probeDeps = readToolDeps();
2057
+ lastDeliveryFailure = "";
1842
2058
  for (const agent of agents?.list() ?? []) {
2059
+ const confinedMode = probeDeps === void 0 ? "" : confinedModeOf(probeDeps, agent);
2060
+ if (confinedMode !== "") {
2061
+ const probe = await runProbe(probeDeps, bashPath, confinedMode, agent);
2062
+ if (!probe.ok) {
2063
+ probeFailure = `${probe.detail}\uFF1B\u53D7\u9650\u6A21\u5F0F ${confinedMode}`;
2064
+ const current2 = installed.get(agent);
2065
+ if (current2 !== void 0) {
2066
+ current2.dispose();
2067
+ installed.delete(agent);
2068
+ }
2069
+ continue;
2070
+ }
2071
+ }
1843
2072
  const current = installed.get(agent);
1844
2073
  if (current !== void 0 && current.path === bashPath) continue;
1845
2074
  if (current !== void 0) {
@@ -1849,12 +2078,15 @@ function installTerminalPolicy(ctx, namespace, settings, readToolDeps, options =
1849
2078
  const record = install(agent, bashPath);
1850
2079
  if (record !== void 0) installed.set(agent, record);
1851
2080
  }
2081
+ if (probeFailure !== "") lastDeliveryFailure = probeFailure;
1852
2082
  } else {
1853
2083
  for (const entry of installed.values()) entry.dispose();
1854
2084
  installed.clear();
1855
2085
  failure = "";
1856
2086
  }
1857
2087
  const firstFailure = [...installed.values()].find((entry) => entry.failure !== void 0)?.failure;
2088
+ const delivered = usable && firstFailure === void 0 && lastDeliveryFailure === "";
2089
+ const failureText = firstFailure ?? (lastDeliveryFailure === "" ? failure : lastDeliveryFailure);
1858
2090
  writeState(ops, {
1859
2091
  status: terminalStatusText({
1860
2092
  platform,
@@ -1862,11 +2094,14 @@ function installTerminalPolicy(ctx, namespace, settings, readToolDeps, options =
1862
2094
  candidates,
1863
2095
  excludedCount: found.excluded.length,
1864
2096
  ...found.explicit === void 0 ? {} : { explicit: found.explicit },
1865
- effective: usable ? "bash" : "pwsh",
1866
- ...usable ? { effectivePath: bashPath } : {},
1867
- ...firstFailure === void 0 && failure === "" ? {} : { failure: firstFailure ?? failure }
2097
+ effective: delivered ? "bash" : "pwsh",
2098
+ ...delivered ? { effectivePath: bashPath } : {},
2099
+ // 自检没过时优先用那条更准确的说法("预检就没过、根本没试"),
2100
+ // 而不是泛泛的"找到了 bash 但没能换上"。
2101
+ ...delivered ? {} : probeFailure === "" ? { deliveryFailed: true } : { probeFailed: probeFailure },
2102
+ ...failureText === "" ? {} : { failure: failureText }
1868
2103
  }),
1869
- effective: usable ? "bash" : "pwsh",
2104
+ effective: delivered ? "bash" : "pwsh",
1870
2105
  candidates
1871
2106
  });
1872
2107
  await flush(ops);
@@ -1983,10 +2218,10 @@ function isAbsolutePath(value) {
1983
2218
  return value.startsWith("/") || /^[A-Za-z]:[\\/]/.test(value) || value.startsWith("\\\\");
1984
2219
  }
1985
2220
  function launchCommand(facts) {
1986
- const { argv1, execArgv, rest, cwd, platform, resolve: resolve3, dirname: dirname3 } = facts;
2221
+ const { argv1, execArgv, rest, cwd, platform, resolve: resolve3, dirname: dirname4 } = facts;
1987
2222
  if (argv1 !== void 0 && /[\\/](?:bin\.(?:js|ts)|dsh)$/.test(argv1)) {
1988
2223
  const absolute = resolve3(argv1);
1989
- return { file: facts.node, args: [...execArgv, absolute, ...rest], cwd: dirname3(absolute), viaShell: false };
2224
+ return { file: facts.node, args: [...execArgv, absolute, ...rest], cwd: dirname4(absolute), viaShell: false };
1990
2225
  }
1991
2226
  return { file: "dsh", args: [...rest], cwd, viaShell: platform === "win32" };
1992
2227
  }
@@ -2191,96 +2426,479 @@ function bootId(pid, now) {
2191
2426
 
2192
2427
  // src/optimizer-prompt.ts
2193
2428
  var RELAY_ROLE = [
2194
- '\u3010\u4F60\u662F\u8C01\u3011\u4F60\u662F\u4E00\u4E2A**\u4F20\u8BDD\u5668/\u6539\u5199\u5668**\uFF0C\u7AD9\u5728\u7528\u6237\u4E0E"\u5DE5\u4F5C AI"\u4E4B\u95F4\uFF1A',
2195
- " \u7528\u6237 \u2192\uFF08\u4F60\uFF1A\u628A\u7528\u6237\u7684\u610F\u601D\u6574\u7406\u6210\u4E00\u6761\u547D\u4EE4\uFF09\u2192 \u5DE5\u4F5C AI\uFF08\u7F16\u7801/\u6267\u884C agent\uFF09\u3002",
2196
- "\u4F60\u7684\u8F93\u51FA**\u4F1A\u88AB\u539F\u6837\u53D1\u7ED9\u5DE5\u4F5C AI**\uFF0C\u7528\u6237\u4E0D\u4F1A\u518D\u770B\u4E00\u904D\u3001\u4E0D\u4F1A\u518D\u8865\u5145\u3002",
2429
+ '\u3010\u4F60\u662F\u8C01\u3011\u4F60\u662F\u4E00\u4E2A**\u4F20\u8BDD\u5668/\u610F\u56FE\u8865\u5168\u5668**\uFF0C\u7AD9\u5728\u7528\u6237\u4E0E"\u5DE5\u4F5C AI"\u4E4B\u95F4\uFF1A',
2430
+ " \u7528\u6237 \u2192\uFF08\u4F60\uFF1A\u628A\u7528\u6237\u7684\u610F\u601D\u6574\u7406\u6210\u6761\u76EE\u5E76\u7ED9\u51FA\u9010\u5B57\u4F9D\u636E\uFF09\u2192 \u5BBF\u4E3B\uFF08\u88C5\u914D\u6210\u4E00\u6761\u547D\u4EE4\uFF09\u2192 \u5DE5\u4F5C AI\u3002",
2431
+ "\u4F60\u7684\u4EA7\u51FA**\u4E0D\u4F1A**\u88AB\u76F4\u63A5\u5C55\u793A\u7ED9\u7528\u6237\uFF0C\u4E5F\u4E0D\u4F1A\u88AB\u539F\u6837\u53D1\u51FA\uFF1A\u5BBF\u4E3B\u4F1A\u7528\u4F60\u7ED9\u7684**\u9010\u5B57\u5F15\u6587**",
2432
+ "\u5728\u539F\u8BDD\u91CC\u5B9A\u4F4D\u3001\u53EA\u91C7\u7EB3\u80FD\u6838\u5BF9\u7684\u6761\u76EE\uFF0C\u518D\u88C5\u914D\u6210\u4E00\u6761\u547D\u4EE4\u4EA4\u7ED9\u5DE5\u4F5C AI\u3002",
2197
2433
  "\u3010\u4F60\u4E0D\u662F\u8C01\u3011\u4F60\u4E0D\u662F\u5728\u548C\u7528\u6237\u804A\u5929\uFF0C\u4E5F\u4E0D\u662F\u5728\u56DE\u7B54\u7528\u6237\uFF1A",
2198
2434
  "- \u4E0D\u8981\u56DE\u5E94\u3001\u4E0D\u8981\u56DE\u7B54\u7528\u6237\u7684\u8981\u6C42\uFF0C\u4E5F\u4E0D\u8981\u66FF\u7528\u6237\u5B8C\u6210\u4EFB\u52A1\uFF08\u4E0D\u8981\u76F4\u63A5\u7ED9\u51FA\u4EE3\u7801/\u7B54\u6848/\u7ED3\u679C\uFF09\u3002",
2199
2435
  '- \u4E0D\u8981\u4EE5\u52A9\u624B\u53E3\u543B\u5BF9\u7528\u6237\u8BF4\u8BDD\uFF08"\u597D\u7684""\u6536\u5230""\u6211\u53EF\u4EE5\u5E2E\u4F60""\u5EFA\u8BAE\u4F60\u2026""\u9700\u8981\u6211\u2026\u5417"\uFF09\u3002',
2200
2436
  '- \u4E0D\u8981\u628A\u7528\u6237\u53D1\u6765\u7684\u6587\u5B57\u5F53\u6210"\u5BF9\u4F60\u8BF4\u7684\u8BDD"\u6765\u56DE\u5E94\uFF1B\u5B83\u53EA\u662F\u4F60\u8981\u8F6C\u8FBE\u7684\u5185\u5BB9\u3002',
2201
- '- \u4E0D\u8981\u5411\u7528\u6237\u63D0\u95EE\uFF0C\u4E5F\u4E0D\u8981\u5728\u7ED3\u5C3E\u95EE"\u662F\u5426\u7EE7\u7EED/\u8FD8\u9700\u8981\u4EC0\u4E48"\u3002\u9700\u8981\u6F84\u6E05\u65F6\uFF0C\u5199\u6210**\u7ED9\u5DE5\u4F5C AI \u7684\u6307\u4EE4**\uFF1A',
2202
- ' "\u82E5 X \u4E0D\u660E\u786E\uFF0C\u5148\u8BFB Y \u6216\u5148\u5411\u6211\u786E\u8BA4\uFF0C\u4E0D\u8981\u81EA\u884C\u5047\u8BBE"\u3002',
2203
- '\u3010\u4F60\u7684\u4EA7\u54C1\u3011\u4E00\u6761"\u50CF\u7528\u6237\u672C\u4EBA\u5728\u4E0B\u547D\u4EE4"\u7684\u6D88\u606F\uFF1A\u8BFB\u8005\u53EA\u6709\u5DE5\u4F5C AI \u4E00\u4E2A\u3002'
2437
+ "- \u4E0D\u8981\u5411\u7528\u6237\u63D0\u95EE\u3002\u8981\u6F84\u6E05\u65F6\uFF0C\u5199\u6210**\u7ED9\u5DE5\u4F5C AI \u7684\u6307\u4EE4**\uFF1A",
2438
+ ' "\u82E5 X \u4E0D\u660E\u786E\uFF0C\u5148\u8BFB Y \u6216\u5148\u5411\u6211\u786E\u8BA4\uFF0C\u4E0D\u8981\u81EA\u884C\u5047\u8BBE"\u3002'
2204
2439
  ].join("\n");
2205
- var NO_META_RULES = [
2206
- "\u3010\u4EA7\u51FA\u7684\u672C\u8D28\u3011\u4F60\u7684\u8F93\u51FA\u4F1A\u88AB\u7528\u6237**\u539F\u6837\u53D1\u9001**\u7ED9\u4E0B\u6E38\u7684\u5DE5\u4F5C AI\uFF08\u7F16\u7801/\u6267\u884C agent\uFF09\u3002",
2207
- '\u6240\u4EE5\u4F60\u53EA\u80FD\u8F93\u51FA"\u8FD9\u6761\u6D88\u606F\u672C\u8EAB"\uFF0C\u5C31\u50CF\u7528\u6237\u5728\u4EB2\u81EA\u6253\u8FD9\u6761\u6D88\u606F\u4E00\u6837\u3002',
2208
- "\u3010\u7EDD\u5BF9\u7981\u6B62\u3011\u4EE5\u4E0B\u5185\u5BB9\u4E00\u5F8B\u4E0D\u5F97\u51FA\u73B0\u5728\u8F93\u51FA\u91CC\uFF1A",
2209
- '- \u4EFB\u4F55\u5143\u8BDD\u8BED\u6216\u5143\u6807\u9898\uFF1A\u5982"\u4F18\u5316\u540E\u7684\u63D0\u793A\u8BCD""\u6539\u5199\u540E""\u6539\u52A8\u8BF4\u660E""\u63A8\u7406\u8865\u5145""\u5173\u952E\u5224\u65AD""\u8FB9\u754C""\u8BF4\u660E""\u4EE5\u4E0B\u662F\u2026"\u3002',
2210
- "- \u4EFB\u4F55\u5BF9\u4F60\u6240\u505A\u5DE5\u4F5C\u7684\u89E3\u91CA\u3001\u7406\u7531\u3001\u4F9D\u636E\u3001\u514D\u8D23\u58F0\u660E\u3001\u7ED9\u7528\u6237\u7684\u5EFA\u8BAE\u3002",
2211
- '- \u4EFB\u4F55\u5BF9"\u7528\u6237"\u8BF4\u8BDD\u6216\u63D0\u5230"\u7528\u6237/\u539F\u6587"\u7684\u8868\u8FF0\uFF08\u4F60\u8981\u5047\u88C5\u81EA\u5DF1\u5C31\u662F\u7528\u6237\uFF09\u3002',
2212
- '- \u4EFB\u4F55\u95EE\u53E5\u629B\u7ED9\u7528\u6237\uFF08\u8981\u5BF9\u5DE5\u4F5C AI \u8BF4\u8BDD\uFF1B\u9700\u8981\u6F84\u6E05\u65F6\u5199\u6210\u6307\u4EE4\uFF1A"\u82E5 X \u4E0D\u660E\u786E\uFF0C\u5148\u8BFB Y \u6216\u5148\u95EE\u6211\u786E\u8BA4"\uFF09\u3002',
2213
- '- \u4EFB\u4F55\u5BF9\u8BDD\u8BDD\u672F\uFF1A\u5F00\u573A\u767D\uFF08"\u597D\u7684/\u660E\u767D\u4E86/\u6CA1\u95EE\u9898"\uFF09\u3001\u6536\u5C3E\u8BED\uFF08"\u5E0C\u671B\u5BF9\u4F60\u6709\u5E2E\u52A9/\u8FD8\u9700\u8981\u6211\u505A\u4EC0\u4E48"\uFF09\u3001',
2214
- ' \u4EE5\u53CA\u628A\u4EFB\u52A1\u5F53\u6210\u8BF7\u6C42\u6765\u56DE\u590D\u7684\u53E5\u5B50\uFF08"\u6211\u6765\u5E2E\u4F60\u2026""\u8FD9\u4E2A\u53EF\u4EE5\u505A\u2026"\uFF09\u3002',
2215
- "- Markdown \u4E00\u7EA7\u6807\u9898\uFF08#\uFF09\u4E0E\u4EFB\u4F55\u5305\u88F9\u6574\u7BC7\u7684\u6807\u9898\u3002",
2216
- '\u3010\u5141\u8BB8\u7684\u7ED3\u6784\u3011\u53EA\u5141\u8BB8"\u4F5C\u4E3A\u6307\u4EE4\u6B63\u6587\u7684\u4E00\u90E8\u5206"\u5B58\u5728\u7684\u7ED3\u6784\uFF1A\u7F16\u53F7\u6B65\u9AA4\u3001\u5C0F\u6807\u9898\uFF08\u5982"\u9A8C\u6536\u6807\u51C6""\u4E0D\u8981\u505A"\uFF09\u3001\u6E05\u5355\u3001\u4EE3\u7801\u5757\u3002',
2217
- '\u3010\u98CE\u683C\u3011\u7948\u4F7F\u53E5\u3001\u76F4\u7ED9\u8981\u6C42\u3001\u53EF\u6267\u884C\uFF1B\u4E2D\u6587\u8FDB\u4E2D\u6587\u51FA\uFF1B\u4E0D\u786E\u5B9A\u7684\u9879\u76EE\u4E8B\u5B9E\u5199\u6210"\u5148\u8BFB\u53D6/\u5148\u786E\u8BA4"\u7684\u52A8\u4F5C\uFF0C\u7EDD\u4E0D\u7F16\u9020\u3002',
2218
- '\u3010\u81EA\u68C0\u3011\u843D\u7B14\u524D\u95EE\u81EA\u5DF1\u4E24\u53E5\uFF1A\u2460 \u8FD9\u6BB5\u8BDD\u662F\u4E0D\u662F"\u7528\u6237\u5728\u547D\u4EE4\u5DE5\u4F5C AI"\uFF1F\u2461 \u5DE5\u4F5C AI \u62FF\u5230\u5B83\u80FD\u4E0D\u80FD\u76F4\u63A5\u5F00\u59CB\u5E72\uFF1F',
2219
- '\u4E24\u4E2A\u7B54\u6848\u90FD\u4E3A"\u662F"\u624D\u8F93\u51FA\u3002\u73B0\u5728\u76F4\u63A5\u8F93\u51FA\u8FD9\u6761\u6D88\u606F\uFF0C\u4E0D\u8981\u4EFB\u4F55\u5F00\u573A\u767D\u4E0E\u6536\u5C3E\u8BED\u3002'
2440
+ var EVIDENCE_RULES = [
2441
+ "\u3010\u4F9D\u636E\u7EAA\u5F8B\uFF08\u5BBF\u4E3B\u4F1A\u9010\u6761\u673A\u68B0\u6838\u5BF9\uFF0C\u5199\u9519\u5C31\u4E22\u8FD9\u4E00\u6761\uFF09\u3011",
2442
+ "1. `rewrite` / `requirement` / `quality` **\u5FC5\u987B**\u5E26 `quote`\uFF1A\u4E00\u6BB5\u5728\u3010\u7528\u6237\u539F\u8BDD\u3011\u91CC",
2443
+ " **\u9010\u5B57\u5B58\u5728**\u7684\u5B50\u4E32 \u2014\u2014 \u7167\u6284\uFF0C\u4E0D\u8981\u6539\u5199\u3001\u4E0D\u8981\u8865\u6807\u70B9\u3001\u4E0D\u8981\u7FFB\u8BD1\u3002\u5BBF\u4E3B\u505A\u5B57\u9762\u5B50\u4E32\u6BD4\u5BF9\uFF0C",
2444
+ " \u5BF9\u4E0D\u4E0A\u5C31\u4E22\u6389\u90A3\u4E00\u6761\uFF08\u4E0D\u4F1A\u62A5\u9519\u7ED9\u4F60\uFF0C\u4E5F\u4E0D\u4F1A\u6709\u4EBA\u63D0\u9192\u4F60\uFF09\u3002",
2445
+ '2. **\u4E0D\u8BB8\u51ED\u7A7A\u65B0\u589E**\uFF1A\u4EA7\u54C1\u76EE\u6807\u3001\u529F\u80FD\u3001\u786C\u7EA6\u675F\uFF08"\u5FC5\u987B\u79BB\u7EBF""\u7981\u6B62\u8054\u7F51""\u53EA\u80FD\u7528\u67D0\u4E2A\u5E93"',
2446
+ ' "\u5FC5\u987B\u652F\u6301\u79FB\u52A8\u7AEF"\uFF09\u4E00\u5F8B\u4E0D\u8BB8\u7531\u4F60\u51B3\u5B9A\u3002\u4F60\u53EA\u80FD\u8865**\u80FD\u4ECE\u539F\u8BDD\u67D0\u4E00\u53E5\u76F4\u63A5\u63A8\u51FA**\u7684\u6700\u4F4E\u8981\u6C42\uFF0C',
2447
+ " \u5E76\u4E14\u5FC5\u987B\u5F15\u7528\u90A3\u4E00\u53E5\u3002",
2448
+ "3. \u4E0D\u786E\u5B9A\u3001\u4F1A\u5F71\u54CD\u7ED3\u679C\u3001\u4E14\u53EA\u6709\u7528\u6237\u80FD\u5B9A\u7684\u53D6\u820D \u2192 \u5199 `unknown`\uFF08`unknownClass` =",
2449
+ ' "user_preference"\uFF09\uFF0C**\u4E0D\u8981\u66FF\u7528\u6237\u731C**\uFF0C\u4E5F\u4E0D\u8981\u5199"\u6309\u6700\u4FDD\u5B88\u7406\u89E3\u6267\u884C"\u3002',
2450
+ ' \u5728\u8BB8\u53EF\u8303\u56F4\u5185\u8BFB\u4EE3\u7801\u5C31\u80FD\u786E\u5B9A\u7684\u4E8B\u5B9E\u5199 "lookupable_fact"\uFF1B\u53EF\u9006\u7684\u5B9E\u73B0\u7EC6\u8282',
2451
+ ' \uFF08\u95F4\u8DDD\u3001\u547D\u540D\u3001\u5E93\u7684\u5185\u90E8\u7528\u6CD5\uFF09\u5199 "implementation_detail"\u3002',
2452
+ "4. \u4E0D\u5F97\u865A\u6784\u9879\u76EE\u4E8B\u5B9E\uFF08\u6587\u4EF6\u5185\u5BB9\u3001\u76EE\u5F55\u7ED3\u6784\u3001\u4F9D\u8D56\u7248\u672C\uFF09\u3002\u4F60\u6CA1\u8BFB\u5230\u7684\u4E1C\u897F\u4E0D\u5B58\u5728\u3002"
2220
2453
  ].join("\n");
2454
+ var STYLE_RULES = [
2455
+ '\u3010\u5199 text \u7684\u7EAA\u5F8B\u3011\u4F60\u7684 text \u4F1A\u88AB\u5BBF\u4E3B\u62FC\u8FDB\u6700\u7EC8\u547D\u4EE4\uFF0C\u8BFB\u8005\u53EA\u6709"\u5DE5\u4F5C AI"\u4E00\u4E2A\uFF1A',
2456
+ "- \u7948\u4F7F\u53E5\u3001\u76F4\u7ED9\u8981\u6C42\u3001\u53EF\u6267\u884C\uFF1B\u4E2D\u6587\u8FDB\u4E2D\u6587\u51FA\u3002",
2457
+ '- \u4E0D\u8981\u5143\u8BDD\u8BED\u4E0E\u5143\u6807\u9898\uFF08"\u4F18\u5316\u540E\u7684\u63D0\u793A\u8BCD""\u6539\u5199\u540E""\u8BF4\u660E""\u4EE5\u4E0B\u662F\u2026"\uFF09\u3002',
2458
+ '- \u4E0D\u8981\u63D0\u5230"\u7528\u6237/\u539F\u6587/\u4E0A\u9762\u7684\u8BDD"\uFF0C\u4E5F\u4E0D\u8981\u5199"\u6211\u6765\u5E2E\u4F60\u2026"\u3002',
2459
+ '- \u4E0D\u8981\u5199\u6D41\u7A0B\u4EEA\u5F0F\u3001\u901A\u7528\u6559\u5B66\u3001\u9A8C\u6536\u5957\u8BDD\uFF08"\u8BF7\u786E\u4FDD\u4EE3\u7801\u8D28\u91CF"\u8FD9\u79CD\u6CA1\u6709\u6307\u5411\u7684\u8BDD\uFF09\u3002',
2460
+ "- `rewrite` \u7684 text \u662F**\u66FF\u6362\u6389\u90A3\u6BB5\u5F15\u6587**\u7684\u6B63\u6587\uFF0C\u6240\u4EE5\u53EA\u5199\u90A3\u6BB5\u8BDD\u672C\u8EAB\uFF0C\u4E0D\u8981\u5E26\u524D\u7F00\u540E\u7F00\u3002"
2461
+ ].join("\n");
2462
+ var OPTIMIZER_OUTPUT_CONTRACT = [
2463
+ "\u3010\u8F93\u51FA\u5951\u7EA6\uFF08\u56FA\u5B9A\uFF0C\u4E0D\u53EF\u66F4\u6539\uFF09\u3011",
2464
+ "\u53EA\u8F93\u51FA\u4E00\u4E2A JSON \u5BF9\u8C61\uFF0C\u4E0D\u8981 Markdown \u4EE3\u7801\u5757\u3001\u4E0D\u8981\u5728 JSON \u524D\u540E\u5199\u4EFB\u4F55\u5B57\uFF1A",
2465
+ '{"items":[{"kind":"rewrite","quote":"\u539F\u8BDD\u91CC\u9010\u5B57\u5B58\u5728\u7684\u7247\u6BB5","text":"\u66FF\u6362\u6389\u8BE5\u7247\u6BB5\u7684\u6B63\u6587"}]}',
2466
+ "\u5B57\u6BB5\u4E0E\u53D6\u503C\uFF08`kind` \u53EA\u80FD\u662F\u8FD9\u516D\u79CD\uFF09\uFF1A",
2467
+ "- rewrite\uFF1A\u8BED\u8A00\u5C42\u4FEE\u590D\u3002`quote` \u5FC5\u586B\uFF1B\u540C\u4E00\u6BB5\u539F\u8BDD\u6700\u591A\u4E00\u6761 rewrite\uFF0C\u4E0D\u8981\u91CD\u53E0\u3002",
2468
+ "- requirement\uFF1A\u4ECE `quote` \u76F4\u63A5\u63A8\u51FA\u7684\u8865\u5168\u8981\u6C42\uFF08\u9A8C\u6536\u6807\u51C6/\u786C\u7EA6\u675F\uFF09\u3002`quote` \u5FC5\u586B\u3002",
2469
+ "- quality\uFF1A\u5BF9\u539F\u8BDD\u91CC\u8D28\u91CF\u8BCD\u7684\u89E3\u91CA\u3002`quote` \u5FC5\u586B\uFF08\u5C31\u586B\u90A3\u51E0\u4E2A\u8D28\u91CF\u5B57\uFF09\u3002",
2470
+ "- unknown\uFF1A\u672A\u51B3\u9879\u3002\u5FC5\u987B\u5E26 `unknownClass`\uFF08user_preference / lookupable_fact /",
2471
+ " implementation_detail\uFF09\uFF0C\u53EF\u9009 `blocking`\uFF08true/false\uFF0C\u662F\u5426\u6321\u4F4F\u4E0B\u4E00\u6B65\uFF09\u3002",
2472
+ '- plan\uFF1A\u5206\u9636\u6BB5\u6267\u884C\u8BA1\u5212\uFF08\u4EC5"\u6781\u7AEF"\u6863\u7528\uFF09\u3002',
2473
+ '- risk\uFF1A\u591A\u60C5\u51B5\u9884\u6848\uFF08\u4EC5"\u6781\u7AEF"\u6863\u7528\uFF09\uFF0C\u6BCF\u6761\u6309"\u89E6\u53D1\u4FE1\u53F7 \u2192 \u5E94\u5BF9 \u2192 \u7981\u6B62\u52A8\u4F5C"\u5199\u3002',
2474
+ "\u9650\u5236\uFF1A`text` \u6BCF\u6761\u4E0D\u8D85\u8FC7 300 \u5B57\uFF1B`items` \u603B\u5171\u4E0D\u8D85\u8FC7 12 \u6761\uFF1Bid \u4E0D\u9700\u8981\u7ED9\u3002",
2475
+ '\u6CA1\u6709\u53EF\u8865\u7684\u5185\u5BB9\u65F6\uFF0C\u8F93\u51FA {"items":[]} \u2014\u2014 \u4F46**\u5148\u60F3\u6E05\u695A**\uFF1A\u7528\u6237\u8FD9\u4E00\u8F6E\u7684\u539F\u8BDD\u91CC',
2476
+ "\u771F\u7684\u6CA1\u6709\u4EFB\u4F55\u53EF\u6838\u5BF9\u7684\u8865\u5168\u5417\uFF1F"
2477
+ ].join("\n");
2478
+ var OPTIMIZE_ITEM_KINDS = ["rewrite", "requirement", "quality", "unknown", "plan", "risk"];
2479
+ var OPTIMIZE_UNKNOWN_CLASSES = ["user_preference", "lookupable_fact", "implementation_detail"];
2480
+ var OPTIMIZE_MAX_ITEMS = 12;
2481
+ var OPTIMIZE_ITEM_MAX_CHARS = 300;
2482
+ var TIER_TASKS = {
2483
+ basic: [
2484
+ "\u3010\u672C\u8F6E\u4EFB\u52A1\uFF1A\u53EA\u505A\u8BED\u8A00\u5C42\u4FEE\u590D\u3011",
2485
+ "\u7528\u6237\u539F\u8BDD\u53EF\u80FD\u6709\u75C5\u53E5\u3001\u6307\u4EE3\u4E0D\u660E\u3001\u7528\u8BCD\u542B\u7CCA\u3002\u628A\u5B83\u6539\u5199\u6210\u901A\u987A\u3001\u7CBE\u786E\u3001\u65E0\u6B67\u4E49\u7684**\u540C\u4E00\u6BB5\u8BDD**\u3002",
2486
+ "\u53EA\u4EA7\u51FA `rewrite`\uFF08\u53EF\u4EE5\u6309\u53E5\u5B50\u62C6\u6210\u591A\u6761\uFF0C\u6BCF\u6761\u5F15\u7528\u539F\u8BDD\u91CC\u5BF9\u5E94\u90A3\u4E00\u53E5\uFF09\u3002",
2487
+ "\u4E25\u7981\u4EA7\u51FA requirement / plan / risk\uFF1A\u8FD9\u4E00\u6863**\u4E0D\u65B0\u589E\u4EFB\u4F55\u8981\u6C42**\uFF0C\u53EA\u628A\u8BDD\u8BF4\u660E\u767D\u3002",
2488
+ "\u539F\u8BDD\u786E\u6709\u6B67\u4E49\u3001\u4E14\u6B67\u4E49\u4F1A\u5BFC\u81F4\u505A\u9519\u65F6\uFF0C\u624D\u8865\u4E00\u6761 `unknown`\uFF08unknownClass \u53D6",
2489
+ "user_preference \u6216 lookupable_fact\uFF09\uFF1B\u786E\u5B9E\u6CA1\u6709\u53EF\u8865\u7684\u5C31\u8F93\u51FA\u7A7A\u6570\u7EC4\u3002",
2490
+ "\u957F\u5EA6\u7EAA\u5F8B\uFF1A\u6210\u54C1\u4E0D\u8D85\u8FC7\u539F\u8BDD\u7684 1.4 \u500D\uFF1B\u539F\u8BDD 30 \u5B57\u4EE5\u5185\u65F6\u4E0D\u8D85\u8FC7 60 \u5B57\u3002"
2491
+ ],
2492
+ advanced: [
2493
+ "\u3010\u672C\u8F6E\u4EFB\u52A1\uFF1A\u5728\u4E0D\u52A8\u76EE\u6807\u7684\u524D\u63D0\u4E0B\u628A\u547D\u4EE4\u8BF4\u6E05\u695A\u3011",
2494
+ '\u7528\u6237\u539F\u8BDD\u542B\u7CCA\u3001\u7F3A\u5173\u952E\u7EA6\u675F\u3002\u4F60\u8981\u8865\u7684\u662F"\u7528\u6237\u663E\u7136\u60F3\u8981\u3001\u4F46\u6CA1\u8BF4\u51FA\u53E3"\u7684\u5FC5\u8981\u4FE1\u606F\uFF0C',
2495
+ "\u8BA9\u5B83\u4E00\u6B21\u505A\u5BF9 \u2014\u2014 \u4F46\u6BCF\u4E00\u6761\u90FD\u5FC5\u987B\u662F**\u4ECE\u539F\u8BDD\u67D0\u4E00\u53E5\u76F4\u63A5\u63A8\u51FA**\u7684\uFF0C\u5E76\u5F15\u7528\u90A3\u4E00\u53E5\u3002",
2496
+ "\u5141\u8BB8\u4EA7\u51FA\uFF1A",
2497
+ "- `rewrite`\uFF1A\u628A\u542B\u7CCA\u3001\u6709\u75C5\u53E5\u7684\u5730\u65B9\u8BF4\u6E05\u695A\uFF08\u540C\u4E00\u6BB5\u539F\u8BDD\u6700\u591A\u4E00\u6761\uFF09\u3002",
2498
+ '- `requirement`\uFF1A\u4ECE\u539F\u8BDD\u76F4\u63A5\u63A8\u51FA\u7684\u6700\u4F4E\u4EA4\u4ED8\u8981\u6C42\u4E0E\u9A8C\u6536\u6807\u51C6\uFF08"\u6539\u5B8C\u80FD\u8DD1\u8D77\u6765""\u9875\u9762\u80FD\u6253\u5F00"',
2499
+ " \u8FD9\u7C7B\u53EF\u89C2\u6D4B\u7684\u8BDD\uFF09\uFF0C\u5199\u6210\u5BF9\u5DE5\u4F5C AI \u7684\u8981\u6C42\u800C\u4E0D\u662F\u8BC4\u8BBA\u3002\u6BCF\u6761\u90FD\u8981\u80FD\u6307\u56DE\u539F\u8BDD\u91CC\u7684\u67D0\u4E00\u53E5\u3002",
2500
+ '- `quality`\uFF1A\u7528\u6237\u8BF4\u7684\u8D28\u91CF\u8BCD\uFF08"\u597D\u770B\u70B9""\u9AD8\u7EA7\u611F""\u6D41\u7545"\uFF09\u2192 \u89E3\u91CA\u6210\u53EF\u89C2\u5BDF\u7684\u8981\u6C42\u3002',
2501
+ " \u5B83\u662F**\u89E3\u91CA**\uFF0C\u4E0D\u662F\u65B0\u589E\u76EE\u6807\uFF1A\u4E00\u6761\u8D28\u91CF\u8BCD\u6700\u591A\u4E00\u6761 quality\u3002",
2502
+ "- `unknown`\uFF1A\u53EA\u6709\u7528\u6237\u80FD\u5B9A\u7684\u53D6\u820D\uFF08user_preference\uFF09/ \u8BE5\u53BB\u67E5\u8BC1\u7684\u4E8B\u5B9E\uFF08lookupable_fact\uFF09\u3002",
2503
+ "\u7981\u6B62\uFF1A\u65B0\u589E\u529F\u80FD\u3001\u65B0\u76EE\u6807\u3001\u65B0\u4F9D\u8D56\uFF1B\u865A\u6784\u9879\u76EE\u4E8B\u5B9E\uFF1B\u5199\u7528\u6237\u6CA1\u6388\u6743\u7684\u6280\u672F\u9009\u578B\u3002",
2504
+ "\u6570\u91CF\u7EAA\u5F8B\uFF1Arequirement \u6700\u591A 3 \u6761\uFF0Cquality \u6700\u591A 2 \u6761\uFF0Cunknown \u6700\u591A 2 \u6761\u3002"
2505
+ ],
2506
+ extreme: [
2507
+ "\u3010\u672C\u8F6E\u4EFB\u52A1\uFF1A\u628A\u8BC9\u6C42\u56FA\u5316\u6210\u4E00\u6761\u53EF\u76F4\u63A5\u6267\u884C\u7684\u547D\u4EE4\u3011",
2508
+ "\u8FD9\u6B21\u662F\u591A\u6B65\u6267\u884C\u7684\u590D\u6742\u4EFB\u52A1\uFF0C\u5DE5\u4F5C AI \u4F1A\u7167\u8FD9\u6761\u547D\u4EE4\u5E72\uFF0C\u7528\u6237\u4E0D\u4F1A\u518D\u8865\u5145\u3002",
2509
+ '\u5728"\u9AD8\u7EA7"\u6863\u5141\u8BB8\u7684\u5168\u90E8\u6761\u76EE\u7684\u57FA\u7840\u4E0A\uFF0C\u518D\u52A0\u4E24\u7C7B\uFF08\u90FD\u8981\u4EE5\u7528\u6237\u5728\u7ED9\u5DE5\u4F5C AI \u4E0B\u547D\u4EE4\u7684\u53E3\u543B\u5199\uFF09\uFF1A',
2510
+ "- `plan`\uFF1A\u5206\u9636\u6BB5\u6267\u884C\u8BA1\u5212\u3002\u6BCF\u9636\u6BB5\u5199\u6E05\u52A8\u4F5C\u4E0E\u4EA7\u51FA\uFF0C\u5E76\u5199\u660E\u7EAA\u5F8B\uFF08\u5148\u9A8C\u8BC1\u518D\u6539\u3001\u5931\u8D25\u5373\u56DE\u9000\u3001",
2511
+ " \u4E0D\u64C5\u81EA\u6269\u5927\u8303\u56F4\u3001\u6539\u5B8C\u7ED9\u51FA\u8BC1\u636E\uFF09\u3002",
2512
+ '- `risk`\uFF1A\u591A\u60C5\u51B5\u9884\u6848 2~4 \u6761\uFF0C\u6BCF\u6761\u5199\u6210"\u5982\u679C\u51FA\u73B0 <\u89E6\u53D1\u4FE1\u53F7>\uFF0C\u5C31\u5148 <\u5E94\u5BF9\u52A8\u4F5C>\uFF0C',
2513
+ ' \u4E0D\u8981 <\u7981\u6B62\u52A8\u4F5C>"\u3002',
2514
+ "\u8FD8\u8981\u5224\u65AD\u8FD9\u6B21\u4EFB\u52A1\u662F\u5426\u503C\u5F97\u8BA9\u5DE5\u4F5C AI \u7528 goal / todo / \u8BA1\u5212\u6A21\u5F0F\u8DDF\u8E2A\uFF0C\u5E76\u628A\u7ED3\u8BBA\u5199\u6210",
2515
+ '`requirement` \u7684\u4E00\u90E8\u5206\uFF08\u4F8B\u5982"\u8BF7\u5148\u5EFA\u7ACB goal\uFF1A\u2026\uFF0C\u518D\u6309\u4E0B\u5217\u9636\u6BB5\u63A8\u8FDB"\uFF09\uFF1B\u4E0D\u9700\u8981\u5C31\u5B8C\u5168\u4E0D\u63D0\u3002',
2516
+ '\u94C1\u5F8B\uFF1A\u4E0D\u5F97\u865A\u6784\u9879\u76EE\u4E8B\u5B9E\u3002\u9700\u8981\u9879\u76EE\u4E8B\u5B9E\u65F6\u5199\u6210"\u5148\u8BFB\u53D6/\u786E\u8BA4 X"\u7684\u67E5\u8BC1\u52A8\u4F5C\u3002',
2517
+ "\u6570\u91CF\u7EAA\u5F8B\uFF1Aplan \u6700\u591A 1 \u6761\uFF0Crisk \u6700\u591A 4 \u6761\uFF0Crequirement \u6700\u591A 3 \u6761\u3002"
2518
+ ]
2519
+ };
2221
2520
  var OPTIMIZER_SPECS = {
2222
2521
  basic: {
2223
2522
  temperature: 0.2,
2224
- system: [
2225
- RELAY_ROLE,
2226
- "\u4E0B\u9762\u8FD9\u6BB5\u662F\u7528\u6237\u8981\u53D1\u7ED9\u5DE5\u4F5C AI \u7684\u6307\u4EE4\uFF0C\u53EF\u80FD\u6709\u75C5\u53E5\u3001\u6307\u4EE3\u4E0D\u660E\u3001\u7528\u8BCD\u542B\u7CCA\u3002",
2227
- "\u4F60\u7684\u552F\u4E00\u4EFB\u52A1\uFF1A\u628A\u5B83\u6539\u5199\u6210\u901A\u987A\u3001\u7CBE\u786E\u3001\u65E0\u6B67\u4E49\u7684**\u540C\u4E00\u6761\u6307\u4EE4**\uFF0C\u6539\u5B8C\u5C31\u80FD\u76F4\u63A5\u53D1\u51FA\u53BB\u3002",
2228
- "\u94C1\u5F8B\uFF1A",
2229
- '- \u53EA\u505A\u8BED\u8A00\u5C42\u4FEE\u590D\uFF1A\u75C5\u53E5\u3001\u9519\u522B\u5B57\u3001\u6807\u70B9\u3001\u6307\u4EE3\u6D88\u89E3\uFF08"\u90A3\u4E2A\u9875\u9762"\u2192\u4FDD\u7559\u4F46\u660E\u786E\u6307\u5411\u540C\u4E00\u5BF9\u8C61\uFF09\u3001\u542B\u7CCA\u8BCD\u6536\u655B\u5230"\u53EF\u6267\u884C\u4F46\u4E0D\u65B0\u589E\u9700\u6C42"\u3002',
2230
- "- \u4E25\u7981\u65B0\u589E\u539F\u6587\u6CA1\u6709\u7684\u9700\u6C42\u3001\u529F\u80FD\u3001\u7EA6\u675F\u3001\u6280\u672F\u9009\u578B\u3001\u4EA4\u4ED8\u7269\uFF1B\u4E25\u7981\u6269\u5927\u6216\u7F29\u5C0F\u8303\u56F4\u3002",
2231
- '- \u539F\u6587\u6CA1\u8BF4\u7684\u5C31\u4E0D\u8981\u66FF\u7528\u6237\u51B3\u5B9A\uFF1A\u65E0\u6CD5\u786E\u5B9A\u5904\u5199\u6210\u7ED9\u5DE5\u4F5C AI \u7684\u67E5\u8BC1\u6307\u4EE4\uFF08\u4F8B\u5982"\u5148\u786E\u8BA4\u6307\u7684\u662F\u54EA\u4E2A\u9875\u9762\uFF0C\u518D\u52A8\u624B"\uFF09\uFF0C\u800C\u4E0D\u662F\u81EA\u5DF1\u62CD\u677F\u3002',
2232
- "- \u7BC7\u5E45\u4E0E\u539F\u6587\u76F8\u5F53\uFF0C\u4E0D\u8981\u81A8\u80C0\u3002",
2233
- NO_META_RULES
2234
- ].join("\n")
2523
+ system: [RELAY_ROLE, TIER_TASKS.basic.join("\n"), EVIDENCE_RULES, STYLE_RULES].join("\n\n")
2235
2524
  },
2236
2525
  advanced: {
2237
2526
  temperature: 0.3,
2238
- system: [
2239
- RELAY_ROLE,
2240
- "\u7528\u6237\u7684\u539F\u8BDD\u542B\u7CCA\u3001\u7F3A\u5173\u952E\u7EA6\u675F\uFF1B\u4F60\u8981**\u4EE5\u7528\u6237\u7684\u540D\u4E49**\u628A\u8FD9\u6761\u547D\u4EE4\u8BF4\u6E05\u695A\uFF0C\u8F6C\u8FBE\u7ED9\u5DE5\u4F5C AI\u3002",
2241
- '\u4F60\u7684\u4EFB\u52A1\uFF1A\u5728\u5B8C\u5168\u4E0D\u6539\u53D8\u7528\u6237\u76EE\u6807\u7684\u524D\u63D0\u4E0B\uFF0C\u7528\u4F60\u81EA\u5DF1\u7684\u63A8\u7406\uFF0C\u628A"\u7528\u6237\u663E\u7136\u60F3\u8981\u3001\u4F46\u6CA1\u8BF4\u51FA\u53E3"\u7684\u5FC5\u8981\u4FE1\u606F**\u5199\u6210\u5BF9\u5DE5\u4F5C AI \u7684\u8981\u6C42**\uFF0C\u8BA9\u5B83\u4E00\u6B21\u505A\u5BF9\u3002',
2242
- "\u5141\u8BB8\u4F60\u505A\uFF08\u4E14\u4EC5\u9650\u8FD9\u4E9B\uFF09\uFF1A",
2243
- '- \u8865\u5168\u4ECE\u76EE\u6807\u53EF\u76F4\u63A5\u63A8\u51FA\u7684\u6700\u4F4E\u4EA4\u4ED8\u8981\u6C42\u4E0E\u9A8C\u6536\u6807\u51C6\uFF08\u4F8B\u5982"\u80FD\u8DD1\u8D77\u6765""\u754C\u9762\u80FD\u7528"\uFF09\uFF0C\u5F62\u5F0F\u662F\u8981\u6C42\u800C\u975E\u8BC4\u8BBA\u3002',
2244
- "- \u628A\u542B\u7CCA\u8BCD\u6536\u655B\u4E3A\u53EF\u89C2\u5BDF\u3001\u53EF\u6267\u884C\u7684\u8981\u6C42\u3002",
2245
- '- \u5B58\u5728\u4E24\u79CD\u5408\u7406\u89E3\u8BFB\u65F6\uFF1A\u6309\u66F4\u5E38\u89C1\u7684\u4E00\u79CD\u5199\u6210\u4E3B\u8981\u6C42\uFF0C\u53E6\u4E00\u79CD\u5199\u6210"\u5982\u679C\u5B9E\u9645\u662F X\uFF0C\u5219\u2026"\u7684\u6307\u4EE4\u3002',
2246
- "\u94C1\u5F8B\uFF1A\u4E0D\u5F97\u589E\u52A0\u65B0\u529F\u80FD/\u65B0\u76EE\u6807/\u65B0\u4F9D\u8D56\uFF1B\u4E0D\u5F97\u865A\u6784\u7528\u6237\u6CA1\u63D0\u7684\u73AF\u5883\u3001\u6570\u636E\u3001\u6280\u672F\u6808\uFF1B\u6BCF\u6761\u8865\u5145\u90FD\u5FC5\u987B\u80FD\u56DE\u6EAF\u5230\u539F\u8BDD\u91CC\u7684\u67D0\u53E5\u3002",
2247
- NO_META_RULES
2248
- ].join("\n")
2527
+ system: [RELAY_ROLE, TIER_TASKS.advanced.join("\n"), EVIDENCE_RULES, STYLE_RULES].join("\n\n")
2249
2528
  },
2250
2529
  extreme: {
2251
2530
  temperature: 0.3,
2252
- system: [
2253
- RELAY_ROLE,
2254
- "\u8FD9\u6B21\u662F\u4E00\u4E2A\u9700\u8981\u591A\u6B65\u6267\u884C\u7684\u590D\u6742\u4EFB\u52A1\u3002\u4F60\u8981\u8F6C\u8FBE\u7684\u662F**\u4E00\u6761\u53EF\u76F4\u63A5\u53D1\u9001\u7684\u547D\u4EE4**\uFF1A\u7528\u6237\u4E0D\u4F1A\u518D\u7F16\u8F91\u6216\u8F6C\u8FF0\uFF0C\u5DE5\u4F5C AI \u4F1A\u7167\u5B83\u6267\u884C\u3002",
2255
- '\u8981\u6C42\uFF08\u5168\u90E8\u4EE5"\u7528\u6237\u5728\u7ED9\u5DE5\u4F5C AI \u4E0B\u547D\u4EE4"\u7684\u53E3\u543B\u5199\uFF09\uFF1A',
2256
- "- \u5148\u628A\u8BC9\u6C42\u56FA\u5316\u4E3A\u547D\u4EE4\u7ED3\u6784\uFF1A\u8981\u505A\u4EC0\u4E48 / \u505A\u5B8C\u7684\u6807\u5FD7\uFF08\u53EF\u89C2\u6D4B\u9A8C\u6536\u6807\u51C6\uFF09/ \u786C\u7EA6\u675F / \u4E0D\u8BB8\u505A\u4EC0\u4E48\u3002",
2257
- "- \u5206\u9636\u6BB5\u6267\u884C\u8BA1\u5212\uFF1A\u6BCF\u9636\u6BB5\u5199\u6E05\u52A8\u4F5C\u4E0E\u4EA7\u51FA\uFF0C\u5E76\u5199\u660E\u7EAA\u5F8B\u8981\u6C42\uFF08\u5148\u9A8C\u8BC1\u518D\u6539\u3001\u5931\u8D25\u5373\u56DE\u9000\u3001\u4E0D\u64C5\u81EA\u6269\u5927\u8303\u56F4\u3001\u6539\u5B8C\u7ED9\u51FA\u8BC1\u636E\uFF09\u3002",
2258
- '- \u591A\u60C5\u51B5\u9884\u6848\uFF1A\u7ED9\u51FA\u6700\u53EF\u80FD\u51FA\u5C94\u5B50\u7684 2\u20134 \u79CD\u60C5\u51B5\uFF0C\u6BCF\u79CD\u5199\u6210"\u5982\u679C\u51FA\u73B0 <\u89E6\u53D1\u4FE1\u53F7>\uFF0C\u5C31\u5148 <\u5E94\u5BF9\u52A8\u4F5C>\uFF0C\u4E0D\u8981 <\u7981\u6B62\u52A8\u4F5C>"\u3002',
2259
- '- \u662F\u5426\u9700\u8981\u529F\u80FD\u6027\u6307\u4EE4\uFF1A\u5224\u65AD\u8FD9\u6B21\u4EFB\u52A1\u662F\u5426\u503C\u5F97\u8BA9\u5DE5\u4F5C AI \u7528 goal / todo / \u8BA1\u5212\u6A21\u5F0F \u8DDF\u8E2A\uFF0C\u5E76\u628A\u7ED3\u8BBA**\u5199\u6210\u547D\u4EE4\u672C\u8EAB\u7684\u4E00\u90E8\u5206**\uFF08\u4F8B\u5982"\u8BF7\u5148\u5EFA\u7ACB goal\uFF1A\u2026\uFF0C\u518D\u6309\u4E0B\u5217\u9636\u6BB5\u63A8\u8FDB"\uFF09\uFF1B\u4E0D\u9700\u8981\u5C31\u5B8C\u5168\u4E0D\u63D0\u3002',
2260
- '\u94C1\u5F8B\uFF1A\u4E0D\u5F97\u865A\u6784\u9879\u76EE\u4E8B\u5B9E\uFF1B\u9700\u8981\u9879\u76EE\u4E8B\u5B9E\u65F6\u5199\u6210"\u5148\u8BFB\u53D6/\u786E\u8BA4 X"\u7684\u67E5\u8BC1\u52A8\u4F5C\uFF0C\u800C\u4E0D\u662F\u7F16\u9020\u7ED3\u8BBA\u3002',
2261
- NO_META_RULES
2262
- ].join("\n")
2531
+ system: [RELAY_ROLE, TIER_TASKS.extreme.join("\n"), EVIDENCE_RULES, STYLE_RULES].join("\n\n")
2263
2532
  }
2264
2533
  };
2265
- function buildOptimizeSystem(tier) {
2266
- const spec = OPTIMIZER_SPECS[tier] ?? OPTIMIZER_SPECS.advanced;
2267
- return spec.system;
2534
+ var FALLBACK_TIER = "advanced";
2535
+ function specOf(tier) {
2536
+ return OPTIMIZER_SPECS[tier] ?? OPTIMIZER_SPECS[FALLBACK_TIER];
2537
+ }
2538
+ function buildOptimizeSystem(tier, custom = "") {
2539
+ const body = custom.trim() === "" ? specOf(tier).system : custom.trim();
2540
+ return `${body}
2541
+
2542
+ ${OPTIMIZER_OUTPUT_CONTRACT}`;
2268
2543
  }
2269
2544
  function buildOptimizeTemperature(tier) {
2270
- const spec = OPTIMIZER_SPECS[tier] ?? OPTIMIZER_SPECS.advanced;
2271
- return spec.temperature;
2545
+ return specOf(tier).temperature;
2272
2546
  }
2273
- function buildOptimizeUser(original) {
2274
- return [
2547
+ function optimizePromptSource(custom) {
2548
+ return String(custom ?? "").trim() === "" ? "builtin" : "custom";
2549
+ }
2550
+ function buildOptimizeUser(original, options = {}) {
2551
+ const parts = [
2275
2552
  '\u3010\u5F85\u8F6C\u8FBE\u5185\u5BB9\u3011\u4E0B\u9762\u662F"\u7528\u6237"\u53D1\u7ED9\u6211\u7684\u539F\u8BDD\u3002**\u5B83\u4E0D\u662F\u8BF4\u7ED9\u4F60\u542C\u7684**\uFF0C\u4F60\u4E0D\u9700\u8981\u56DE\u5E94\u5B83\u3001\u4E5F\u4E0D\u9700\u8981\u66FF\u7528\u6237\u53BB\u505A\u8FD9\u4EF6\u4E8B\u3002',
2276
2553
  "<\u539F\u6587>",
2277
2554
  String(original ?? ""),
2278
2555
  "</\u539F\u6587>",
2279
2556
  "",
2280
- '\u3010\u4F60\u7684\u4EFB\u52A1\u3011\u4EE5\u7528\u6237\u7684\u540D\u4E49\uFF0C\u628A\u4E0A\u9762\u7684\u5185\u5BB9\u6574\u7406\u6210**\u4E00\u6761\u53EF\u4EE5\u76F4\u63A5\u53D1\u7ED9"\u5DE5\u4F5C AI"\u7684\u547D\u4EE4**\uFF1A',
2281
- "- \u53EA\u8F93\u51FA\u8FD9\u6761\u547D\u4EE4\u672C\u8EAB\uFF1B\u4E0D\u8981\u56DE\u5E94\u6211\u3001\u4E0D\u8981\u56DE\u7B54\u95EE\u9898\u3001\u4E0D\u8981\u8C22\u5E55\u3001\u4E0D\u8981\u89E3\u91CA\u4F60\u505A\u4E86\u4EC0\u4E48\u3002",
2282
- '- \u8BFB\u4F60\u8FD9\u6761\u547D\u4EE4\u7684\u4EBA\u53EA\u6709"\u5DE5\u4F5C AI"\u4E00\u4E2A\u3002'
2283
- ].join("\n");
2557
+ "\u3010\u4F60\u7684\u4EFB\u52A1\u3011\u6309\u7CFB\u7EDF\u63D0\u793A\u8BCD\u7684\u89C4\u5219\uFF0C\u628A\u4E0A\u9762\u7684\u539F\u8BDD\u62C6\u6210**\u6761\u76EE**\u5E76\u7ED9\u6BCF\u6761\u9644\u4E0A**\u9010\u5B57\u5F15\u6587**\u3002",
2558
+ "- \u53EA\u8F93\u51FA\u90A3\u4EFD JSON \u5951\u7EA6\u8981\u6C42\u7684\u4E1C\u897F\uFF1B\u4E0D\u8981\u56DE\u5E94\u6211\u3001\u4E0D\u8981\u56DE\u7B54\u95EE\u9898\u3001\u4E0D\u8981\u8C22\u5E55\u3001\u4E0D\u8981\u89E3\u91CA\u4F60\u505A\u4E86\u4EC0\u4E48\u3002",
2559
+ '- \u8BFB\u8005\u53EA\u6709"\u5DE5\u4F5C AI"\u4E00\u4E2A\uFF0C\u800C\u4F60\u7684\u4EA7\u51FA\u4F1A\u5148\u7ECF\u5BBF\u4E3B\u9010\u6761\u6838\u5BF9\u5F15\u6587\u3002'
2560
+ ];
2561
+ if (options.retry === true) {
2562
+ parts.push(
2563
+ "",
2564
+ `\u3010\u91CD\u8981\uFF1A\u4F60\u4E0A\u4E00\u6B21\u7684\u8F93\u51FA\u662F\u7A7A\u7684${options.reason ? `\uFF08${options.reason}\uFF09` : ""}\u3011`,
2565
+ "\u4E0A\u4E00\u6B21\u4F60\u6CA1\u6709\u7ED9\u51FA\u4EFB\u4F55\u6761\u76EE\uFF0C\u8FD9\u4E00\u8F6E\u56E0\u6B64**\u6CA1\u6709\u4EFB\u4F55\u8865\u5168**\u53EF\u4EE5\u4EA4\u7ED9\u5DE5\u4F5C AI\u3002",
2566
+ "\u8BF7\u6309\u7CFB\u7EDF\u63D0\u793A\u8BCD\u7684\u786C\u89C4\u5219\u91CD\u505A\uFF1A**\u54EA\u6015\u7528\u6237\u53EA\u5199\u4E86\u4E00\u4E24\u4E2A\u5B57\uFF0C\u4E5F\u8981\u5C3D\u91CF\u628A\u5B83\u62C6\u6210\u53EF\u5F15\u7528\u7684\u6761\u76EE**",
2567
+ '\uFF08\u81F3\u5C11\u4E00\u6761 `rewrite`\uFF0C\u6216\u4E00\u6761 `quality` / `unknown`\uFF09\uFF1B\u786E\u5B9E\u6CA1\u6709\u53EF\u8865\u7684\u5185\u5BB9\u65F6\u624D\u8F93\u51FA {"items":[]}\u3002',
2568
+ "\u53EA\u8F93\u51FA JSON\u3002"
2569
+ );
2570
+ }
2571
+ return parts.join("\n");
2572
+ }
2573
+
2574
+ // src/optimizer-assemble.ts
2575
+ var TIER_BUDGET = {
2576
+ basic: { factor: 1.4, floor: 400 },
2577
+ advanced: { factor: 2.6, floor: 700 },
2578
+ extreme: { factor: 4, floor: 1400 }
2579
+ };
2580
+ var TIER_KINDS = {
2581
+ basic: ["rewrite", "unknown"],
2582
+ advanced: ["rewrite", "requirement", "quality", "unknown"],
2583
+ extreme: ["rewrite", "requirement", "quality", "unknown", "plan", "risk"]
2584
+ };
2585
+ function allowedKindsFor(tier) {
2586
+ return TIER_KINDS[tier] ?? TIER_KINDS.advanced;
2587
+ }
2588
+ function optimizeBudgetFor(tier, originalChars) {
2589
+ const spec = TIER_BUDGET[tier] ?? TIER_BUDGET.advanced;
2590
+ const scaled = Math.ceil(Math.max(0, originalChars) * spec.factor);
2591
+ return Math.min(OPTIMIZE_OUTPUT_MAX, Math.max(spec.floor, scaled));
2592
+ }
2593
+ function looksLikeEnvelope(raw) {
2594
+ const text = String(raw ?? "").trim();
2595
+ if (text.startsWith("{") || text.startsWith("```")) return true;
2596
+ return /"(items|ops)"\s*:/.test(text);
2597
+ }
2598
+ function extractJson(raw) {
2599
+ const text = String(raw ?? "");
2600
+ const fenced = text.match(/```(?:json)?\s*([\s\S]*?)```/i);
2601
+ const candidate = fenced ? fenced[1] : text;
2602
+ const start = candidate.indexOf("{");
2603
+ const end = candidate.lastIndexOf("}");
2604
+ const envelope = looksLikeEnvelope(text);
2605
+ if (start < 0 || end <= start) {
2606
+ return envelope ? { ok: false, code: "BAD_JSON", reason: "\u8F93\u51FA\u770B\u8D77\u6765\u662F\u672C\u63D2\u4EF6\u7684\u6761\u76EE JSON\uFF0C\u4F46\u62EC\u53F7\u4E0D\u5B8C\u6574\uFF08\u53EF\u80FD\u88AB\u622A\u65AD\uFF09" } : { ok: false, code: "NO_JSON", reason: "\u8F93\u51FA\u91CC\u6CA1\u6709 JSON \u5BF9\u8C61" };
2607
+ }
2608
+ try {
2609
+ return { ok: true, value: JSON.parse(candidate.slice(start, end + 1)) };
2610
+ } catch (error) {
2611
+ return {
2612
+ ok: false,
2613
+ code: envelope ? "BAD_JSON" : "NO_JSON",
2614
+ reason: error instanceof Error ? error.message : String(error)
2615
+ };
2616
+ }
2617
+ }
2618
+ function findQuoteSpan(original, quote) {
2619
+ const q = String(quote ?? "").trim();
2620
+ if (q === "") return void 0;
2621
+ const exact = original.indexOf(q);
2622
+ if (exact >= 0) return { start: exact, end: exact + q.length };
2623
+ const target = q.replace(/\s+/g, " ");
2624
+ let acc = "";
2625
+ const map = [];
2626
+ for (let i = 0; i < original.length; i += 1) {
2627
+ const ch = original[i];
2628
+ if (/\s/.test(ch)) {
2629
+ if (acc === "" || acc.endsWith(" ")) continue;
2630
+ acc += " ";
2631
+ map.push(i);
2632
+ continue;
2633
+ }
2634
+ acc += ch;
2635
+ map.push(i);
2636
+ }
2637
+ const at = acc.indexOf(target);
2638
+ if (at < 0) return void 0;
2639
+ const start = map[at];
2640
+ const last = map[at + target.length - 1] ?? start;
2641
+ return { start, end: last + 1 };
2642
+ }
2643
+ function itemsOf(value, warnings) {
2644
+ if (typeof value !== "object" || value === null || Array.isArray(value)) return void 0;
2645
+ const row = value;
2646
+ if (Array.isArray(row.items)) return row.items;
2647
+ if (Array.isArray(row.ops)) {
2648
+ const out = [];
2649
+ for (const op of row.ops) {
2650
+ if (typeof op !== "object" || op === null) continue;
2651
+ const entry = op;
2652
+ if (entry.op === "add_item" && typeof entry.item === "object" && entry.item !== null) {
2653
+ out.push(entry.item);
2654
+ continue;
2655
+ }
2656
+ warnings.push(`\u5FFD\u7565\u4E86\u4E0D\u652F\u6301\u7684 op\u300C${String(entry.op ?? "(\u7A7A)")}\u300D\uFF1A\u672C\u63D2\u4EF6\u4E0D\u505A\u8DE8\u8F6E\u72B6\u6001\uFF0C\u53EA\u8BA4 add_item`);
2657
+ }
2658
+ return out;
2659
+ }
2660
+ return void 0;
2661
+ }
2662
+ function hasEnvelopeKey(value) {
2663
+ if (typeof value !== "object" || value === null || Array.isArray(value)) return false;
2664
+ const row = value;
2665
+ return "items" in row || "ops" in row;
2666
+ }
2667
+ function unknownClassOf(value) {
2668
+ return OPTIMIZE_UNKNOWN_CLASSES.includes(value) ? value : "user_preference";
2669
+ }
2670
+ function parseOptimizeOutput(raw, original) {
2671
+ const extracted = extractJson(raw);
2672
+ if (!extracted.ok) return { ok: false, code: extracted.code, reason: extracted.reason };
2673
+ const warnings = [];
2674
+ const raw_items = itemsOf(extracted.value, warnings);
2675
+ if (raw_items === void 0) {
2676
+ return hasEnvelopeKey(extracted.value) ? { ok: false, code: "BAD_SHAPE", reason: '\u671F\u671B {"items":[...]} \u6216 {"ops":[{"op":"add_item","item":{...}}]}' } : { ok: false, code: "NOT_ENVELOPE", reason: "\u8FD9\u662F\u4E00\u6BB5 JSON\uFF0C\u4F46\u4E0D\u662F\u672C\u63D2\u4EF6\u7684\u6761\u76EE\u4FE1\u5C01" };
2677
+ }
2678
+ const items = [];
2679
+ const dropped = [];
2680
+ const seenRewrite = /* @__PURE__ */ new Set();
2681
+ for (let index = 0; index < raw_items.length; index += 1) {
2682
+ const id = `item#${index + 1}`;
2683
+ const entry = raw_items[index];
2684
+ if (typeof entry !== "object" || entry === null || Array.isArray(entry)) {
2685
+ dropped.push({ id, kind: "(\u975E\u5BF9\u8C61)", reason: "\u6761\u76EE\u4E0D\u662F\u5BF9\u8C61" });
2686
+ continue;
2687
+ }
2688
+ const row = entry;
2689
+ const kind = String(row.kind ?? "");
2690
+ if (!OPTIMIZE_ITEM_KINDS.includes(kind)) {
2691
+ dropped.push({ id, kind: kind === "" ? "(\u7A7A)" : kind, reason: `kind \u4E0D\u5728\u5141\u8BB8\u5217\u8868\u91CC\uFF08${OPTIMIZE_ITEM_KINDS.join(" / ")}\uFF09` });
2692
+ continue;
2693
+ }
2694
+ let text = typeof row.text === "string" ? row.text.trim() : "";
2695
+ if (text === "") {
2696
+ dropped.push({ id, kind, reason: "text \u662F\u7A7A\u7684" });
2697
+ continue;
2698
+ }
2699
+ if (text.length > OPTIMIZE_ITEM_MAX_CHARS) {
2700
+ warnings.push(`${id}\uFF1Atext \u8D85 ${OPTIMIZE_ITEM_MAX_CHARS} \u5B57\uFF0C\u5DF2\u622A\u65AD`);
2701
+ text = text.slice(0, OPTIMIZE_ITEM_MAX_CHARS);
2702
+ }
2703
+ if (items.length >= OPTIMIZE_MAX_ITEMS) {
2704
+ dropped.push({ id, kind, reason: `\u8D85\u8FC7\u5355\u8F6E\u4E0A\u9650 ${OPTIMIZE_MAX_ITEMS} \u6761 \u21D2 \u622A\u65AD\u4E22\u5F03\uFF08\u6574\u8F6E\u7167\u5E38\u51FA\u6210\u54C1\uFF09` });
2705
+ continue;
2706
+ }
2707
+ const needsQuote = kind === "rewrite" || kind === "requirement" || kind === "quality";
2708
+ const quote = typeof row.quote === "string" ? row.quote.trim() : "";
2709
+ if (needsQuote) {
2710
+ if (quote === "") {
2711
+ dropped.push({ id, kind, reason: "\u7F3A\u5C11 quote\uFF08\u8FD9\u4E00\u7C7B\u6761\u76EE\u5FC5\u987B\u6709\u9010\u5B57\u5F15\u6587\uFF09" });
2712
+ continue;
2713
+ }
2714
+ const span2 = findQuoteSpan(original, quote);
2715
+ if (span2 === void 0) {
2716
+ dropped.push({ id, kind, reason: `\u5F15\u6587\u4E0D\u662F\u539F\u8BDD\u91CC\u7684\u9010\u5B57\u7247\u6BB5\uFF1A\u300C${quote.slice(0, 40)}\u300D` });
2717
+ continue;
2718
+ }
2719
+ if (kind === "rewrite") {
2720
+ const key = `${String(span2.start)}-${String(span2.end)}`;
2721
+ if (seenRewrite.has(key)) {
2722
+ dropped.push({ id, kind, reason: "\u4E0E\u53E6\u4E00\u6761 rewrite \u5F15\u7528\u4E86\u540C\u4E00\u6BB5\u539F\u8BDD\uFF08\u91CD\u590D\uFF09" });
2723
+ continue;
2724
+ }
2725
+ seenRewrite.add(key);
2726
+ }
2727
+ items.push({ id, kind, text, quote, span: span2, quoteSource: "user" });
2728
+ continue;
2729
+ }
2730
+ const span = quote === "" ? void 0 : findQuoteSpan(original, quote);
2731
+ if (quote !== "" && span === void 0) {
2732
+ warnings.push(`${id}\uFF1A\u5F15\u6587\u5BF9\u4E0D\u4E0A\u539F\u8BDD\uFF0C\u5DF2\u6309"\u6A21\u578B\u81EA\u5DF1\u8865\u7684"\u8BB0\u8D26\uFF08\u4E0D\u5192\u5145\u4F60\u8BF4\u8FC7\u7684\u8BDD\uFF09`);
2733
+ }
2734
+ items.push({
2735
+ id,
2736
+ kind,
2737
+ text,
2738
+ quoteSource: span === void 0 ? "none" : "user",
2739
+ ...span === void 0 ? {} : { span, quote },
2740
+ ...kind === "unknown" ? { unknownClass: unknownClassOf(row.unknownClass), blocking: row.blocking === true } : {}
2741
+ });
2742
+ }
2743
+ if (items.length === 0 && dropped.length === 0 && raw_items.length === 0) warnings.push("\u6A21\u578B\u4EA4\u56DE\u7A7A\u6570\u7EC4\uFF1A\u8FD9\u4E00\u8F6E\u6CA1\u6709\u53EF\u6838\u5B9E\u7684\u8865\u5168");
2744
+ return { ok: true, items, dropped, warnings };
2745
+ }
2746
+ var SECTIONS = [
2747
+ { key: "requirement", label: "\u8865\u5168\u8981\u6C42\uFF08\u6BCF\u6761\u90FD\u6307\u56DE\u4F60\u539F\u8BDD\u91CC\u7684\u67D0\u53E5\uFF09", required: true },
2748
+ { key: "quality", label: "\u5BF9\u8D28\u91CF\u8BCD\u7684\u7406\u89E3", required: false },
2749
+ { key: "plan", label: "\u5206\u9636\u6BB5\u6267\u884C\u8BA1\u5212", required: false },
2750
+ { key: "risk", label: "\u591A\u60C5\u51B5\u9884\u6848", required: false },
2751
+ { key: "unknown", label: "\u4E0D\u660E\u786E\u5904", required: false }
2752
+ ];
2753
+ var DROP_ORDER = ["risk", "plan", "quality", "unknown"];
2754
+ var UNKNOWN_SUFFIX = {
2755
+ user_preference: "\uFF08\u53EA\u6709\u6211\u80FD\u5B9A\uFF1A\u5148\u95EE\u6211\uFF0C\u4E0D\u8981\u81EA\u884C\u5047\u8BBE\uFF09",
2756
+ lookupable_fact: "\uFF08\u53EF\u67E5\u8BC1\u7684\u4E8B\u5B9E\uFF1A\u5148\u8BFB\u4EE3\u7801/\u6587\u6863\u786E\u8BA4\uFF0C\u4E0D\u8981\u731C\uFF09",
2757
+ implementation_detail: "\uFF08\u5B9E\u73B0\u7EC6\u8282\uFF1A\u4F60\u81EA\u5DF1\u5B9A\uFF09"
2758
+ };
2759
+ function lineFor(item) {
2760
+ if (item.kind === "unknown") {
2761
+ const cls = item.unknownClass ?? "user_preference";
2762
+ const blocking = item.blocking === true ? "[\u6321\u4F4F\u4E0B\u4E00\u6B65] " : "";
2763
+ return `- ${blocking}${item.text}${UNKNOWN_SUFFIX[cls]}`;
2764
+ }
2765
+ const evidence = item.quote === void 0 ? "" : `\uFF08\u4F9D\u636E\uFF1A"${item.quote}"\uFF09`;
2766
+ return `- ${item.text}${evidence}`;
2767
+ }
2768
+ function assembleCommand(original, items, options) {
2769
+ const body0 = String(original ?? "");
2770
+ const warnings = [];
2771
+ const dropped = [];
2772
+ const allowed = allowedKindsFor(options.tier);
2773
+ const usable = [];
2774
+ for (const item of items) {
2775
+ if (allowed.includes(item.kind)) {
2776
+ usable.push(item);
2777
+ continue;
2778
+ }
2779
+ dropped.push({ id: item.id, kind: item.kind, reason: `\u5F53\u524D\u6863\u4F4D\uFF08${options.tier}\uFF09\u4E0D\u4EA7\u51FA\u300C${item.kind}\u300D\u8FD9\u4E00\u7C7B\u6761\u76EE` });
2780
+ }
2781
+ const rewrites = usable.filter((item) => item.kind === "rewrite" && item.span !== void 0).slice().sort((a, b) => a.span.start - b.span.start || a.span.end - b.span.end);
2782
+ const keptRewrites = [];
2783
+ let cursor = -1;
2784
+ for (const item of rewrites) {
2785
+ if (item.span.start < cursor) {
2786
+ dropped.push({ id: item.id, kind: item.kind, reason: "\u6539\u5199\u533A\u95F4\u4E0E\u53E6\u4E00\u6761 rewrite \u91CD\u53E0\uFF0C\u53EA\u4FDD\u7559\u9760\u524D\u7684\u90A3\u6761" });
2787
+ continue;
2788
+ }
2789
+ cursor = item.span.end;
2790
+ keptRewrites.push(item);
2791
+ }
2792
+ let body = body0;
2793
+ let rewrittenChars = 0;
2794
+ for (let i = keptRewrites.length - 1; i >= 0; i -= 1) {
2795
+ const span = keptRewrites[i].span;
2796
+ body = body.slice(0, span.start) + keptRewrites[i].text + body.slice(span.end);
2797
+ rewrittenChars += span.end - span.start;
2798
+ }
2799
+ const included = {};
2800
+ for (const section of SECTIONS) included[section.key] = [];
2801
+ for (const item of usable) {
2802
+ if (item.kind === "rewrite") continue;
2803
+ if (included[item.kind] === void 0) continue;
2804
+ included[item.kind].push(item);
2805
+ }
2806
+ const renderBlocks = () => {
2807
+ const blocks2 = [];
2808
+ for (const section of SECTIONS) {
2809
+ const bucket = included[section.key] ?? [];
2810
+ if (bucket.length === 0) continue;
2811
+ blocks2.push(`\u3010${section.label}\u3011
2812
+ ${bucket.map(lineFor).join("\n")}`);
2813
+ }
2814
+ return blocks2;
2815
+ };
2816
+ const compose = (blocks2, removed2, overBy2) => {
2817
+ const tail = [];
2818
+ if (removed2.length > 0) {
2819
+ tail.push(`\u3010\u8BF4\u660E\u3011\u56E0\u7BC7\u5E45\u9884\u7B97\u7701\u7565 ${removed2.length} \u6761\u8865\u5168\uFF08${removed2.map((d) => d.kind).join("\u3001")}\uFF09\uFF1B\u5982\u679C\u5176\u4E2D\u6709\u7528\u4FE1\u606F\u5F71\u54CD\u5224\u65AD\uFF0C\u8BF7\u5148\u5411\u6211\u786E\u8BA4\u3002`);
2820
+ }
2821
+ if (overBy2 > 0) {
2822
+ tail.push(`\u3010\u9884\u7B97\u4E0D\u8DB3\u3011\u5DF2\u7701\u7565\u5168\u90E8\u53EF\u7701\u7565\u9879\uFF0C\u4ECD\u8D85\u51FA\u7EA6 ${overBy2} \u5B57\u7B26\uFF1B\u672C\u8F6E\u5148\u6309\u4E0B\u8FBE\u7684\u8FD9\u4E9B\u505A\uFF0C\u9700\u8981\u4FDD\u7559\u88AB\u7701\u7565\u7684\u5185\u5BB9\u8BF7\u7F29\u5C0F\u8303\u56F4\u3002`);
2823
+ }
2824
+ const head = blocks2.length === 0 ? "" : `${body}
2825
+
2826
+ ${blocks2.join("\n\n")}`;
2827
+ if (tail.length === 0) return blocks2.length === 0 ? body : head;
2828
+ return head === "" ? tail.join("\n\n") : `${head}
2829
+
2830
+ ${tail.join("\n\n")}`;
2831
+ };
2832
+ const budget = optimizeBudgetFor(options.tier, body0.length);
2833
+ const removed = [];
2834
+ let blocks = renderBlocks();
2835
+ let overBy = 0;
2836
+ let text = compose(blocks, removed, 0);
2837
+ for (let guard = 0; guard < 200; guard += 1) {
2838
+ const bare = compose(blocks, [], 0);
2839
+ overBy = bare.length > budget ? bare.length - budget : 0;
2840
+ text = compose(blocks, removed, overBy);
2841
+ if (bare.length <= budget) break;
2842
+ const victim = DROP_ORDER.find((key) => (included[key] ?? []).length > 0);
2843
+ if (victim === void 0) break;
2844
+ const taken = included[victim].pop();
2845
+ removed.push({ id: taken.id, kind: taken.kind, reason: "budget\uFF08\u7BC7\u5E45\u9884\u7B97\uFF09" });
2846
+ blocks = renderBlocks();
2847
+ }
2848
+ if (removed.length > 0) {
2849
+ warnings.push(`\u7BC7\u5E45\u9884\u7B97 ${budget} \u5B57\u7B26\uFF1A\u7701\u7565\u4E86 ${removed.length} \u6761\uFF08${removed.map((d) => d.id).join("\u3001")}\uFF09`);
2850
+ }
2851
+ if (overBy > 0) warnings.push(`\u88C5\u4E86\u5FC5\u4FDD\u8282\u540E\u4ECD\u8D85\u51FA\u9884\u7B97\u7EA6 ${overBy} \u5B57\u7B26\uFF0C\u5DF2\u5728\u6210\u54C1\u91CC\u5982\u5B9E\u8BF4\u660E`);
2852
+ const itemCount = keptRewrites.length + SECTIONS.reduce((sum, section) => sum + (included[section.key] ?? []).length, 0);
2853
+ return {
2854
+ text,
2855
+ sections: SECTIONS.filter((section) => (included[section.key] ?? []).length > 0).map((section) => section.label),
2856
+ dropped: [...dropped, ...removed],
2857
+ warnings,
2858
+ chars: text.length,
2859
+ budget,
2860
+ overBudget: overBy > 0,
2861
+ overBy,
2862
+ rewrittenChars,
2863
+ itemCount
2864
+ };
2865
+ }
2866
+ function runOptimizePipeline(raw, original, options) {
2867
+ const parsed = parseOptimizeOutput(raw, original);
2868
+ if (!parsed.ok) {
2869
+ if (parsed.code === "NO_JSON" || parsed.code === "NOT_ENVELOPE") {
2870
+ return {
2871
+ ok: true,
2872
+ text: raw.trim(),
2873
+ fallback: true,
2874
+ itemCount: 0,
2875
+ dropped: [],
2876
+ warnings: ["\u6A21\u578B\u6CA1\u6709\u6309 JSON \u5951\u7EA6\u8F93\u51FA\uFF1A\u5DF2\u6309\u539F\u6837\u5199\u56DE\uFF08\u8FD9\u4E00\u8F6E\u6CA1\u6709\u505A\u4F9D\u636E\u6821\u9A8C\uFF09"],
2877
+ sections: [],
2878
+ chars: raw.trim().length,
2879
+ budget: optimizeBudgetFor(options.tier, original.length),
2880
+ overBudget: false,
2881
+ rewrittenChars: 0
2882
+ };
2883
+ }
2884
+ return { ok: false, code: parsed.code, reason: parsed.reason };
2885
+ }
2886
+ const assembled = assembleCommand(original, parsed.items, options);
2887
+ const warnings = [...parsed.warnings, ...assembled.warnings];
2888
+ if (parsed.items.length === 0 && parsed.dropped.length === 0) warnings.push("\u8FD9\u4E00\u8F6E\u6CA1\u6709\u53EF\u6838\u5B9E\u7684\u8865\u5168\uFF0C\u539F\u8BDD\u539F\u6837\u5199\u56DE");
2889
+ return {
2890
+ ok: true,
2891
+ text: assembled.text,
2892
+ fallback: false,
2893
+ itemCount: assembled.itemCount,
2894
+ dropped: [...parsed.dropped, ...assembled.dropped],
2895
+ warnings,
2896
+ sections: assembled.sections,
2897
+ chars: assembled.chars,
2898
+ budget: assembled.budget,
2899
+ overBudget: assembled.overBudget,
2900
+ rewrittenChars: assembled.rewrittenChars
2901
+ };
2284
2902
  }
2285
2903
 
2286
2904
  // src/quick-store.ts
@@ -2418,16 +3036,103 @@ async function ensureQuickBook(seed, file = quickStorePath()) {
2418
3036
  }
2419
3037
  }
2420
3038
 
3039
+ // src/settings-lock.ts
3040
+ import { readFile as readFile2, rm } from "node:fs/promises";
3041
+ import { dirname as dirname2, join as join2 } from "node:path";
3042
+ var SETTINGS_LOCK_FILENAME = "package.json.lock";
3043
+ function staleLockDecision(content, alive) {
3044
+ const text = content.trim();
3045
+ if (!/^[1-9][0-9]{0,9}$/.test(text)) return { action: "ignore", reason: "unreadable-holder" };
3046
+ const pid = Number(text);
3047
+ return alive(pid) ? { action: "keep", pid } : { action: "remove", pid };
3048
+ }
3049
+ function isProcessAlive(pid) {
3050
+ try {
3051
+ process.kill(pid, 0);
3052
+ return true;
3053
+ } catch (error) {
3054
+ return error?.code === "EPERM";
3055
+ }
3056
+ }
3057
+ function profileDirOfPatchPath(patchPath) {
3058
+ return dirname2(patchPath);
3059
+ }
3060
+ async function recoverStaleSettingsLock(profileDir, log) {
3061
+ const path = join2(profileDir, SETTINGS_LOCK_FILENAME);
3062
+ let content;
3063
+ try {
3064
+ content = await readFile2(path, "utf8");
3065
+ } catch (error) {
3066
+ if (error?.code === "ENOENT") return "absent";
3067
+ log(`\u8BFB\u53D6\u5199\u5165\u9501\u5931\u8D25\uFF08${path}\uFF09\uFF1A${String(error)}`);
3068
+ return "ignored";
3069
+ }
3070
+ const decision = staleLockDecision(content, isProcessAlive);
3071
+ if (decision.action === "keep") {
3072
+ log(`\u5199\u5165\u9501\u7531 PID ${decision.pid} \u6301\u6709\u4E14\u8BE5\u8FDB\u7A0B\u4ECD\u5728\uFF0C\u4FDD\u6301\u4E0D\u52A8\uFF08${path}\uFF09\u3002`);
3073
+ return "kept";
3074
+ }
3075
+ if (decision.action === "ignore") {
3076
+ log(`\u5199\u5165\u9501\u5185\u5BB9\u4E0D\u662F PID\uFF0C\u4FDD\u6301\u4E0D\u52A8\uFF08${path}\uFF09\u3002`);
3077
+ return "ignored";
3078
+ }
3079
+ try {
3080
+ await rm(path, { force: true });
3081
+ } catch (error) {
3082
+ log(`\u56DE\u6536\u5B64\u513F\u5199\u5165\u9501\u5931\u8D25\uFF08${path}\uFF09\uFF1A${String(error)}`);
3083
+ return "ignored";
3084
+ }
3085
+ log(
3086
+ `\u5DF2\u56DE\u6536\u5B64\u513F\u5199\u5165\u9501\uFF08\u6301\u6709\u8005 PID ${decision.pid} \u4E0D\u5B58\u5728\uFF09\uFF1A${path}\u3002\u5728\u6B64\u4E4B\u524D\u8BE5 profile \u7684\u6BCF\u4E00\u6B21\u8BBE\u7F6E\u5199\u5165\u90FD\u4F1A\u8D85\u65F6\u5931\u8D25\uFF08\u754C\u9762\u8868\u73B0\u4E3A"\u70B9\u4E86\u6CA1\u53CD\u5E94"\uFF09\u3002`
3087
+ );
3088
+ return "removed";
3089
+ }
3090
+
2421
3091
  // src/host.ts
2422
3092
  var name = "composer-ux";
2423
3093
  var PANEL_MIN = 560;
2424
3094
  var PANEL_MAX = 4e3;
3095
+ function plainConfig(value) {
3096
+ if (typeof value === "object" && value !== null && typeof value.get === "function") {
3097
+ return plainConfig(value.get());
3098
+ }
3099
+ if (Array.isArray(value)) return value.map(plainConfig);
3100
+ if (typeof value === "object" && value !== null) {
3101
+ return Object.fromEntries(Object.entries(value).map(([key, child]) => [key, plainConfig(child)]));
3102
+ }
3103
+ return value;
3104
+ }
3105
+ function makeReader(settings, config) {
3106
+ return (ns) => {
3107
+ try {
3108
+ if (typeof settings?.get === "function") return objectOf2(settings.get(ns));
3109
+ if (ns === NAMESPACE) {
3110
+ const own = objectOf2(plainConfig(config));
3111
+ if (own !== void 0) return own;
3112
+ }
3113
+ const row = (settings?.describe?.() ?? []).find((candidate) => candidate?.ns === ns);
3114
+ return objectOf2(row?.value);
3115
+ } catch {
3116
+ return void 0;
3117
+ }
3118
+ };
3119
+ }
2425
3120
  function objectOf2(value) {
2426
3121
  return typeof value === "object" && value !== null && !Array.isArray(value) ? value : void 0;
2427
3122
  }
2428
3123
  function textOf2(value) {
2429
3124
  return typeof value === "string" ? value : "";
2430
3125
  }
3126
+ function readOwnSetting(scope, config, field) {
3127
+ try {
3128
+ const get = scope?.get;
3129
+ if (typeof get !== "function") return "";
3130
+ const service = get.call(scope, "settings");
3131
+ return textOf2(makeReader(service, config)(NAMESPACE)?.[field]);
3132
+ } catch {
3133
+ return "";
3134
+ }
3135
+ }
2431
3136
  function firstHeaderValue(value) {
2432
3137
  if (value === void 0) return void 0;
2433
3138
  return typeof value === "string" ? value : value[0];
@@ -2475,8 +3180,8 @@ function errorText3(error) {
2475
3180
  if (error instanceof Error && error.message !== "") return error.message;
2476
3181
  return String(error);
2477
3182
  }
2478
- async function mirrorHeader(settings) {
2479
- const own = objectOf2(settings.get(NAMESPACE));
3183
+ async function mirrorHeader(settings, read) {
3184
+ const own = read(NAMESPACE);
2480
3185
  if (own === void 0) return;
2481
3186
  const enabled = own[ENABLED_FIELD] === true && own[HEADER_ENABLED_FIELD] === true;
2482
3187
  const name2 = textOf2(own[HEADER_NAME_FIELD]);
@@ -2487,7 +3192,7 @@ async function mirrorHeader(settings) {
2487
3192
  value = newSessionId();
2488
3193
  await settings.mutate(NAMESPACE, [{ op: "set", path: [HEADER_VALUE_FIELD], value }]);
2489
3194
  }
2490
- const llm = objectOf2(settings.get(LLM_NAMESPACE));
3195
+ const llm = read(LLM_NAMESPACE);
2491
3196
  const providers = llm === void 0 ? void 0 : objectOf2(llm.providers);
2492
3197
  const writes = [];
2493
3198
  const applied = [];
@@ -2553,70 +3258,109 @@ async function mirrorHeader(settings) {
2553
3258
  }
2554
3259
  if (ownOps.length > 0) await settings.mutate(NAMESPACE, ownOps);
2555
3260
  }
2556
- function apply(ctx) {
3261
+ function ownSchema() {
3262
+ return Schema.object({
3263
+ [ENABLED_FIELD]: Schema.boolean().default(DEFAULT_SETTINGS.enabled),
3264
+ // 五栏开关。**故意不给默认值、声明成可选**:迁移要用的信息就是"文档里有没有这个键"
3265
+ // —— 没有 ⇒ 从没碰过这一栏 ⇒ 关闭;有 ⇒ 用户碰过 ⇒ 保持他写下的值。
3266
+ // 给了静态默认值就再也分不出这两种情况了(净化的 sectionEnabledOf 靠它)。
3267
+ [KEYS_ENABLED_FIELD]: Schema.boolean().required(false),
3268
+ [MENU_ENABLED_FIELD]: Schema.boolean().required(false),
3269
+ [QUICK_ENABLED_FIELD]: Schema.boolean().required(false),
3270
+ [PANEL_ENABLED_FIELD]: Schema.boolean().required(false),
3271
+ [TERMINAL_ENABLED_FIELD]: Schema.boolean().required(false),
3272
+ [SEND_KEY_FIELD]: Schema.string().default(DEFAULT_SETTINGS.sendKey),
3273
+ [NEWLINE_KEY_FIELD]: Schema.string().default(DEFAULT_SETTINGS.newlineKey),
3274
+ ...Object.fromEntries(MENU_FIELDS.map((field) => [
3275
+ field,
3276
+ Schema.boolean().default(DEFAULT_SETTINGS[field])
3277
+ ])),
3278
+ // 右键菜单模式(0.5.0 起三档)。**故意不给默认值、且声明成可选**:
3279
+ // 「文档里没有这个键」本身就是迁移要用的信息——净化据此按旧布尔 menuNative
3280
+ // 推断(见 menuModeFrom)。给了默认值就再也分不出「从没设置过」与「明确设成了它」。
3281
+ [MENU_MODE_FIELD]: Schema.string().required(false),
3282
+ // 旧的布尔字段:只作迁移线索,故同样不给默认值——旧文档里的 true / false
3283
+ // 都要保住原意(true = 浏览器菜单、false = 明确选过自定义菜单)。
3284
+ [MENU_NATIVE_FIELD]: Schema.boolean().required(false),
3285
+ // 「导航滚动」在 0.6.0 随功能一起删掉了(DSH 0.1.7 的官方设置页自带导航列滚动)。
3286
+ // 旧文档里可能仍留着 panelScroll:schemastery 对未声明键是**原样放行**(已实测),
3287
+ // 所以不需要为它保留一个宽容字段,升级时也不会因此判非法。
3288
+ [PANEL_RESIZE_FIELD]: Schema.boolean().default(DEFAULT_SETTINGS.panelResize),
3289
+ // schemastery 无 .optional():可选键用 .required(false)。
3290
+ [PANEL_WIDTH_FIELD]: Schema.number().min(PANEL_MIN).max(PANEL_MAX).required(false),
3291
+ [PANEL_HEIGHT_FIELD]: Schema.number().min(320).max(PANEL_MAX).required(false),
3292
+ // OpenCode 请求头栏目(值由宿主半按 llm-pi-ai 的 Fetch 规则自校验后再写入)。
3293
+ [HEADER_ENABLED_FIELD]: Schema.boolean().default(DEFAULT_SETTINGS.headerEnabled),
3294
+ [HEADER_NAME_FIELD]: Schema.string().default(DEFAULT_HEADER_NAME),
3295
+ [HEADER_VALUE_FIELD]: Schema.string().default(DEFAULT_SETTINGS.headerValue),
3296
+ [HEADER_ROUTES_FIELD]: Schema.string().default(DEFAULT_SETTINGS.headerRoutes),
3297
+ [HEADER_APPLIED_NAME_FIELD]: Schema.string().default(DEFAULT_SETTINGS.headerAppliedName),
3298
+ [HEADER_APPLIED_VALUE_FIELD]: Schema.string().default(DEFAULT_SETTINGS.headerAppliedValue),
3299
+ [HEADER_STATUS_FIELD]: Schema.string().default(DEFAULT_SETTINGS.headerStatus),
3300
+ // 快捷指令列表:结构固定为 {id,label,prompt,always},逐条自带默认值,
3301
+ // 让旧设置文档(没有这个键)在读取时直接得到内置 9 条。
3302
+ [QUICK_PROMPTS_FIELD]: Schema.array(Schema.object({
3303
+ id: Schema.string().default(""),
3304
+ label: Schema.string().default(""),
3305
+ prompt: Schema.string().default(""),
3306
+ always: Schema.boolean().default(false)
3307
+ })).default(DEFAULT_QUICK_PROMPTS.map((item) => ({ ...item }))),
3308
+ [OPTIMIZER_TIER_FIELD]: Schema.string().default(DEFAULT_SETTINGS.optimizerTier),
3309
+ // 三档的自定义系统提示词:默认空串 = 用内置那份(空串同时就是「恢复内置」写回的值)。
3310
+ [OPTIMIZER_PROMPT_FIELDS.basic]: Schema.string().default(DEFAULT_SETTINGS.optimizerPromptBasic),
3311
+ [OPTIMIZER_PROMPT_FIELDS.advanced]: Schema.string().default(DEFAULT_SETTINGS.optimizerPromptAdvanced),
3312
+ [OPTIMIZER_PROMPT_FIELDS.extreme]: Schema.string().default(DEFAULT_SETTINGS.optimizerPromptExtreme),
3313
+ // 「默认终端」(0.5.0 起):用户档位与可选路径。
3314
+ [TERMINAL_MODE_FIELD]: Schema.string().default(DEFAULT_SETTINGS.terminalMode),
3315
+ [TERMINAL_BASH_PATH_FIELD]: Schema.string().default(DEFAULT_SETTINGS.terminalBashPath),
3316
+ // 宿主半自持的三项:探测候选、状态行、当前生效 shell(见 HOST_OWNED_FIELDS)。
3317
+ [TERMINAL_CANDIDATES_FIELD]: Schema.array(Schema.object({
3318
+ path: Schema.string().default(""),
3319
+ label: Schema.string().default(""),
3320
+ kind: Schema.string().default("path"),
3321
+ explicit: Schema.boolean().default(false)
3322
+ })).default([]),
3323
+ [TERMINAL_STATUS_FIELD]: Schema.string().default(DEFAULT_SETTINGS.terminalStatus),
3324
+ [TERMINAL_EFFECTIVE_FIELD]: Schema.string().default(DEFAULT_SETTINGS.terminalEffective)
3325
+ });
3326
+ }
3327
+ function markVolatileField(node) {
3328
+ const withMethod = node;
3329
+ if (typeof withMethod.volatile === "function") return withMethod.volatile();
3330
+ const target = node;
3331
+ if (target.meta !== void 0) target.meta.volatile = true;
3332
+ return node;
3333
+ }
3334
+ function markVolatile(schema) {
3335
+ const dict = schema.dict;
3336
+ if (dict === void 0) return schema;
3337
+ for (const key of Object.keys(dict)) dict[key] = markVolatileField(dict[key]);
3338
+ return schema;
3339
+ }
3340
+ var Config = markVolatile(ownSchema());
3341
+ function apply(ctx, config) {
3342
+ const readView = (service) => {
3343
+ if (service === null || service === void 0) return void 0;
3344
+ const real = service;
3345
+ return { get: makeReader(real, config), mutate: (ns, ops) => real.mutate(ns, ops) };
3346
+ };
3347
+ ctx.inject(["configEditor"], (lockCtx) => {
3348
+ try {
3349
+ const documentPath = lockCtx.configEditor.documentPath;
3350
+ if (typeof documentPath !== "string" || documentPath === "") return;
3351
+ void recoverStaleSettingsLock(profileDirOfPatchPath(documentPath), (message) => {
3352
+ console.warn(`[composer-ux] ${message}`);
3353
+ }).catch((error) => {
3354
+ console.error("[composer-ux] \u5B64\u513F\u5199\u5165\u9501\u68C0\u67E5\u5931\u8D25", error);
3355
+ });
3356
+ } catch (error) {
3357
+ console.error("[composer-ux] \u5B64\u513F\u5199\u5165\u9501\u68C0\u67E5\u5931\u8D25", error);
3358
+ }
3359
+ });
2557
3360
  ctx.inject(["settings"], (settingsCtx) => {
2558
- settingsCtx.settings.register(
2559
- NAMESPACE,
2560
- Schema.object({
2561
- [ENABLED_FIELD]: Schema.boolean().default(DEFAULT_SETTINGS.enabled),
2562
- // 五栏开关。**故意不给默认值、声明成可选**:迁移要用的信息就是"文档里有没有这个键"
2563
- // —— 没有 ⇒ 从没碰过这一栏 ⇒ 关闭;有 ⇒ 用户碰过 ⇒ 保持他写下的值。
2564
- // 给了静态默认值就再也分不出这两种情况了(净化的 sectionEnabledOf 靠它)。
2565
- [KEYS_ENABLED_FIELD]: Schema.boolean().required(false),
2566
- [MENU_ENABLED_FIELD]: Schema.boolean().required(false),
2567
- [QUICK_ENABLED_FIELD]: Schema.boolean().required(false),
2568
- [PANEL_ENABLED_FIELD]: Schema.boolean().required(false),
2569
- [TERMINAL_ENABLED_FIELD]: Schema.boolean().required(false),
2570
- [SEND_KEY_FIELD]: Schema.string().default(DEFAULT_SETTINGS.sendKey),
2571
- [NEWLINE_KEY_FIELD]: Schema.string().default(DEFAULT_SETTINGS.newlineKey),
2572
- ...Object.fromEntries(MENU_FIELDS.map((field) => [
2573
- field,
2574
- Schema.boolean().default(DEFAULT_SETTINGS[field])
2575
- ])),
2576
- // 右键菜单模式(0.5.0 起三档)。**故意不给默认值、且声明成可选**:
2577
- // 「文档里没有这个键」本身就是迁移要用的信息——净化据此按旧布尔 menuNative
2578
- // 推断(见 menuModeFrom)。给了默认值就再也分不出「从没设置过」与「明确设成了它」。
2579
- [MENU_MODE_FIELD]: Schema.string().required(false),
2580
- // 旧的布尔字段:只作迁移线索,故同样不给默认值——旧文档里的 true / false
2581
- // 都要保住原意(true = 浏览器菜单、false = 明确选过自定义菜单)。
2582
- [MENU_NATIVE_FIELD]: Schema.boolean().required(false),
2583
- [PANEL_SCROLL_FIELD]: Schema.boolean().default(DEFAULT_SETTINGS.panelScroll),
2584
- [PANEL_RESIZE_FIELD]: Schema.boolean().default(DEFAULT_SETTINGS.panelResize),
2585
- // schemastery 无 .optional():可选键用 .required(false)。
2586
- [PANEL_WIDTH_FIELD]: Schema.number().min(PANEL_MIN).max(PANEL_MAX).required(false),
2587
- [PANEL_HEIGHT_FIELD]: Schema.number().min(320).max(PANEL_MAX).required(false),
2588
- // OpenCode 请求头栏目(值由宿主半按 llm-pi-ai 的 Fetch 规则自校验后再写入)。
2589
- [HEADER_ENABLED_FIELD]: Schema.boolean().default(DEFAULT_SETTINGS.headerEnabled),
2590
- [HEADER_NAME_FIELD]: Schema.string().default(DEFAULT_HEADER_NAME),
2591
- [HEADER_VALUE_FIELD]: Schema.string().default(DEFAULT_SETTINGS.headerValue),
2592
- [HEADER_ROUTES_FIELD]: Schema.string().default(DEFAULT_SETTINGS.headerRoutes),
2593
- [HEADER_APPLIED_NAME_FIELD]: Schema.string().default(DEFAULT_SETTINGS.headerAppliedName),
2594
- [HEADER_APPLIED_VALUE_FIELD]: Schema.string().default(DEFAULT_SETTINGS.headerAppliedValue),
2595
- [HEADER_STATUS_FIELD]: Schema.string().default(DEFAULT_SETTINGS.headerStatus),
2596
- // 快捷指令列表:结构固定为 {id,label,prompt,always},逐条自带默认值,
2597
- // 让旧设置文档(没有这个键)在读取时直接得到内置 9 条。
2598
- [QUICK_PROMPTS_FIELD]: Schema.array(Schema.object({
2599
- id: Schema.string().default(""),
2600
- label: Schema.string().default(""),
2601
- prompt: Schema.string().default(""),
2602
- always: Schema.boolean().default(false)
2603
- })).default(DEFAULT_QUICK_PROMPTS.map((item) => ({ ...item }))),
2604
- [OPTIMIZER_TIER_FIELD]: Schema.string().default(DEFAULT_SETTINGS.optimizerTier),
2605
- // 「默认终端」(0.5.0 起):用户档位与可选路径。
2606
- [TERMINAL_MODE_FIELD]: Schema.string().default(DEFAULT_SETTINGS.terminalMode),
2607
- [TERMINAL_BASH_PATH_FIELD]: Schema.string().default(DEFAULT_SETTINGS.terminalBashPath),
2608
- // 宿主半自持的三项:探测候选、状态行、当前生效 shell(见 HOST_OWNED_FIELDS)。
2609
- [TERMINAL_CANDIDATES_FIELD]: Schema.array(Schema.object({
2610
- path: Schema.string().default(""),
2611
- label: Schema.string().default(""),
2612
- kind: Schema.string().default("path"),
2613
- explicit: Schema.boolean().default(false)
2614
- })).default([]),
2615
- [TERMINAL_STATUS_FIELD]: Schema.string().default(DEFAULT_SETTINGS.terminalStatus),
2616
- [TERMINAL_EFFECTIVE_FIELD]: Schema.string().default(DEFAULT_SETTINGS.terminalEffective)
2617
- })
2618
- );
2619
3361
  const settings = settingsCtx.settings;
3362
+ if (typeof settings.register === "function") settings.register(NAMESPACE, Config);
3363
+ const read = makeReader(settings, config);
2620
3364
  let busy = false;
2621
3365
  let again = false;
2622
3366
  const sync = () => {
@@ -2625,7 +3369,7 @@ function apply(ctx) {
2625
3369
  return;
2626
3370
  }
2627
3371
  busy = true;
2628
- void mirrorHeader(settings).catch((error) => {
3372
+ void mirrorHeader(settings, read).catch((error) => {
2629
3373
  console.error("[composer-ux] \u8BF7\u6C42\u5934\u955C\u50CF\u5931\u8D25", error);
2630
3374
  }).finally(() => {
2631
3375
  busy = false;
@@ -2638,7 +3382,9 @@ function apply(ctx) {
2638
3382
  const onSettingsUpdated = (ns) => {
2639
3383
  if (ns === NAMESPACE || ns === LLM_NAMESPACE) sync();
2640
3384
  };
2641
- ctx.on("settings/updated", onSettingsUpdated);
3385
+ for (const event of ["settings/updated", "settings/document-updated"]) {
3386
+ ctx.on(event, onSettingsUpdated);
3387
+ }
2642
3388
  sync();
2643
3389
  });
2644
3390
  ctx.inject(["webServer", "llm"], (optCtx) => {
@@ -2699,57 +3445,98 @@ function apply(ctx) {
2699
3445
  sendJson(res, 200, { ok: false, error: "\u62FF\u4E0D\u5230\u5F53\u524D\u7684\u6A21\u578B\u8DEF\u7531\uFF0C\u65E0\u6CD5\u4F18\u5316\uFF08\u8BF7\u5148\u5728\u8F93\u5165\u6846\u65C1\u7684\u6A21\u578B\u9009\u62E9\u5668\u91CC\u9009\u4E00\u4E2A\u6A21\u578B\uFF09" });
2700
3446
  return;
2701
3447
  }
3448
+ const customPrompt = readOwnSetting(optCtx, config, optimizerPromptFieldOf(tier));
3449
+ const system = buildOptimizeSystem(tier, customPrompt);
2702
3450
  const controller = new AbortController();
2703
3451
  const timer = setTimeout(() => {
2704
3452
  controller.abort();
2705
3453
  }, LLM_TIMEOUT_MS);
2706
- let out = "";
2707
- let failure = "";
2708
- try {
2709
- const stream = optCtx.llm.stream({
2710
- provider: route.provider,
2711
- model: route.model,
2712
- system: buildOptimizeSystem(tier),
2713
- temperature: buildOptimizeTemperature(tier),
2714
- signal: controller.signal,
2715
- messages: [{
2716
- id: `optimize-${Date.now().toString(36)}`,
2717
- role: "user",
2718
- content: [{ type: "text", text: buildOptimizeUser(text) }],
2719
- source: { kind: "user" }
2720
- }]
2721
- });
2722
- for await (const chunk of stream) {
2723
- if (chunk.type === "text-delta") {
2724
- out += String(chunk.text ?? "");
2725
- if (out.length > OPTIMIZE_OUTPUT_MAX) break;
2726
- } else if (chunk.type === "finish") {
2727
- const reason = objectOf2(chunk.reason);
2728
- if (reason?.kind === "error" || reason?.kind === "aborted") {
2729
- const detail = objectOf2(reason.failure);
2730
- failure = textOf2(detail?.message) || (reason.kind === "aborted" ? "\u4F18\u5316\u88AB\u4E2D\u65AD" : "\u6A21\u578B\u8FD4\u56DE\u9519\u8BEF");
3454
+ const runOnce = async (userText) => {
3455
+ let out = "";
3456
+ let failure = "";
3457
+ try {
3458
+ const stream = optCtx.llm.stream({
3459
+ provider: route.provider,
3460
+ model: route.model,
3461
+ system,
3462
+ temperature: buildOptimizeTemperature(tier),
3463
+ signal: controller.signal,
3464
+ messages: [{
3465
+ id: `optimize-${Date.now().toString(36)}`,
3466
+ role: "user",
3467
+ content: [{ type: "text", text: userText }],
3468
+ source: { kind: "user" }
3469
+ }]
3470
+ });
3471
+ for await (const chunk of stream) {
3472
+ if (chunk.type === "text-delta") {
3473
+ out += String(chunk.text ?? "");
3474
+ if (out.length > OPTIMIZE_OUTPUT_MAX * 4) break;
3475
+ } else if (chunk.type === "finish") {
3476
+ const reason = objectOf2(chunk.reason);
3477
+ if (reason?.kind === "error" || reason?.kind === "aborted") {
3478
+ const detail = objectOf2(reason.failure);
3479
+ failure = textOf2(detail?.message) || (reason.kind === "aborted" ? "\u4F18\u5316\u88AB\u4E2D\u65AD" : "\u6A21\u578B\u8FD4\u56DE\u9519\u8BEF");
3480
+ }
2731
3481
  }
2732
3482
  }
3483
+ } catch (error) {
3484
+ failure = errorText3(error);
3485
+ }
3486
+ return { out, failure };
3487
+ };
3488
+ let retried = false;
3489
+ let result;
3490
+ try {
3491
+ result = await runOnce(buildOptimizeUser(text));
3492
+ if (result.out.trim() === "" && result.failure === "") {
3493
+ retried = true;
3494
+ const second = await runOnce(buildOptimizeUser(text, { retry: true, reason: "\u5BBF\u4E3B\u6CA1\u6709\u6536\u5230\u4EFB\u4F55\u6761\u76EE" }));
3495
+ if (second.out.trim() !== "") result = second;
3496
+ else if (result.failure === "") result = second;
2733
3497
  }
2734
- } catch (error) {
2735
- failure = errorText3(error);
2736
3498
  } finally {
2737
3499
  clearTimeout(timer);
2738
3500
  }
2739
- const optimized = out.trim();
2740
- if (optimized === "") {
3501
+ if (result.out.trim() === "") {
3502
+ sendJson(res, 200, {
3503
+ ok: false,
3504
+ error: result.failure === "" ? "\u6A21\u578B\u6CA1\u6709\u4EA7\u51FA\u4EFB\u4F55\u5185\u5BB9" : `\u4F18\u5316\u5931\u8D25\uFF1A${result.failure}`,
3505
+ retried
3506
+ });
3507
+ return;
3508
+ }
3509
+ const assembled = runOptimizePipeline(result.out, text, { tier });
3510
+ if (!assembled.ok) {
2741
3511
  sendJson(res, 200, {
2742
3512
  ok: false,
2743
- error: failure === "" ? "\u6A21\u578B\u6CA1\u6709\u4EA7\u51FA\u4EFB\u4F55\u5185\u5BB9" : `\u4F18\u5316\u5931\u8D25\uFF1A${failure}`
3513
+ error: `\u6A21\u578B\u8F93\u51FA\u4E0D\u662F\u53EF\u7528\u7684\u6761\u76EE JSON\uFF08${assembled.code}\uFF09\uFF1A${assembled.reason}`,
3514
+ retried
2744
3515
  });
2745
3516
  return;
2746
3517
  }
3518
+ const optimized = assembled.text.trim();
3519
+ if (optimized === "") {
3520
+ sendJson(res, 200, { ok: false, error: "\u88C5\u914D\u540E\u662F\u7A7A\u7684\uFF08\u6A21\u578B\u6CA1\u6709\u7ED9\u51FA\u53EF\u6838\u5B9E\u7684\u6761\u76EE\uFF09", retried });
3521
+ return;
3522
+ }
2747
3523
  sendJson(res, 200, {
2748
3524
  ok: true,
2749
- text: optimized.slice(0, OPTIMIZE_OUTPUT_MAX),
2750
- truncated: out.length > OPTIMIZE_OUTPUT_MAX,
3525
+ text: optimized,
3526
+ // 语义收窄:`truncated` 现在专指"篇幅闸门真的动过手"(装了必保节仍超预算)。
3527
+ truncated: assembled.overBudget,
2751
3528
  provider: route.provider,
2752
- model: route.model
3529
+ model: route.model,
3530
+ // ── 以下为 0.6.0 新增的**附加**字段:老客户端不读它们也不会坏。
3531
+ promptSource: optimizePromptSource(customPrompt),
3532
+ retried,
3533
+ fallback: assembled.fallback,
3534
+ itemCount: assembled.itemCount,
3535
+ rewrittenChars: assembled.rewrittenChars,
3536
+ dropped: assembled.dropped,
3537
+ warnings: assembled.warnings,
3538
+ budget: assembled.budget,
3539
+ chars: assembled.chars
2753
3540
  });
2754
3541
  };
2755
3542
  optCtx.effect(() => optCtx.webServer.register({
@@ -2778,7 +3565,7 @@ function apply(ctx) {
2778
3565
  const legacyPrompts = () => {
2779
3566
  try {
2780
3567
  const settings = storeCtx.get("settings");
2781
- const row = objectOf2(settings?.get(NAMESPACE));
3568
+ const row = makeReader(settings, config)(NAMESPACE);
2782
3569
  const list = row?.[QUICK_PROMPTS_FIELD];
2783
3570
  return Array.isArray(list) ? list : void 0;
2784
3571
  } catch {
@@ -2858,7 +3645,7 @@ function apply(ctx) {
2858
3645
  installTerminalPolicy(
2859
3646
  termCtx,
2860
3647
  NAMESPACE,
2861
- termCtx.get("settings"),
3648
+ readView(termCtx.get("settings")),
2862
3649
  readToolDeps
2863
3650
  );
2864
3651
  });
@@ -2866,7 +3653,7 @@ function apply(ctx) {
2866
3653
  void (async () => {
2867
3654
  const service = migrateCtx.get("settings");
2868
3655
  if (service === void 0) return;
2869
- const row = objectOf2(service.get(NAMESPACE));
3656
+ const row = makeReader(service, config)(NAMESPACE);
2870
3657
  if (row === void 0 || row[QUICK_ENABLED_FIELD] !== void 0) return;
2871
3658
  try {
2872
3659
  const outcome = await readQuickBook(quickStorePath());
@@ -2916,8 +3703,8 @@ function apply(ctx) {
2916
3703
  stamp: (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-").slice(0, 19),
2917
3704
  exists: (path) => existsSync2(path),
2918
3705
  resolve: (path) => resolve2(path),
2919
- dirname: (path) => dirname2(path),
2920
- join: (...parts) => join2(...parts),
3706
+ dirname: (path) => dirname3(path),
3707
+ join: (...parts) => join3(...parts),
2921
3708
  spawn: (command, args, options) => spawn(command, [...args], {
2922
3709
  detached: options.detached,
2923
3710
  stdio: options.stdio,
@@ -2948,7 +3735,7 @@ function apply(ctx) {
2948
3735
  return {
2949
3736
  command: [plan.respawn.file, ...plan.respawn.args].join(" "),
2950
3737
  execPath: plan.node,
2951
- logHint: join2(io.tmpdir, `${RESTART_LOG_PREFIX}*.err.log`)
3738
+ logHint: join3(io.tmpdir, `${RESTART_LOG_PREFIX}*.err.log`)
2952
3739
  };
2953
3740
  };
2954
3741
  restartCtx.effect(() => restartCtx.webServer.register({
@@ -3008,6 +3795,7 @@ function apply(ctx) {
3008
3795
  });
3009
3796
  }
3010
3797
  export {
3798
+ Config,
3011
3799
  apply,
3012
3800
  name
3013
3801
  };