@stalfh233/omc-cli 0.5.0 → 0.5.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.
Files changed (226) hide show
  1. package/LICENSE +21 -21
  2. package/README.md +359 -359
  3. package/coverage/m1-coverage-manifest-v1.json +1414 -1414
  4. package/dist/approval-flow.js +0 -0
  5. package/dist/approval-token.js +102 -0
  6. package/dist/args.js +0 -0
  7. package/dist/artifacts.js +0 -0
  8. package/dist/batch-call.js +0 -0
  9. package/dist/bench/call-face-eval.js +0 -0
  10. package/dist/bench/context-attribution.js +0 -0
  11. package/dist/bench/discovery-cost-eval.js +0 -0
  12. package/dist/bench/driver.js +0 -0
  13. package/dist/bench/evals.js +0 -0
  14. package/dist/bench/fake-http-server.js +65 -0
  15. package/dist/bench/instrument.js +0 -0
  16. package/dist/bench/intent-face-eval.js +40 -40
  17. package/dist/bench/run.js +0 -0
  18. package/dist/bench/scenario.js +0 -0
  19. package/dist/bench/types.js +0 -0
  20. package/dist/bi-wire.js +0 -0
  21. package/dist/bizservice-config.js +0 -0
  22. package/dist/call.js +0 -0
  23. package/dist/capability-absences.js +0 -0
  24. package/dist/capability-overview.js +0 -0
  25. package/dist/capability-shape.js +0 -0
  26. package/dist/cli-contract.js +38 -1
  27. package/dist/cli-output.js +8 -2
  28. package/dist/cli.js +9 -1
  29. package/dist/closest-match.js +0 -0
  30. package/dist/code-rules.js +0 -0
  31. package/dist/command-transport.js +0 -0
  32. package/dist/compound-writes.js +0 -0
  33. package/dist/compute.js +0 -0
  34. package/dist/config-store.js +0 -0
  35. package/dist/context.js +0 -0
  36. package/dist/contract-consistency.js +0 -0
  37. package/dist/contract-resources.js +0 -0
  38. package/dist/coverage-consistency.js +0 -0
  39. package/dist/coverage-registry.js +0 -0
  40. package/dist/coverage.js +0 -0
  41. package/dist/data-list-filter.js +0 -0
  42. package/dist/describe-cache.js +0 -0
  43. package/dist/discovery.js +0 -0
  44. package/dist/endpoints.js +0 -0
  45. package/dist/environment-policy.js +0 -0
  46. package/dist/execution-metadata.js +0 -0
  47. package/dist/fake/app.js +45 -0
  48. package/dist/fake/b2-registration.js +594 -0
  49. package/dist/fake/businessrule.js +242 -0
  50. package/dist/fake/datarule.js +72 -0
  51. package/dist/fake/dictionary.js +108 -0
  52. package/dist/fake/environment.js +30 -0
  53. package/dist/fake/field.js +167 -0
  54. package/dist/fake/form.js +101 -0
  55. package/dist/fake/index.js +121 -0
  56. package/dist/fake/list-view.js +289 -0
  57. package/dist/fake/model.js +136 -0
  58. package/dist/fake/online-js.js +18 -0
  59. package/dist/fake/report.js +253 -0
  60. package/dist/fake/routes.js +47 -0
  61. package/dist/fake/rule-lifecycle.js +37 -0
  62. package/dist/fake/runtime-data.js +367 -0
  63. package/dist/fake/state.js +67 -0
  64. package/dist/fake/workflow.js +364 -0
  65. package/dist/field-change.js +75 -7
  66. package/dist/field-families.js +59 -9
  67. package/dist/form-default.js +0 -0
  68. package/dist/form-layout.js +0 -0
  69. package/dist/form-support.js +0 -0
  70. package/dist/formula-lint.js +0 -0
  71. package/dist/goal-routes.js +16 -16
  72. package/dist/governed-execution.js +0 -0
  73. package/dist/graph.js +29 -29
  74. package/dist/human-summary.js +0 -0
  75. package/dist/identity.js +0 -0
  76. package/dist/intent/baseline.js +0 -0
  77. package/dist/intent/capabilities/bizservice.js +0 -0
  78. package/dist/intent/capabilities/businessrule.js +0 -0
  79. package/dist/intent/capabilities/datarule.js +0 -0
  80. package/dist/intent/capabilities/field.js +0 -0
  81. package/dist/intent/capabilities/form.js +0 -0
  82. package/dist/intent/capabilities/index.js +0 -0
  83. package/dist/intent/capabilities/listview.js +0 -0
  84. package/dist/intent/capabilities/model.js +0 -0
  85. package/dist/intent/capabilities/onlinejs.js +0 -0
  86. package/dist/intent/capabilities/report.js +0 -0
  87. package/dist/intent/capabilities/workflow.js +0 -0
  88. package/dist/intent/capability.js +0 -0
  89. package/dist/intent/cli.js +0 -0
  90. package/dist/intent/compare.js +0 -0
  91. package/dist/intent/compiler.js +0 -0
  92. package/dist/intent/dsl.js +0 -0
  93. package/dist/intent/plan-apply.js +0 -0
  94. package/dist/intent/plan-file.js +0 -0
  95. package/dist/intent/readback.js +0 -0
  96. package/dist/intent/schema.js +0 -0
  97. package/dist/intent/validation.js +0 -0
  98. package/dist/intent/yaml.js +0 -0
  99. package/dist/journal.js +0 -0
  100. package/dist/json-column.js +0 -0
  101. package/dist/lanes/app-contract.js +0 -0
  102. package/dist/lanes/app-coverage.js +0 -0
  103. package/dist/lanes/app.js +0 -0
  104. package/dist/lanes/apply-changes.js +0 -0
  105. package/dist/lanes/b2-registration-contract.js +0 -0
  106. package/dist/lanes/b2-registration-coverage.js +0 -0
  107. package/dist/lanes/b2-registration.js +0 -0
  108. package/dist/lanes/businessrule-contract.js +0 -0
  109. package/dist/lanes/businessrule-coverage.js +0 -0
  110. package/dist/lanes/businessrule.js +0 -0
  111. package/dist/lanes/contract-support.js +0 -0
  112. package/dist/lanes/coverage-declaration.js +0 -0
  113. package/dist/lanes/custom-page.js +0 -0
  114. package/dist/lanes/datarule-contract.js +0 -0
  115. package/dist/lanes/datarule-coverage.js +0 -0
  116. package/dist/lanes/datarule-protocol.js +0 -0
  117. package/dist/lanes/datarule.js +0 -0
  118. package/dist/lanes/dictionary-contract.js +0 -0
  119. package/dist/lanes/dictionary-coverage.js +0 -0
  120. package/dist/lanes/dictionary.js +0 -0
  121. package/dist/lanes/environment-contract.js +0 -0
  122. package/dist/lanes/environment-coverage.js +0 -0
  123. package/dist/lanes/environment.js +0 -0
  124. package/dist/lanes/field-contract.js +0 -0
  125. package/dist/lanes/field-coverage.js +0 -0
  126. package/dist/lanes/field.js +6 -1
  127. package/dist/lanes/folder-contract.js +0 -0
  128. package/dist/lanes/folder-coverage.js +0 -0
  129. package/dist/lanes/folder.js +0 -0
  130. package/dist/lanes/form-contract.js +0 -0
  131. package/dist/lanes/form-coverage.js +0 -0
  132. package/dist/lanes/form.js +0 -0
  133. package/dist/lanes/lane-ids.js +0 -0
  134. package/dist/lanes/list-view-contract.js +0 -0
  135. package/dist/lanes/list-view-coverage.js +0 -0
  136. package/dist/lanes/list-view-shapes.js +0 -0
  137. package/dist/lanes/list-view.js +0 -0
  138. package/dist/lanes/meta-contract.js +0 -0
  139. package/dist/lanes/meta.js +6 -0
  140. package/dist/lanes/model-contract.js +0 -0
  141. package/dist/lanes/model-coverage.js +0 -0
  142. package/dist/lanes/model.js +14 -29
  143. package/dist/lanes/online-js-contract.js +0 -0
  144. package/dist/lanes/online-js-coverage.js +0 -0
  145. package/dist/lanes/online-js.js +0 -0
  146. package/dist/lanes/org-tree.js +0 -0
  147. package/dist/lanes/organization.js +0 -0
  148. package/dist/lanes/report-contract.js +0 -0
  149. package/dist/lanes/report-coverage.js +0 -0
  150. package/dist/lanes/report.js +0 -0
  151. package/dist/lanes/rule-graph.js +0 -0
  152. package/dist/lanes/rule-lifecycle-contract.js +0 -0
  153. package/dist/lanes/rule-lifecycle-coverage.js +0 -0
  154. package/dist/lanes/rule-lifecycle.js +0 -0
  155. package/dist/lanes/runtime-data-contract.js +0 -0
  156. package/dist/lanes/runtime-data-coverage.js +0 -0
  157. package/dist/lanes/runtime-data.js +0 -0
  158. package/dist/lanes/workflow-contract.js +0 -0
  159. package/dist/lanes/workflow-coverage.js +0 -0
  160. package/dist/lanes/workflow.js +0 -0
  161. package/dist/model-purge.js +2 -2
  162. package/dist/model-record.js +40 -0
  163. package/dist/model-scaffold.js +0 -0
  164. package/dist/omc-core-lazy.js +0 -0
  165. package/dist/online-js-layout.js +0 -0
  166. package/dist/online-js-source.js +28 -28
  167. package/dist/package-tool.js +0 -0
  168. package/dist/package.js +0 -0
  169. package/dist/path-projection.js +0 -0
  170. package/dist/plan.js +0 -0
  171. package/dist/read.js +0 -0
  172. package/dist/recipes/b2-registration.js +0 -0
  173. package/dist/recipes/businessrule.js +0 -0
  174. package/dist/recipes/datarule.js +0 -0
  175. package/dist/recipes/dictionary.js +0 -0
  176. package/dist/recipes/field.js +0 -0
  177. package/dist/recipes/form.js +0 -0
  178. package/dist/recipes/list-view.js +0 -0
  179. package/dist/recipes/model.js +0 -0
  180. package/dist/recipes/online-js.js +0 -0
  181. package/dist/recipes/runtime-data.js +0 -0
  182. package/dist/recipes/shared.js +0 -0
  183. package/dist/recipes/workflow.js +0 -0
  184. package/dist/redact.js +0 -0
  185. package/dist/refusal-guidance.js +0 -0
  186. package/dist/rollback-spec.js +0 -0
  187. package/dist/rollback.js +0 -0
  188. package/dist/rule-support.js +5 -11
  189. package/dist/rule-when.js +0 -0
  190. package/dist/runtime-support.js +0 -0
  191. package/dist/script.js +0 -0
  192. package/dist/server.js +92 -0
  193. package/dist/service-doctor.js +0 -0
  194. package/dist/session-manager.js +0 -0
  195. package/dist/session.js +12 -0
  196. package/dist/skills.js +12 -2
  197. package/dist/sqlite-warning-filter.js +0 -0
  198. package/dist/support.js +0 -0
  199. package/dist/token-cache.js +0 -0
  200. package/dist/tool-types.js +0 -0
  201. package/dist/tools.js +0 -0
  202. package/dist/usage-log.js +0 -0
  203. package/dist/wire.js +0 -0
  204. package/dist/workflow-support.js +0 -0
  205. package/dist/write-lease.js +0 -0
  206. package/dist/write-lock.js +0 -0
  207. package/dist/write.js +0 -0
  208. package/dist/zip.js +0 -0
  209. package/docs/images/omc-nixi/README.md +41 -41
  210. package/docs/omc-introduction.md +851 -851
  211. package/docs/tool-surface-map.md +42 -42
  212. package/package.json +4 -4
  213. package/skills/omc-acceptance-criteria.md +50 -50
  214. package/skills/omc-business-configuration.md +334 -334
  215. package/skills/omc-capabilities.md +214 -214
  216. package/skills/omc-capability-scouting.md +55 -55
  217. package/skills/omc-config-draft-review.md +175 -175
  218. package/skills/omc-five-piece-flow.md +35 -35
  219. package/skills/omc-glossary.md +86 -86
  220. package/skills/omc-refusals.md +127 -127
  221. package/skills/omc-requirement-analysis.md +161 -161
  222. package/skills/omc-requirement-vocabulary.md +61 -61
  223. package/skills/omc-start-here.md +106 -106
  224. package/skills/omc-tool-selection.md +119 -119
  225. package/skills/omc-write-hazards.md +200 -200
  226. package/dist/lanes/organization-coverage.js +0 -27
File without changes
@@ -0,0 +1,102 @@
1
+ import { createHash, createHmac, randomUUID, timingSafeEqual } from "node:crypto";
2
+ export class ApprovalTokenRejectedError extends Error {
3
+ reason;
4
+ constructor(reason) {
5
+ super(`approval token rejected: ${reason}`);
6
+ this.reason = reason;
7
+ }
8
+ }
9
+ function canonical(value) {
10
+ return JSON.stringify(sort(value));
11
+ }
12
+ function sort(value) {
13
+ if (Array.isArray(value))
14
+ return value.map(sort);
15
+ if (value && typeof value === "object")
16
+ return Object.fromEntries(Object.entries(value)
17
+ .sort(([a], [b]) => a.localeCompare(b))
18
+ .map(([k, v]) => [k, sort(v)]));
19
+ return value;
20
+ }
21
+ export function packageFingerprint(plan) {
22
+ return createHash("sha256")
23
+ .update(canonical({
24
+ environment: plan.environment,
25
+ environmentFingerprint: plan.environmentFingerprint,
26
+ actions: plan.actions.map((a) => ({ tool: a.tool, args: sort(a.args) })),
27
+ }))
28
+ .digest("hex");
29
+ }
30
+ export class ApprovalTokenService {
31
+ key;
32
+ ttlMs;
33
+ now;
34
+ used = new Set();
35
+ constructor(options) {
36
+ const key = Buffer.from(options.key, "base64");
37
+ if (key.length !== 32)
38
+ throw new Error("approval token key must be exactly 32 bytes (base64).");
39
+ this.key = key;
40
+ this.ttlMs = options.ttlMs ?? 10 * 60 * 1000;
41
+ this.now = options.now ?? Date.now;
42
+ }
43
+ requestApproval(plan) {
44
+ const approvalId = randomUUID();
45
+ const fingerprint = packageFingerprint(plan);
46
+ const issuedAt = this.now();
47
+ const expiresAt = issuedAt + this.ttlMs;
48
+ const payload = canonical({
49
+ v: 1,
50
+ approvalId,
51
+ packageFingerprint: fingerprint,
52
+ issuedAt,
53
+ expiresAt,
54
+ });
55
+ const sig = createHmac("sha256", this.key).update(payload).digest("base64url");
56
+ const token = Buffer.from(payload, "utf8").toString("base64url") + "." + sig;
57
+ return {
58
+ token,
59
+ approvalId,
60
+ packageFingerprint: fingerprint,
61
+ expiresAt: new Date(expiresAt).toISOString(),
62
+ ttlSeconds: Math.round(this.ttlMs / 1000),
63
+ singleUse: true,
64
+ };
65
+ }
66
+ verify(plan, token) {
67
+ const parts = token.split(".");
68
+ if (parts.length !== 2 || !parts[0] || !parts[1])
69
+ throw new ApprovalTokenRejectedError("token-malformed");
70
+ let payload;
71
+ try {
72
+ payload = Buffer.from(parts[0], "base64url").toString("utf8");
73
+ }
74
+ catch {
75
+ throw new ApprovalTokenRejectedError("token-malformed");
76
+ }
77
+ let decoded;
78
+ try {
79
+ decoded = JSON.parse(payload);
80
+ }
81
+ catch {
82
+ throw new ApprovalTokenRejectedError("token-malformed");
83
+ }
84
+ const expectedSig = createHmac("sha256", this.key).update(payload).digest("base64url");
85
+ if (!timingSafeEqualB64(parts[1], expectedSig))
86
+ throw new ApprovalTokenRejectedError("token-signature-invalid");
87
+ if (this.used.has(String(decoded.approvalId)))
88
+ throw new ApprovalTokenRejectedError("token-already-used");
89
+ if (Number(decoded.expiresAt) <= this.now())
90
+ throw new ApprovalTokenRejectedError("token-expired");
91
+ if (decoded.packageFingerprint !== packageFingerprint(plan))
92
+ throw new ApprovalTokenRejectedError("package-fingerprint-mismatch");
93
+ this.used.add(String(decoded.approvalId));
94
+ return { approvalId: String(decoded.approvalId) };
95
+ }
96
+ }
97
+ function timingSafeEqualB64(a, b) {
98
+ const ba = Buffer.from(a);
99
+ const bb = Buffer.from(b);
100
+ return ba.length === bb.length && timingSafeEqual(ba, bb);
101
+ }
102
+ //# sourceMappingURL=approval-token.js.map
package/dist/args.js CHANGED
File without changes
package/dist/artifacts.js CHANGED
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
@@ -0,0 +1,65 @@
1
+ import { createServer } from "node:http";
2
+ import { FakeCloudPivot } from "../fake/index.js";
3
+ export async function startFakeHttpServer() {
4
+ const server = createServer();
5
+ await new Promise((resolve) => server.listen(0, "127.0.0.1", resolve));
6
+ const address = server.address();
7
+ if (address === null || typeof address === "string") {
8
+ server.closeAllConnections?.();
9
+ await new Promise((resolve) => server.close(() => resolve()));
10
+ throw new Error("fake http server has no listen address");
11
+ }
12
+ const url = `http://127.0.0.1:${address.port}`;
13
+ const fake = new FakeCloudPivot({ version: "8.6.26", baseUrl: url });
14
+ server.on("request", (request, response) => {
15
+ const chunks = [];
16
+ request.on("data", (chunk) => chunks.push(chunk));
17
+ request.on("end", () => {
18
+ const target = new URL(request.url ?? "/", url);
19
+ const query = {};
20
+ for (const [key, value] of target.searchParams)
21
+ query[key] = value;
22
+ const raw = Buffer.concat(chunks);
23
+ const contentType = request.headers["content-type"] ?? "";
24
+ const body = raw.length === 0 ? undefined : contentType.includes("json") ? JSON.parse(raw.toString("utf8")) : raw.toString("utf8");
25
+ const headers = {};
26
+ for (const [key, value] of Object.entries(request.headers)) {
27
+ if (typeof value === "string") {
28
+ const capitalized = key.replace(/\b\w/g, (c) => c.toUpperCase()).replace(/-(\w)/g, (_m, c) => `-${c.toUpperCase()}`);
29
+ headers[capitalized] = value;
30
+ }
31
+ }
32
+ fake
33
+ .request({
34
+ path: target.pathname,
35
+ method: request.method === "POST" ? "POST" : "GET",
36
+ ...(Object.keys(query).length > 0 ? { query } : {}),
37
+ ...(body === undefined ? {} : { body }),
38
+ headers,
39
+ })
40
+ .then((result) => {
41
+ const payload = Buffer.isBuffer(result.body) ? result.body : JSON.stringify(result.body);
42
+ response.writeHead(result.status, Buffer.isBuffer(result.body) ? undefined : { "Content-Type": "application/json" });
43
+ response.end(payload);
44
+ })
45
+ .catch(() => {
46
+ response.writeHead(500, { "Content-Type": "application/json" });
47
+ response.end(JSON.stringify({ errcode: 500, errmsg: "fake transport failure", data: null }));
48
+ });
49
+ });
50
+ });
51
+ return {
52
+ fake,
53
+ url,
54
+ close: () => new Promise((resolve, reject) => {
55
+ server.closeAllConnections?.();
56
+ server.close((error) => (error ? reject(error) : resolve()));
57
+ }),
58
+ };
59
+ }
60
+ export function canonicalBusinessCalls(fake) {
61
+ return JSON.stringify(fake.state.callLog
62
+ .filter((entry) => !entry.path.startsWith("/user/") && !entry.path.startsWith("/api/public/") && !entry.path.startsWith("/api/api/organization/"))
63
+ .map((entry) => ({ path: entry.path, method: entry.method, body: entry.body ?? null, query: entry.query && Object.keys(entry.query).length > 0 ? entry.query : null })));
64
+ }
65
+ //# sourceMappingURL=fake-http-server.js.map
File without changes
@@ -122,24 +122,24 @@ function freshHome(url) {
122
122
  writeFileSync(join(home, "credentials"), JSON.stringify({ eval: { username: "tester", password: "eval-face-password" } }));
123
123
  return home;
124
124
  }
125
- const LADDER_INTENT = `intentVersion: 1
126
- models:
127
- - code: eval_m1
128
- name: 评测建模
129
- fields:
130
- - code: f_title
131
- name: 标题
132
- type: SHORT_TEXT
133
- - code: f_amount
134
- name: 金额
135
- type: NUMERICAL
136
- options:
137
- scale: 3
138
- - code: f_when
139
- name: 日期
140
- type: DATE
141
- form:
142
- publish: true
125
+ const LADDER_INTENT = `intentVersion: 1
126
+ models:
127
+ - code: eval_m1
128
+ name: 评测建模
129
+ fields:
130
+ - code: f_title
131
+ name: 标题
132
+ type: SHORT_TEXT
133
+ - code: f_amount
134
+ name: 金额
135
+ type: NUMERICAL
136
+ options:
137
+ scale: 3
138
+ - code: f_when
139
+ name: 日期
140
+ type: DATE
141
+ form:
142
+ publish: true
143
143
  `;
144
144
  export const INTENT_FACE_TASKS = [
145
145
  {
@@ -163,12 +163,12 @@ export const INTENT_FACE_TASKS = [
163
163
  id: "rule-backfill-ladder",
164
164
  description: "two models via intent + DYNAMIC backfill rule + enable + trigger row + verified target readback (mixed face: intent + escape hatch)",
165
165
  async run(agent, server) {
166
- const modelsIntent = agent.writeIntent("models.yaml", `intentVersion: 1
167
- models:
168
- - code: eval_rs
169
- name: 回填源
170
- - code: eval_rt
171
- name: 回填目标
166
+ const modelsIntent = agent.writeIntent("models.yaml", `intentVersion: 1
167
+ models:
168
+ - code: eval_rs
169
+ name: 回填源
170
+ - code: eval_rt
171
+ name: 回填目标
172
172
  `);
173
173
  await agent.intentValidate(modelsIntent);
174
174
  const { plan, planFile } = await agent.intentPlan(modelsIntent);
@@ -209,27 +209,27 @@ models:
209
209
  id: "input-error-recovery",
210
210
  description: "bad intent -> validate exit 10 with source location -> fix -> plan/apply -> idempotent re-plan/re-apply with zero duplicate wires",
211
211
  async run(agent, server) {
212
- const bad = agent.writeIntent("intent.yaml", `intentVersion: 1
213
- models:
214
- - code: eval_idem
215
- name: 幂等模型
216
- fields:
217
- - code: f_bad
218
- name: 坏类型
219
- type: HOLOGRAM
212
+ const bad = agent.writeIntent("intent.yaml", `intentVersion: 1
213
+ models:
214
+ - code: eval_idem
215
+ name: 幂等模型
216
+ fields:
217
+ - code: f_bad
218
+ name: 坏类型
219
+ type: HOLOGRAM
220
220
  `);
221
221
  const rejected = await agent.run(["intent", "validate", bad]);
222
222
  ok(rejected.code === 10 && rejected.envelope.error?.code === "invalid-input", `expected invalid-input exit 10, got ${String(rejected.code)}`);
223
223
  const first = rejected.envelope.result.errors[0];
224
224
  ok(first.code === "intent-field-type-unknown" && typeof first.line === "number", `located error: ${JSON.stringify(first).slice(0, 200)}`);
225
- const fixed = agent.writeIntent("intent.yaml", `intentVersion: 1
226
- models:
227
- - code: eval_idem
228
- name: 幂等模型
229
- fields:
230
- - code: f_bad
231
- name: 改正的字段
232
- type: SHORT_TEXT
225
+ const fixed = agent.writeIntent("intent.yaml", `intentVersion: 1
226
+ models:
227
+ - code: eval_idem
228
+ name: 幂等模型
229
+ fields:
230
+ - code: f_bad
231
+ name: 改正的字段
232
+ type: SHORT_TEXT
233
233
  `);
234
234
  const validated = await agent.intentValidate(fixed);
235
235
  ok(validated.valid === true, `fixed validate: ${JSON.stringify(validated).slice(0, 200)}`);
package/dist/bench/run.js CHANGED
File without changes
File without changes
File without changes
package/dist/bi-wire.js CHANGED
File without changes
File without changes
package/dist/call.js CHANGED
File without changes
File without changes
File without changes
File without changes
@@ -39,15 +39,52 @@ export function asciiStdout() {
39
39
  return true;
40
40
  if (override === "0")
41
41
  return false;
42
+ // Do not spawn chcp on POSIX: it costs a process per invocation and can only
43
+ // ever answer "not a Windows code page here".
44
+ if (process.platform !== "win32")
45
+ return false;
42
46
  return asciiStdoutFor(process.platform, process.stdout.isTTY === true, activeCodePage());
43
47
  }
48
+ /** Escape every non-ASCII code unit to `\uXXXX` (idempotent, JSON-safe). */
49
+ export function escapeNonAscii(text) {
50
+ return text.replace(/[\u007f-\uffff]/g, (character) => `\\u${character.charCodeAt(0).toString(16).padStart(4, "0")}`);
51
+ }
52
+ /**
53
+ * Apply the SAME code-page policy to stderr diagnostics.
54
+ *
55
+ * stdout was made ASCII-safe years ago, but every human/diagnostic line goes
56
+ * through `console.error`/`process.stderr.write` as raw UTF-8. Under a legacy
57
+ * Windows pipe (`omc … 2>&1 | tee log`, `npm test`, an agent capturing output)
58
+ * that is cp936 vs UTF-8 — the user sees `����` instead of the message. Escaping
59
+ * the same way as the envelope keeps diagnostics decodable, and a real console
60
+ * (TTY) is untouched because Node writes UTF-16 through WriteConsoleW there.
61
+ *
62
+ * Gated on `asciiStdout()` so `OMC_ASCII_STDOUT=0` (what the test suite sets)
63
+ * still yields raw UTF-8 for assertions that match Chinese text.
64
+ */
65
+ let stderrHardened = false;
66
+ export function installCodePageSafeStderr() {
67
+ if (stderrHardened)
68
+ return;
69
+ stderrHardened = true;
70
+ if (!asciiStdout())
71
+ return;
72
+ const original = process.stderr.write.bind(process.stderr);
73
+ process.stderr.write = ((chunk, encodingOrCb, maybeCb) => {
74
+ if (typeof chunk !== "string")
75
+ return original(chunk, encodingOrCb, maybeCb);
76
+ return original(escapeNonAscii(chunk), encodingOrCb, maybeCb);
77
+ });
78
+ }
44
79
  let cachedCodePage;
45
80
  function activeCodePage() {
46
81
  if (cachedCodePage !== undefined)
47
82
  return cachedCodePage;
48
83
  cachedCodePage = null;
49
84
  try {
50
- const chcp = spawnSync("chcp.com", [], { encoding: "utf8" });
85
+ // windowsHide: a GUI host (editor task, shortcut) must not flash a console
86
+ // window for a probe; timeout: never hang a one-shot CLI on chcp.
87
+ const chcp = spawnSync("chcp.com", [], { encoding: "utf8", windowsHide: true, timeout: 2000 });
51
88
  cachedCodePage = /([0-9]{3,5})\s*$/.exec((chcp.stdout || "").trim().split("\n").pop() ?? "")?.[1] ?? null;
52
89
  }
53
90
  catch {
@@ -82,8 +82,14 @@ function emitAsciiBannerOnce() {
82
82
  return;
83
83
  asciiBannerEmitted = true;
84
84
  // r73 win32 实测:管道 + 非 UTF-8 代码页时输出被 ASCII 转义(\uXXXX),用户视为乱码。
85
- // 横幅走 stderr(不进管道、不破坏单信封契约),在痛点时刻给出两个解法。
86
- process.stderr.write("[omc] 当前控制台代码页非 UTF-8,中文已 ASCII 转义防乱码。要原样中文:本次调用加 --utf8,或 chcp 65001 后重跑,或设 OMC_ASCII_STDOUT=0。\n");
85
+ // 横幅走 stderr(不进管道、不破坏单信封契约),在痛点时刻给出解法。
86
+ // 只有「自动判定」成 ascii 时才提示:调用方显式设了 OMC_ASCII_STDOUT/--utf8
87
+ // 说明已知情,再提示就是每次调用多两行噪音。
88
+ // 注意:ascii 模式下 stderr 本身也会被转义,所以横幅必须是纯 ASCII。
89
+ if (process.env.OMC_ASCII_STDOUT !== undefined)
90
+ return;
91
+ process.stderr.write("[omc] non-UTF-8 console code page: non-ASCII is \\u-escaped to survive the pipe. "
92
+ + "Raw text: add --utf8, or `chcp 65001`, or set OMC_ASCII_STDOUT=0.\n");
87
93
  }
88
94
  /**
89
95
  * Attach a repeat warning to a refusal that repeats the previous one. Refusal
package/dist/cli.js CHANGED
@@ -15,7 +15,7 @@ import { BATCH_MAX_ENTRIES, runBatchCall } from "./batch-call.js";
15
15
  import { cmdModelScaffold } from "./model-scaffold.js";
16
16
  import { cmdModelPurge } from "./model-purge.js";
17
17
  import { environmentWritable, requiresProductionConfirmation } from "./environment-policy.js";
18
- import { CLI_EXIT_CODES, classifyResult, envelope, errorEnvelope, refusalText, requestId } from "./cli-contract.js";
18
+ import { CLI_EXIT_CODES, classifyResult, envelope, errorEnvelope, installCodePageSafeStderr, refusalText, requestId } from "./cli-contract.js";
19
19
  import { configureOutput, humanSummary, stripOutputFlags, writeEnvelope } from "./cli-output.js";
20
20
  import { beginUsage, completeUsage, currentUsageArgsDigest, ensureProjectIgnore, priorIdenticalSuccess, recordUsageOutput, usageReport } from "./usage-log.js";
21
21
  import { artifactFetchHint, artifactRoot, boundResult, emitInvalid, emitResult, emitThrown, emitTransportResult, outputBudget, readArtifact, withTransport, } from "./command-transport.js";
@@ -174,6 +174,11 @@ async function cmdDoctor(store, environment, serviceCode) {
174
174
  const session = new CloudPivotSession(resolved.name, resolved.profile, credential);
175
175
  await session.login();
176
176
  steps.push(`登录 ok 企业=${session.enterpriseId()} 管理员=${session.administrator()}`);
177
+ // A deployment whose system/config `client` blob is absent/unreadable still
178
+ // logs in (client_secret is optional on 8.x). Say so as a WARN: it used to
179
+ // abort the login and report env-unreachable on a perfectly usable box.
180
+ for (const note of session.loginNotes())
181
+ steps.push(`OAuth 配置 WARN ${note}`);
177
182
  const version = session.detectedVersion();
178
183
  if (!version) {
179
184
  steps.push(`版本 WARN 无版本证据;写入将保守拒绝(fail closed)`);
@@ -1456,6 +1461,9 @@ function joinSplitFlagValues(raw, flags) {
1456
1461
  }
1457
1462
  async function main() {
1458
1463
  let commandExitCode = 0;
1464
+ // stderr is the diagnostics stream: apply the same Windows code-page policy
1465
+ // as stdout before ANY message is written (banner, doctor, refusals).
1466
+ installCodePageSafeStderr();
1459
1467
  beginUsage(argv);
1460
1468
  const command = new Command();
1461
1469
  const ownPackageVersion = () => {
File without changes
File without changes
File without changes
File without changes
package/dist/compute.js CHANGED
File without changes
File without changes
package/dist/context.js CHANGED
File without changes
File without changes
File without changes
File without changes
File without changes
package/dist/coverage.js CHANGED
File without changes
File without changes
File without changes
package/dist/discovery.js CHANGED
File without changes
package/dist/endpoints.js CHANGED
File without changes
File without changes
File without changes
@@ -0,0 +1,45 @@
1
+ import { NOT_HANDLED } from "./state.js";
2
+ /** app-management lane: application package CRUD. Twin of src/lanes/app.ts. */
3
+ export const appRoute = (ctx, input) => {
4
+ const q = input.query ?? {};
5
+ const body = (input.body ?? {});
6
+ switch (input.path) {
7
+ case "/api/api/app/apppackage/list":
8
+ return ctx.options.applicationList ?? [...ctx.state.apps.values()].map((app) => ({ appCode: app.code, appName: app.name, id: app.id, groupId: app.groupId, published: app.published !== false }));
9
+ case "/api/api/app/apppackage/list_all_group":
10
+ return [...ctx.state.appGroups.values()].map((group) => ({
11
+ id: group.id,
12
+ code: group.code,
13
+ name: group.name,
14
+ children: [...ctx.state.apps.values()].filter((app) => app.groupId === group.id).map((app) => ({ id: app.id, code: app.code, name: app.name, groupId: app.groupId })),
15
+ }));
16
+ case "/api/api/app/apppackage/create": {
17
+ const code = String(body.code);
18
+ ctx.state.apps.set(code, { id: `app-${ctx.state.apps.size + 1}`, code, name: String(body.name ?? code), groupId: String(body.groupId ?? [...ctx.state.appGroups.keys()][0] ?? "group-1") });
19
+ return null;
20
+ }
21
+ case "/api/api/app/apppackage/get": {
22
+ const app = [...ctx.state.apps.values()].find((row) => row.code === q.appCode);
23
+ return app ? { id: app.id, code: app.code, name: app.name, name_i18n: JSON.stringify({ "zh-CN": app.name }), groupId: app.groupId, enabled: true } : null;
24
+ }
25
+ case "/api/api/app/apppackage/update": {
26
+ // AppIntegrationFacadeImpl.updateAppPackage: id-required whole-record
27
+ // update; explicit groupId honored; blank groupId would be forced to
28
+ // the "default" group; enabled !== true wipes workflow favorites.
29
+ const target = [...ctx.state.apps.values()].find((row) => row.id === body.id);
30
+ if (target) {
31
+ if (typeof body.groupId === "string" && body.groupId)
32
+ target.groupId = body.groupId;
33
+ if (typeof body.name === "string" && body.name)
34
+ target.name = body.name;
35
+ }
36
+ return null;
37
+ }
38
+ case "/api/api/app/apppackage/delete":
39
+ ctx.state.apps.delete(String(q.appCode));
40
+ return null;
41
+ default:
42
+ return NOT_HANDLED;
43
+ }
44
+ };
45
+ //# sourceMappingURL=app.js.map