@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.
Files changed (116) hide show
  1. package/README.md +61 -33
  2. package/dist/agent-workflow-Gk0Vfnv1.d.ts +64 -0
  3. package/dist/analysis-LLnPhmGa.d.ts +23 -0
  4. package/dist/{common-DxLg3eXX.d.ts → common-DHJalS-Q.d.ts} +6 -1
  5. package/dist/constituenta-DnGR6bnM.d.ts +54 -0
  6. package/dist/diagnostic-D9yl_mEL.d.ts +19 -0
  7. package/dist/evaluation-Cns8BFm4.d.ts +31 -0
  8. package/dist/index.d.ts +11 -11
  9. package/dist/index.js +1 -2
  10. package/dist/mappers/model-adapter.d.ts +3 -3
  11. package/dist/mappers/schema-adapter.d.ts +4 -4
  12. package/dist/mappers/types.d.ts +6 -2
  13. package/dist/mappers/types.js +2 -0
  14. package/dist/mappers/types.js.map +1 -1
  15. package/dist/{model-value-SFAVj0dw.d.ts → model-value-BbonPzMz.d.ts} +14 -3
  16. package/dist/models/agent-workflow.d.ts +2 -0
  17. package/dist/models/agent-workflow.js +1 -0
  18. package/dist/models/analysis.d.ts +1 -1
  19. package/dist/models/common.d.ts +1 -1
  20. package/dist/models/constituenta.d.ts +2 -2
  21. package/dist/models/diagnostic.d.ts +1 -1
  22. package/dist/models/evaluation.d.ts +2 -2
  23. package/dist/models/index.d.ts +11 -11
  24. package/dist/models/index.js +2 -2
  25. package/dist/models/model-value.d.ts +2 -2
  26. package/dist/models/rstool-agent.d.ts +1 -1
  27. package/dist/models/rstool-agent.js +1 -1
  28. package/dist/models/session.d.ts +1 -1
  29. package/dist/models/tool-contract.d.ts +2 -2
  30. package/dist/models/tool-contract.js +2 -1
  31. package/dist/models/tool-contract.js.map +1 -1
  32. package/dist/models-Bw6Uum8i.js +685 -0
  33. package/dist/models-Bw6Uum8i.js.map +1 -0
  34. package/dist/rstool-agent-D2cQze_b.d.ts +71 -0
  35. package/dist/session/session-store.d.ts +18 -5
  36. package/dist/session/session-store.js +1 -64
  37. package/dist/{session-BPgsE80c.d.ts → session-ChexW8i7.d.ts} +11 -8
  38. package/dist/tool-contract-0uRGhEfW.d.ts +164 -0
  39. package/dist/wrapper/client.d.ts +23 -0
  40. package/dist/wrapper/client.js +17 -0
  41. package/dist/wrapper/client.js.map +1 -1
  42. package/dist/wrapper/stdio-wrapper.js +75 -63
  43. package/dist/wrapper/stdio-wrapper.js.map +1 -1
  44. package/docs/CONSTITUENTA.md +2 -2
  45. package/docs/DIAGNOSTICS.md +6 -5
  46. package/docs/MODEL-TESTING.md +3 -3
  47. package/docs/PORTAL-API.md +24 -18
  48. package/examples/README.md +1 -1
  49. package/examples/agent-client.ts +11 -41
  50. package/examples/build-chocolate-nim-rsform.ts +21 -70
  51. package/examples/chocolate-nim/build-rsform.ts +23 -18
  52. package/examples/chocolate-nim/build-rsmodel.ts +10 -12
  53. package/examples/chocolate-nim/rsform-session.json +290 -290
  54. package/examples/chocolate-nim/rsmodel-session.json +291 -291
  55. package/examples/expression-bank/bank-constituents.ts +304 -53
  56. package/examples/expression-bank/build-rsform.ts +19 -16
  57. package/examples/expression-bank/rsform-session.json +1551 -1551
  58. package/examples/kinship/build-rsform.ts +23 -18
  59. package/examples/kinship/build-rsmodel.ts +16 -16
  60. package/examples/kinship/rsform-session.json +219 -219
  61. package/examples/kinship/rsmodel-session.json +221 -221
  62. package/examples/kinship/session.ts +19 -21
  63. package/examples/movd/build-rsform.ts +23 -18
  64. package/examples/movd/build-rsmodel.ts +18 -20
  65. package/examples/movd/rsform-session.json +262 -262
  66. package/examples/movd/rsmodel-session.json +264 -264
  67. package/examples/sample/build-rsform.ts +18 -51
  68. package/examples/sample/build-rsmodel.ts +25 -44
  69. package/examples/sample/rsform-session.json +10 -7
  70. package/examples/sample/rsmodel-session.json +36 -33
  71. package/examples/template-apply/build-rsform.ts +27 -24
  72. package/examples/template-apply/rsform-session.json +48 -48
  73. package/package.json +4 -2
  74. package/skills/rstool-helper/EXAMPLES.md +44 -116
  75. package/skills/rstool-helper/GUIDE.md +40 -25
  76. package/skills/rstool-helper/REFERENCE.md +40 -177
  77. package/src/index.ts +24 -17
  78. package/src/mappers/portal-adapter.ts +49 -0
  79. package/src/mappers/types.ts +4 -0
  80. package/src/models/agent-workflow.ts +66 -0
  81. package/src/models/analysis.ts +7 -0
  82. package/src/models/common.ts +7 -0
  83. package/src/models/constituenta.ts +24 -6
  84. package/src/models/diagnostic.ts +4 -0
  85. package/src/models/evaluation.ts +11 -0
  86. package/src/models/import-detect.test.ts +66 -0
  87. package/src/models/import-detect.ts +42 -0
  88. package/src/models/import-export.ts +24 -0
  89. package/src/models/index.ts +22 -14
  90. package/src/models/model-value.ts +12 -0
  91. package/src/models/portal-json.test.ts +38 -0
  92. package/src/models/portal-json.ts +54 -1
  93. package/src/models/rstool-agent.test.ts +698 -146
  94. package/src/models/rstool-agent.ts +392 -92
  95. package/src/models/session.ts +8 -5
  96. package/src/models/tool-contract.ts +81 -42
  97. package/src/session/batch-apply.test.ts +123 -0
  98. package/src/session/batch-apply.ts +82 -0
  99. package/src/session/persistence.test.ts +63 -0
  100. package/src/session/persistence.ts +69 -0
  101. package/src/session/session-store.ts +76 -6
  102. package/src/wrapper/client.test.ts +58 -0
  103. package/src/wrapper/client.ts +23 -0
  104. package/src/wrapper/stdio-handler.test.ts +101 -0
  105. package/src/wrapper/stdio-handler.ts +195 -0
  106. package/src/wrapper/stdio-wrapper.ts +4 -187
  107. package/dist/analysis-JiwOYDKx.d.ts +0 -16
  108. package/dist/constituenta-Dnd6iToB.d.ts +0 -36
  109. package/dist/diagnostic-BMYvciz8.d.ts +0 -15
  110. package/dist/evaluation-CCVYH0wA.d.ts +0 -21
  111. package/dist/index-uhkmwruf.d.ts +0 -46
  112. package/dist/rstool-agent-BZi5jO1y.js +0 -158
  113. package/dist/rstool-agent-BZi5jO1y.js.map +0 -1
  114. package/dist/rstool-agent-pRaPnZay.d.ts +0 -35
  115. package/dist/session/session-store.js.map +0 -1
  116. package/dist/tool-contract-n1ghUOrK.d.ts +0 -32
@@ -1,31 +1,44 @@
1
+ import {
2
+ portalDetailsToDrafts,
3
+ portalDetailsToSessionSeed,
4
+ portalSchemaToDrafts,
5
+ portalSchemaToSessionSeed
6
+ } from '../mappers/portal-adapter';
1
7
  import { ModelAdapter } from '../mappers/model-adapter';
2
8
  import { SchemaAdapter } from '../mappers/schema-adapter';
9
+ import { orderDrafts, reorderSessionItemsByDrafts } from '../session/batch-apply';
3
10
  import { SessionStore } from '../session/session-store';
11
+ import {
12
+ type AgentConstituentaPatch,
13
+ type ApplySchemaPatchInput,
14
+ type ApplySchemaPatchResult,
15
+ type SessionStateDetail,
16
+ type SessionStateResult,
17
+ type SessionSummary
18
+ } from './agent-workflow';
4
19
  import { type AnalysisResult, type AnalyzeExpressionInput } from './analysis';
20
+ import { CstType } from './common';
5
21
  import {
6
22
  type AddOrUpdateConstituentaInput,
7
- type AddOrUpdateConstituentaResult
23
+ type AddOrUpdateConstituentaResult,
24
+ type ApplyConstituentsInput,
25
+ type ApplyConstituentsResult,
26
+ type ConstituentaDraft,
27
+ type ConstituentaState
8
28
  } from './constituenta';
9
- import { type ListDiagnosticsFilters } from './diagnostic';
10
- import {
11
- type EvaluateConstituentaInput,
12
- type EvaluateExpressionInput,
13
- type EvaluationResult
14
- } from './evaluation';
15
- import {
16
- type ClearConstituentaValuesInput,
17
- type RecalculateModelResult,
18
- type SessionModelState,
19
- type SetConstituentaValueInput,
20
- type SetConstituentaValuesInput
21
- } from './model-value';
29
+ import { type DiagnosticRecord, type ListDiagnosticsFilters } from './diagnostic';
30
+ import { type EvaluateInput, type EvaluationResult } from './evaluation';
31
+ import { detectImportKind, parseImportPayload } from './import-detect';
32
+ import { type ExportPortalInput, type ExportPortalResult, type ImportDataKind } from './import-export';
33
+ import { type RecalculateModelResult, type SessionModelState, type SetModelValuesInput } from './model-value';
22
34
  import {
23
35
  PORTAL_JSON_CONTRACT_VERSION,
24
36
  type PortalModelImportData,
37
+ type PortalRsformDetails,
25
38
  type PortalSchemaImportData
26
39
  } from './portal-json';
27
40
  import { type SessionHandle, type SessionRevision, type SessionState } from './session';
28
- import { CONTRACT_VERSION, type RSToolAgentContract } from './tool-contract';
41
+ import { CONTRACT_VERSION, type RSToolAgentContract, type RSToolAgentOptions } from './tool-contract';
29
42
 
30
43
  function normalizeImportedState(state: SessionState): SessionState {
31
44
  return {
@@ -42,9 +55,7 @@ function portalImportMetadata(
42
55
  kind: 'schema' | 'model'
43
56
  ): Pick<PortalSchemaImportData, 'title' | 'alias' | 'description'> {
44
57
  const defaults =
45
- kind === 'schema'
46
- ? { title: 'Conceptual schema', alias: 'SCHEMA' }
47
- : { title: 'Conceptual model', alias: 'MODEL' };
58
+ kind === 'schema' ? { title: 'Conceptual schema', alias: 'SCHEMA' } : { title: 'Conceptual model', alias: 'MODEL' };
48
59
  const title = session.title.trim();
49
60
  const alias = session.alias.trim();
50
61
  return {
@@ -54,60 +65,149 @@ function portalImportMetadata(
54
65
  };
55
66
  }
56
67
 
68
+ function inferCstType(alias: string): CstType {
69
+ const prefix = alias.trim().charAt(0).toUpperCase();
70
+ switch (prefix) {
71
+ case 'X':
72
+ return CstType.BASE;
73
+ case 'C':
74
+ return CstType.CONSTANT;
75
+ case 'S':
76
+ return CstType.STRUCTURED;
77
+ case 'D':
78
+ return CstType.TERM;
79
+ case 'A':
80
+ return CstType.AXIOM;
81
+ case 'F':
82
+ return CstType.FUNCTION;
83
+ case 'P':
84
+ return CstType.PREDICATE;
85
+ case 'N':
86
+ return CstType.NOMINAL;
87
+ case 'T':
88
+ return CstType.STATEMENT;
89
+ default:
90
+ throw new Error(`Cannot infer cstType from alias "${alias}"; pass cstType explicitly`);
91
+ }
92
+ }
93
+
94
+ /**
95
+ * Agent-facing entry point for incremental RSForm editing, analysis, diagnostics,
96
+ * modeling, and evaluation.
97
+ *
98
+ * Holds in-memory (optionally persisted) sessions and delegates language work
99
+ * to internal schema and model adapters.
100
+ */
57
101
  export class RSToolAgent implements RSToolAgentContract {
58
102
  public readonly contractVersion = CONTRACT_VERSION;
59
- private readonly sessions = new SessionStore();
103
+ private readonly sessions: SessionStore;
60
104
  private readonly adapter = new SchemaAdapter();
61
105
  private readonly evaluation = new ModelAdapter();
106
+ private currentSessionId: string | null;
107
+
108
+ /** @param options - Optional persistence directory for session storage. */
109
+ public constructor(options: RSToolAgentOptions = {}) {
110
+ this.sessions = new SessionStore({ persistenceDir: options.persistenceDir });
111
+ this.currentSessionId = this.sessions.loadCurrentSessionId();
112
+ }
113
+
114
+ /** @inheritdoc */
115
+ public ensureSession(initial?: Partial<SessionState>): SessionHandle {
116
+ const current = this.getCurrentSession();
117
+ return current ?? this.createSession(initial);
118
+ }
62
119
 
120
+ /** @inheritdoc */
63
121
  public createSession(initial?: Partial<SessionState>): SessionHandle {
64
- return this.sessions.create(initial, this.contractVersion);
122
+ return this.trackSession(this.sessions.create(initial, this.contractVersion));
65
123
  }
66
124
 
67
- public addOrUpdateConstituenta(
68
- sessionId: string,
69
- input: AddOrUpdateConstituentaInput
70
- ): AddOrUpdateConstituentaResult {
71
- const envelope = this.sessions.get(sessionId);
72
- const { result, diagnostics } = this.adapter.analyzeAgainstSession(envelope.state, input.draft);
73
- const state = this.adapter.mergeStateWithDraft(envelope.state, input.draft, result);
74
- this.sessions.appendDiagnostics(sessionId, diagnostics);
125
+ /** @inheritdoc */
126
+ public getCurrentSession(): SessionHandle | null {
127
+ if (!this.currentSessionId) {
128
+ return null;
129
+ }
130
+ if (!this.sessions.has(this.currentSessionId)) {
131
+ this.currentSessionId = null;
132
+ this.sessions.saveCurrentSessionId(null);
133
+ return null;
134
+ }
135
+ return { sessionId: this.currentSessionId, contractVersion: this.contractVersion };
136
+ }
137
+
138
+ /** @inheritdoc */
139
+ public setCurrentSession(sessionId: string): SessionHandle {
140
+ if (!this.sessions.has(sessionId)) {
141
+ throw new Error(`Unknown session: ${sessionId}`);
142
+ }
143
+ return this.trackSession({ sessionId, contractVersion: this.contractVersion });
144
+ }
145
+
146
+ /** @inheritdoc */
147
+ public applySchemaPatch(input: ApplySchemaPatchInput, sessionId?: string): ApplySchemaPatchResult {
148
+ const session = sessionId
149
+ ? { sessionId: this.resolveSessionId(sessionId), contractVersion: this.contractVersion }
150
+ : this.ensureSession(input.initial);
151
+ const drafts = this.resolveAgentPatches(session.sessionId, input.items);
152
+ const result = this.applyConstituents(
153
+ {
154
+ drafts,
155
+ mode: input.mode
156
+ },
157
+ session.sessionId
158
+ );
159
+ const revision =
160
+ result.success && input.commitMessage ? this.commitStep(input.commitMessage, session.sessionId) : undefined;
75
161
  return {
76
- state,
77
- diagnostics
162
+ ...result,
163
+ session,
164
+ summary: this.buildSessionSummary(session.sessionId),
165
+ revision
78
166
  };
79
167
  }
80
168
 
81
- public analyzeExpression(sessionId: string, input: AnalyzeExpressionInput): AnalysisResult {
82
- const envelope = this.sessions.get(sessionId);
169
+ /** @inheritdoc */
170
+ public getSessionState(detail: SessionStateDetail = 'summary', sessionId?: string): SessionStateResult {
171
+ if (detail === 'full') {
172
+ const envelope = this.sessions.get(this.resolveSessionId(sessionId));
173
+ return structuredClone(envelope.state);
174
+ }
175
+ return this.buildSessionSummary(sessionId);
176
+ }
177
+
178
+ /** @inheritdoc */
179
+ public listDiagnostics(filters?: ListDiagnosticsFilters, sessionId?: string) {
180
+ return this.sessions.listDiagnostics(this.resolveSessionId(sessionId), filters);
181
+ }
182
+
183
+ /** @inheritdoc */
184
+ public analyzeExpression(input: AnalyzeExpressionInput, sessionId?: string): AnalysisResult {
185
+ const id = this.resolveSessionId(sessionId);
186
+ const envelope = this.sessions.get(id);
83
187
  const { result, diagnostics } = this.adapter.analyzeAgainstSession(envelope.state, {
84
188
  id: -1,
85
189
  alias: '_analysis',
86
190
  cstType: input.cstType,
87
191
  definitionFormal: input.expression
88
192
  });
89
- this.sessions.appendDiagnostics(
90
- sessionId,
91
- diagnostics.map(item => ({ ...item, constituentId: undefined }))
92
- );
193
+ if (input.recordDiagnostics) {
194
+ this.sessions.replaceDiagnosticsForConstituent(
195
+ id,
196
+ undefined,
197
+ diagnostics.map(item => ({ ...item, constituentId: undefined }))
198
+ );
199
+ }
93
200
  return result;
94
201
  }
95
202
 
96
- public getFormState(sessionId: string): SessionState {
97
- const envelope = this.sessions.get(sessionId);
98
- return structuredClone(envelope.state);
203
+ /** @inheritdoc */
204
+ public commitStep(message?: string, sessionId?: string): SessionRevision {
205
+ return this.sessions.addRevision(this.resolveSessionId(sessionId), message);
99
206
  }
100
207
 
101
- public listDiagnostics(sessionId: string, filters?: ListDiagnosticsFilters) {
102
- return this.sessions.listDiagnostics(sessionId, filters);
103
- }
104
-
105
- public commitStep(sessionId: string, message?: string): SessionRevision {
106
- return this.sessions.addRevision(sessionId, message);
107
- }
108
-
109
- public exportSession(sessionId: string): string {
110
- const envelope = this.sessions.get(sessionId);
208
+ /** @inheritdoc */
209
+ public exportSession(sessionId?: string): string {
210
+ const envelope = this.sessions.get(this.resolveSessionId(sessionId));
111
211
  return JSON.stringify(
112
212
  {
113
213
  contractVersion: this.contractVersion,
@@ -119,9 +219,164 @@ export class RSToolAgent implements RSToolAgentContract {
119
219
  );
120
220
  }
121
221
 
122
- public exportPortalSchema(sessionId: string): string {
123
- const envelope = this.sessions.get(sessionId);
124
- const payload: PortalSchemaImportData = {
222
+ /** @inheritdoc */
223
+ public exportPortal(input: ExportPortalInput, sessionId?: string): ExportPortalResult {
224
+ const format = input.format ?? 'json';
225
+ const object =
226
+ input.kind === 'schema' ? this.buildPortalSchemaObject(sessionId) : this.buildPortalModelObject(sessionId);
227
+ return format === 'object' ? object : JSON.stringify(object, null, 2);
228
+ }
229
+
230
+ /** @inheritdoc */
231
+ public importData(payload: string | object, kind: ImportDataKind = 'auto'): SessionHandle {
232
+ const parsed = parseImportPayload(payload);
233
+ const resolvedKind = kind === 'auto' ? detectImportKind(parsed) : kind;
234
+
235
+ switch (resolvedKind) {
236
+ case 'session':
237
+ return this.importSessionExport(parsed);
238
+ case 'portal-schema':
239
+ return this.importPortalSchemaData(parsed as PortalSchemaImportData);
240
+ case 'portal-details':
241
+ return this.importPortalDetailsData(parsed as PortalRsformDetails);
242
+ default:
243
+ throw new Error(`Unsupported import kind: ${resolvedKind as string}`);
244
+ }
245
+ }
246
+
247
+ /** @inheritdoc */
248
+ public async setModelValues(input: SetModelValuesInput, sessionId?: string): Promise<SessionModelState> {
249
+ const id = this.resolveSessionId(sessionId);
250
+ const snapshot = this.sessions.snapshot(id);
251
+
252
+ try {
253
+ let state = this.sessions.get(id).state;
254
+
255
+ if (input.clear?.length) {
256
+ const model = await this.evaluation.clearConstituentaValues(state, input.clear);
257
+ state = { ...state, model };
258
+ this.sessions.replaceState(id, state);
259
+ }
260
+
261
+ if (input.set?.length) {
262
+ state = this.sessions.get(id).state;
263
+ const model = await this.evaluation.setConstituentaValues(state, { items: input.set });
264
+ state = { ...state, model };
265
+ this.sessions.replaceState(id, state);
266
+ return model;
267
+ }
268
+
269
+ return structuredClone(this.sessions.get(id).state.model);
270
+ } catch (error) {
271
+ this.sessions.restore(id, snapshot);
272
+ throw error;
273
+ }
274
+ }
275
+
276
+ /** @inheritdoc */
277
+ public getModelState(sessionId?: string): SessionModelState {
278
+ const envelope = this.sessions.get(this.resolveSessionId(sessionId));
279
+ return structuredClone(envelope.state.model);
280
+ }
281
+
282
+ /** @inheritdoc */
283
+ public evaluate(input: EvaluateInput, sessionId?: string): EvaluationResult {
284
+ const envelope = this.sessions.get(this.resolveSessionId(sessionId));
285
+
286
+ if (input.constituentId !== undefined) {
287
+ return this.evaluation.evaluateConstituenta(envelope.state, input.constituentId);
288
+ }
289
+
290
+ if (input.expression !== undefined && input.cstType !== undefined) {
291
+ return this.evaluation.evaluateExpression(envelope.state, input.expression, input.cstType);
292
+ }
293
+
294
+ throw new Error('evaluate requires constituentId or expression with cstType');
295
+ }
296
+
297
+ /** @inheritdoc */
298
+ public recalculateModel(sessionId?: string): RecalculateModelResult {
299
+ const envelope = this.sessions.get(this.resolveSessionId(sessionId));
300
+ return this.evaluation.recalculateModel(envelope.state);
301
+ }
302
+
303
+ private addOrUpdateConstituenta(
304
+ input: AddOrUpdateConstituentaInput,
305
+ sessionId?: string
306
+ ): AddOrUpdateConstituentaResult {
307
+ const id = this.resolveSessionId(sessionId);
308
+ const envelope = this.sessions.get(id);
309
+ const { result, diagnostics } = this.adapter.analyzeAgainstSession(envelope.state, input.draft);
310
+ const state = this.adapter.mergeStateWithDraft(envelope.state, input.draft, result);
311
+ this.sessions.replaceDiagnosticsForConstituent(id, input.draft.id, diagnostics);
312
+ return { state, diagnostics };
313
+ }
314
+
315
+ private applyConstituents(input: ApplyConstituentsInput, sessionId?: string): ApplyConstituentsResult {
316
+ const id = this.resolveSessionId(sessionId);
317
+ const mode = input.mode ?? 'atomic';
318
+ const ordered = orderDrafts(this.sessions.get(id).state.items, input.drafts);
319
+ const snapshot = this.sessions.snapshot(id);
320
+ const applied: ConstituentaState[] = [];
321
+ const failed: ApplyConstituentsResult['failed'] = [];
322
+
323
+ for (const draft of ordered) {
324
+ const result = this.addOrUpdateConstituenta({ draft }, id);
325
+ if (result.state.analysis.success) {
326
+ applied.push(result.state);
327
+ continue;
328
+ }
329
+ failed.push({ draft, diagnostics: result.diagnostics });
330
+ if (mode === 'atomic') {
331
+ this.sessions.restore(id, snapshot);
332
+ return {
333
+ success: false,
334
+ applied: [],
335
+ failed,
336
+ diagnostics: this.sessions.listDiagnostics(id)
337
+ };
338
+ }
339
+ }
340
+
341
+ const envelope = this.sessions.get(id);
342
+ reorderSessionItemsByDrafts(envelope.state.items, input.drafts);
343
+ this.sessions.replaceState(id, envelope.state);
344
+
345
+ return {
346
+ success: failed.length === 0,
347
+ applied,
348
+ failed,
349
+ diagnostics: this.sessions.listDiagnostics(id)
350
+ };
351
+ }
352
+
353
+ private buildSessionSummary(sessionId?: string): SessionSummary {
354
+ const id = this.resolveSessionId(sessionId);
355
+ const envelope = this.sessions.get(id);
356
+ const diagnostics = this.sessions.listDiagnostics(id);
357
+ return {
358
+ sessionId: id,
359
+ contractVersion: this.contractVersion,
360
+ alias: envelope.state.alias,
361
+ title: envelope.state.title,
362
+ comment: envelope.state.comment,
363
+ itemCount: envelope.state.items.length,
364
+ modelItemCount: envelope.state.model.items.length,
365
+ diagnosticsCount: diagnostics.length,
366
+ items: envelope.state.items.map(item => ({
367
+ id: item.id,
368
+ alias: item.alias,
369
+ cstType: item.cstType,
370
+ analysisSuccess: item.analysis.success
371
+ })),
372
+ diagnostics,
373
+ lastRevision: envelope.state.revisions.at(-1)
374
+ };
375
+ }
376
+
377
+ private buildPortalSchemaObject(sessionId?: string): PortalSchemaImportData {
378
+ const envelope = this.sessions.get(this.resolveSessionId(sessionId));
379
+ return {
125
380
  contract_version: PORTAL_JSON_CONTRACT_VERSION,
126
381
  ...portalImportMetadata(envelope.state, 'schema'),
127
382
  items: envelope.state.items.map(item => ({
@@ -141,12 +396,11 @@ export class RSToolAgent implements RSToolAgentContract {
141
396
  })),
142
397
  attribution: []
143
398
  };
144
- return JSON.stringify(payload, null, 2);
145
399
  }
146
400
 
147
- public exportPortalModel(sessionId: string): string {
148
- const envelope = this.sessions.get(sessionId);
149
- const payload: PortalModelImportData = {
401
+ private buildPortalModelObject(sessionId?: string): PortalModelImportData {
402
+ const envelope = this.sessions.get(this.resolveSessionId(sessionId));
403
+ return {
150
404
  contract_version: PORTAL_JSON_CONTRACT_VERSION,
151
405
  ...portalImportMetadata(envelope.state, 'model'),
152
406
  items: envelope.state.model.items.map(item => ({
@@ -155,57 +409,103 @@ export class RSToolAgent implements RSToolAgentContract {
155
409
  value: item.value
156
410
  }))
157
411
  };
158
- return JSON.stringify(payload, null, 2);
159
412
  }
160
413
 
161
- public importSession(payload: string): SessionHandle {
162
- const parsed = JSON.parse(payload) as {
414
+ private importSessionExport(parsed: unknown): SessionHandle {
415
+ if (!parsed || typeof parsed !== 'object' || !('state' in parsed)) {
416
+ throw new Error('Invalid session export payload');
417
+ }
418
+ const data = parsed as {
163
419
  state: SessionState;
420
+ diagnostics?: DiagnosticRecord[];
164
421
  };
165
- return this.sessions.create(normalizeImportedState(parsed.state), this.contractVersion);
422
+ const handle = this.sessions.create(normalizeImportedState(data.state), this.contractVersion);
423
+ if (data.diagnostics?.length) {
424
+ this.sessions.setDiagnostics(handle.sessionId, data.diagnostics);
425
+ }
426
+ return this.trackSession(handle);
166
427
  }
167
428
 
168
- public async setConstituentaValue(
169
- sessionId: string,
170
- input: SetConstituentaValueInput
171
- ): Promise<SessionModelState> {
172
- const envelope = this.sessions.get(sessionId);
173
- return this.evaluation.setConstituentaValue(envelope.state, input);
429
+ private importPortalSchemaData(data: PortalSchemaImportData): SessionHandle {
430
+ const handle = this.createSession(portalSchemaToSessionSeed(data));
431
+ this.applyConstituents({ drafts: portalSchemaToDrafts(data), mode: 'best_effort' }, handle.sessionId);
432
+ return handle;
174
433
  }
175
434
 
176
- public async setConstituentaValues(
177
- sessionId: string,
178
- input: SetConstituentaValuesInput
179
- ): Promise<SessionModelState> {
180
- const envelope = this.sessions.get(sessionId);
181
- return this.evaluation.setConstituentaValues(envelope.state, input);
435
+ private importPortalDetailsData(data: PortalRsformDetails): SessionHandle {
436
+ const handle = this.createSession(portalDetailsToSessionSeed(data));
437
+ this.applyConstituents({ drafts: portalDetailsToDrafts(data), mode: 'best_effort' }, handle.sessionId);
438
+ return handle;
182
439
  }
183
440
 
184
- public async clearConstituentaValues(
185
- sessionId: string,
186
- input: ClearConstituentaValuesInput
187
- ): Promise<SessionModelState> {
188
- const envelope = this.sessions.get(sessionId);
189
- return this.evaluation.clearConstituentaValues(envelope.state, input.items);
190
- }
441
+ private resolveAgentPatches(sessionId: string, patches: AgentConstituentaPatch[]): ConstituentaDraft[] {
442
+ const items = this.sessions.get(sessionId).state.items;
443
+ const existingByAlias = new Map(items.map(item => [item.alias, item]));
444
+ const usedIds = new Set(items.map(item => item.id));
445
+ let nextId = items.reduce((max, item) => Math.max(max, item.id), 0) + 1;
191
446
 
192
- public getModelState(sessionId: string): SessionModelState {
193
- const envelope = this.sessions.get(sessionId);
194
- return structuredClone(envelope.state.model);
195
- }
447
+ const reserveId = (id: number): void => {
448
+ usedIds.add(id);
449
+ if (id >= nextId) {
450
+ nextId = id + 1;
451
+ }
452
+ };
453
+
454
+ const allocateId = (): number => {
455
+ while (usedIds.has(nextId)) {
456
+ nextId += 1;
457
+ }
458
+ const id = nextId;
459
+ nextId += 1;
460
+ usedIds.add(id);
461
+ return id;
462
+ };
196
463
 
197
- public evaluateExpression(sessionId: string, input: EvaluateExpressionInput): EvaluationResult {
198
- const envelope = this.sessions.get(sessionId);
199
- return this.evaluation.evaluateExpression(envelope.state, input.expression, input.cstType);
464
+ return patches.map(patch => {
465
+ const existing = existingByAlias.get(patch.alias);
466
+ let id: number;
467
+ if (patch.id !== undefined) {
468
+ id = patch.id;
469
+ reserveId(id);
470
+ } else if (existing !== undefined) {
471
+ id = existing.id;
472
+ } else {
473
+ id = allocateId();
474
+ }
475
+ const draft = {
476
+ id,
477
+ alias: patch.alias,
478
+ cstType: patch.cstType ?? existing?.cstType ?? inferCstType(patch.alias),
479
+ definitionFormal: patch.definitionFormal ?? existing?.definitionFormal ?? '',
480
+ term: patch.term ?? existing?.term ?? '',
481
+ definitionText: patch.definitionText ?? existing?.definitionText ?? '',
482
+ convention: patch.convention ?? existing?.convention ?? ''
483
+ };
484
+ existingByAlias.set(patch.alias, {
485
+ ...draft,
486
+ analysis: existing?.analysis ?? { success: true, type: null, valueClass: 'value', diagnostics: [] }
487
+ });
488
+ return draft;
489
+ });
200
490
  }
201
491
 
202
- public evaluateConstituenta(sessionId: string, input: EvaluateConstituentaInput): EvaluationResult {
203
- const envelope = this.sessions.get(sessionId);
204
- return this.evaluation.evaluateConstituenta(envelope.state, input.constituentId);
492
+ private resolveSessionId(sessionId?: string): string {
493
+ const id = sessionId ?? this.currentSessionId;
494
+ if (!id) {
495
+ return this.createSession().sessionId;
496
+ }
497
+ if (!this.sessions.has(id)) {
498
+ if (sessionId) {
499
+ throw new Error(`Unknown session: ${sessionId}`);
500
+ }
501
+ return this.createSession().sessionId;
502
+ }
503
+ return id;
205
504
  }
206
505
 
207
- public recalculateModel(sessionId: string): RecalculateModelResult {
208
- const envelope = this.sessions.get(sessionId);
209
- return this.evaluation.recalculateModel(envelope.state);
506
+ private trackSession(handle: SessionHandle): SessionHandle {
507
+ this.currentSessionId = handle.sessionId;
508
+ this.sessions.saveCurrentSessionId(handle.sessionId);
509
+ return handle;
210
510
  }
211
511
  }
@@ -1,17 +1,20 @@
1
1
  import { type ConstituentaState } from './constituenta';
2
2
  import { type SessionModelState } from './model-value';
3
3
 
4
+ /** Opaque session reference returned by create/import operations. */
4
5
  export interface SessionHandle {
5
6
  sessionId: string;
6
7
  contractVersion: string;
7
8
  }
8
9
 
10
+ /** Recorded checkpoint in session revision history. */
9
11
  export interface SessionRevision {
10
12
  revisionId: string;
11
13
  at: string;
12
14
  message?: string;
13
15
  }
14
16
 
17
+ /** Full in-memory session state. */
15
18
  export interface SessionState {
16
19
  sessionId: string;
17
20
  /** Library item alias for the conceptual schema or model. */
@@ -20,14 +23,14 @@ export interface SessionState {
20
23
  title: string;
21
24
  /** Developer comment (Portal JSON `description` on export). */
22
25
  comment: string;
23
- /** Date of creation. */
26
+ /** ISO timestamp of session creation. */
24
27
  createdAt: string;
25
- /** Date of last update. */
28
+ /** ISO timestamp of last mutation. */
26
29
  updatedAt: string;
27
- /** List of revisions. */
30
+ /** Revision checkpoints recorded via {@link RSToolAgent.commitStep}. */
28
31
  revisions: SessionRevision[];
29
- /** List of constituents in the session. */
32
+ /** Analyzed constituents in the conceptual schema. */
30
33
  items: ConstituentaState[];
31
- /** Model state. */
34
+ /** Evaluated model values. */
32
35
  model: SessionModelState;
33
36
  }