@stalfh233/omc-cli 0.0.0-stage → 0.4.10

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (181) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +293 -3
  3. package/coverage/m1-coverage-manifest-v1.json +1278 -0
  4. package/dist/approval-token.js +102 -0
  5. package/dist/args.js +25 -0
  6. package/dist/artifacts.js +119 -0
  7. package/dist/bench/call-face-eval.js +256 -0
  8. package/dist/bench/context-attribution.js +151 -0
  9. package/dist/bench/discovery-cost-eval.js +230 -0
  10. package/dist/bench/driver.js +81 -0
  11. package/dist/bench/evals.js +236 -0
  12. package/dist/bench/fake-http-server.js +65 -0
  13. package/dist/bench/instrument.js +87 -0
  14. package/dist/bench/intent-face-eval.js +343 -0
  15. package/dist/bench/run.js +166 -0
  16. package/dist/bench/scenario.js +343 -0
  17. package/dist/bench/types.js +76 -0
  18. package/dist/bi-wire.js +41 -0
  19. package/dist/bizservice-config.js +581 -0
  20. package/dist/call.js +153 -0
  21. package/dist/capability-absences.js +23 -0
  22. package/dist/capability-overview.js +492 -0
  23. package/dist/capability-shape.js +154 -0
  24. package/dist/cli-contract.js +70 -0
  25. package/dist/cli-output.js +73 -0
  26. package/dist/cli.js +1418 -0
  27. package/dist/code-rules.js +69 -0
  28. package/dist/command-transport.js +155 -0
  29. package/dist/config-store.js +195 -0
  30. package/dist/context.js +21 -0
  31. package/dist/contract-consistency.js +66 -0
  32. package/dist/contract-resources.js +62 -0
  33. package/dist/coverage-consistency.js +62 -0
  34. package/dist/coverage-registry.js +76 -0
  35. package/dist/coverage.js +130 -0
  36. package/dist/data-list-filter.js +114 -0
  37. package/dist/discovery.js +390 -0
  38. package/dist/endpoints.js +154 -0
  39. package/dist/environment-policy.js +26 -0
  40. package/dist/execution-metadata.js +1092 -0
  41. package/dist/fake/app.js +45 -0
  42. package/dist/fake/b2-registration.js +594 -0
  43. package/dist/fake/businessrule.js +242 -0
  44. package/dist/fake/datarule.js +72 -0
  45. package/dist/fake/dictionary.js +108 -0
  46. package/dist/fake/environment.js +30 -0
  47. package/dist/fake/field.js +167 -0
  48. package/dist/fake/form.js +101 -0
  49. package/dist/fake/index.js +121 -0
  50. package/dist/fake/list-view.js +289 -0
  51. package/dist/fake/model.js +136 -0
  52. package/dist/fake/online-js.js +18 -0
  53. package/dist/fake/report.js +253 -0
  54. package/dist/fake/routes.js +47 -0
  55. package/dist/fake/rule-lifecycle.js +37 -0
  56. package/dist/fake/runtime-data.js +367 -0
  57. package/dist/fake/state.js +67 -0
  58. package/dist/fake/workflow.js +364 -0
  59. package/dist/field-change.js +200 -0
  60. package/dist/field-families.js +896 -0
  61. package/dist/form-layout.js +111 -0
  62. package/dist/form-support.js +821 -0
  63. package/dist/goal-routes.js +468 -0
  64. package/dist/governed-execution.js +87 -0
  65. package/dist/human-summary.js +212 -0
  66. package/dist/identity.js +62 -0
  67. package/dist/intent/baseline.js +57 -0
  68. package/dist/intent/capabilities/bizservice.js +274 -0
  69. package/dist/intent/capabilities/businessrule.js +493 -0
  70. package/dist/intent/capabilities/datarule.js +187 -0
  71. package/dist/intent/capabilities/field.js +545 -0
  72. package/dist/intent/capabilities/form.js +136 -0
  73. package/dist/intent/capabilities/index.js +64 -0
  74. package/dist/intent/capabilities/listview.js +157 -0
  75. package/dist/intent/capabilities/model.js +100 -0
  76. package/dist/intent/capabilities/onlinejs.js +108 -0
  77. package/dist/intent/capabilities/report.js +355 -0
  78. package/dist/intent/capabilities/workflow.js +458 -0
  79. package/dist/intent/capability.js +6 -0
  80. package/dist/intent/cli.js +91 -0
  81. package/dist/intent/compare.js +56 -0
  82. package/dist/intent/compiler.js +79 -0
  83. package/dist/intent/dsl.js +129 -0
  84. package/dist/intent/plan-file.js +63 -0
  85. package/dist/intent/readback.js +65 -0
  86. package/dist/intent/schema.js +158 -0
  87. package/dist/intent/validation.js +30 -0
  88. package/dist/intent/yaml.js +315 -0
  89. package/dist/json-column.js +68 -0
  90. package/dist/lanes/app-contract.js +95 -0
  91. package/dist/lanes/app-coverage.js +16 -0
  92. package/dist/lanes/app.js +174 -0
  93. package/dist/lanes/apply-changes.js +231 -0
  94. package/dist/lanes/b2-registration-contract.js +292 -0
  95. package/dist/lanes/b2-registration-coverage.js +48 -0
  96. package/dist/lanes/b2-registration.js +1187 -0
  97. package/dist/lanes/businessrule-contract.js +232 -0
  98. package/dist/lanes/businessrule-coverage.js +16 -0
  99. package/dist/lanes/businessrule.js +221 -0
  100. package/dist/lanes/contract-support.js +65 -0
  101. package/dist/lanes/coverage-declaration.js +9 -0
  102. package/dist/lanes/datarule-contract.js +155 -0
  103. package/dist/lanes/datarule-coverage.js +19 -0
  104. package/dist/lanes/datarule-protocol.js +308 -0
  105. package/dist/lanes/datarule.js +818 -0
  106. package/dist/lanes/dictionary-contract.js +78 -0
  107. package/dist/lanes/dictionary-coverage.js +24 -0
  108. package/dist/lanes/dictionary.js +235 -0
  109. package/dist/lanes/environment-contract.js +70 -0
  110. package/dist/lanes/environment-coverage.js +14 -0
  111. package/dist/lanes/environment.js +163 -0
  112. package/dist/lanes/field-contract.js +223 -0
  113. package/dist/lanes/field-coverage.js +27 -0
  114. package/dist/lanes/field.js +374 -0
  115. package/dist/lanes/form-contract.js +97 -0
  116. package/dist/lanes/form-coverage.js +16 -0
  117. package/dist/lanes/form.js +185 -0
  118. package/dist/lanes/lane-ids.js +34 -0
  119. package/dist/lanes/list-view-contract.js +175 -0
  120. package/dist/lanes/list-view-coverage.js +20 -0
  121. package/dist/lanes/list-view-shapes.js +1207 -0
  122. package/dist/lanes/list-view.js +578 -0
  123. package/dist/lanes/meta-contract.js +85 -0
  124. package/dist/lanes/meta.js +255 -0
  125. package/dist/lanes/model-contract.js +168 -0
  126. package/dist/lanes/model-coverage.js +20 -0
  127. package/dist/lanes/model.js +986 -0
  128. package/dist/lanes/online-js-contract.js +99 -0
  129. package/dist/lanes/online-js-coverage.js +28 -0
  130. package/dist/lanes/online-js.js +127 -0
  131. package/dist/lanes/report-contract.js +144 -0
  132. package/dist/lanes/report-coverage.js +21 -0
  133. package/dist/lanes/report.js +476 -0
  134. package/dist/lanes/rule-graph.js +1846 -0
  135. package/dist/lanes/rule-lifecycle-contract.js +92 -0
  136. package/dist/lanes/rule-lifecycle-coverage.js +20 -0
  137. package/dist/lanes/rule-lifecycle.js +176 -0
  138. package/dist/lanes/runtime-data-contract.js +264 -0
  139. package/dist/lanes/runtime-data-coverage.js +25 -0
  140. package/dist/lanes/runtime-data.js +1054 -0
  141. package/dist/lanes/workflow-contract.js +223 -0
  142. package/dist/lanes/workflow-coverage.js +40 -0
  143. package/dist/lanes/workflow.js +1813 -0
  144. package/dist/online-js-layout.js +58 -0
  145. package/dist/online-js-source.js +276 -0
  146. package/dist/package-tool.js +51 -0
  147. package/dist/package.js +73 -0
  148. package/dist/plan.js +73 -0
  149. package/dist/read.js +144 -0
  150. package/dist/redact.js +28 -0
  151. package/dist/rule-support.js +587 -0
  152. package/dist/runtime-support.js +134 -0
  153. package/dist/server.js +92 -0
  154. package/dist/session-manager.js +30 -0
  155. package/dist/session.js +149 -0
  156. package/dist/skills.js +112 -0
  157. package/dist/support.js +98 -0
  158. package/dist/tool-types.js +127 -0
  159. package/dist/tools.js +59 -0
  160. package/dist/usage-log.js +197 -0
  161. package/dist/wire.js +287 -0
  162. package/dist/workflow-support.js +99 -0
  163. package/dist/write-lease.js +26 -0
  164. package/dist/write-lock.js +109 -0
  165. package/dist/write.js +176 -0
  166. package/dist/zip.js +156 -0
  167. package/docs/tool-surface-map.md +42 -0
  168. package/package.json +71 -6
  169. package/skills/omc-acceptance-criteria.md +47 -0
  170. package/skills/omc-business-configuration.md +257 -0
  171. package/skills/omc-capabilities.md +197 -0
  172. package/skills/omc-capability-scouting.md +55 -0
  173. package/skills/omc-config-draft-review.md +171 -0
  174. package/skills/omc-five-piece-flow.md +35 -0
  175. package/skills/omc-glossary.md +86 -0
  176. package/skills/omc-refusals.md +110 -0
  177. package/skills/omc-requirement-analysis.md +161 -0
  178. package/skills/omc-requirement-vocabulary.md +51 -0
  179. package/skills/omc-start-here.md +82 -0
  180. package/skills/omc-tool-selection.md +119 -0
  181. package/skills/omc-write-hazards.md +87 -0
@@ -0,0 +1,58 @@
1
+ const PLATFORM_STYLE_ELEMENT = Object.freeze({ tagName: "style", innerHTML: "\n", id: null, attrs: [] });
2
+ const PLATFORM_TOOLBAR_SECTION = Object.freeze({ tagName: "section", id: "toolbar", attrs: [{ name: "id", value: "toolbar" }] });
3
+ const PLATFORM_TEMPLATE_SECTION = Object.freeze({
4
+ tagName: "section",
5
+ id: "template",
6
+ attrs: [
7
+ { name: "type", value: "template" },
8
+ { name: "id", value: "template" },
9
+ ],
10
+ });
11
+ function platformScriptElement(id, innerHTML) {
12
+ return { tagName: "script", id, innerHTML, attrs: [] };
13
+ }
14
+ export function composeLayoutElements(existingElements, source) {
15
+ const styleIndex = existingElements.findIndex((element) => element.tagName === "style");
16
+ const styleElement = styleIndex >= 0 ? existingElements[styleIndex] : { ...PLATFORM_STYLE_ELEMENT, attrs: [] };
17
+ const toolbarElement = existingElements.find((element) => element.id === "toolbar") ?? { ...PLATFORM_TOOLBAR_SECTION, attrs: [...PLATFORM_TOOLBAR_SECTION.attrs] };
18
+ const templateElement = existingElements.find((element) => element.id === "template") ?? { ...PLATFORM_TEMPLATE_SECTION, attrs: [...PLATFORM_TEMPLATE_SECTION.attrs] };
19
+ const others = existingElements.filter((element, index) => index !== styleIndex &&
20
+ element.id !== "customScript" &&
21
+ element.id !== "beforeCustomScript" &&
22
+ element.id !== "afterCustomScript" &&
23
+ element.id !== "toolbar" &&
24
+ element.id !== "template");
25
+ const beforeElement = existingElements.find((element) => element.id === "beforeCustomScript") ?? platformScriptElement("beforeCustomScript", "");
26
+ const afterElement = existingElements.find((element) => element.id === "afterCustomScript") ?? platformScriptElement("afterCustomScript", "");
27
+ const customScriptElement = platformScriptElement("customScript", source);
28
+ return [styleElement, toolbarElement, templateElement, ...others, beforeElement, customScriptElement, afterElement];
29
+ }
30
+ export function assertPlatformOnlineJsLayout() {
31
+ const source = "(function(form){ form.on('onLoad', function(){}); })";
32
+ const empty = composeLayoutElements([], source);
33
+ const ids = empty.map((element) => element.id);
34
+ if (empty.length !== 6)
35
+ throw new Error("empty layout must produce six platform elements");
36
+ if (JSON.stringify(ids) !== JSON.stringify([null, "toolbar", "template", "beforeCustomScript", "customScript", "afterCustomScript"]))
37
+ throw new Error("empty layout has the wrong platform element order");
38
+ if (!empty.every((element) => Array.isArray(element.attrs)))
39
+ throw new Error("platform layout elements must have array attrs");
40
+ const customScript = empty.find((element) => element.id === "customScript");
41
+ if (customScript?.tagName !== "script" || customScript.innerHTML !== source)
42
+ throw new Error("customScript payload has the wrong canonical shape");
43
+ const style = { tagName: "style", innerHTML: ".x{}", id: null, attrs: [{ name: "data-style", value: "1" }] };
44
+ const toolbar = { tagName: "section", id: "toolbar", attrs: [{ name: "id", value: "toolbar" }, { name: "data-toolbar", value: "1" }] };
45
+ const template = { tagName: "section", id: "template", attrs: [{ name: "type", value: "template" }, { name: "id", value: "template" }] };
46
+ const before = { tagName: "script", id: "beforeCustomScript", innerHTML: "before", attrs: [] };
47
+ const after = { tagName: "script", id: "afterCustomScript", innerHTML: "after", attrs: [] };
48
+ const control = { tagName: "div", id: "control", attrs: [], type: 1 };
49
+ const existing = [style, toolbar, template, control, before, { tagName: "script", id: "customScript", innerHTML: "old", attrs: [] }, after];
50
+ const updated = composeLayoutElements(existing, source);
51
+ if (JSON.stringify(updated[0]) !== JSON.stringify(style) || JSON.stringify(updated[1]) !== JSON.stringify(toolbar) || JSON.stringify(updated[2]) !== JSON.stringify(template))
52
+ throw new Error("existing platform sections were not preserved");
53
+ if (JSON.stringify(updated.find((element) => element.id === "control")) !== JSON.stringify(control))
54
+ throw new Error("existing control was not preserved");
55
+ if (updated.find((element) => element.id === "customScript")?.innerHTML !== source)
56
+ throw new Error("customScript was not replaced in the payload");
57
+ }
58
+ //# sourceMappingURL=online-js-layout.js.map
@@ -0,0 +1,276 @@
1
+ import { ToolRefusalError } from "./tool-types.js";
2
+ /**
3
+ * The platform's default Online-JS scaffold — the exact body the CloudPivot
4
+ * form designer shows in the Online Editor (cpdocs
5
+ * `form-online-js/references/default-lifecycle-8.5.x.js`, matched against the
6
+ * live editor 2026-09-17). The runtime invokes this as a function and supplies
7
+ * `form`; that is why every registration must live INSIDE the wrapper.
8
+ *
9
+ * History: `onlinejs.compose` used to write `source` verbatim as the whole
10
+ * customScript. A caller who supplied a bare `form.on('onRendered', ...)`
11
+ * (or, worse, `export function …`) produced a script with no wrapper and no
12
+ * `form` in scope — the form page then rendered BLANK. The default form is
13
+ * validly EMPTY (live: a freshly published form stores customScript == ""), so
14
+ * the fix is to seed this scaffold and merge the caller's code into it, never
15
+ * to replace it.
16
+ */
17
+ export const PLATFORM_LIFECYCLE_WRAPPER_MARKER = "(function(form)";
18
+ export const PLATFORM_DEFAULT_LIFECYCLE_SCRIPT = `/**
19
+ * 用户自定义JS区域;
20
+ * 注:外部链接的JS统一放置在HTML中,原有script标签下的js统一放置在当前编码区域
21
+ * 使用环境变量,可通过"$env("唤醒提示框,并通过$env(分类名称.变量名称)的方式使用
22
+ */
23
+ (function(form){
24
+ /**
25
+ * 事件绑定,form.on
26
+ * @param eventType 事件类型
27
+ * @params function 事件
28
+ * @param cover true, false 是否覆盖root的生命周期 默认不覆盖
29
+ */
30
+ //数据加载后,渲染之前,this为window
31
+ form.on('onLoad',function(data, dataPermission){},'cover');
32
+ //渲染完成后
33
+ form.on('onRendered',function(data){});
34
+ //内置校验完成后,返回false阻止提交
35
+ form.on('onValidate',function(action,data){});
36
+ //操作前(包含自定义按钮)执行,返回false阻止按钮操作
37
+ form.on('onPreAction',function(action,data){});
38
+ //自定义按钮执行
39
+ form.on('onCustomAction',function(action,data){});
40
+ //操作完成后执行
41
+ form.on('onActionDone',function(action,data,httpRes){});
42
+ //表单字段打印数据格式扩展,可自行定义调用打印模板时,表单字段对应所打印数据的内容
43
+ form.on('onPrintFormDataHandle',function(vm, printData, sourceData, formValues, key, locale, printTemplateCode, control, dataType, sheetData){});
44
+ //流程日志打印数据格式扩展,可自行定义调用打印模板时,流程日志对应所打印数据的内容
45
+ form.on('onPrintWorkflowLogDataHandle',function(vm, sourceData, printData, locale, printTemplateCode){});
46
+ })`;
47
+ /** The lifecycle events the platform dispatches; anything else is a typo the runtime silently drops (or throws on). */
48
+ export const ONLINE_JS_EVENTS = new Set([
49
+ "onLoad",
50
+ "onRendered",
51
+ "onValidate",
52
+ "onPreAction",
53
+ "onCustomAction",
54
+ "onActionDone",
55
+ "onPrintFormDataHandle",
56
+ "onPrintWorkflowLogDataHandle",
57
+ ]);
58
+ const WRAPPER_OPEN = /\(\s*function\s*\(\s*form\s*\)/;
59
+ const WRAPPER_CLOSE = "})";
60
+ const EVENT_AT = /form\.on\(\s*['"]([^'"]+)['"]/g;
61
+ /** True when the script already carries the platform's `(function(form){ … })` scaffold. */
62
+ export function hasLifecycleWrapper(script) {
63
+ return WRAPPER_OPEN.test(script) && /\}\s*\)\s*;?\s*$/.test(script);
64
+ }
65
+ /**
66
+ * Refuse a source that cannot run in the form's script slot BEFORE any wire
67
+ * call — the exact class of mistake that blanked the form:
68
+ * - `export` / `import`: the slot is not an ES module, so a top-level
69
+ * `export function` is a SyntaxError that kills the whole form render;
70
+ * - an unknown `form.on('<event>', …)`: the event never fires (or throws).
71
+ */
72
+ /** The platform's lifecycle names; a `form.<name>(…)` CALL (not `form.on('<name>')`) is a fatal typo. */
73
+ const LIFECYCLE_CALL = /\bform\.(onLoad|onRendered|onValidate|onPreAction|onCustomAction|onActionDone|onPrintFormDataHandle|onPrintWorkflowLogDataHandle)\s*\(/;
74
+ /** Strip `//` line comments and `/* … *\/` blocks so a scan does not trip on prose. */
75
+ function stripComments(source) {
76
+ return source.replace(/\/\*[\s\S]*?\*\//g, "").replace(/\/\/[^\n]*/g, "");
77
+ }
78
+ export function lintOnlineJsSource(source) {
79
+ const esm = source.match(/^\s*(export|import)\b/m);
80
+ if (esm !== null)
81
+ throw new ToolRefusalError({
82
+ status: "refused",
83
+ blocker: "onlinejs-source-invalid",
84
+ message: `在线 JS 脚本槽不是 ES 模块,顶层 \`${esm[1]}\` 会让整个表单渲染失败(SyntaxError)。请改为平台生命周期形式:form.on('onLoad', function(data, dataPermission){…});脚本必须位于 (function(form){ … }) 模板内`,
85
+ });
86
+ for (const match of source.matchAll(EVENT_AT)) {
87
+ const event = match[1];
88
+ if (!ONLINE_JS_EVENTS.has(event))
89
+ throw new ToolRefusalError({
90
+ status: "refused",
91
+ blocker: "onlinejs-source-invalid",
92
+ message: `未知的在线 JS 事件 form.on('${event}')。平台事件:${[...ONLINE_JS_EVENTS].join(" / ")}。常见误用:beforeSubmit → 应为 onValidate;submit → onPreAction/onValidate`,
93
+ });
94
+ }
95
+ // Live 2026-09-24 (bench36): `form.onLoad(fn)` is NOT an API — the platform
96
+ // exposes `form.on('<name>', fn[, cover])` for registration and calls a
97
+ // top-level `function <name>(form){}` for the lifecycle. Calling
98
+ // `form.<name>(…)` throws inside the custom-script IIFE, so the ENTIRE portal
99
+ // form fails to load and renders as /shared/empty. Refuse it before any wire.
100
+ const lifecycleCall = stripComments(source).match(LIFECYCLE_CALL);
101
+ if (lifecycleCall !== null)
102
+ throw new ToolRefusalError({
103
+ status: "refused",
104
+ blocker: "onlinejs-source-invalid",
105
+ message: `form.${lifecycleCall[1]}(...) 不是平台 API,调用会在自定义脚本里抛错、让整个表单渲染成空白页(复现 bench36)。注册生命周期用 form.on('${lifecycleCall[1]}', function(…){}[, 'cover']),或写顶层 function ${lifecycleCall[1]}(form){ … }`,
106
+ });
107
+ }
108
+ /**
109
+ * Index just past the `)` matching the `(` at `openIndex`, skipping string
110
+ * literals (single/double/backtick) and line comments so a `)` inside a string
111
+ * or a `//` does not unbalance the scan.
112
+ */
113
+ function matchingParenEnd(text, openIndex) {
114
+ let depth = 0;
115
+ let quote = null;
116
+ for (let i = openIndex; i < text.length; i += 1) {
117
+ const ch = text[i];
118
+ if (quote !== null) {
119
+ if (ch === "\\") {
120
+ i += 1;
121
+ continue;
122
+ }
123
+ if (ch === quote)
124
+ quote = null;
125
+ continue;
126
+ }
127
+ if (ch === "'" || ch === '"' || ch === "`") {
128
+ quote = ch;
129
+ continue;
130
+ }
131
+ if (ch === "/" && text[i + 1] === "/") {
132
+ const newline = text.indexOf("\n", i);
133
+ if (newline < 0)
134
+ return text.length;
135
+ i = newline;
136
+ continue;
137
+ }
138
+ if (ch === "(")
139
+ depth += 1;
140
+ else if (ch === ")") {
141
+ depth -= 1;
142
+ if (depth === 0)
143
+ return i + 1;
144
+ }
145
+ }
146
+ return text.length;
147
+ }
148
+ /** Split source into top-level `form.on(...)` registrations plus any residual raw code. */
149
+ function splitRegistrations(source) {
150
+ const blocks = [];
151
+ let cursor = 0;
152
+ let searchFrom = 0;
153
+ const pattern = /form\.on\(/g;
154
+ for (;;) {
155
+ pattern.lastIndex = searchFrom;
156
+ const match = pattern.exec(source);
157
+ if (match === null)
158
+ break;
159
+ const openParen = match.index + "form.on".length;
160
+ const end = matchingParenEnd(source, openParen);
161
+ let stop = end;
162
+ while (stop < source.length && /[;\s]/.test(source[stop]))
163
+ stop += 1;
164
+ const lead = source.slice(cursor, match.index);
165
+ if (lead.trim() !== "")
166
+ blocks.push({ event: null, text: lead.trim() });
167
+ const eventMatch = /^form\.on\(\s*['"]([^'"]+)['"]/.exec(source.slice(match.index, end));
168
+ blocks.push({ event: eventMatch?.[1] ?? null, text: source.slice(match.index, stop).trim() });
169
+ cursor = stop;
170
+ searchFrom = stop;
171
+ }
172
+ const tail = source.slice(cursor);
173
+ if (tail.trim() !== "")
174
+ blocks.push({ event: null, text: tail.trim() });
175
+ return blocks;
176
+ }
177
+ /** Remove every top-level `form.on('<event>', …)` registration from `script`. */
178
+ function removeRegistration(script, event) {
179
+ let result = script;
180
+ for (;;) {
181
+ const match = new RegExp(`form\\.on\\(\\s*['"]${event.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}['"]`).exec(result);
182
+ if (match === null)
183
+ return result;
184
+ const openParen = match.index + "form.on".length;
185
+ const end = matchingParenEnd(result, openParen);
186
+ let stop = end;
187
+ while (stop < result.length && /[;\s]/.test(result[stop]))
188
+ stop += 1;
189
+ result = result.slice(0, match.index) + result.slice(stop);
190
+ }
191
+ }
192
+ /** Insert `body` at the end of the `(function(form){ … })` body, before its closing `})`. */
193
+ function insertIntoLifecycleBody(script, body) {
194
+ const marker = script.lastIndexOf(WRAPPER_CLOSE);
195
+ return `${script.slice(0, marker).trimEnd()}\n\n${body}\n${script.slice(marker)}`;
196
+ }
197
+ /** Seed the platform scaffold and place `body` inside it. */
198
+ function wrapInLifecycle(body) {
199
+ return insertIntoLifecycleBody(PLATFORM_DEFAULT_LIFECYCLE_SCRIPT, body);
200
+ }
201
+ /**
202
+ * Build the customScript the platform stores.
203
+ *
204
+ * mode "merge" (default) is the INCREMENTAL write:
205
+ * - a `.on('<event>', …)` registration in `source` SUPERSEDES the script's
206
+ * existing registration for that same event (including the platform's empty
207
+ * default stub), so re-running updates that event instead of stacking a
208
+ * duplicate — this is what a human does when they fill in the editor's stub;
209
+ * - anything else in `source` is appended inside the lifecycle body;
210
+ * - an empty existing script (the platform default) is seeded with the
211
+ * platform scaffold first, and a wrapper-less existing script is wrapped.
212
+ *
213
+ * mode "replace" is the explicit escape hatch: `source` IS the script.
214
+ */
215
+ export function composeCustomScript(existing, incoming, mode) {
216
+ lintOnlineJsSource(incoming);
217
+ if (mode === "replace") {
218
+ if (!hasLifecycleWrapper(incoming))
219
+ throw new ToolRefusalError({
220
+ status: "refused",
221
+ blocker: "onlinejs-source-invalid",
222
+ message: "replace 模式必须传入完整的 (function(form){…}) 脚本,不能以裸代码覆盖平台生命周期模板",
223
+ });
224
+ return incoming;
225
+ }
226
+ const trimmedExisting = existing.trim();
227
+ // A caller-supplied complete wrapper cannot be merged into an existing script
228
+ // without nesting wrappers and silently dropping one side — require the
229
+ // explicit replace intent instead of guessing.
230
+ if (hasLifecycleWrapper(incoming))
231
+ throw new ToolRefusalError({
232
+ status: "refused",
233
+ blocker: "onlinejs-source-invalid",
234
+ message: "merge 模式下传入的是完整的 (function(form){…}) 脚本——无法与既有脚本增量合并且不丢内容。若确要以它整体覆盖,请传 replace:true;若只想追加逻辑,请只传要加入生命周期内的代码片段",
235
+ });
236
+ if (trimmedExisting === "") {
237
+ let base = PLATFORM_DEFAULT_LIFECYCLE_SCRIPT;
238
+ for (const block of splitRegistrations(incoming)) {
239
+ if (block.event !== null)
240
+ base = removeRegistration(base, block.event);
241
+ base = insertIntoLifecycleBody(base, block.text);
242
+ }
243
+ return base;
244
+ }
245
+ // Merge into a lifecycle body: seed one when the existing script has none.
246
+ let base = hasLifecycleWrapper(trimmedExisting) ? existing : wrapInLifecycle(existing);
247
+ for (const block of splitRegistrations(incoming)) {
248
+ if (block.event !== null)
249
+ base = removeRegistration(base, block.event);
250
+ base = insertIntoLifecycleBody(base, block.text);
251
+ }
252
+ return base;
253
+ }
254
+ export function assertPlatformOnlineJsComposition() {
255
+ const source = "form.on('onLoad', function(){});";
256
+ const composed = composeCustomScript("", source, "merge");
257
+ if (!hasLifecycleWrapper(composed))
258
+ throw new Error("composition must retain the platform lifecycle wrapper");
259
+ if (!composed.includes(source))
260
+ throw new Error("composition must retain the caller registration");
261
+ const updated = composeCustomScript(composed, "form.on('onLoad', function(){ updated(); });", "merge");
262
+ if (updated.includes(source))
263
+ throw new Error("incremental composition must supersede the existing event registration");
264
+ if (!updated.includes("form.on('onLoad', function(){ updated(); });"))
265
+ throw new Error("incremental composition must retain the replacement registration");
266
+ let refused = false;
267
+ try {
268
+ composeCustomScript("", source, "replace");
269
+ }
270
+ catch {
271
+ refused = true;
272
+ }
273
+ if (!refused)
274
+ throw new Error("replace composition must refuse a wrapper-less script");
275
+ }
276
+ //# sourceMappingURL=online-js-source.js.map
@@ -0,0 +1,51 @@
1
+ import { z } from "zod";
2
+ import { capabilityArg, confirmProductionArg, environmentArg, WRITE_PROTOCOL } from "./args.js";
3
+ import { executePackage } from "./package.js";
4
+ import { plannableArgs } from "./plan.js";
5
+ import { writeSummary } from "./human-summary.js";
6
+ import { defineTool } from "./tool-types.js";
7
+ /**
8
+ * Shared package construction for high-level write tools (`configure_model`,
9
+ * `configure_form`, `configure_rule`, and the batch entry `apply_changes`).
10
+ * One call site builds the plan (every action runs through `plannableArgs` so
11
+ * the whole package participates in the fingerprint), then delegates the
12
+ * coverage gate, write permission gate, production confirmation, write lease,
13
+ * and artifact recording to `executePackage`.
14
+ *
15
+ * A preset supplies: its identity (name/lane/scope/description), its input
16
+ * schema (environment/confirmProduction/capability appended in canonical order),
17
+ * a `plan` derived from the real arguments, a sequential `executor` reusing
18
+ * the proven low-level write paths, and a `summarize` hook so the plan card
19
+ * (即将…) and the result (已…) come from ONE human-summary source.
20
+ */
21
+ export function registerPackageTool(tools, spec) {
22
+ tools.push(defineTool({
23
+ name: spec.name,
24
+ layer: "high",
25
+ kind: "write",
26
+ lane: spec.lane,
27
+ scope: spec.scope,
28
+ description: `${spec.description} ${WRITE_PROTOCOL}`,
29
+ inputSchema: { environment: environmentArg, ...spec.inputSchema, confirmProduction: confirmProductionArg, dryRun: z.boolean().optional(), capability: capabilityArg },
30
+ handler: async (ctx, args, self) => {
31
+ spec.preflight?.(args);
32
+ const planned = spec.plan(args);
33
+ const plan = {
34
+ environment: "",
35
+ environmentFingerprint: "",
36
+ summary: planned.summary,
37
+ actions: planned.actions.map((action) => ({ tool: action.tool, args: plannableArgs(action.tool, action.args) })),
38
+ };
39
+ return executePackage(ctx, plan, { lane: spec.lane, scope: spec.scope }, args, async (session) => {
40
+ const outcome = await spec.execute(session, args);
41
+ const summary = spec.summarize ? spec.summarize(args, outcome, "done") : undefined;
42
+ return summary === undefined ? outcome : { ...outcome, summary };
43
+ });
44
+ },
45
+ }));
46
+ }
47
+ /** Convenience: a preset's human summary from the shared source (single import site). */
48
+ export function packageSummary(tool, args, phase) {
49
+ return writeSummary(tool, args, undefined, phase);
50
+ }
51
+ //# sourceMappingURL=package-tool.js.map
@@ -0,0 +1,73 @@
1
+ import { randomUUID } from "node:crypto";
2
+ import { planFingerprint } from "./plan.js";
3
+ import { admitGovernedWrite } from "./governed-execution.js";
4
+ import { sha256, writeArtifact } from "./support.js";
5
+ export function isPackageStopOutcome(outcome) {
6
+ return (typeof outcome === "object" &&
7
+ outcome !== null &&
8
+ outcome.status !== undefined &&
9
+ Array.isArray(outcome.steps) &&
10
+ typeof outcome.failedStep === "object");
11
+ }
12
+ /** Shared high-level execution: admission gate + lease + artifacts + delivery state. */
13
+ export async function executePackage(ctx, plan, coverage, args, execute) {
14
+ const claims = "perAction" in coverage
15
+ ? coverage.perAction.map((claim) => ({ lane: claim.lane, scope: claim.scope }))
16
+ : [{ lane: args.capability ?? coverage.lane, scope: coverage.scope }];
17
+ const outcome = await admitGovernedWrite(ctx, {
18
+ environment: args.environment,
19
+ confirmProduction: args.confirmProduction,
20
+ dryRun: args.dryRun,
21
+ capability: args.capability,
22
+ coverage: claims,
23
+ plan: () => plan,
24
+ // A package dry-run is a TRUE preview: the environment is resolved and the
25
+ // exact plan is rendered, but nothing executes.
26
+ dryRunResult: (boundPlan) => ({
27
+ status: "dry-run",
28
+ wouldChange: true,
29
+ plan: boundPlan,
30
+ planFingerprint: planFingerprint(boundPlan),
31
+ ...(typeof args.requestId === "string" ? { requestId: args.requestId } : {}),
32
+ }),
33
+ });
34
+ if (outcome.kind === "settled")
35
+ return outcome.result;
36
+ const { session, plan: boundPlan, productionConfirmed } = outcome.admission;
37
+ return ctx.lease.run(session.environmentName, `${plan.actions[0]?.tool ?? "package"}:${randomUUID().slice(0, 8)}`, async () => {
38
+ const executed = await execute(session);
39
+ if (isPackageStopOutcome(executed)) {
40
+ // A stopped batch is never DELIVERED: report the completed steps, the
41
+ // failed step, and its envelope; the artifact records the stop for audit.
42
+ const artifact = {
43
+ plan: boundPlan,
44
+ steps: executed.steps,
45
+ failedStep: executed.failedStep,
46
+ stop: executed.envelope,
47
+ ...(productionConfirmed ? { productionConfirmed: true } : {}),
48
+ deliveryState: "STOPPED",
49
+ recordedAt: new Date().toISOString(),
50
+ };
51
+ const artifactRef = writeArtifact(ctx, `package-${randomUUID().slice(0, 8)}.json`, artifact);
52
+ return {
53
+ status: executed.status,
54
+ steps: executed.steps,
55
+ failedStep: executed.failedStep,
56
+ ...executed.envelope,
57
+ artifactFingerprint: artifactRef.detailSha256,
58
+ };
59
+ }
60
+ const { steps, readback, summary } = executed;
61
+ const artifact = {
62
+ plan: boundPlan,
63
+ steps,
64
+ readback,
65
+ ...(productionConfirmed ? { productionConfirmed: true } : {}),
66
+ deliveryState: "DELIVERED",
67
+ recordedAt: new Date().toISOString(),
68
+ };
69
+ writeArtifact(ctx, `package-${randomUUID().slice(0, 8)}.json`, artifact);
70
+ return { status: "ok", deliveryState: "DELIVERED", steps, readback, ...(productionConfirmed ? { productionConfirmed: true } : {}), ...(summary !== undefined ? { summary } : {}), artifactFingerprint: sha256(artifact) };
71
+ });
72
+ }
73
+ //# sourceMappingURL=package.js.map
package/dist/plan.js ADDED
@@ -0,0 +1,73 @@
1
+ import { createHash } from "node:crypto";
2
+ import { writeSummary } from "./human-summary.js";
3
+ import { sha256, summarizeValue } from "./support.js";
4
+ import { environmentNotice } from "./environment-policy.js";
5
+ function canonical(value) {
6
+ return JSON.stringify(sort(value));
7
+ }
8
+ function sort(value) {
9
+ if (Array.isArray(value))
10
+ return value.map(sort);
11
+ if (value && typeof value === "object")
12
+ return Object.fromEntries(Object.entries(value)
13
+ .sort(([a], [b]) => a.localeCompare(b))
14
+ .map(([k, v]) => [k, sort(v)]));
15
+ return value;
16
+ }
17
+ /** A stable fingerprint of the plan, recorded in audit artifacts so a run can be identified. */
18
+ export function planFingerprint(plan) {
19
+ return createHash("sha256")
20
+ .update(canonical({
21
+ environment: plan.environment,
22
+ environmentFingerprint: plan.environmentFingerprint,
23
+ actions: plan.actions.map((action) => ({ tool: action.tool, args: sort(action.args) })),
24
+ }))
25
+ .digest("hex");
26
+ }
27
+ function summarizedArgs(tool, args) {
28
+ if (typeof args.summary === "string" && typeof args.fingerprint === "string")
29
+ return { ...args };
30
+ const summary = { summary: tool };
31
+ for (const [key, value] of Object.entries(args)) {
32
+ if (key === "environment" || key === "full")
33
+ continue;
34
+ if (["schemaCode", "sheetCode", "applicationCode", "modelCode", "fieldCode", "code", "id", "ruleCode", "workflowCode", "workflowInstanceId", "workItemId", "bizObjectId", "name"].includes(key)) {
35
+ summary[key] = summarizeValue(value);
36
+ }
37
+ else if (Array.isArray(value) || (value && typeof value === "object") || typeof value === "string") {
38
+ summary[`${key}Summary`] = summarizeValue(value);
39
+ }
40
+ else {
41
+ summary[key] = value;
42
+ }
43
+ }
44
+ summary.fingerprint = sha256(summary);
45
+ return summary;
46
+ }
47
+ /** The raw plan-relevant args: everything except secrets/environment/full. */
48
+ export function plannableArgsRaw(args) {
49
+ const clone = {};
50
+ for (const [key, value] of Object.entries(args)) {
51
+ if (key === "environment" || key === "full")
52
+ continue;
53
+ clone[key] = value;
54
+ }
55
+ return clone;
56
+ }
57
+ export function plannableArgs(tool, args) {
58
+ return summarizedArgs(tool, args);
59
+ }
60
+ export async function planFor(ctx, environment, tool, args) {
61
+ const session = await ctx.sessions.session(environment);
62
+ // The preview reads as a Chinese sentence produced by the SAME source as the
63
+ // result summary (即将… / 已…), from the real arguments.
64
+ const summary = writeSummary(tool, args, undefined, "plan");
65
+ return {
66
+ environment: session.environmentName,
67
+ environmentFingerprint: session.environmentFingerprint(),
68
+ summary,
69
+ environmentNotice: environmentNotice(ctx.sessions.config().resolveEnvironment(environment).profile),
70
+ actions: [{ tool, args: summarizedArgs(tool, args) }],
71
+ };
72
+ }
73
+ //# sourceMappingURL=plan.js.map