@rsconcept/rstool 0.10.3 → 1.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +61 -33
- package/dist/agent-workflow-Gk0Vfnv1.d.ts +64 -0
- package/dist/analysis-LLnPhmGa.d.ts +23 -0
- package/dist/{common-DxLg3eXX.d.ts → common-DHJalS-Q.d.ts} +6 -1
- package/dist/constituenta-DnGR6bnM.d.ts +54 -0
- package/dist/diagnostic-D9yl_mEL.d.ts +19 -0
- package/dist/evaluation-Cns8BFm4.d.ts +31 -0
- package/dist/index.d.ts +11 -11
- package/dist/index.js +1 -2
- package/dist/mappers/model-adapter.d.ts +3 -3
- package/dist/mappers/schema-adapter.d.ts +4 -4
- package/dist/mappers/types.d.ts +6 -2
- package/dist/mappers/types.js +2 -0
- package/dist/mappers/types.js.map +1 -1
- package/dist/{model-value-SFAVj0dw.d.ts → model-value-BbonPzMz.d.ts} +14 -3
- package/dist/models/agent-workflow.d.ts +2 -0
- package/dist/models/agent-workflow.js +1 -0
- package/dist/models/analysis.d.ts +1 -1
- package/dist/models/common.d.ts +1 -1
- package/dist/models/constituenta.d.ts +2 -2
- package/dist/models/diagnostic.d.ts +1 -1
- package/dist/models/evaluation.d.ts +2 -2
- package/dist/models/index.d.ts +11 -11
- package/dist/models/index.js +2 -2
- package/dist/models/model-value.d.ts +2 -2
- package/dist/models/rstool-agent.d.ts +1 -1
- package/dist/models/rstool-agent.js +1 -1
- package/dist/models/session.d.ts +1 -1
- package/dist/models/tool-contract.d.ts +2 -2
- package/dist/models/tool-contract.js +2 -1
- package/dist/models/tool-contract.js.map +1 -1
- package/dist/models-Bw6Uum8i.js +685 -0
- package/dist/models-Bw6Uum8i.js.map +1 -0
- package/dist/rstool-agent-D2cQze_b.d.ts +71 -0
- package/dist/session/session-store.d.ts +18 -5
- package/dist/session/session-store.js +1 -64
- package/dist/{session-BPgsE80c.d.ts → session-ChexW8i7.d.ts} +11 -8
- package/dist/tool-contract-0uRGhEfW.d.ts +164 -0
- package/dist/wrapper/client.d.ts +23 -0
- package/dist/wrapper/client.js +17 -0
- package/dist/wrapper/client.js.map +1 -1
- package/dist/wrapper/stdio-wrapper.js +75 -63
- package/dist/wrapper/stdio-wrapper.js.map +1 -1
- package/docs/CONSTITUENTA.md +2 -2
- package/docs/DIAGNOSTICS.md +6 -5
- package/docs/MODEL-TESTING.md +3 -3
- package/docs/PORTAL-API.md +24 -18
- package/examples/README.md +1 -1
- package/examples/agent-client.ts +11 -41
- package/examples/build-chocolate-nim-rsform.ts +21 -70
- package/examples/chocolate-nim/build-rsform.ts +23 -18
- package/examples/chocolate-nim/build-rsmodel.ts +10 -12
- package/examples/chocolate-nim/rsform-session.json +290 -290
- package/examples/chocolate-nim/rsmodel-session.json +291 -291
- package/examples/expression-bank/bank-constituents.ts +304 -53
- package/examples/expression-bank/build-rsform.ts +19 -16
- package/examples/expression-bank/rsform-session.json +1551 -1551
- package/examples/kinship/build-rsform.ts +23 -18
- package/examples/kinship/build-rsmodel.ts +16 -16
- package/examples/kinship/rsform-session.json +219 -219
- package/examples/kinship/rsmodel-session.json +221 -221
- package/examples/kinship/session.ts +19 -21
- package/examples/movd/build-rsform.ts +23 -18
- package/examples/movd/build-rsmodel.ts +18 -20
- package/examples/movd/rsform-session.json +262 -262
- package/examples/movd/rsmodel-session.json +264 -264
- package/examples/sample/build-rsform.ts +18 -51
- package/examples/sample/build-rsmodel.ts +25 -44
- package/examples/sample/rsform-session.json +10 -7
- package/examples/sample/rsmodel-session.json +36 -33
- package/examples/template-apply/build-rsform.ts +27 -24
- package/examples/template-apply/rsform-session.json +48 -48
- package/package.json +4 -2
- package/skills/rstool-helper/EXAMPLES.md +44 -116
- package/skills/rstool-helper/GUIDE.md +40 -25
- package/skills/rstool-helper/REFERENCE.md +40 -177
- package/src/index.ts +24 -17
- package/src/mappers/portal-adapter.ts +49 -0
- package/src/mappers/types.ts +4 -0
- package/src/models/agent-workflow.ts +66 -0
- package/src/models/analysis.ts +7 -0
- package/src/models/common.ts +7 -0
- package/src/models/constituenta.ts +24 -6
- package/src/models/diagnostic.ts +4 -0
- package/src/models/evaluation.ts +11 -0
- package/src/models/import-detect.test.ts +66 -0
- package/src/models/import-detect.ts +42 -0
- package/src/models/import-export.ts +24 -0
- package/src/models/index.ts +22 -14
- package/src/models/model-value.ts +12 -0
- package/src/models/portal-json.test.ts +38 -0
- package/src/models/portal-json.ts +54 -1
- package/src/models/rstool-agent.test.ts +698 -146
- package/src/models/rstool-agent.ts +392 -92
- package/src/models/session.ts +8 -5
- package/src/models/tool-contract.ts +81 -42
- package/src/session/batch-apply.test.ts +123 -0
- package/src/session/batch-apply.ts +82 -0
- package/src/session/persistence.test.ts +63 -0
- package/src/session/persistence.ts +69 -0
- package/src/session/session-store.ts +76 -6
- package/src/wrapper/client.test.ts +58 -0
- package/src/wrapper/client.ts +23 -0
- package/src/wrapper/stdio-handler.test.ts +101 -0
- package/src/wrapper/stdio-handler.ts +195 -0
- package/src/wrapper/stdio-wrapper.ts +4 -187
- package/dist/analysis-JiwOYDKx.d.ts +0 -16
- package/dist/constituenta-Dnd6iToB.d.ts +0 -36
- package/dist/diagnostic-BMYvciz8.d.ts +0 -15
- package/dist/evaluation-CCVYH0wA.d.ts +0 -21
- package/dist/index-uhkmwruf.d.ts +0 -46
- package/dist/rstool-agent-BZi5jO1y.js +0 -158
- package/dist/rstool-agent-BZi5jO1y.js.map +0 -1
- package/dist/rstool-agent-pRaPnZay.d.ts +0 -35
- package/dist/session/session-store.js.map +0 -1
- package/dist/tool-contract-n1ghUOrK.d.ts +0 -32
|
@@ -0,0 +1,685 @@
|
|
|
1
|
+
import { CstType } from "./models/common.js";
|
|
2
|
+
import { ModelAdapter } from "./mappers/model-adapter.js";
|
|
3
|
+
import { SchemaAdapter } from "./mappers/schema-adapter.js";
|
|
4
|
+
import { CONTRACT_VERSION } from "./models/tool-contract.js";
|
|
5
|
+
import { Graph } from "@rsconcept/domain/graph/graph";
|
|
6
|
+
import { extractGlobals } from "@rsconcept/domain/rslang/api";
|
|
7
|
+
import { randomUUID } from "node:crypto";
|
|
8
|
+
import fs from "node:fs";
|
|
9
|
+
import path from "node:path";
|
|
10
|
+
//#region src/models/portal-json.ts
|
|
11
|
+
const CST_TYPE_VALUES = new Set(Object.values(CstType));
|
|
12
|
+
function parsePortalCstType(value, alias) {
|
|
13
|
+
if (!CST_TYPE_VALUES.has(value)) throw new Error(`Invalid cst_type "${value}" for constituent "${alias}"`);
|
|
14
|
+
return value;
|
|
15
|
+
}
|
|
16
|
+
/** Portal JSON import/export format version (schema and model files). */
|
|
17
|
+
const PORTAL_JSON_CONTRACT_VERSION = "1.0.0";
|
|
18
|
+
/** Map a Portal API or JSON schema item to an agent {@link ConstituentaDraft}. */
|
|
19
|
+
function portalItemToDraft(item) {
|
|
20
|
+
return {
|
|
21
|
+
id: item.id,
|
|
22
|
+
alias: item.alias,
|
|
23
|
+
cstType: parsePortalCstType(item.cst_type, item.alias),
|
|
24
|
+
definitionFormal: item.definition_formal ?? "",
|
|
25
|
+
term: item.term_raw ?? "",
|
|
26
|
+
definitionText: item.definition_raw ?? "",
|
|
27
|
+
convention: item.convention ?? ""
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
//#endregion
|
|
31
|
+
//#region src/mappers/portal-adapter.ts
|
|
32
|
+
function portalMetadataToSessionSeed(data) {
|
|
33
|
+
return {
|
|
34
|
+
alias: data.alias ?? "",
|
|
35
|
+
title: data.title ?? "",
|
|
36
|
+
comment: data.description ?? "",
|
|
37
|
+
items: []
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
function portalSchemaToSessionSeed(data) {
|
|
41
|
+
return portalMetadataToSessionSeed(data);
|
|
42
|
+
}
|
|
43
|
+
function portalDetailsToSessionSeed(data) {
|
|
44
|
+
return portalMetadataToSessionSeed(data);
|
|
45
|
+
}
|
|
46
|
+
function portalItemsToDrafts(items) {
|
|
47
|
+
return items.map((item) => portalItemToDraft(item));
|
|
48
|
+
}
|
|
49
|
+
function portalSchemaToDrafts(data) {
|
|
50
|
+
return portalItemsToDrafts(data.items);
|
|
51
|
+
}
|
|
52
|
+
function portalDetailsToDrafts(data) {
|
|
53
|
+
return portalItemsToDrafts(data.items);
|
|
54
|
+
}
|
|
55
|
+
//#endregion
|
|
56
|
+
//#region src/session/batch-apply.ts
|
|
57
|
+
/** Order drafts so suppliers are applied before dependents. */
|
|
58
|
+
function orderDrafts(sessionItems, drafts) {
|
|
59
|
+
const merged = /* @__PURE__ */ new Map();
|
|
60
|
+
for (const item of sessionItems) merged.set(item.id, {
|
|
61
|
+
id: item.id,
|
|
62
|
+
alias: item.alias,
|
|
63
|
+
cstType: item.cstType,
|
|
64
|
+
definitionFormal: item.definitionFormal
|
|
65
|
+
});
|
|
66
|
+
for (const draft of drafts) merged.set(draft.id, draft);
|
|
67
|
+
const graph = new Graph();
|
|
68
|
+
const aliasToId = /* @__PURE__ */ new Map();
|
|
69
|
+
for (const [id, draft] of merged) {
|
|
70
|
+
graph.addNode(id);
|
|
71
|
+
aliasToId.set(draft.alias, id);
|
|
72
|
+
}
|
|
73
|
+
for (const [id, draft] of merged) {
|
|
74
|
+
if (!draft.definitionFormal) continue;
|
|
75
|
+
for (const alias of extractGlobals(draft.definitionFormal)) {
|
|
76
|
+
const depId = aliasToId.get(alias);
|
|
77
|
+
if (depId !== void 0 && depId !== id) graph.addEdge(depId, id);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
const draftIds = new Set(drafts.map((draft) => draft.id));
|
|
81
|
+
const topoIds = graph.topologicalOrder().filter((id) => draftIds.has(id));
|
|
82
|
+
const seen = new Set(topoIds);
|
|
83
|
+
const missing = drafts.filter((draft) => !seen.has(draft.id)).map((draft) => draft.id);
|
|
84
|
+
return [...topoIds, ...missing].map((id) => drafts.find((draft) => draft.id === id)).filter(Boolean);
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* Restore declaration order in session items after a batch apply.
|
|
88
|
+
* Topological apply order is only needed for analysis; Portal JSON uses array order.
|
|
89
|
+
*/
|
|
90
|
+
function reorderSessionItemsByDrafts(items, drafts) {
|
|
91
|
+
if (drafts.length === 0 || items.length === 0) return;
|
|
92
|
+
const draftIds = drafts.map((draft) => draft.id);
|
|
93
|
+
const draftIdSet = new Set(draftIds);
|
|
94
|
+
if (items.filter((item) => draftIdSet.has(item.id)).length === 0) return;
|
|
95
|
+
const unmentioned = items.filter((item) => !draftIdSet.has(item.id));
|
|
96
|
+
if (unmentioned.length === 0) {
|
|
97
|
+
const byId = new Map(items.map((item) => [item.id, item]));
|
|
98
|
+
items.splice(0, items.length, ...draftIds.map((id) => byId.get(id)));
|
|
99
|
+
return;
|
|
100
|
+
}
|
|
101
|
+
const existingIds = new Set(unmentioned.map((item) => item.id));
|
|
102
|
+
const newDrafts = drafts.filter((draft) => !existingIds.has(draft.id));
|
|
103
|
+
if (newDrafts.length === 0) return;
|
|
104
|
+
const newIds = new Set(newDrafts.map((draft) => draft.id));
|
|
105
|
+
const kept = items.filter((item) => !newIds.has(item.id));
|
|
106
|
+
const byId = new Map(items.map((item) => [item.id, item]));
|
|
107
|
+
items.splice(0, items.length, ...kept, ...newDrafts.map((draft) => byId.get(draft.id)));
|
|
108
|
+
}
|
|
109
|
+
//#endregion
|
|
110
|
+
//#region src/session/persistence.ts
|
|
111
|
+
const CURRENT_SESSION_FILE = "_current.json";
|
|
112
|
+
const UNSAFE_SESSION_ID = /[/\\]|\.\./;
|
|
113
|
+
function assertSafeSessionId(sessionId) {
|
|
114
|
+
if (!sessionId || UNSAFE_SESSION_ID.test(sessionId)) throw new Error(`Invalid session ID: ${sessionId}`);
|
|
115
|
+
}
|
|
116
|
+
var SessionPersistence = class {
|
|
117
|
+
dir;
|
|
118
|
+
constructor(dir) {
|
|
119
|
+
this.dir = dir;
|
|
120
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
121
|
+
}
|
|
122
|
+
save(sessionId, envelope) {
|
|
123
|
+
fs.writeFileSync(this.filePath(sessionId), JSON.stringify(envelope, null, 2), "utf-8");
|
|
124
|
+
}
|
|
125
|
+
load(sessionId) {
|
|
126
|
+
const file = this.filePath(sessionId);
|
|
127
|
+
if (!fs.existsSync(file)) return null;
|
|
128
|
+
return JSON.parse(fs.readFileSync(file, "utf-8"));
|
|
129
|
+
}
|
|
130
|
+
delete(sessionId) {
|
|
131
|
+
const file = this.filePath(sessionId);
|
|
132
|
+
if (fs.existsSync(file)) fs.unlinkSync(file);
|
|
133
|
+
}
|
|
134
|
+
saveCurrentSessionId(sessionId) {
|
|
135
|
+
fs.writeFileSync(path.join(this.dir, CURRENT_SESSION_FILE), JSON.stringify({ sessionId }, null, 2), "utf-8");
|
|
136
|
+
}
|
|
137
|
+
loadCurrentSessionId() {
|
|
138
|
+
const file = path.join(this.dir, CURRENT_SESSION_FILE);
|
|
139
|
+
if (!fs.existsSync(file)) return null;
|
|
140
|
+
return JSON.parse(fs.readFileSync(file, "utf-8")).sessionId ?? null;
|
|
141
|
+
}
|
|
142
|
+
filePath(sessionId) {
|
|
143
|
+
assertSafeSessionId(sessionId);
|
|
144
|
+
const file = path.resolve(this.dir, `${sessionId}.json`);
|
|
145
|
+
const relative = path.relative(path.resolve(this.dir), file);
|
|
146
|
+
if (relative.startsWith("..") || path.isAbsolute(relative)) throw new Error(`Invalid session ID: ${sessionId}`);
|
|
147
|
+
return file;
|
|
148
|
+
}
|
|
149
|
+
};
|
|
150
|
+
//#endregion
|
|
151
|
+
//#region src/session/session-store.ts
|
|
152
|
+
var SessionStore = class {
|
|
153
|
+
sessions = /* @__PURE__ */ new Map();
|
|
154
|
+
persistence;
|
|
155
|
+
constructor(options = {}) {
|
|
156
|
+
this.persistence = options.persistenceDir ? new SessionPersistence(options.persistenceDir) : null;
|
|
157
|
+
}
|
|
158
|
+
create(initial, contractVersion) {
|
|
159
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
160
|
+
const sessionId = initial?.sessionId ?? randomUUID();
|
|
161
|
+
const envelope = {
|
|
162
|
+
state: {
|
|
163
|
+
sessionId,
|
|
164
|
+
alias: initial?.alias ?? "",
|
|
165
|
+
title: initial?.title ?? "",
|
|
166
|
+
comment: initial?.comment ?? "",
|
|
167
|
+
createdAt: initial?.createdAt ?? now,
|
|
168
|
+
updatedAt: now,
|
|
169
|
+
revisions: initial?.revisions ?? [],
|
|
170
|
+
items: initial?.items ?? [],
|
|
171
|
+
model: initial?.model ?? { items: [] }
|
|
172
|
+
},
|
|
173
|
+
diagnostics: []
|
|
174
|
+
};
|
|
175
|
+
this.sessions.set(sessionId, envelope);
|
|
176
|
+
this.persist(sessionId, envelope);
|
|
177
|
+
return {
|
|
178
|
+
sessionId,
|
|
179
|
+
contractVersion: contractVersion ?? "2.0.0"
|
|
180
|
+
};
|
|
181
|
+
}
|
|
182
|
+
get(sessionId) {
|
|
183
|
+
const found = this.sessions.get(sessionId) ?? this.loadFromDisk(sessionId);
|
|
184
|
+
if (!found) throw new Error(`Unknown session: ${sessionId}`);
|
|
185
|
+
return found;
|
|
186
|
+
}
|
|
187
|
+
has(sessionId) {
|
|
188
|
+
if (this.sessions.has(sessionId)) return true;
|
|
189
|
+
if (!this.persistence) return false;
|
|
190
|
+
return this.persistence.load(sessionId) !== null;
|
|
191
|
+
}
|
|
192
|
+
replaceState(sessionId, nextState) {
|
|
193
|
+
const found = this.get(sessionId);
|
|
194
|
+
found.state = {
|
|
195
|
+
...nextState,
|
|
196
|
+
updatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
197
|
+
};
|
|
198
|
+
this.persist(sessionId, found);
|
|
199
|
+
}
|
|
200
|
+
addRevision(sessionId, message) {
|
|
201
|
+
const found = this.get(sessionId);
|
|
202
|
+
const revision = {
|
|
203
|
+
revisionId: randomUUID(),
|
|
204
|
+
at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
205
|
+
message
|
|
206
|
+
};
|
|
207
|
+
found.state.revisions.push(revision);
|
|
208
|
+
found.state.updatedAt = revision.at;
|
|
209
|
+
this.persist(sessionId, found);
|
|
210
|
+
return revision;
|
|
211
|
+
}
|
|
212
|
+
/** Replace active diagnostics for one constituent (or scratch when constituentId is undefined). */
|
|
213
|
+
replaceDiagnosticsForConstituent(sessionId, constituentId, records) {
|
|
214
|
+
const found = this.get(sessionId);
|
|
215
|
+
found.diagnostics = found.diagnostics.filter((record) => record.constituentId !== constituentId);
|
|
216
|
+
found.diagnostics.push(...records);
|
|
217
|
+
found.state.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
218
|
+
this.persist(sessionId, found);
|
|
219
|
+
}
|
|
220
|
+
setDiagnostics(sessionId, records) {
|
|
221
|
+
const found = this.get(sessionId);
|
|
222
|
+
found.diagnostics = [...records];
|
|
223
|
+
found.state.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
224
|
+
this.persist(sessionId, found);
|
|
225
|
+
}
|
|
226
|
+
listDiagnostics(sessionId, filters) {
|
|
227
|
+
const found = this.get(sessionId);
|
|
228
|
+
if (filters?.constituentId === void 0) return [...found.diagnostics];
|
|
229
|
+
return found.diagnostics.filter((record) => record.constituentId === filters.constituentId);
|
|
230
|
+
}
|
|
231
|
+
snapshot(sessionId) {
|
|
232
|
+
const found = this.get(sessionId);
|
|
233
|
+
return structuredClone(found);
|
|
234
|
+
}
|
|
235
|
+
restore(sessionId, snapshot) {
|
|
236
|
+
this.sessions.set(sessionId, structuredClone(snapshot));
|
|
237
|
+
this.persist(sessionId, this.sessions.get(sessionId));
|
|
238
|
+
}
|
|
239
|
+
saveCurrentSessionId(sessionId) {
|
|
240
|
+
this.persistence?.saveCurrentSessionId(sessionId);
|
|
241
|
+
}
|
|
242
|
+
loadCurrentSessionId() {
|
|
243
|
+
return this.persistence?.loadCurrentSessionId() ?? null;
|
|
244
|
+
}
|
|
245
|
+
loadFromDisk(sessionId) {
|
|
246
|
+
const loaded = this.persistence?.load(sessionId);
|
|
247
|
+
if (!loaded) return null;
|
|
248
|
+
this.sessions.set(sessionId, loaded);
|
|
249
|
+
return loaded;
|
|
250
|
+
}
|
|
251
|
+
persist(sessionId, envelope) {
|
|
252
|
+
this.persistence?.save(sessionId, envelope);
|
|
253
|
+
}
|
|
254
|
+
};
|
|
255
|
+
//#endregion
|
|
256
|
+
//#region src/models/import-detect.ts
|
|
257
|
+
function isRecord(value) {
|
|
258
|
+
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
259
|
+
}
|
|
260
|
+
function parseImportPayload(payload) {
|
|
261
|
+
if (typeof payload === "string") return JSON.parse(payload);
|
|
262
|
+
return payload;
|
|
263
|
+
}
|
|
264
|
+
function detectImportKind(data) {
|
|
265
|
+
if (!isRecord(data)) throw new Error("Invalid import payload");
|
|
266
|
+
if ("contractVersion" in data && "state" in data) return "session";
|
|
267
|
+
if ("contract_version" in data && Array.isArray(data.items)) {
|
|
268
|
+
const items = data.items;
|
|
269
|
+
if (items.length > 0) {
|
|
270
|
+
const first = items[0];
|
|
271
|
+
if (isRecord(first) && "cst_type" in first) return "portal-schema";
|
|
272
|
+
throw new Error("Portal model JSON cannot be imported as a schema session");
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
if (Array.isArray(data.items) && data.items.length > 0) {
|
|
276
|
+
const first = data.items[0];
|
|
277
|
+
if (isRecord(first) && "cst_type" in first) return "portal-details";
|
|
278
|
+
}
|
|
279
|
+
throw new Error("Cannot detect import kind; pass kind explicitly");
|
|
280
|
+
}
|
|
281
|
+
//#endregion
|
|
282
|
+
//#region src/models/rstool-agent.ts
|
|
283
|
+
function normalizeImportedState(state) {
|
|
284
|
+
return {
|
|
285
|
+
...state,
|
|
286
|
+
alias: state.alias ?? "",
|
|
287
|
+
title: state.title ?? "",
|
|
288
|
+
comment: state.comment ?? "",
|
|
289
|
+
model: state.model ?? { items: [] }
|
|
290
|
+
};
|
|
291
|
+
}
|
|
292
|
+
function portalImportMetadata(session, kind) {
|
|
293
|
+
const defaults = kind === "schema" ? {
|
|
294
|
+
title: "Conceptual schema",
|
|
295
|
+
alias: "SCHEMA"
|
|
296
|
+
} : {
|
|
297
|
+
title: "Conceptual model",
|
|
298
|
+
alias: "MODEL"
|
|
299
|
+
};
|
|
300
|
+
const title = session.title.trim();
|
|
301
|
+
const alias = session.alias.trim();
|
|
302
|
+
return {
|
|
303
|
+
title: title.length > 0 ? title : defaults.title,
|
|
304
|
+
alias: alias.length > 0 ? alias : defaults.alias,
|
|
305
|
+
description: session.comment.trim()
|
|
306
|
+
};
|
|
307
|
+
}
|
|
308
|
+
function inferCstType(alias) {
|
|
309
|
+
switch (alias.trim().charAt(0).toUpperCase()) {
|
|
310
|
+
case "X": return CstType.BASE;
|
|
311
|
+
case "C": return CstType.CONSTANT;
|
|
312
|
+
case "S": return CstType.STRUCTURED;
|
|
313
|
+
case "D": return CstType.TERM;
|
|
314
|
+
case "A": return CstType.AXIOM;
|
|
315
|
+
case "F": return CstType.FUNCTION;
|
|
316
|
+
case "P": return CstType.PREDICATE;
|
|
317
|
+
case "N": return CstType.NOMINAL;
|
|
318
|
+
case "T": return CstType.STATEMENT;
|
|
319
|
+
default: throw new Error(`Cannot infer cstType from alias "${alias}"; pass cstType explicitly`);
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
/**
|
|
323
|
+
* Agent-facing entry point for incremental RSForm editing, analysis, diagnostics,
|
|
324
|
+
* modeling, and evaluation.
|
|
325
|
+
*
|
|
326
|
+
* Holds in-memory (optionally persisted) sessions and delegates language work
|
|
327
|
+
* to internal schema and model adapters.
|
|
328
|
+
*/
|
|
329
|
+
var RSToolAgent = class {
|
|
330
|
+
contractVersion = CONTRACT_VERSION;
|
|
331
|
+
sessions;
|
|
332
|
+
adapter = new SchemaAdapter();
|
|
333
|
+
evaluation = new ModelAdapter();
|
|
334
|
+
currentSessionId;
|
|
335
|
+
/** @param options - Optional persistence directory for session storage. */
|
|
336
|
+
constructor(options = {}) {
|
|
337
|
+
this.sessions = new SessionStore({ persistenceDir: options.persistenceDir });
|
|
338
|
+
this.currentSessionId = this.sessions.loadCurrentSessionId();
|
|
339
|
+
}
|
|
340
|
+
/** @inheritdoc */
|
|
341
|
+
ensureSession(initial) {
|
|
342
|
+
return this.getCurrentSession() ?? this.createSession(initial);
|
|
343
|
+
}
|
|
344
|
+
/** @inheritdoc */
|
|
345
|
+
createSession(initial) {
|
|
346
|
+
return this.trackSession(this.sessions.create(initial, this.contractVersion));
|
|
347
|
+
}
|
|
348
|
+
/** @inheritdoc */
|
|
349
|
+
getCurrentSession() {
|
|
350
|
+
if (!this.currentSessionId) return null;
|
|
351
|
+
if (!this.sessions.has(this.currentSessionId)) {
|
|
352
|
+
this.currentSessionId = null;
|
|
353
|
+
this.sessions.saveCurrentSessionId(null);
|
|
354
|
+
return null;
|
|
355
|
+
}
|
|
356
|
+
return {
|
|
357
|
+
sessionId: this.currentSessionId,
|
|
358
|
+
contractVersion: this.contractVersion
|
|
359
|
+
};
|
|
360
|
+
}
|
|
361
|
+
/** @inheritdoc */
|
|
362
|
+
setCurrentSession(sessionId) {
|
|
363
|
+
if (!this.sessions.has(sessionId)) throw new Error(`Unknown session: ${sessionId}`);
|
|
364
|
+
return this.trackSession({
|
|
365
|
+
sessionId,
|
|
366
|
+
contractVersion: this.contractVersion
|
|
367
|
+
});
|
|
368
|
+
}
|
|
369
|
+
/** @inheritdoc */
|
|
370
|
+
applySchemaPatch(input, sessionId) {
|
|
371
|
+
const session = sessionId ? {
|
|
372
|
+
sessionId: this.resolveSessionId(sessionId),
|
|
373
|
+
contractVersion: this.contractVersion
|
|
374
|
+
} : this.ensureSession(input.initial);
|
|
375
|
+
const drafts = this.resolveAgentPatches(session.sessionId, input.items);
|
|
376
|
+
const result = this.applyConstituents({
|
|
377
|
+
drafts,
|
|
378
|
+
mode: input.mode
|
|
379
|
+
}, session.sessionId);
|
|
380
|
+
const revision = result.success && input.commitMessage ? this.commitStep(input.commitMessage, session.sessionId) : void 0;
|
|
381
|
+
return {
|
|
382
|
+
...result,
|
|
383
|
+
session,
|
|
384
|
+
summary: this.buildSessionSummary(session.sessionId),
|
|
385
|
+
revision
|
|
386
|
+
};
|
|
387
|
+
}
|
|
388
|
+
/** @inheritdoc */
|
|
389
|
+
getSessionState(detail = "summary", sessionId) {
|
|
390
|
+
if (detail === "full") {
|
|
391
|
+
const envelope = this.sessions.get(this.resolveSessionId(sessionId));
|
|
392
|
+
return structuredClone(envelope.state);
|
|
393
|
+
}
|
|
394
|
+
return this.buildSessionSummary(sessionId);
|
|
395
|
+
}
|
|
396
|
+
/** @inheritdoc */
|
|
397
|
+
listDiagnostics(filters, sessionId) {
|
|
398
|
+
return this.sessions.listDiagnostics(this.resolveSessionId(sessionId), filters);
|
|
399
|
+
}
|
|
400
|
+
/** @inheritdoc */
|
|
401
|
+
analyzeExpression(input, sessionId) {
|
|
402
|
+
const id = this.resolveSessionId(sessionId);
|
|
403
|
+
const envelope = this.sessions.get(id);
|
|
404
|
+
const { result, diagnostics } = this.adapter.analyzeAgainstSession(envelope.state, {
|
|
405
|
+
id: -1,
|
|
406
|
+
alias: "_analysis",
|
|
407
|
+
cstType: input.cstType,
|
|
408
|
+
definitionFormal: input.expression
|
|
409
|
+
});
|
|
410
|
+
if (input.recordDiagnostics) this.sessions.replaceDiagnosticsForConstituent(id, void 0, diagnostics.map((item) => ({
|
|
411
|
+
...item,
|
|
412
|
+
constituentId: void 0
|
|
413
|
+
})));
|
|
414
|
+
return result;
|
|
415
|
+
}
|
|
416
|
+
/** @inheritdoc */
|
|
417
|
+
commitStep(message, sessionId) {
|
|
418
|
+
return this.sessions.addRevision(this.resolveSessionId(sessionId), message);
|
|
419
|
+
}
|
|
420
|
+
/** @inheritdoc */
|
|
421
|
+
exportSession(sessionId) {
|
|
422
|
+
const envelope = this.sessions.get(this.resolveSessionId(sessionId));
|
|
423
|
+
return JSON.stringify({
|
|
424
|
+
contractVersion: this.contractVersion,
|
|
425
|
+
state: envelope.state,
|
|
426
|
+
diagnostics: envelope.diagnostics
|
|
427
|
+
}, null, 2);
|
|
428
|
+
}
|
|
429
|
+
/** @inheritdoc */
|
|
430
|
+
exportPortal(input, sessionId) {
|
|
431
|
+
const format = input.format ?? "json";
|
|
432
|
+
const object = input.kind === "schema" ? this.buildPortalSchemaObject(sessionId) : this.buildPortalModelObject(sessionId);
|
|
433
|
+
return format === "object" ? object : JSON.stringify(object, null, 2);
|
|
434
|
+
}
|
|
435
|
+
/** @inheritdoc */
|
|
436
|
+
importData(payload, kind = "auto") {
|
|
437
|
+
const parsed = parseImportPayload(payload);
|
|
438
|
+
const resolvedKind = kind === "auto" ? detectImportKind(parsed) : kind;
|
|
439
|
+
switch (resolvedKind) {
|
|
440
|
+
case "session": return this.importSessionExport(parsed);
|
|
441
|
+
case "portal-schema": return this.importPortalSchemaData(parsed);
|
|
442
|
+
case "portal-details": return this.importPortalDetailsData(parsed);
|
|
443
|
+
default: throw new Error(`Unsupported import kind: ${resolvedKind}`);
|
|
444
|
+
}
|
|
445
|
+
}
|
|
446
|
+
/** @inheritdoc */
|
|
447
|
+
async setModelValues(input, sessionId) {
|
|
448
|
+
const id = this.resolveSessionId(sessionId);
|
|
449
|
+
const snapshot = this.sessions.snapshot(id);
|
|
450
|
+
try {
|
|
451
|
+
let state = this.sessions.get(id).state;
|
|
452
|
+
if (input.clear?.length) {
|
|
453
|
+
const model = await this.evaluation.clearConstituentaValues(state, input.clear);
|
|
454
|
+
state = {
|
|
455
|
+
...state,
|
|
456
|
+
model
|
|
457
|
+
};
|
|
458
|
+
this.sessions.replaceState(id, state);
|
|
459
|
+
}
|
|
460
|
+
if (input.set?.length) {
|
|
461
|
+
state = this.sessions.get(id).state;
|
|
462
|
+
const model = await this.evaluation.setConstituentaValues(state, { items: input.set });
|
|
463
|
+
state = {
|
|
464
|
+
...state,
|
|
465
|
+
model
|
|
466
|
+
};
|
|
467
|
+
this.sessions.replaceState(id, state);
|
|
468
|
+
return model;
|
|
469
|
+
}
|
|
470
|
+
return structuredClone(this.sessions.get(id).state.model);
|
|
471
|
+
} catch (error) {
|
|
472
|
+
this.sessions.restore(id, snapshot);
|
|
473
|
+
throw error;
|
|
474
|
+
}
|
|
475
|
+
}
|
|
476
|
+
/** @inheritdoc */
|
|
477
|
+
getModelState(sessionId) {
|
|
478
|
+
const envelope = this.sessions.get(this.resolveSessionId(sessionId));
|
|
479
|
+
return structuredClone(envelope.state.model);
|
|
480
|
+
}
|
|
481
|
+
/** @inheritdoc */
|
|
482
|
+
evaluate(input, sessionId) {
|
|
483
|
+
const envelope = this.sessions.get(this.resolveSessionId(sessionId));
|
|
484
|
+
if (input.constituentId !== void 0) return this.evaluation.evaluateConstituenta(envelope.state, input.constituentId);
|
|
485
|
+
if (input.expression !== void 0 && input.cstType !== void 0) return this.evaluation.evaluateExpression(envelope.state, input.expression, input.cstType);
|
|
486
|
+
throw new Error("evaluate requires constituentId or expression with cstType");
|
|
487
|
+
}
|
|
488
|
+
/** @inheritdoc */
|
|
489
|
+
recalculateModel(sessionId) {
|
|
490
|
+
const envelope = this.sessions.get(this.resolveSessionId(sessionId));
|
|
491
|
+
return this.evaluation.recalculateModel(envelope.state);
|
|
492
|
+
}
|
|
493
|
+
addOrUpdateConstituenta(input, sessionId) {
|
|
494
|
+
const id = this.resolveSessionId(sessionId);
|
|
495
|
+
const envelope = this.sessions.get(id);
|
|
496
|
+
const { result, diagnostics } = this.adapter.analyzeAgainstSession(envelope.state, input.draft);
|
|
497
|
+
const state = this.adapter.mergeStateWithDraft(envelope.state, input.draft, result);
|
|
498
|
+
this.sessions.replaceDiagnosticsForConstituent(id, input.draft.id, diagnostics);
|
|
499
|
+
return {
|
|
500
|
+
state,
|
|
501
|
+
diagnostics
|
|
502
|
+
};
|
|
503
|
+
}
|
|
504
|
+
applyConstituents(input, sessionId) {
|
|
505
|
+
const id = this.resolveSessionId(sessionId);
|
|
506
|
+
const mode = input.mode ?? "atomic";
|
|
507
|
+
const ordered = orderDrafts(this.sessions.get(id).state.items, input.drafts);
|
|
508
|
+
const snapshot = this.sessions.snapshot(id);
|
|
509
|
+
const applied = [];
|
|
510
|
+
const failed = [];
|
|
511
|
+
for (const draft of ordered) {
|
|
512
|
+
const result = this.addOrUpdateConstituenta({ draft }, id);
|
|
513
|
+
if (result.state.analysis.success) {
|
|
514
|
+
applied.push(result.state);
|
|
515
|
+
continue;
|
|
516
|
+
}
|
|
517
|
+
failed.push({
|
|
518
|
+
draft,
|
|
519
|
+
diagnostics: result.diagnostics
|
|
520
|
+
});
|
|
521
|
+
if (mode === "atomic") {
|
|
522
|
+
this.sessions.restore(id, snapshot);
|
|
523
|
+
return {
|
|
524
|
+
success: false,
|
|
525
|
+
applied: [],
|
|
526
|
+
failed,
|
|
527
|
+
diagnostics: this.sessions.listDiagnostics(id)
|
|
528
|
+
};
|
|
529
|
+
}
|
|
530
|
+
}
|
|
531
|
+
const envelope = this.sessions.get(id);
|
|
532
|
+
reorderSessionItemsByDrafts(envelope.state.items, input.drafts);
|
|
533
|
+
this.sessions.replaceState(id, envelope.state);
|
|
534
|
+
return {
|
|
535
|
+
success: failed.length === 0,
|
|
536
|
+
applied,
|
|
537
|
+
failed,
|
|
538
|
+
diagnostics: this.sessions.listDiagnostics(id)
|
|
539
|
+
};
|
|
540
|
+
}
|
|
541
|
+
buildSessionSummary(sessionId) {
|
|
542
|
+
const id = this.resolveSessionId(sessionId);
|
|
543
|
+
const envelope = this.sessions.get(id);
|
|
544
|
+
const diagnostics = this.sessions.listDiagnostics(id);
|
|
545
|
+
return {
|
|
546
|
+
sessionId: id,
|
|
547
|
+
contractVersion: this.contractVersion,
|
|
548
|
+
alias: envelope.state.alias,
|
|
549
|
+
title: envelope.state.title,
|
|
550
|
+
comment: envelope.state.comment,
|
|
551
|
+
itemCount: envelope.state.items.length,
|
|
552
|
+
modelItemCount: envelope.state.model.items.length,
|
|
553
|
+
diagnosticsCount: diagnostics.length,
|
|
554
|
+
items: envelope.state.items.map((item) => ({
|
|
555
|
+
id: item.id,
|
|
556
|
+
alias: item.alias,
|
|
557
|
+
cstType: item.cstType,
|
|
558
|
+
analysisSuccess: item.analysis.success
|
|
559
|
+
})),
|
|
560
|
+
diagnostics,
|
|
561
|
+
lastRevision: envelope.state.revisions.at(-1)
|
|
562
|
+
};
|
|
563
|
+
}
|
|
564
|
+
buildPortalSchemaObject(sessionId) {
|
|
565
|
+
const envelope = this.sessions.get(this.resolveSessionId(sessionId));
|
|
566
|
+
return {
|
|
567
|
+
contract_version: PORTAL_JSON_CONTRACT_VERSION,
|
|
568
|
+
...portalImportMetadata(envelope.state, "schema"),
|
|
569
|
+
items: envelope.state.items.map((item) => ({
|
|
570
|
+
id: item.id,
|
|
571
|
+
alias: item.alias,
|
|
572
|
+
convention: item.convention,
|
|
573
|
+
crucial: false,
|
|
574
|
+
cst_type: item.cstType,
|
|
575
|
+
definition_formal: item.definitionFormal,
|
|
576
|
+
typification_manual: "",
|
|
577
|
+
value_is_property: false,
|
|
578
|
+
definition_raw: item.definitionText,
|
|
579
|
+
definition_resolved: item.definitionText,
|
|
580
|
+
term_raw: item.term,
|
|
581
|
+
term_resolved: item.term,
|
|
582
|
+
term_forms: []
|
|
583
|
+
})),
|
|
584
|
+
attribution: []
|
|
585
|
+
};
|
|
586
|
+
}
|
|
587
|
+
buildPortalModelObject(sessionId) {
|
|
588
|
+
const envelope = this.sessions.get(this.resolveSessionId(sessionId));
|
|
589
|
+
return {
|
|
590
|
+
contract_version: PORTAL_JSON_CONTRACT_VERSION,
|
|
591
|
+
...portalImportMetadata(envelope.state, "model"),
|
|
592
|
+
items: envelope.state.model.items.map((item) => ({
|
|
593
|
+
id: item.id,
|
|
594
|
+
type: item.type,
|
|
595
|
+
value: item.value
|
|
596
|
+
}))
|
|
597
|
+
};
|
|
598
|
+
}
|
|
599
|
+
importSessionExport(parsed) {
|
|
600
|
+
if (!parsed || typeof parsed !== "object" || !("state" in parsed)) throw new Error("Invalid session export payload");
|
|
601
|
+
const data = parsed;
|
|
602
|
+
const handle = this.sessions.create(normalizeImportedState(data.state), this.contractVersion);
|
|
603
|
+
if (data.diagnostics?.length) this.sessions.setDiagnostics(handle.sessionId, data.diagnostics);
|
|
604
|
+
return this.trackSession(handle);
|
|
605
|
+
}
|
|
606
|
+
importPortalSchemaData(data) {
|
|
607
|
+
const handle = this.createSession(portalSchemaToSessionSeed(data));
|
|
608
|
+
this.applyConstituents({
|
|
609
|
+
drafts: portalSchemaToDrafts(data),
|
|
610
|
+
mode: "best_effort"
|
|
611
|
+
}, handle.sessionId);
|
|
612
|
+
return handle;
|
|
613
|
+
}
|
|
614
|
+
importPortalDetailsData(data) {
|
|
615
|
+
const handle = this.createSession(portalDetailsToSessionSeed(data));
|
|
616
|
+
this.applyConstituents({
|
|
617
|
+
drafts: portalDetailsToDrafts(data),
|
|
618
|
+
mode: "best_effort"
|
|
619
|
+
}, handle.sessionId);
|
|
620
|
+
return handle;
|
|
621
|
+
}
|
|
622
|
+
resolveAgentPatches(sessionId, patches) {
|
|
623
|
+
const items = this.sessions.get(sessionId).state.items;
|
|
624
|
+
const existingByAlias = new Map(items.map((item) => [item.alias, item]));
|
|
625
|
+
const usedIds = new Set(items.map((item) => item.id));
|
|
626
|
+
let nextId = items.reduce((max, item) => Math.max(max, item.id), 0) + 1;
|
|
627
|
+
const reserveId = (id) => {
|
|
628
|
+
usedIds.add(id);
|
|
629
|
+
if (id >= nextId) nextId = id + 1;
|
|
630
|
+
};
|
|
631
|
+
const allocateId = () => {
|
|
632
|
+
while (usedIds.has(nextId)) nextId += 1;
|
|
633
|
+
const id = nextId;
|
|
634
|
+
nextId += 1;
|
|
635
|
+
usedIds.add(id);
|
|
636
|
+
return id;
|
|
637
|
+
};
|
|
638
|
+
return patches.map((patch) => {
|
|
639
|
+
const existing = existingByAlias.get(patch.alias);
|
|
640
|
+
let id;
|
|
641
|
+
if (patch.id !== void 0) {
|
|
642
|
+
id = patch.id;
|
|
643
|
+
reserveId(id);
|
|
644
|
+
} else if (existing !== void 0) id = existing.id;
|
|
645
|
+
else id = allocateId();
|
|
646
|
+
const draft = {
|
|
647
|
+
id,
|
|
648
|
+
alias: patch.alias,
|
|
649
|
+
cstType: patch.cstType ?? existing?.cstType ?? inferCstType(patch.alias),
|
|
650
|
+
definitionFormal: patch.definitionFormal ?? existing?.definitionFormal ?? "",
|
|
651
|
+
term: patch.term ?? existing?.term ?? "",
|
|
652
|
+
definitionText: patch.definitionText ?? existing?.definitionText ?? "",
|
|
653
|
+
convention: patch.convention ?? existing?.convention ?? ""
|
|
654
|
+
};
|
|
655
|
+
existingByAlias.set(patch.alias, {
|
|
656
|
+
...draft,
|
|
657
|
+
analysis: existing?.analysis ?? {
|
|
658
|
+
success: true,
|
|
659
|
+
type: null,
|
|
660
|
+
valueClass: "value",
|
|
661
|
+
diagnostics: []
|
|
662
|
+
}
|
|
663
|
+
});
|
|
664
|
+
return draft;
|
|
665
|
+
});
|
|
666
|
+
}
|
|
667
|
+
resolveSessionId(sessionId) {
|
|
668
|
+
const id = sessionId ?? this.currentSessionId;
|
|
669
|
+
if (!id) return this.createSession().sessionId;
|
|
670
|
+
if (!this.sessions.has(id)) {
|
|
671
|
+
if (sessionId) throw new Error(`Unknown session: ${sessionId}`);
|
|
672
|
+
return this.createSession().sessionId;
|
|
673
|
+
}
|
|
674
|
+
return id;
|
|
675
|
+
}
|
|
676
|
+
trackSession(handle) {
|
|
677
|
+
this.currentSessionId = handle.sessionId;
|
|
678
|
+
this.sessions.saveCurrentSessionId(handle.sessionId);
|
|
679
|
+
return handle;
|
|
680
|
+
}
|
|
681
|
+
};
|
|
682
|
+
//#endregion
|
|
683
|
+
export { portalItemToDraft as i, SessionStore as n, PORTAL_JSON_CONTRACT_VERSION as r, RSToolAgent as t };
|
|
684
|
+
|
|
685
|
+
//# sourceMappingURL=models-Bw6Uum8i.js.map
|