@rsconcept/rstool 0.10.3 → 1.0.0

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 (112) hide show
  1. package/README.md +41 -31
  2. package/dist/agent-workflow-D-PSIb-m.d.ts +70 -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 -1
  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/rstool-agent-_8bplZnb.d.ts +71 -0
  33. package/dist/rstool-agent-kijHA9ML.js +476 -0
  34. package/dist/rstool-agent-kijHA9ML.js.map +1 -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/session-store-C3jyOSqI.js +142 -0
  39. package/dist/session-store-C3jyOSqI.js.map +1 -0
  40. package/dist/tool-contract-5_Q44DGE.d.ts +164 -0
  41. package/dist/wrapper/client.d.ts +23 -0
  42. package/dist/wrapper/client.js +17 -0
  43. package/dist/wrapper/client.js.map +1 -1
  44. package/dist/wrapper/stdio-wrapper.js +62 -52
  45. package/dist/wrapper/stdio-wrapper.js.map +1 -1
  46. package/docs/CONSTITUENTA.md +2 -2
  47. package/docs/DIAGNOSTICS.md +6 -5
  48. package/docs/MODEL-TESTING.md +3 -3
  49. package/docs/PORTAL-API.md +24 -18
  50. package/examples/README.md +1 -1
  51. package/examples/agent-client.ts +11 -41
  52. package/examples/build-chocolate-nim-rsform.ts +23 -18
  53. package/examples/chocolate-nim/build-rsform.ts +23 -18
  54. package/examples/chocolate-nim/build-rsmodel.ts +10 -12
  55. package/examples/chocolate-nim/rsform-session.json +290 -290
  56. package/examples/chocolate-nim/rsmodel-session.json +291 -291
  57. package/examples/expression-bank/bank-constituents.ts +304 -53
  58. package/examples/expression-bank/build-rsform.ts +19 -16
  59. package/examples/expression-bank/rsform-session.json +1551 -1551
  60. package/examples/kinship/build-rsform.ts +23 -18
  61. package/examples/kinship/build-rsmodel.ts +13 -15
  62. package/examples/kinship/rsform-session.json +219 -219
  63. package/examples/kinship/rsmodel-session.json +221 -221
  64. package/examples/kinship/session.ts +19 -21
  65. package/examples/movd/build-rsform.ts +23 -18
  66. package/examples/movd/build-rsmodel.ts +18 -20
  67. package/examples/movd/rsform-session.json +262 -262
  68. package/examples/movd/rsmodel-session.json +264 -264
  69. package/examples/sample/build-rsform.ts +19 -50
  70. package/examples/sample/build-rsmodel.ts +25 -44
  71. package/examples/sample/rsform-session.json +36 -33
  72. package/examples/sample/rsmodel-session.json +36 -33
  73. package/examples/template-apply/build-rsform.ts +27 -24
  74. package/examples/template-apply/rsform-session.json +48 -48
  75. package/package.json +2 -2
  76. package/skills/rstool-helper/EXAMPLES.md +44 -116
  77. package/skills/rstool-helper/GUIDE.md +40 -25
  78. package/skills/rstool-helper/REFERENCE.md +40 -177
  79. package/src/index.ts +24 -17
  80. package/src/mappers/portal-adapter.ts +43 -0
  81. package/src/mappers/types.ts +4 -0
  82. package/src/models/agent-workflow.ts +78 -0
  83. package/src/models/analysis.ts +7 -0
  84. package/src/models/common.ts +7 -0
  85. package/src/models/constituenta.ts +24 -6
  86. package/src/models/diagnostic.ts +4 -0
  87. package/src/models/evaluation.ts +11 -0
  88. package/src/models/import-detect.ts +39 -0
  89. package/src/models/import-export.ts +24 -0
  90. package/src/models/index.ts +22 -14
  91. package/src/models/model-value.ts +12 -0
  92. package/src/models/portal-json.ts +44 -0
  93. package/src/models/rstool-agent.test.ts +300 -147
  94. package/src/models/rstool-agent.ts +350 -93
  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 +28 -0
  98. package/src/session/batch-apply.ts +47 -0
  99. package/src/session/persistence.ts +56 -0
  100. package/src/session/session-store.ts +67 -4
  101. package/src/wrapper/client.ts +23 -0
  102. package/src/wrapper/stdio-wrapper.ts +59 -49
  103. package/dist/analysis-JiwOYDKx.d.ts +0 -16
  104. package/dist/constituenta-Dnd6iToB.d.ts +0 -36
  105. package/dist/diagnostic-BMYvciz8.d.ts +0 -15
  106. package/dist/evaluation-CCVYH0wA.d.ts +0 -21
  107. package/dist/index-uhkmwruf.d.ts +0 -46
  108. package/dist/rstool-agent-BZi5jO1y.js +0 -158
  109. package/dist/rstool-agent-BZi5jO1y.js.map +0 -1
  110. package/dist/rstool-agent-pRaPnZay.d.ts +0 -35
  111. package/dist/session/session-store.js.map +0 -1
  112. package/dist/tool-contract-n1ghUOrK.d.ts +0 -32
@@ -1,17 +1,21 @@
1
+ import { mkdtempSync, rmSync } from 'node:fs';
2
+ import { tmpdir } from 'node:os';
3
+ import { join } from 'node:path';
1
4
  import { describe, expect, it } from 'vitest';
2
5
 
3
6
  import { CstType, EvalStatus, RSErrorCode, RSToolAgent } from './index';
4
7
 
5
8
  function buildSampleForm(tool: RSToolAgent, sessionId: string) {
6
- tool.addOrUpdateConstituenta(sessionId, {
7
- draft: { id: 1, alias: 'X1', cstType: CstType.BASE, definitionFormal: '' }
8
- });
9
- tool.addOrUpdateConstituenta(sessionId, {
10
- draft: { id: 2, alias: 'D1', cstType: CstType.TERM, definitionFormal: '1+2' }
11
- });
12
- tool.addOrUpdateConstituenta(sessionId, {
13
- draft: { id: 3, alias: 'A1', cstType: CstType.AXIOM, definitionFormal: '1=1' }
14
- });
9
+ tool.applySchemaPatch(
10
+ {
11
+ items: [{ alias: 'X1' }, { alias: 'D1', definitionFormal: '1+2' }, { alias: 'A1', definitionFormal: '1=1' }]
12
+ },
13
+ sessionId
14
+ );
15
+ }
16
+
17
+ function fullState(tool: RSToolAgent, sessionId?: string) {
18
+ return tool.getSessionState('full', sessionId) as import('./session').SessionState;
15
19
  }
16
20
 
17
21
  describe('RSToolAgent', () => {
@@ -26,10 +30,7 @@ describe('RSToolAgent', () => {
26
30
  it('analyzes a valid expression', () => {
27
31
  const tool = new RSToolAgent();
28
32
  const session = tool.createSession();
29
- const analysis = tool.analyzeExpression(session.sessionId, {
30
- expression: '1+2',
31
- cstType: CstType.TERM
32
- });
33
+ const analysis = tool.analyzeExpression({ expression: '1+2', cstType: CstType.TERM }, session.sessionId);
33
34
  expect(analysis.success).toBe(true);
34
35
  expect(analysis.diagnostics.length).toBe(0);
35
36
  });
@@ -37,10 +38,7 @@ describe('RSToolAgent', () => {
37
38
  it('returns syntax diagnostics for invalid expression', () => {
38
39
  const tool = new RSToolAgent();
39
40
  const session = tool.createSession();
40
- const analysis = tool.analyzeExpression(session.sessionId, {
41
- expression: '(',
42
- cstType: CstType.TERM
43
- });
41
+ const analysis = tool.analyzeExpression({ expression: '(', cstType: CstType.TERM }, session.sessionId);
44
42
  expect(analysis.success).toBe(false);
45
43
  expect(analysis.diagnostics.length).toBeGreaterThan(0);
46
44
  });
@@ -48,68 +46,64 @@ describe('RSToolAgent', () => {
48
46
  it('rejects formal definition for constants', () => {
49
47
  const tool = new RSToolAgent();
50
48
  const session = tool.createSession();
51
- const result = tool.addOrUpdateConstituenta(session.sessionId, {
52
- draft: {
53
- id: 11,
54
- alias: 'C1',
55
- cstType: CstType.CONSTANT,
56
- definitionFormal: 'X1'
57
- }
58
- });
59
- expect(result.state.analysis.success).toBe(false);
60
- expect(result.diagnostics[0]?.error.code).toBe(RSErrorCode.definitionNotAllowed);
49
+ const result = tool.applySchemaPatch(
50
+ {
51
+ items: [{ alias: 'C1', cstType: CstType.CONSTANT, definitionFormal: 'X1' }]
52
+ },
53
+ session.sessionId
54
+ );
55
+ expect(result.success).toBe(false);
56
+ expect(result.failed[0]?.diagnostics[0]?.error.code).toBe(RSErrorCode.definitionNotAllowed);
61
57
  });
62
58
 
63
59
  it('rejects formal definition for basic sets', () => {
64
60
  const tool = new RSToolAgent();
65
61
  const session = tool.createSession();
66
- const result = tool.addOrUpdateConstituenta(session.sessionId, {
67
- draft: {
68
- id: 12,
69
- alias: 'X1',
70
- cstType: CstType.BASE,
71
- definitionFormal: 'Z'
72
- }
73
- });
74
- expect(result.state.analysis.success).toBe(false);
75
- expect(result.diagnostics[0]?.error.code).toBe(RSErrorCode.definitionNotAllowed);
62
+ const result = tool.applySchemaPatch(
63
+ {
64
+ items: [{ alias: 'X1', cstType: CstType.BASE, definitionFormal: 'Z' }]
65
+ },
66
+ session.sessionId
67
+ );
68
+ expect(result.success).toBe(false);
69
+ expect(result.failed[0]?.diagnostics[0]?.error.code).toBe(RSErrorCode.definitionNotAllowed);
76
70
  });
77
71
 
78
72
  it('returns known analysis for empty base definition', () => {
79
73
  const tool = new RSToolAgent();
80
74
  const session = tool.createSession();
81
- const result = tool.addOrUpdateConstituenta(session.sessionId, {
82
- draft: {
83
- id: 13,
84
- alias: 'X1',
85
- cstType: CstType.BASE,
86
- definitionFormal: ''
87
- }
88
- });
89
- expect(result.state.analysis.success).toBe(true);
90
- expect(result.state.analysis.type).not.toBeNull();
91
- expect(result.state.analysis.valueClass).toBe('value');
75
+ const result = tool.applySchemaPatch(
76
+ {
77
+ items: [{ alias: 'X1', cstType: CstType.BASE, definitionFormal: '' }]
78
+ },
79
+ session.sessionId
80
+ );
81
+ expect(result.success).toBe(true);
82
+ const state = fullState(tool, session.sessionId);
83
+ expect(state.items[0]?.analysis.type).not.toBeNull();
84
+ expect(state.items[0]?.analysis.valueClass).toBe('value');
92
85
  });
93
86
 
94
87
  it('persists term, definitionText, and convention in session state', () => {
95
88
  const tool = new RSToolAgent();
96
89
  const session = tool.createSession();
97
- const result = tool.addOrUpdateConstituenta(session.sessionId, {
98
- draft: {
99
- id: 15,
100
- alias: 'D2',
101
- cstType: CstType.TERM,
102
- definitionFormal: '1',
103
- term: 'natural number',
104
- definitionText: 'A positive integer',
105
- convention: 'Standard arithmetic'
106
- }
107
- });
108
- expect(result.state.term).toBe('natural number');
109
- expect(result.state.definitionText).toBe('A positive integer');
110
- expect(result.state.convention).toBe('Standard arithmetic');
111
-
112
- const form = tool.getFormState(session.sessionId);
90
+ tool.applySchemaPatch(
91
+ {
92
+ items: [
93
+ {
94
+ alias: 'D2',
95
+ cstType: CstType.TERM,
96
+ definitionFormal: '1',
97
+ term: 'natural number',
98
+ definitionText: 'A positive integer',
99
+ convention: 'Standard arithmetic'
100
+ }
101
+ ]
102
+ },
103
+ session.sessionId
104
+ );
105
+
106
+ const form = fullState(tool, session.sessionId);
113
107
  expect(form.items[0]).toMatchObject({
114
108
  term: 'natural number',
115
109
  definitionText: 'A positive integer',
@@ -117,8 +111,8 @@ describe('RSToolAgent', () => {
117
111
  });
118
112
 
119
113
  const exported = tool.exportSession(session.sessionId);
120
- const imported = tool.importSession(exported);
121
- const restored = tool.getFormState(imported.sessionId);
114
+ const imported = tool.importData(exported, 'session');
115
+ const restored = fullState(tool, imported.sessionId);
122
116
  expect(restored.items[0]).toMatchObject({
123
117
  term: 'natural number',
124
118
  definitionText: 'A positive integer',
@@ -134,13 +128,13 @@ describe('RSToolAgent', () => {
134
128
  comment: 'Example schema'
135
129
  });
136
130
 
137
- expect(tool.getFormState(session.sessionId)).toMatchObject({
131
+ expect(fullState(tool, session.sessionId)).toMatchObject({
138
132
  alias: 'KIN',
139
133
  title: 'Kinship',
140
134
  comment: 'Example schema'
141
135
  });
142
136
 
143
- const exported = JSON.parse(tool.exportPortalSchema(session.sessionId)) as {
137
+ const exported = JSON.parse(tool.exportPortal({ kind: 'schema' }, session.sessionId) as string) as {
144
138
  title: string;
145
139
  alias: string;
146
140
  description: string;
@@ -155,19 +149,23 @@ describe('RSToolAgent', () => {
155
149
  it('exports schema data for Portal JSON import', () => {
156
150
  const tool = new RSToolAgent();
157
151
  const session = tool.createSession();
158
- tool.addOrUpdateConstituenta(session.sessionId, {
159
- draft: {
160
- id: 15,
161
- alias: 'D2',
162
- cstType: CstType.TERM,
163
- definitionFormal: '1',
164
- term: 'natural number',
165
- definitionText: 'A positive integer',
166
- convention: 'Standard arithmetic'
167
- }
168
- });
169
-
170
- const exported = JSON.parse(tool.exportPortalSchema(session.sessionId)) as {
152
+ tool.applySchemaPatch(
153
+ {
154
+ items: [
155
+ {
156
+ alias: 'D2',
157
+ cstType: CstType.TERM,
158
+ definitionFormal: '1',
159
+ term: 'natural number',
160
+ definitionText: 'A positive integer',
161
+ convention: 'Standard arithmetic'
162
+ }
163
+ ]
164
+ },
165
+ session.sessionId
166
+ );
167
+
168
+ const exported = JSON.parse(tool.exportPortal({ kind: 'schema' }, session.sessionId) as string) as {
171
169
  contract_version: string;
172
170
  title: string;
173
171
  alias: string;
@@ -182,7 +180,6 @@ describe('RSToolAgent', () => {
182
180
  expect(exported.description).toBe('');
183
181
 
184
182
  expect(exported.items[0]).toMatchObject({
185
- id: 15,
186
183
  alias: 'D2',
187
184
  cst_type: CstType.TERM,
188
185
  definition_formal: '1',
@@ -198,12 +195,9 @@ describe('RSToolAgent', () => {
198
195
  const tool = new RSToolAgent();
199
196
  const session = tool.createSession();
200
197
  buildSampleForm(tool, session.sessionId);
201
- await tool.setConstituentaValue(session.sessionId, {
202
- target: 1,
203
- value: { 1: 'Alice' }
204
- });
198
+ await tool.setModelValues({ set: [{ target: 1, value: { 1: 'Alice' } }] }, session.sessionId);
205
199
 
206
- const exported = JSON.parse(tool.exportPortalModel(session.sessionId)) as {
200
+ const exported = JSON.parse(tool.exportPortal({ kind: 'model' }, session.sessionId) as string) as {
207
201
  contract_version: string;
208
202
  title: string;
209
203
  alias: string;
@@ -227,33 +221,31 @@ describe('RSToolAgent', () => {
227
221
  it('defaults missing text fields to empty strings', () => {
228
222
  const tool = new RSToolAgent();
229
223
  const session = tool.createSession();
230
- const result = tool.addOrUpdateConstituenta(session.sessionId, {
231
- draft: {
232
- id: 16,
233
- alias: 'D3',
234
- cstType: CstType.TERM,
235
- definitionFormal: '2'
236
- }
237
- });
238
- expect(result.state.term).toBe('');
239
- expect(result.state.definitionText).toBe('');
240
- expect(result.state.convention).toBe('');
224
+ tool.applySchemaPatch(
225
+ {
226
+ items: [{ alias: 'D3', cstType: CstType.TERM, definitionFormal: '2' }]
227
+ },
228
+ session.sessionId
229
+ );
230
+ const state = fullState(tool, session.sessionId);
231
+ expect(state.items[0]?.term).toBe('');
232
+ expect(state.items[0]?.definitionText).toBe('');
233
+ expect(state.items[0]?.convention).toBe('');
241
234
  });
242
235
 
243
236
  it('returns known analysis for empty constant definition', () => {
244
237
  const tool = new RSToolAgent();
245
238
  const session = tool.createSession();
246
- const result = tool.addOrUpdateConstituenta(session.sessionId, {
247
- draft: {
248
- id: 14,
249
- alias: 'C1',
250
- cstType: CstType.CONSTANT,
251
- definitionFormal: ''
252
- }
253
- });
254
- expect(result.state.analysis.success).toBe(true);
255
- expect(result.state.analysis.type).not.toBeNull();
256
- expect(result.state.analysis.valueClass).toBe('value');
239
+ const result = tool.applySchemaPatch(
240
+ {
241
+ items: [{ alias: 'C1', cstType: CstType.CONSTANT, definitionFormal: '' }]
242
+ },
243
+ session.sessionId
244
+ );
245
+ expect(result.success).toBe(true);
246
+ const state = fullState(tool, session.sessionId);
247
+ expect(state.items[0]?.analysis.type).not.toBeNull();
248
+ expect(state.items[0]?.analysis.valueClass).toBe('value');
257
249
  });
258
250
  });
259
251
 
@@ -270,10 +262,10 @@ describe('RSToolAgent modeling and evaluation', () => {
270
262
  const session = tool.createSession();
271
263
  buildSampleForm(tool, session.sessionId);
272
264
 
273
- const model = await tool.setConstituentaValue(session.sessionId, {
274
- target: 1,
275
- value: { 0: 'zero', 1: 'one' }
276
- });
265
+ const model = await tool.setModelValues(
266
+ { set: [{ target: 1, value: { 0: 'zero', 1: 'one' } }] },
267
+ session.sessionId
268
+ );
277
269
  expect(model.items).toHaveLength(1);
278
270
  expect(model.items[0]).toMatchObject({
279
271
  id: 1,
@@ -287,12 +279,9 @@ describe('RSToolAgent modeling and evaluation', () => {
287
279
  const session = tool.createSession();
288
280
  buildSampleForm(tool, session.sessionId);
289
281
 
290
- await expect(
291
- tool.setConstituentaValue(session.sessionId, {
292
- target: 2,
293
- value: 3
294
- })
295
- ).rejects.toThrow(/inferrable/);
282
+ await expect(tool.setModelValues({ set: [{ target: 2, value: 3 }] }, session.sessionId)).rejects.toThrow(
283
+ /inferrable/
284
+ );
296
285
  });
297
286
 
298
287
  it('evaluates expression against session context', () => {
@@ -300,10 +289,7 @@ describe('RSToolAgent modeling and evaluation', () => {
300
289
  const session = tool.createSession();
301
290
  buildSampleForm(tool, session.sessionId);
302
291
 
303
- const result = tool.evaluateExpression(session.sessionId, {
304
- expression: '1+2',
305
- cstType: CstType.TERM
306
- });
292
+ const result = tool.evaluate({ expression: '1+2', cstType: CstType.TERM }, session.sessionId);
307
293
  expect(result.success).toBe(true);
308
294
  expect(result.value).toBe(3);
309
295
  expect(result.status).toBe(EvalStatus.HAS_DATA);
@@ -315,7 +301,7 @@ describe('RSToolAgent modeling and evaluation', () => {
315
301
  const session = tool.createSession();
316
302
  buildSampleForm(tool, session.sessionId);
317
303
 
318
- const result = tool.evaluateConstituenta(session.sessionId, { constituentId: 2 });
304
+ const result = tool.evaluate({ constituentId: 2 }, session.sessionId);
319
305
  expect(result.success).toBe(true);
320
306
  expect(result.value).toBe(3);
321
307
  expect(result.status).toBe(EvalStatus.HAS_DATA);
@@ -326,7 +312,7 @@ describe('RSToolAgent modeling and evaluation', () => {
326
312
  const session = tool.createSession();
327
313
  buildSampleForm(tool, session.sessionId);
328
314
 
329
- const result = tool.evaluateConstituenta(session.sessionId, { constituentId: 3 });
315
+ const result = tool.evaluate({ constituentId: 3 }, session.sessionId);
330
316
  expect(result.success).toBe(true);
331
317
  expect(result.value).toBe(1);
332
318
  });
@@ -349,31 +335,31 @@ describe('RSToolAgent modeling and evaluation', () => {
349
335
  const tool = new RSToolAgent();
350
336
  const session = tool.createSession();
351
337
  buildSampleForm(tool, session.sessionId);
352
- await tool.setConstituentaValue(session.sessionId, {
353
- target: 1,
354
- value: { 0: 'a' }
355
- });
338
+ await tool.setModelValues({ set: [{ target: 1, value: { 0: 'a' } }] }, session.sessionId);
356
339
 
357
- const model = await tool.clearConstituentaValues(session.sessionId, { items: [1] });
340
+ const model = await tool.setModelValues({ clear: [1] }, session.sessionId);
358
341
  expect(model.items).toHaveLength(0);
359
342
  });
360
343
 
361
344
  it('batch sets model values', async () => {
362
345
  const tool = new RSToolAgent();
363
346
  const session = tool.createSession();
364
- tool.addOrUpdateConstituenta(session.sessionId, {
365
- draft: { id: 1, alias: 'X1', cstType: CstType.BASE, definitionFormal: '' }
366
- });
367
- tool.addOrUpdateConstituenta(session.sessionId, {
368
- draft: { id: 2, alias: 'C1', cstType: CstType.CONSTANT, definitionFormal: '' }
369
- });
347
+ tool.applySchemaPatch(
348
+ {
349
+ items: [{ alias: 'X1' }, { alias: 'C1', cstType: CstType.CONSTANT, definitionFormal: '' }]
350
+ },
351
+ session.sessionId
352
+ );
370
353
 
371
- const model = await tool.setConstituentaValues(session.sessionId, {
372
- items: [
373
- { target: 1, value: { 0: 'a', 1: 'b' } },
374
- { target: 2, value: { 0: 'c' } }
375
- ]
376
- });
354
+ const model = await tool.setModelValues(
355
+ {
356
+ set: [
357
+ { target: 1, value: { 0: 'a', 1: 'b' } },
358
+ { target: 2, value: { 0: 'c' } }
359
+ ]
360
+ },
361
+ session.sessionId
362
+ );
377
363
  expect(model.items).toHaveLength(2);
378
364
  });
379
365
 
@@ -381,18 +367,185 @@ describe('RSToolAgent modeling and evaluation', () => {
381
367
  const tool = new RSToolAgent();
382
368
  const session = tool.createSession();
383
369
  buildSampleForm(tool, session.sessionId);
384
- await tool.setConstituentaValue(session.sessionId, {
385
- target: 1,
386
- value: { 0: 'zero' }
387
- });
370
+ await tool.setModelValues({ set: [{ target: 1, value: { 0: 'zero' } }] }, session.sessionId);
388
371
 
389
372
  const exported = tool.exportSession(session.sessionId);
390
373
  expect(exported).toContain('"model"');
391
374
 
392
375
  const newTool = new RSToolAgent();
393
- const imported = newTool.importSession(exported);
376
+ const imported = newTool.importData(exported, 'session');
394
377
  const model = newTool.getModelState(imported.sessionId);
395
378
  expect(model.items).toHaveLength(1);
396
379
  expect(model.items[0]?.value).toEqual({ 0: 'zero' });
397
380
  });
398
381
  });
382
+
383
+ describe('RSToolAgent agent ergonomics', () => {
384
+ it('tracks current session and allows omitting sessionId', () => {
385
+ const tool = new RSToolAgent();
386
+ const session = tool.createSession({ title: 'Active' });
387
+ expect(tool.getCurrentSession()?.sessionId).toBe(session.sessionId);
388
+ expect(fullState(tool).title).toBe('Active');
389
+ });
390
+
391
+ it('auto-creates a session when sessionId is omitted', () => {
392
+ const tool = new RSToolAgent();
393
+ const result = tool.applySchemaPatch({
394
+ items: [{ alias: 'X1' }]
395
+ });
396
+
397
+ expect(result.success).toBe(true);
398
+ expect(tool.getCurrentSession()).not.toBeNull();
399
+ expect(fullState(tool).items).toHaveLength(1);
400
+ });
401
+
402
+ it('applies agent schema patches with inferred ids and cstType', () => {
403
+ const tool = new RSToolAgent();
404
+
405
+ const result = tool.applySchemaPatch({
406
+ initial: { title: 'Agent patch' },
407
+ commitMessage: 'initial schema',
408
+ items: [
409
+ { alias: 'D1', definitionFormal: 'Pr1(S1)' },
410
+ { alias: 'X1' },
411
+ { alias: 'S1', definitionFormal: 'ℬ(X1×X1)' }
412
+ ]
413
+ });
414
+
415
+ expect(result.success).toBe(true);
416
+ expect(result.summary.title).toBe('Agent patch');
417
+ expect(result.summary.itemCount).toBe(3);
418
+ expect(result.revision?.message).toBe('initial schema');
419
+ expect(fullState(tool).items.map(item => item.alias)).toEqual(['X1', 'S1', 'D1']);
420
+ expect(fullState(tool).items.map(item => item.cstType)).toEqual([CstType.BASE, CstType.STRUCTURED, CstType.TERM]);
421
+ });
422
+
423
+ it('exports Portal payloads as structured objects', () => {
424
+ const tool = new RSToolAgent();
425
+ tool.applySchemaPatch({
426
+ items: [{ alias: 'D1', definitionFormal: '1+2' }]
427
+ });
428
+
429
+ const schema = tool.exportPortal({ kind: 'schema', format: 'object' });
430
+ expect(schema).toMatchObject({ items: [{ alias: 'D1', cst_type: CstType.TERM }] });
431
+ expect(JSON.parse(tool.exportPortal({ kind: 'schema' }) as string).items[0]).toMatchObject(
432
+ (schema as { items: unknown[] }).items[0] as object
433
+ );
434
+ });
435
+
436
+ it('replaces active diagnostics per constituent on upsert', () => {
437
+ const tool = new RSToolAgent();
438
+ const session = tool.createSession();
439
+ tool.applySchemaPatch(
440
+ { mode: 'best_effort', items: [{ alias: 'X1', cstType: CstType.BASE, definitionFormal: 'Z' }] },
441
+ session.sessionId
442
+ );
443
+ expect(tool.listDiagnostics(undefined, session.sessionId)).toHaveLength(1);
444
+
445
+ tool.applySchemaPatch({ items: [{ alias: 'X1', cstType: CstType.BASE, definitionFormal: '' }] }, session.sessionId);
446
+ expect(tool.listDiagnostics(undefined, session.sessionId)).toHaveLength(0);
447
+ });
448
+
449
+ it('does not record analyzeExpression diagnostics by default', () => {
450
+ const tool = new RSToolAgent();
451
+ const session = tool.createSession();
452
+ tool.analyzeExpression({ expression: '(', cstType: CstType.TERM }, session.sessionId);
453
+ expect(tool.listDiagnostics(undefined, session.sessionId)).toHaveLength(0);
454
+ });
455
+
456
+ it('records analyzeExpression diagnostics when requested', () => {
457
+ const tool = new RSToolAgent();
458
+ const session = tool.createSession();
459
+ tool.analyzeExpression({ expression: '(', cstType: CstType.TERM, recordDiagnostics: true }, session.sessionId);
460
+ expect(tool.listDiagnostics(undefined, session.sessionId).length).toBeGreaterThan(0);
461
+ });
462
+
463
+ it('applySchemaPatch rolls back in atomic mode', () => {
464
+ const tool = new RSToolAgent();
465
+ const session = tool.createSession();
466
+ tool.applySchemaPatch({ items: [{ alias: 'X1' }] }, session.sessionId);
467
+
468
+ const result = tool.applySchemaPatch(
469
+ {
470
+ mode: 'atomic',
471
+ items: [
472
+ { alias: 'D1', definitionFormal: '1+2' },
473
+ { alias: 'D2', definitionFormal: 'Pr1(MISSING)' }
474
+ ]
475
+ },
476
+ session.sessionId
477
+ );
478
+
479
+ expect(result.success).toBe(false);
480
+ expect(result.applied).toHaveLength(0);
481
+ expect(fullState(tool, session.sessionId).items).toHaveLength(1);
482
+ });
483
+
484
+ it('applySchemaPatch applies valid drafts in best_effort mode', () => {
485
+ const tool = new RSToolAgent();
486
+ const session = tool.createSession();
487
+ tool.applySchemaPatch(
488
+ {
489
+ items: [{ alias: 'X1' }, { alias: 'S1', definitionFormal: 'ℬ(X1×X1)' }]
490
+ },
491
+ session.sessionId
492
+ );
493
+
494
+ const result = tool.applySchemaPatch(
495
+ {
496
+ mode: 'best_effort',
497
+ items: [
498
+ { alias: 'D1', definitionFormal: 'Pr1(S1)' },
499
+ { alias: 'D2', definitionFormal: 'Pr1(MISSING)' }
500
+ ]
501
+ },
502
+ session.sessionId
503
+ );
504
+
505
+ expect(result.applied).toHaveLength(1);
506
+ expect(result.failed).toHaveLength(1);
507
+ expect(fullState(tool, session.sessionId).items.map(item => item.alias)).toContain('D1');
508
+ });
509
+
510
+ it('imports portal details JSON', () => {
511
+ const tool = new RSToolAgent();
512
+ const payload = {
513
+ title: 'Kinship',
514
+ alias: 'KIN',
515
+ description: 'Example',
516
+ items: [
517
+ { id: 1, alias: 'X1', cst_type: CstType.BASE, definition_formal: '' },
518
+ { id: 2, alias: 'D1', cst_type: CstType.TERM, definition_formal: '1+2' }
519
+ ]
520
+ };
521
+ const session = tool.importData(payload, 'portal-details');
522
+ const state = fullState(tool, session.sessionId);
523
+ expect(state.title).toBe('Kinship');
524
+ expect(state.items).toHaveLength(2);
525
+ expect(state.items.map(item => item.alias).sort()).toEqual(['D1', 'X1']);
526
+ });
527
+
528
+ it('auto-detects portal details import kind', () => {
529
+ const tool = new RSToolAgent();
530
+ const session = tool.importData({
531
+ title: 'Auto',
532
+ items: [{ id: 1, alias: 'X1', cst_type: CstType.BASE, definition_formal: '' }]
533
+ });
534
+ expect(fullState(tool, session.sessionId).title).toBe('Auto');
535
+ });
536
+
537
+ it('persists sessions across agent restarts', () => {
538
+ const dir = mkdtempSync(join(tmpdir(), 'rstool-test-sessions-'));
539
+ try {
540
+ const tool = new RSToolAgent({ persistenceDir: dir });
541
+ const session = tool.createSession({ title: 'Persisted' });
542
+ tool.applySchemaPatch({ items: [{ alias: 'X1' }] }, session.sessionId);
543
+
544
+ const restored = new RSToolAgent({ persistenceDir: dir });
545
+ expect(restored.getCurrentSession()?.sessionId).toBe(session.sessionId);
546
+ expect(fullState(restored, session.sessionId).items).toHaveLength(1);
547
+ } finally {
548
+ rmSync(dir, { recursive: true, force: true });
549
+ }
550
+ });
551
+ });