@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,134 @@
1
+ import { wireCall, wireRows, WireError } from "./wire.js";
2
+ import { ENDPOINTS } from "./endpoints.js";
3
+ import { ToolRefusalError } from "./tool-types.js";
4
+ import { resolveModelTreeNode } from "./lanes/model.js";
5
+ /**
6
+ * Ticket 78 — map the two same-root-cause platform failures of an
7
+ * UNPUBLISHED application to the explicit `app-not-published` blocker:
8
+ * - data.save/data.submit: stable 50000 on unpublished-app models (the same
9
+ * call succeeds on a published app);
10
+ * - datarule.create type2 CALCULATE: 305003, a FALSE dead-cycle verdict —
11
+ * BizDataRuleFacadeImpl.checkCalculateRulesDeadCycle (decompiled :84-170)
12
+ * falls back to `schemaCode.propertyCode` concatenation when
13
+ * applicationFacade.getAppFunctionByCode(schemaCode) is null.
14
+ *
15
+ * Fail-closed corroboration: the mapping fires ONLY when the app face
16
+ * (apppackage/list) actually reports `published === false` for the model's
17
+ * application; anything unresolved rethrows the original wire error (an
18
+ * uncorroborated 50000 stays a generic failure). App publish itself is
19
+ * outside OMC's coverage — the refusal says so instead of retrying blindly.
20
+ *
21
+ * Lives at the src/ support seam (like form-support/rule-support) because it
22
+ * reads lane metadata; support.ts stays a leaf module.
23
+ */
24
+ export async function mapUnpublishedAppFailure(session, schemaCode, errcode, rootCause, error) {
25
+ if (!(error instanceof WireError && error.kind === "business" && error.errcode === errcode))
26
+ throw error;
27
+ // Corroboration reads failing (transport etc.) rethrow the ORIGINAL error —
28
+ // the mapping must never mask the failure it is diagnosing.
29
+ let resolved;
30
+ let row;
31
+ try {
32
+ resolved = await resolveModelTreeNode(session, schemaCode);
33
+ if (!resolved.node)
34
+ throw error;
35
+ const apps = wireRows((await wireCall(session, { path: ENDPOINTS.applicationList, query: {} })).data);
36
+ row = apps.find((candidate) => String(candidate.appCode ?? candidate.code ?? "") === resolved.applicationCode);
37
+ }
38
+ catch {
39
+ throw error;
40
+ }
41
+ if (!row || row.published !== false)
42
+ throw error;
43
+ throw new ToolRefusalError({
44
+ status: "refused",
45
+ blocker: "app-not-published",
46
+ schemaCode,
47
+ applicationCode: resolved.applicationCode,
48
+ errcode,
49
+ message: `${rootCause}——根因:模型 ${schemaCode} 所在应用 ${resolved.applicationCode} 未发布(同调用在已发布应用模型上成功)。应用发布属 OMC 覆盖边界外(unsupported):设计态验证可继续,运行时验证请在已发布应用的模型上进行;不要重复盲试`,
50
+ });
51
+ }
52
+ /**
53
+ * Ticket 78 convenience seam: run a runtime write and map its corroborated
54
+ * unpublished-app failure. Used by data.save/data.submit (errcode 50000) and
55
+ * datarule.create type2 (errcode 305003) so the try/catch wiring lives once.
56
+ */
57
+ export async function runtimeWriteWithAppMapping(session, schemaCode, errcode, rootCause, call, formData) {
58
+ try {
59
+ return await call();
60
+ }
61
+ catch (error) {
62
+ let mapped;
63
+ try {
64
+ mapped = await mapUnpublishedAppFailure(session, schemaCode, errcode, rootCause, error);
65
+ }
66
+ catch (appMapped) {
67
+ // The app mapping either produced a refusal or rethrew the original; if a
68
+ // caller supplied the payload, also try the single-relation shape diagnosis
69
+ // (it passes refusals through untouched).
70
+ throw formData === undefined ? appMapped : await diagnoseRelationShapeFailure(session, schemaCode, errcode, appMapped, formData);
71
+ }
72
+ throw mapped;
73
+ }
74
+ }
75
+ /** WORK_SHEET single relation; a runtime value must NOT be an array. */
76
+ export const SINGLE_RELATION_PROPERTY_TYPE = 9;
77
+ /** CHILD_TABLE(8): a sub-table field; its rows carry the child schema's own field shapes. */
78
+ export const CHILD_TABLE_PROPERTY_TYPE = 8;
79
+ /**
80
+ * Dogfood 2026-09-24 (bench31): a SINGLE relation (WORK_SHEET, propertyType 9)
81
+ * given an ARRAY makes runtime/form/save answer an opaque 50000. `{id}`/`id`
82
+ * work, and multi relations (11) / staff-org fields (50/51) legitimately take
83
+ * arrays — so the SAME shape silently fails only for single relations.
84
+ *
85
+ * On a still-unexplained 50000 (the app-published mapping already passed),
86
+ * name the offending field(s) instead of leaving an opaque error. Metadata is
87
+ * read ONLY on failure (zero cost on the success path); when nothing matches,
88
+ * or the failure is a corroborated refusal, the ORIGINAL error is rethrown.
89
+ */
90
+ export async function diagnoseRelationShapeFailure(session, schemaCode, errcode, error, formData) {
91
+ if (!(error instanceof WireError && error.kind === "business" && error.errcode === errcode))
92
+ throw error;
93
+ let rows;
94
+ try {
95
+ rows = wireRows((await wireCall(session, { path: ENDPOINTS.fieldList, query: { schemaCode, isPublish: "true" } })).data);
96
+ }
97
+ catch {
98
+ throw error;
99
+ }
100
+ const culprits = [];
101
+ const typeOf = (row) => Number(row.propertyType ?? row.type);
102
+ for (const row of rows) {
103
+ const code = String(row.code ?? row.propertyCode ?? "");
104
+ const value = formData[code];
105
+ if (typeOf(row) === SINGLE_RELATION_PROPERTY_TYPE && Array.isArray(value) && value.length > 0) {
106
+ culprits.push(code);
107
+ continue;
108
+ }
109
+ if (typeOf(row) === CHILD_TABLE_PROPERTY_TYPE && Array.isArray(value)) {
110
+ const subSchema = row.subSchema;
111
+ const singleChildren = new Set((subSchema?.properties ?? []).filter((property) => typeOf(property) === SINGLE_RELATION_PROPERTY_TYPE).map((property) => String(property.code ?? property.propertyCode ?? "")));
112
+ for (const entry of value) {
113
+ if (!entry || typeof entry !== "object" || Array.isArray(entry))
114
+ continue;
115
+ for (const childCode of singleChildren) {
116
+ const childValue = entry[childCode];
117
+ if (Array.isArray(childValue) && childValue.length > 0)
118
+ culprits.push(`${code}.${childCode}`);
119
+ }
120
+ }
121
+ }
122
+ }
123
+ if (culprits.length === 0)
124
+ throw error;
125
+ throw new ToolRefusalError({
126
+ status: "refused",
127
+ blocker: "relation-single-array-shape",
128
+ schemaCode,
129
+ errcode,
130
+ fields: culprits,
131
+ message: `errcode ${errcode}——根因:单选关联字段 ${culprits.join(", ")} 传了数组(WORK_SHEET/propertyType 9 只接受 id 字符串或 {id};数组会稳定 ${errcode})。多选关联(11)与人员/组织(50/51)才用数组`,
132
+ });
133
+ }
134
+ //# sourceMappingURL=runtime-support.js.map
package/dist/server.js ADDED
@@ -0,0 +1,92 @@
1
+ #!/usr/bin/env node
2
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
3
+ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
4
+ import { randomBytes } from "node:crypto";
5
+ import { realpathSync } from "node:fs";
6
+ import { fileURLToPath } from "node:url";
7
+ import { buildToolRegistry, makeToolContext } from "./tools.js";
8
+ import { SessionManager } from "./session-manager.js";
9
+ import { redact, redactError } from "./redact.js";
10
+ import { ConfigStore } from "./config-store.js";
11
+ import { contractMarkdown, contractResourceUri, LANE_CONTRACTS, shortToolDescription } from "./contract-resources.js";
12
+ import { listSkills, readSkill } from "./skills.js";
13
+ export function createMcpServer(options = {}) {
14
+ const context = makeToolContext({
15
+ sessions: options.sessions ?? new SessionManager(new ConfigStore()),
16
+ artifactsDir: options.artifactsDir,
17
+ tokenKey: options.tokenKey ?? randomBytes(32).toString("base64"),
18
+ tokenTtlMs: options.tokenTtlMs,
19
+ });
20
+ const secrets = options.secrets ?? (() => []);
21
+ const server = new McpServer({ name: "omc-cli", version: "0.1.0" }, {
22
+ capabilities: { tools: {}, resources: {} },
23
+ instructions: "Governed CloudPivot 8.6 configuration server. Tool schemas are self-describing (field families, relation options, rule nodeGraph, workflow template, formData, and the two-step approval flow). Methodology is served as MCP resources under omc://skills/ (five-piece flow, tool selection, acceptance criteria, requirement analysis, capability scouting, glossary, config draft review); read them with resources/list + resources/read rather than reading repository source. Before ANY write, read omc://skills/omc-config-draft-review: present the design (exact names/codes/types, layout, rule graph, view) as a plain-language draft and get explicit human approval first — the conversational human gate (approval granularity is the enumerated change package; an interrupted package loses its approval and resuming needs a fresh draft and go-ahead). Then: call without approvalToken to preview the plan, approval.request to get a token (the machine gate — it carries no human check), re-call with it. Production environments refuse writes by default (dual-key grant). Deliver ONE closing report per run — never interrupt per step.",
24
+ });
25
+ const register = server.tool.bind(server);
26
+ for (const definition of buildToolRegistry()) {
27
+ register(definition.name, shortToolDescription(definition.name, definition.kind, definition.lane) ?? definition.description, definition.inputSchema, async (args) => {
28
+ try {
29
+ const result = await definition.handler(context, args, definition);
30
+ return { content: [{ type: "text", text: redact(result, secrets()) }] };
31
+ }
32
+ catch (error) {
33
+ const refusal = error.payload;
34
+ if (refusal)
35
+ return { content: [{ type: "text", text: redact(refusal, secrets()) }] };
36
+ const redacted = redactError(error, secrets());
37
+ return { content: [{ type: "text", text: redact({ status: "error", message: redacted.message }, secrets()) }], isError: true };
38
+ }
39
+ });
40
+ }
41
+ registerSkillResources(server);
42
+ registerContractResources(server);
43
+ return { server, context };
44
+ }
45
+ /**
46
+ * Serve the package's methodology notes as MCP resources so any client can
47
+ * read them on demand (host-agnostic) instead of the agent globbing the
48
+ * filesystem for them. Each skill file's YAML frontmatter supplies its name
49
+ * and description.
50
+ */
51
+ function registerSkillResources(server) {
52
+ for (const entry of listSkills()) {
53
+ const document = readSkill(entry.name);
54
+ if (document === undefined)
55
+ continue;
56
+ server.registerResource(entry.name, entry.uri, { title: entry.name, description: entry.description, mimeType: "text/markdown" }, async (uri) => ({ contents: [{ uri: uri.href, mimeType: "text/markdown", text: document.content }] }));
57
+ }
58
+ }
59
+ function registerContractResources(server) {
60
+ for (const contract of LANE_CONTRACTS) {
61
+ const text = contractMarkdown(contract);
62
+ server.registerResource(`contract-${contract.lane}`, contractResourceUri(contract), {
63
+ title: `${contract.lane} contract`,
64
+ description: `CloudPivot ${contract.family} contract for ${contract.lane}; reference ${contract.referenceVersion}`,
65
+ mimeType: "text/markdown",
66
+ }, async (uri) => ({ contents: [{ uri: uri.href, mimeType: "text/markdown", text }] }));
67
+ }
68
+ }
69
+ export async function runStdioServer(options = {}) {
70
+ const { server } = createMcpServer(options);
71
+ const transport = new StdioServerTransport();
72
+ await server.connect(transport);
73
+ }
74
+ function isProcessEntryPoint() {
75
+ const entry = process.argv[1];
76
+ if (!entry)
77
+ return false;
78
+ try {
79
+ return realpathSync(entry) === fileURLToPath(import.meta.url);
80
+ }
81
+ catch {
82
+ return false;
83
+ }
84
+ }
85
+ if (isProcessEntryPoint()) {
86
+ runStdioServer().catch((error) => {
87
+ console.error(error instanceof Error ? error.message : String(error));
88
+ process.exit(1);
89
+ });
90
+ }
91
+ export { buildToolRegistry, makeToolContext };
92
+ //# sourceMappingURL=server.js.map
@@ -0,0 +1,30 @@
1
+ import { CloudPivotSession } from "./session.js";
2
+ /** Caches one logged-in session per named environment per process. */
3
+ export class SessionManager {
4
+ store;
5
+ httpFactory;
6
+ systemConfigFactory;
7
+ sessions = new Map();
8
+ constructor(store, httpFactory, systemConfigFactory) {
9
+ this.store = store;
10
+ this.httpFactory = httpFactory;
11
+ this.systemConfigFactory = systemConfigFactory;
12
+ }
13
+ config() {
14
+ return this.store;
15
+ }
16
+ async session(environment) {
17
+ const resolved = this.store.resolveEnvironment(environment);
18
+ const profileKey = JSON.stringify(resolved.profile);
19
+ const cached = this.sessions.get(resolved.name);
20
+ let session = cached?.profileKey === profileKey ? cached.session : undefined;
21
+ if (!session) {
22
+ const credential = this.store.readCredential(resolved.name);
23
+ session = new CloudPivotSession(resolved.name, resolved.profile, credential, this.httpFactory?.(resolved.profile), this.systemConfigFactory?.(resolved.profile));
24
+ this.sessions.set(resolved.name, { profileKey, session });
25
+ }
26
+ await session.login();
27
+ return session;
28
+ }
29
+ }
30
+ //# sourceMappingURL=session-manager.js.map
@@ -0,0 +1,149 @@
1
+ import { CloudPivotAuthenticator, cloudPivotTenantHeaders, fetchCloudPivotHttp, resolveCloudPivotAuthoritativeOrigin, } from "omc-core";
2
+ import { canonicalCloudPivotEnvironmentFingerprint } from "omc-core";
3
+ /**
4
+ * An authenticated CloudPivot session for one named environment. Wraps the
5
+ * core authenticator; exposes a version-family probe used by the 8.6-only
6
+ * fail-closed write gate, and the canonical environment fingerprint that all
7
+ * plans/approvals/artifacts must carry.
8
+ */
9
+ export class CloudPivotSession {
10
+ environmentName;
11
+ profile;
12
+ credential;
13
+ http;
14
+ systemConfig;
15
+ requestTimeoutMs;
16
+ loginResult;
17
+ version;
18
+ constructor(environmentName, profile, credential, http = fetchCloudPivotHttp(profile.baseUrl, undefined, fetch, cloudPivotTenantHeaders(profile.engineCode)), systemConfig = undefined, requestTimeoutMs = defaultRequestTimeoutMs()) {
19
+ this.environmentName = environmentName;
20
+ this.profile = profile;
21
+ this.credential = credential;
22
+ this.http = http;
23
+ this.systemConfig = systemConfig;
24
+ this.requestTimeoutMs = requestTimeoutMs;
25
+ }
26
+ async login() {
27
+ if (this.loginResult)
28
+ return this.loginResult;
29
+ const { systemConfig } = this.systemConfig
30
+ ? { systemConfig: this.systemConfig }
31
+ : await resolveCloudPivotAuthoritativeOrigin(this.profile.baseUrl, undefined, fetch, cloudPivotTenantHeaders(this.profile.engineCode)).then((r) => ({ systemConfig: r.systemConfig }), () => ({ systemConfig: undefined }));
32
+ const authenticator = new CloudPivotAuthenticator(this.http);
33
+ this.loginResult = await authenticator.login({
34
+ baseUrl: this.profile.baseUrl,
35
+ username: this.credential.username,
36
+ password: this.credential.password,
37
+ ...(systemConfig ? { systemConfig } : {}),
38
+ });
39
+ this.version = detectVersion(systemConfig);
40
+ return this.loginResult;
41
+ }
42
+ /** Version-family evidence; absent evidence returns undefined (fail closed upstream). */
43
+ detectedVersion() {
44
+ return this.version;
45
+ }
46
+ token() {
47
+ if (!this.loginResult)
48
+ throw new Error("session is not logged in");
49
+ return this.loginResult.token;
50
+ }
51
+ enterpriseId() {
52
+ if (!this.loginResult)
53
+ throw new Error("session is not logged in");
54
+ return this.loginResult.enterpriseId;
55
+ }
56
+ administrator() {
57
+ if (!this.loginResult)
58
+ throw new Error("session is not logged in");
59
+ return this.loginResult.administrator;
60
+ }
61
+ headers() {
62
+ return { Authorization: `Bearer ${this.token()}`, "X-LowCode-Corpid": this.enterpriseId() };
63
+ }
64
+ environmentFingerprint() {
65
+ if (!this.loginResult)
66
+ throw new Error("session is not logged in");
67
+ return canonicalCloudPivotEnvironmentFingerprint({
68
+ environment: this.environmentName,
69
+ authoritativeOrigin: new URL(this.profile.baseUrl).origin,
70
+ applicationCode: this.profile.applicationCode,
71
+ enterpriseId: this.loginResult.enterpriseId,
72
+ });
73
+ }
74
+ async request(input) {
75
+ const first = await this.send(input);
76
+ // A 401 means the cached token was invalidated out-of-band (e.g. the
77
+ // operator cleared Redis for a license recovery). Re-login once and retry;
78
+ // anything else is returned as-is for the wire layer to classify.
79
+ if (first.status !== 401)
80
+ return first;
81
+ this.loginResult = undefined;
82
+ this.version = undefined;
83
+ return this.send(input);
84
+ }
85
+ async send(input) {
86
+ if (!this.loginResult)
87
+ await this.login();
88
+ let timer;
89
+ try {
90
+ return await Promise.race([
91
+ this.http.request({
92
+ path: input.path,
93
+ method: input.method,
94
+ ...(input.query ? { query: input.query } : {}),
95
+ ...(input.body === undefined ? {} : { body: input.body }),
96
+ ...(input.responseType === undefined ? {} : { responseType: input.responseType }),
97
+ ...(input.multipart === undefined ? {} : { multipart: input.multipart }),
98
+ headers: { ...this.headers(), ...(input.headers ?? {}) },
99
+ }),
100
+ new Promise((_, reject) => {
101
+ timer = setTimeout(() => reject(new CloudPivotRequestTimeoutError(input.path, this.requestTimeoutMs)), this.requestTimeoutMs);
102
+ }),
103
+ ]);
104
+ }
105
+ finally {
106
+ if (timer)
107
+ clearTimeout(timer);
108
+ }
109
+ }
110
+ get httpHandle() {
111
+ return this.http;
112
+ }
113
+ }
114
+ function detectVersion(systemConfig) {
115
+ if (!systemConfig)
116
+ return undefined;
117
+ for (const key of ["version", "sysVersion", "systemVersion", "productVersion", "serverVersion"]) {
118
+ const value = systemConfig[key];
119
+ if (typeof value !== "string")
120
+ continue;
121
+ // Accept a bare "8.6.26" and a product-prefixed string such as
122
+ // "AI-Platform-v1.1.22" (pull the first dotted numeric run).
123
+ const match = value.match(/(\d+\.\d+(?:\.\d+)?)/);
124
+ if (match)
125
+ return match[1];
126
+ }
127
+ return undefined;
128
+ }
129
+ /**
130
+ * A request that produced no response within the timeout. Without this, a
131
+ * stalled CloudPivot (e.g. mid-restart) would leave a write hanging forever and
132
+ * hold the per-environment write lease, blocking every later call.
133
+ */
134
+ export class CloudPivotRequestTimeoutError extends Error {
135
+ path;
136
+ timeoutMs;
137
+ constructor(path, timeoutMs) {
138
+ super(`CloudPivot request timed out after ${timeoutMs}ms: ${path}`);
139
+ this.path = path;
140
+ this.timeoutMs = timeoutMs;
141
+ this.name = "CloudPivotRequestTimeoutError";
142
+ }
143
+ }
144
+ function defaultRequestTimeoutMs() {
145
+ const raw = process.env.OMC_HTTP_TIMEOUT_MS;
146
+ const parsed = raw ? Number(raw) : NaN;
147
+ return Number.isFinite(parsed) && parsed > 0 ? parsed : 120_000;
148
+ }
149
+ //# sourceMappingURL=session.js.map
package/dist/skills.js ADDED
@@ -0,0 +1,112 @@
1
+ import { readFileSync, readdirSync } from "node:fs";
2
+ import { dirname, join } from "node:path";
3
+ import { fileURLToPath } from "node:url";
4
+ /**
5
+ * The methodology layer is reachable from the CLI as `omc help <topic>`.
6
+ * These package files are the single source of truth for the shipped help
7
+ * topics.
8
+ *
9
+ * The files ship with the package (package-root `skills/`), not inside dist/,
10
+ * so the docs travel with the binary at the same version — the property that
11
+ * makes `git help <concept>` reliable and a docs-site link unreliable.
12
+ */
13
+ const skillsDir = join(dirname(fileURLToPath(import.meta.url)), "..", "skills");
14
+ function frontmatterDescription(text) {
15
+ const match = /^---\r?\n([\s\S]*?)\r?\n---/.exec(text);
16
+ const line = match?.[1]?.split(/\r?\n/).find((entry) => entry.startsWith("description:"));
17
+ return line?.slice("description:".length).trim() || undefined;
18
+ }
19
+ function skillFiles() {
20
+ try {
21
+ return readdirSync(skillsDir).filter((file) => file.endsWith(".md")).sort();
22
+ }
23
+ catch {
24
+ return [];
25
+ }
26
+ }
27
+ /** Every methodology topic exposed by `omc help`. */
28
+ export function listSkills() {
29
+ return skillFiles().map((file) => {
30
+ const name = file.replace(/\.md$/, "");
31
+ let description = `OMC methodology: ${name}`;
32
+ try {
33
+ description = frontmatterDescription(readFileSync(join(skillsDir, file), "utf8")) ?? description;
34
+ }
35
+ catch {
36
+ // Unreadable file still lists by name; reading it reports the error.
37
+ }
38
+ return { name, description };
39
+ });
40
+ }
41
+ /** Read one methodology topic for `omc help <topic>`. */
42
+ export function readSkill(name) {
43
+ if (!/^[a-z0-9-]+$/.test(name))
44
+ return undefined;
45
+ const entry = listSkills().find((skill) => skill.name === name);
46
+ if (entry === undefined)
47
+ return undefined;
48
+ try {
49
+ return { ...entry, content: readFileSync(join(skillsDir, `${name}.md`), "utf8") };
50
+ }
51
+ catch {
52
+ return undefined;
53
+ }
54
+ }
55
+ /** Return one Markdown heading and its nested content without loading the full topic into the envelope. */
56
+ export function readSkillSection(name, requested) {
57
+ const document = readSkill(name);
58
+ if (!document)
59
+ return undefined;
60
+ const lines = document.content.split(/\r?\n/);
61
+ const headings = lines.flatMap((line) => {
62
+ const match = /^(#{1,6})\s+(.+?)\s*$/.exec(line);
63
+ return match ? [match[2]] : [];
64
+ });
65
+ const normalized = requested.trim().toLowerCase();
66
+ // A requested section may be given by its ordered number ("8") instead of the
67
+ // full heading ("8. Canonical configuration order(规范配置顺序)") — the
68
+ // route lists headings, but agents also number-jump. Match on the leading
69
+ // ordinal too, so a numeric request does not waste a retry.
70
+ const requestedOrdinal = /^(\d+)(?:[.\)、]|\s|$)/.exec(normalized)?.[1];
71
+ const matches = (heading) => {
72
+ if (heading === normalized)
73
+ return true;
74
+ if (heading.replace(/[^\p{L}\p{N}]+/gu, "-").toLowerCase() === normalized)
75
+ return true;
76
+ if (requestedOrdinal !== undefined) {
77
+ const headingOrdinal = /^(\d+)(?:[.\)、]|\s|$)/.exec(heading)?.[1];
78
+ if (headingOrdinal === requestedOrdinal)
79
+ return true;
80
+ }
81
+ return false;
82
+ };
83
+ let start = lines.findIndex((line) => {
84
+ const match = /^(#{1,6})\s+(.+?)\s*$/.exec(line);
85
+ return match !== null && matches(match[2].trim().toLowerCase());
86
+ });
87
+ // Forgiving fallback: a unique prefix/substring match ("流程" for "流程字段权限").
88
+ // A non-unique partial matches nothing (precision over recall) and the
89
+ // not-found error lists the real headings, so the caller can retry once.
90
+ if (start < 0 && normalized.length >= 2) {
91
+ const partial = lines.filter((line) => {
92
+ const match = /^(#{1,6})\s+(.+?)\s*$/.exec(line);
93
+ return match !== null && match[2].trim().toLowerCase().includes(normalized);
94
+ });
95
+ if (partial.length === 1)
96
+ start = lines.indexOf(partial[0]);
97
+ }
98
+ if (start < 0)
99
+ return { document, section: "", headings };
100
+ const startMatch = /^(#{1,6})\s+(.+?)\s*$/.exec(lines[start]);
101
+ const level = startMatch[1].length;
102
+ let end = lines.length;
103
+ for (let index = start + 1; index < lines.length; index += 1) {
104
+ const match = /^(#{1,6})\s+(.+?)\s*$/.exec(lines[index]);
105
+ if (match && match[1].length <= level) {
106
+ end = index;
107
+ break;
108
+ }
109
+ }
110
+ return { document, section: lines.slice(start, end).join("\n").trim() + "\n", headings };
111
+ }
112
+ //# sourceMappingURL=skills.js.map
@@ -0,0 +1,98 @@
1
+ import { mkdirSync, writeFileSync } from "node:fs";
2
+ import { join } from "node:path";
3
+ import { createHash } from "node:crypto";
4
+ import { wireCall, wireRows, WireError } from "./wire.js";
5
+ import { ENDPOINTS } from "./endpoints.js";
6
+ /**
7
+ * Cross-cutting primitives with no governance policy of their own: hashing,
8
+ * artifact writing, wire-error classification, the coverage refusal shape, and
9
+ * application-tree resolution. Every pipeline module (plan/read/write/package/
10
+ * context) leans on these; none of them may lean back.
11
+ */
12
+ export function sha256(value) {
13
+ return createHash("sha256").update(JSON.stringify(sortForHash(value))).digest("hex");
14
+ }
15
+ function sortForHash(value) {
16
+ if (Array.isArray(value))
17
+ return value.map(sortForHash);
18
+ if (value && typeof value === "object")
19
+ return Object.fromEntries(Object.entries(value).sort(([a], [b]) => a.localeCompare(b)).map(([key, entry]) => [key, sortForHash(entry)]));
20
+ return value;
21
+ }
22
+ export function jsonBytes(value) {
23
+ return Buffer.byteLength(JSON.stringify(value) ?? "null");
24
+ }
25
+ /** Token-diet summary of an arbitrary value: arrays/objects/long strings become counts + hashes. */
26
+ export function summarizeValue(value) {
27
+ if (Array.isArray(value))
28
+ return { count: value.length, sha256: sha256(value) };
29
+ if (value && typeof value === "object")
30
+ return { keys: Object.keys(value).sort(), sha256: sha256(value) };
31
+ if (typeof value === "string" && value.length > 160)
32
+ return { length: value.length, sha256: sha256(value) };
33
+ return value;
34
+ }
35
+ export function writeArtifact(ctx, name, body) {
36
+ mkdirSync(ctx.artifactsDir, { recursive: true });
37
+ const detailFile = join(ctx.artifactsDir, name).replaceAll("\\\\", "/");
38
+ const text = JSON.stringify(body, null, 2) + "\n";
39
+ writeFileSync(detailFile, text);
40
+ return { detailFile, detailSha256: createHash("sha256").update(text).digest("hex") };
41
+ }
42
+ export function isUncertainWireError(error) {
43
+ if (error instanceof WireError)
44
+ return error.kind === "transport" || error.kind === "http-status";
45
+ return error instanceof Error && /transport failed|transport outage|fetch failed|ENOTFOUND|ECONNREFUSED|timed? ?out|network/i.test(error.message);
46
+ }
47
+ export function wireErrorKind(error) {
48
+ return error instanceof WireError ? error.kind : "auth-transport";
49
+ }
50
+ export function refusalFromCoverage(error) {
51
+ const citation = error.citation;
52
+ // Ticket 84: surface the citation message at the top level too, so the
53
+ // self-correcting lane hints (closest lane id, valid values, omission
54
+ // default) are visible without opening the citation.
55
+ if (citation)
56
+ return { status: "refused", blocker: "capability-out-of-coverage", message: citation.message, citation };
57
+ throw error;
58
+ }
59
+ /**
60
+ * Resolve the application code for a call. Priority order (the user's
61
+ * directive, encoded end-to-end):
62
+ * 1. explicit argument (`args.applicationCode`)
63
+ * 2. project-environment binding (the env that OMC_ENV / .omc-env selects,
64
+ * via `session.profile.applicationCode` — `resolveEnvironment` itself
65
+ * honors the OMC_ENV > .omc-env > default priority chain, so this
66
+ * field already reflects the project-env binding)
67
+ * 3. the tenant's first application (fallback when the configured env
68
+ * leaves `applicationCode` blank — the agent can still operate across
69
+ * applications via discover_environment)
70
+ * The configured applicationCode is a hint, never the only application —
71
+ * callers may operate across applications.
72
+ */
73
+ export async function resolveApplicationCode(session, requested) {
74
+ if (typeof requested === "string" && requested.trim())
75
+ return requested.trim();
76
+ if (session.profile.applicationCode && session.profile.applicationCode.trim())
77
+ return session.profile.applicationCode;
78
+ const apps = wireRows((await wireCall(session, { path: ENDPOINTS.applicationList, query: {} })).data);
79
+ const first = apps.find((row) => typeof (row.appCode ?? row.code) === "string");
80
+ const code = first ? String(first.appCode ?? first.code) : "";
81
+ if (!code)
82
+ throw new WireError("business", "no application found for this environment", 302001);
83
+ return code;
84
+ }
85
+ export function flattenTreeRows(rows) {
86
+ const flat = [];
87
+ const visit = (row) => {
88
+ flat.push(row);
89
+ if (Array.isArray(row.children))
90
+ for (const child of row.children)
91
+ if (child && typeof child === "object")
92
+ visit(child);
93
+ };
94
+ for (const row of rows)
95
+ visit(row);
96
+ return flat;
97
+ }
98
+ //# sourceMappingURL=support.js.map