@rsconcept/rstool 1.7.0 → 1.9.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.
- package/README.md +5 -5
- package/dist/{agent-workflow-DY-hfAap.d.ts → agent-workflow-BqEC_Foh.d.ts} +5 -6
- package/dist/{analysis-ktWFu4BY.d.ts → analysis-C7GrmB17.d.ts} +3 -4
- package/dist/{common-D6TFl6l_.d.ts → common-oafVB-wX.d.ts} +1 -2
- package/dist/{constituenta-HTnFtmxt.d.ts → constituenta-CwJogNKY.d.ts} +4 -5
- package/dist/{diagnostic-CODFUU-Q.d.ts → diagnostic-CtxXifJY.d.ts} +4 -3
- package/dist/{diagnostic-assembly-B-6vIA5I.js → diagnostic-assembly-B-r0NyHL.js} +3 -2
- package/dist/diagnostic-assembly-B-r0NyHL.js.map +1 -0
- package/dist/{evaluation-6A7rK4t7.d.ts → evaluation-BHv2xNwf.d.ts} +3 -4
- package/dist/index.d.ts +11 -10
- package/dist/index.js +1 -1
- package/dist/mappers/model-adapter.d.ts +9 -4
- package/dist/mappers/model-adapter.js +29 -4
- package/dist/mappers/model-adapter.js.map +1 -1
- package/dist/mappers/schema-adapter.d.ts +5 -5
- package/dist/mappers/schema-adapter.js +1 -1
- package/dist/mappers/types.d.ts +5 -4
- package/dist/mappers/types.js +1 -1
- package/dist/mappers/types.js.map +1 -1
- package/dist/{model-value-BUhr2IjA.d.ts → model-value-C0QA-ji2.d.ts} +2 -3
- package/dist/models/agent-workflow.d.ts +1 -1
- package/dist/models/analysis.d.ts +1 -1
- package/dist/models/common.d.ts +1 -1
- package/dist/models/constituenta.d.ts +1 -1
- package/dist/models/diagnostic.d.ts +1 -1
- package/dist/models/diagnostic.js +1 -1
- package/dist/models/evaluation.d.ts +1 -1
- package/dist/models/index.d.ts +10 -10
- package/dist/models/index.js +1 -1
- package/dist/models/model-value.d.ts +1 -1
- 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 +1 -1
- package/dist/models/tool-contract.js +1 -1
- package/dist/models/tool-contract.js.map +1 -1
- package/dist/{models-BKmWx92e.js → models-D06g8LId.js} +8 -7
- package/dist/{models-BKmWx92e.js.map → models-D06g8LId.js.map} +1 -1
- package/dist/{rsform-builder-DKwZfJvN.js → rsform-builder-BlXEe5Ow.js} +2 -2
- package/dist/{rsform-builder-DKwZfJvN.js.map → rsform-builder-BlXEe5Ow.js.map} +1 -1
- package/dist/{rstool-agent-D-w4DlEo.d.ts → rstool-agent-DLq0mtym.d.ts} +9 -10
- package/dist/session/session-store.d.ts +3 -2
- package/dist/session/session-store.js +1 -1
- package/dist/{session-Uc_vHrq9.d.ts → session-OaLqLq0H.d.ts} +3 -4
- package/dist/{tool-contract-i0v2gbWs.d.ts → tool-contract-ZVyh7hhp.d.ts} +10 -11
- package/dist/wrapper/client.d.ts +12 -5
- package/dist/wrapper/client.js +79 -12
- package/dist/wrapper/client.js.map +1 -1
- package/dist/wrapper/stdio-wrapper.d.ts +1 -1
- package/dist/wrapper/stdio-wrapper.js +18 -10
- package/dist/wrapper/stdio-wrapper.js.map +1 -1
- package/docs/CONCEPTUAL-SCHEMA.md +1 -1
- package/docs/CONSTRUCTIONS.md +372 -0
- package/docs/DIAGNOSTICS.md +70 -28
- package/docs/DOMAIN.md +7 -2
- package/docs/MODEL-TESTING.md +48 -0
- package/docs/README.md +1 -0
- package/docs/SYNTAX.md +1 -1
- package/docs/TYPIFICATION.md +11 -2
- package/examples/diagnostics-utils.ts +3 -4
- package/package.json +20 -19
- package/skills/README.md +1 -1
- package/skills/rstool-helper/EXAMPLES.md +24 -0
- package/skills/rstool-helper/GUIDE.md +49 -22
- package/skills/rstool-helper/REFERENCE.md +24 -5
- package/skills/rstool-helper/SKILL.md +7 -4
- package/src/mappers/diagnostic-assembly.ts +5 -2
- package/src/mappers/model-adapter.ts +34 -1
- package/src/mappers/types.ts +1 -0
- package/src/models/rstool-agent.test.ts +95 -0
- package/src/models/tool-contract.ts +1 -1
- package/src/wrapper/client.test.ts +43 -4
- package/src/wrapper/client.ts +95 -16
- package/src/wrapper/stdio-handler.test.ts +43 -0
- package/src/wrapper/stdio-handler.ts +19 -5
- package/dist/diagnostic-assembly-B-6vIA5I.js.map +0 -1
|
@@ -1029,6 +1029,101 @@ describe('RSToolAgent modeling semantics', () => {
|
|
|
1029
1029
|
expect(modelIssues.some(record => record.code === RSDiagnosticCode.modelAxiomFalse)).toBe(true);
|
|
1030
1030
|
});
|
|
1031
1031
|
|
|
1032
|
+
function buildGraphScratch(tool: RSToolAgent, sessionId: string) {
|
|
1033
|
+
tool.applySchemaPatch(
|
|
1034
|
+
{
|
|
1035
|
+
items: [
|
|
1036
|
+
{ alias: 'X1' },
|
|
1037
|
+
{ alias: 'S1', definitionFormal: 'ℬ(X1×X1)' },
|
|
1038
|
+
{ alias: 'D1', definitionFormal: 'Pr2,1(S1)' },
|
|
1039
|
+
{ alias: 'D2', definitionFormal: 'S1\\D1' },
|
|
1040
|
+
{ alias: 'F1', definitionFormal: '[α∈X1] Pr2(Fi1[{α}](D2))' }
|
|
1041
|
+
]
|
|
1042
|
+
},
|
|
1043
|
+
sessionId
|
|
1044
|
+
);
|
|
1045
|
+
}
|
|
1046
|
+
|
|
1047
|
+
it('normalizes unsorted structure values before evaluation', async () => {
|
|
1048
|
+
const tool = new RSToolAgent();
|
|
1049
|
+
const session = tool.createSession();
|
|
1050
|
+
buildGraphScratch(tool, session.sessionId);
|
|
1051
|
+
|
|
1052
|
+
await tool.setModelValues(
|
|
1053
|
+
{
|
|
1054
|
+
set: [
|
|
1055
|
+
{ target: 1, value: { 0: 'a', 1: 'b', 2: 'c' } },
|
|
1056
|
+
{
|
|
1057
|
+
target: 2,
|
|
1058
|
+
value: [
|
|
1059
|
+
[TUPLE_ID, 1, 2],
|
|
1060
|
+
[TUPLE_ID, 0, 2],
|
|
1061
|
+
[TUPLE_ID, 0, 1],
|
|
1062
|
+
[TUPLE_ID, 1, 0],
|
|
1063
|
+
[TUPLE_ID, 0, 1]
|
|
1064
|
+
]
|
|
1065
|
+
}
|
|
1066
|
+
]
|
|
1067
|
+
},
|
|
1068
|
+
session.sessionId
|
|
1069
|
+
);
|
|
1070
|
+
|
|
1071
|
+
const stored = tool.getModelState(session.sessionId).items.find(item => item.id === 2);
|
|
1072
|
+
expect(stored?.value).toEqual([
|
|
1073
|
+
[TUPLE_ID, 0, 1],
|
|
1074
|
+
[TUPLE_ID, 0, 2],
|
|
1075
|
+
[TUPLE_ID, 1, 0],
|
|
1076
|
+
[TUPLE_ID, 1, 2]
|
|
1077
|
+
]);
|
|
1078
|
+
|
|
1079
|
+
// S1 \ inverse(S1): (0,1) and (1,0) cancel out
|
|
1080
|
+
const result = tool.evaluate({ constituentId: 4 }, session.sessionId);
|
|
1081
|
+
expect(result.status).toBe(EvalStatus.HAS_DATA);
|
|
1082
|
+
expect(result.value).toEqual([
|
|
1083
|
+
[TUPLE_ID, 0, 2],
|
|
1084
|
+
[TUPLE_ID, 1, 2]
|
|
1085
|
+
]);
|
|
1086
|
+
});
|
|
1087
|
+
|
|
1088
|
+
it('evaluates scratch expressions that reference derived constituents', async () => {
|
|
1089
|
+
const tool = new RSToolAgent();
|
|
1090
|
+
const session = tool.createSession();
|
|
1091
|
+
buildGraphScratch(tool, session.sessionId);
|
|
1092
|
+
await tool.setModelValues(
|
|
1093
|
+
{
|
|
1094
|
+
set: [
|
|
1095
|
+
{ target: 1, value: { 0: 'a', 1: 'b', 2: 'c' } },
|
|
1096
|
+
{
|
|
1097
|
+
target: 2,
|
|
1098
|
+
value: [
|
|
1099
|
+
[TUPLE_ID, 0, 1],
|
|
1100
|
+
[TUPLE_ID, 0, 2],
|
|
1101
|
+
[TUPLE_ID, 1, 0],
|
|
1102
|
+
[TUPLE_ID, 1, 2]
|
|
1103
|
+
]
|
|
1104
|
+
}
|
|
1105
|
+
]
|
|
1106
|
+
},
|
|
1107
|
+
session.sessionId
|
|
1108
|
+
);
|
|
1109
|
+
|
|
1110
|
+
const direct = tool.evaluate({ expression: 'card(D2)', cstType: CstType.TERM }, session.sessionId);
|
|
1111
|
+
expect(direct.success).toBe(true);
|
|
1112
|
+
expect(direct.value).toBe(2);
|
|
1113
|
+
|
|
1114
|
+
// F1 body references D2, which must be computed before the call
|
|
1115
|
+
const viaFunction = tool.evaluate(
|
|
1116
|
+
{ expression: 'card(red(I{F1[ξ] | ξ:∈Pr1(D2)}))', cstType: CstType.TERM },
|
|
1117
|
+
session.sessionId
|
|
1118
|
+
);
|
|
1119
|
+
expect(viaFunction.success).toBe(true);
|
|
1120
|
+
expect(viaFunction.value).toBe(1);
|
|
1121
|
+
|
|
1122
|
+
const logic = tool.evaluate({ expression: 'D2⊆S1', cstType: CstType.AXIOM }, session.sessionId);
|
|
1123
|
+
expect(logic.success).toBe(true);
|
|
1124
|
+
expect(logic.value).toBe(1);
|
|
1125
|
+
});
|
|
1126
|
+
|
|
1032
1127
|
it('throws when evaluate input is incomplete', () => {
|
|
1033
1128
|
const tool = new RSToolAgent();
|
|
1034
1129
|
const session = tool.createSession();
|
|
@@ -15,7 +15,7 @@ import { type RecalculateModelResult, type SessionModelState, type SetModelValue
|
|
|
15
15
|
import { type SessionHandle, type SessionRevision, type SessionState } from './session';
|
|
16
16
|
|
|
17
17
|
/** Agent-visible contract version; bump on breaking API changes. */
|
|
18
|
-
export const CONTRACT_VERSION = '3.
|
|
18
|
+
export const CONTRACT_VERSION = '3.3.0';
|
|
19
19
|
|
|
20
20
|
/** Options for constructing an {@link RSToolAgent}. */
|
|
21
21
|
export interface RSToolAgentOptions {
|
|
@@ -5,14 +5,53 @@ import { describe, expect, it } from 'vitest';
|
|
|
5
5
|
import { CstType, EvalStatus, RSToolWrapperClient } from '../index';
|
|
6
6
|
|
|
7
7
|
const packageRoot = resolve(dirname(fileURLToPath(import.meta.url)), '../..');
|
|
8
|
+
const tsxCli = fileURLToPath(import.meta.resolve('tsx/cli'));
|
|
9
|
+
const wrapperEntry = resolve(packageRoot, 'src/wrapper/stdio-wrapper.ts');
|
|
8
10
|
|
|
9
11
|
describe('RSToolWrapperClient integration', () => {
|
|
12
|
+
it('rejects readiness and calls when the wrapper command cannot start', async () => {
|
|
13
|
+
const client = new RSToolWrapperClient({
|
|
14
|
+
command: resolve(packageRoot, 'definitely-missing-rstool-wrapper-binary'),
|
|
15
|
+
args: [],
|
|
16
|
+
cwd: packageRoot
|
|
17
|
+
});
|
|
18
|
+
try {
|
|
19
|
+
await expect(client.waitUntilReady()).rejects.toThrow(/ENOENT/);
|
|
20
|
+
await expect(client.call('methods')).rejects.toThrow(/ENOENT/);
|
|
21
|
+
} finally {
|
|
22
|
+
await client.close();
|
|
23
|
+
}
|
|
24
|
+
}, 30_000);
|
|
25
|
+
|
|
26
|
+
it('rejects outstanding calls when the wrapper exits before responding', async () => {
|
|
27
|
+
const client = new RSToolWrapperClient({
|
|
28
|
+
command: process.execPath,
|
|
29
|
+
args: ['-e', 'process.stdin.resume(); setTimeout(() => process.exit(3), 200)'],
|
|
30
|
+
cwd: packageRoot
|
|
31
|
+
});
|
|
32
|
+
try {
|
|
33
|
+
await expect(client.call('methods')).rejects.toThrow(/exited before responding \(code 3/);
|
|
34
|
+
} finally {
|
|
35
|
+
await client.close();
|
|
36
|
+
}
|
|
37
|
+
}, 30_000);
|
|
38
|
+
|
|
39
|
+
it('starts the default npm wrapper without a shell', async () => {
|
|
40
|
+
const client = new RSToolWrapperClient({ cwd: packageRoot });
|
|
41
|
+
try {
|
|
42
|
+
await client.waitUntilReady();
|
|
43
|
+
const methods = await client.call<string[]>('methods');
|
|
44
|
+
expect(methods).toContain('applySchemaPatch');
|
|
45
|
+
} finally {
|
|
46
|
+
await client.close();
|
|
47
|
+
}
|
|
48
|
+
}, 30_000);
|
|
49
|
+
|
|
10
50
|
it('runs createSession, patch, model, and evaluate over stdio', async () => {
|
|
11
51
|
const client = new RSToolWrapperClient({
|
|
12
|
-
command:
|
|
13
|
-
args: [
|
|
14
|
-
cwd: packageRoot
|
|
15
|
-
shell: true
|
|
52
|
+
command: process.execPath,
|
|
53
|
+
args: [tsxCli, wrapperEntry],
|
|
54
|
+
cwd: packageRoot
|
|
16
55
|
});
|
|
17
56
|
|
|
18
57
|
try {
|
package/src/wrapper/client.ts
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import { spawn, type ChildProcessByStdio } from 'node:child_process';
|
|
2
|
+
import { existsSync } from 'node:fs';
|
|
3
|
+
import { dirname, join } from 'node:path';
|
|
2
4
|
import readline from 'node:readline';
|
|
3
5
|
import { type Readable, type Writable } from 'node:stream';
|
|
4
6
|
|
|
@@ -16,14 +18,38 @@ export interface WrapperResponse<T = unknown> {
|
|
|
16
18
|
|
|
17
19
|
/** Options for spawning the `rstool-wrapper` child process. */
|
|
18
20
|
export interface RSToolWrapperClientOptions {
|
|
19
|
-
/** Executable to spawn. Default: `npm`. */
|
|
21
|
+
/** Executable to spawn. Default: `npm`. Passed as argv, not a shell string. */
|
|
20
22
|
command?: string;
|
|
21
23
|
/** Arguments passed to `command`. Default: `['run', 'wrapper']`. */
|
|
22
24
|
args?: string[];
|
|
23
25
|
/** Working directory for the child process. Default: `process.cwd()`. */
|
|
24
26
|
cwd?: string;
|
|
25
|
-
|
|
26
|
-
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Rewrite Windows npm/npx shims to `node <npm-cli.js>` so spawn() never needs a shell.
|
|
31
|
+
* Node 20+ refuses to exec `.cmd` files without `shell: true` (CVE-2024-27980).
|
|
32
|
+
*/
|
|
33
|
+
function resolveSpawn(command: string, args: string[]): { command: string; args: string[] } {
|
|
34
|
+
if (process.platform !== 'win32') {
|
|
35
|
+
return { command, args };
|
|
36
|
+
}
|
|
37
|
+
const base = (command.replaceAll('\\', '/').split('/').pop() ?? command).toLowerCase();
|
|
38
|
+
if (base === 'npm' || base === 'npm.cmd') {
|
|
39
|
+
return { command: process.execPath, args: [bundledNpmBin('npm-cli.js'), ...args] };
|
|
40
|
+
}
|
|
41
|
+
if (base === 'npx' || base === 'npx.cmd') {
|
|
42
|
+
return { command: process.execPath, args: [bundledNpmBin('npx-cli.js'), ...args] };
|
|
43
|
+
}
|
|
44
|
+
return { command, args };
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function bundledNpmBin(bin: 'npm-cli.js' | 'npx-cli.js'): string {
|
|
48
|
+
const cli = join(dirname(process.execPath), 'node_modules', 'npm', 'bin', bin);
|
|
49
|
+
if (!existsSync(cli)) {
|
|
50
|
+
throw new Error(`Cannot resolve ${bin} next to Node at ${process.execPath}`);
|
|
51
|
+
}
|
|
52
|
+
return cli;
|
|
27
53
|
}
|
|
28
54
|
|
|
29
55
|
/**
|
|
@@ -34,16 +60,20 @@ export interface RSToolWrapperClientOptions {
|
|
|
34
60
|
export class RSToolWrapperClient {
|
|
35
61
|
private process: ChildProcessByStdio<Writable, Readable, null>;
|
|
36
62
|
private input: readline.Interface;
|
|
37
|
-
private pending = new Map<string,
|
|
63
|
+
private pending = new Map<string, PendingCall>();
|
|
38
64
|
private requestCounter = 1;
|
|
65
|
+
/** Set once the child cannot start or has exited; every later request fails with this error. */
|
|
66
|
+
private failure: Error | null = null;
|
|
67
|
+
private closing = false;
|
|
39
68
|
|
|
40
69
|
/**
|
|
41
70
|
* @param options - Spawn configuration; defaults run `npm run wrapper` in the current directory.
|
|
42
71
|
*/
|
|
43
72
|
public constructor(options: RSToolWrapperClientOptions = {}) {
|
|
44
|
-
|
|
73
|
+
const resolved = resolveSpawn(options.command ?? 'npm', options.args ?? ['run', 'wrapper']);
|
|
74
|
+
this.process = spawn(resolved.command, resolved.args, {
|
|
45
75
|
cwd: options.cwd ?? process.cwd(),
|
|
46
|
-
|
|
76
|
+
windowsHide: true,
|
|
47
77
|
stdio: ['pipe', 'pipe', 'inherit']
|
|
48
78
|
});
|
|
49
79
|
this.input = readline.createInterface({
|
|
@@ -51,9 +81,25 @@ export class RSToolWrapperClient {
|
|
|
51
81
|
crlfDelay: Infinity
|
|
52
82
|
});
|
|
53
83
|
this.input.on('line', line => this.handleLine(line));
|
|
84
|
+
|
|
85
|
+
// Without these listeners a failed spawn (ENOENT, EACCES) or a broken stdin pipe
|
|
86
|
+
// surfaces as an uncaught exception instead of a rejected promise.
|
|
87
|
+
this.process.on('error', error => this.fail(error));
|
|
88
|
+
this.process.stdin.on('error', error => this.fail(error));
|
|
89
|
+
this.process.on('exit', (code, signal) => {
|
|
90
|
+
if (this.closing) {
|
|
91
|
+
this.fail(new Error('Wrapper closed'));
|
|
92
|
+
return;
|
|
93
|
+
}
|
|
94
|
+
this.fail(new Error(`Wrapper exited before responding (code ${code ?? 'null'}, signal ${signal ?? 'null'})`));
|
|
95
|
+
});
|
|
54
96
|
}
|
|
55
97
|
|
|
56
|
-
/**
|
|
98
|
+
/**
|
|
99
|
+
* Block until the wrapper emits its initial `{ ready: true }` event.
|
|
100
|
+
*
|
|
101
|
+
* @throws When the wrapper process cannot be started or exits before becoming ready.
|
|
102
|
+
*/
|
|
57
103
|
public async waitUntilReady(): Promise<void> {
|
|
58
104
|
for (;;) {
|
|
59
105
|
const line = await this.readOneEvent();
|
|
@@ -74,13 +120,16 @@ export class RSToolWrapperClient {
|
|
|
74
120
|
*
|
|
75
121
|
* @param method - Stdio method name (matches {@link RSToolAgentContract} operations).
|
|
76
122
|
* @param params - Method parameters object.
|
|
77
|
-
* @throws When the wrapper responds with `ok: false
|
|
123
|
+
* @throws When the wrapper responds with `ok: false`, cannot be started, or exits before responding.
|
|
78
124
|
*/
|
|
79
125
|
public async call<T>(method: string, params: unknown = {}): Promise<T> {
|
|
126
|
+
if (this.failure) {
|
|
127
|
+
throw this.failure;
|
|
128
|
+
}
|
|
80
129
|
const id = String(this.requestCounter++);
|
|
81
130
|
const payload = JSON.stringify({ id, method, params });
|
|
82
|
-
const responsePromise = new Promise<WrapperResponse>(resolve => {
|
|
83
|
-
this.pending.set(id, resolve);
|
|
131
|
+
const responsePromise = new Promise<WrapperResponse>((resolve, reject) => {
|
|
132
|
+
this.pending.set(id, { resolve, reject });
|
|
84
133
|
});
|
|
85
134
|
this.process.stdin.write(`${payload}\n`);
|
|
86
135
|
const response = await responsePromise;
|
|
@@ -92,14 +141,20 @@ export class RSToolWrapperClient {
|
|
|
92
141
|
|
|
93
142
|
/** Close stdin and terminate the wrapper process. */
|
|
94
143
|
public async close(): Promise<void> {
|
|
144
|
+
this.closing = true;
|
|
95
145
|
this.input.close();
|
|
96
|
-
this.process.stdin.
|
|
97
|
-
|
|
146
|
+
if (!this.process.stdin.destroyed) {
|
|
147
|
+
this.process.stdin.end();
|
|
148
|
+
}
|
|
149
|
+
if (this.process.exitCode === null && !this.process.killed) {
|
|
98
150
|
this.process.kill();
|
|
99
151
|
}
|
|
100
152
|
}
|
|
101
153
|
|
|
102
154
|
private async readOneEvent(): Promise<string> {
|
|
155
|
+
if (this.failure) {
|
|
156
|
+
throw this.failure;
|
|
157
|
+
}
|
|
103
158
|
return new Promise((resolve, reject) => {
|
|
104
159
|
const onLine = (line: string) => {
|
|
105
160
|
cleanup();
|
|
@@ -107,17 +162,36 @@ export class RSToolWrapperClient {
|
|
|
107
162
|
};
|
|
108
163
|
const onExit = () => {
|
|
109
164
|
cleanup();
|
|
110
|
-
reject(new Error('Wrapper exited before ready'));
|
|
165
|
+
reject(this.failure ?? new Error('Wrapper exited before ready'));
|
|
166
|
+
};
|
|
167
|
+
const onError = (error: Error) => {
|
|
168
|
+
cleanup();
|
|
169
|
+
reject(error);
|
|
111
170
|
};
|
|
112
171
|
const cleanup = () => {
|
|
113
172
|
this.input.off('line', onLine);
|
|
114
173
|
this.process.off('exit', onExit);
|
|
174
|
+
this.process.off('error', onError);
|
|
115
175
|
};
|
|
116
176
|
this.input.on('line', onLine);
|
|
117
177
|
this.process.on('exit', onExit);
|
|
178
|
+
this.process.on('error', onError);
|
|
118
179
|
});
|
|
119
180
|
}
|
|
120
181
|
|
|
182
|
+
/** Records the terminal failure and rejects every outstanding request. */
|
|
183
|
+
private fail(error: Error): void {
|
|
184
|
+
if (this.failure) {
|
|
185
|
+
return;
|
|
186
|
+
}
|
|
187
|
+
this.failure = error;
|
|
188
|
+
const outstanding = [...this.pending.values()];
|
|
189
|
+
this.pending.clear();
|
|
190
|
+
for (const entry of outstanding) {
|
|
191
|
+
entry.reject(error);
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
|
|
121
195
|
private handleLine(line: string): void {
|
|
122
196
|
let parsed: WrapperResponse;
|
|
123
197
|
try {
|
|
@@ -129,11 +203,16 @@ export class RSToolWrapperClient {
|
|
|
129
203
|
return;
|
|
130
204
|
}
|
|
131
205
|
const id = String(parsed.id);
|
|
132
|
-
const
|
|
133
|
-
if (!
|
|
206
|
+
const entry = this.pending.get(id);
|
|
207
|
+
if (!entry) {
|
|
134
208
|
return;
|
|
135
209
|
}
|
|
136
210
|
this.pending.delete(id);
|
|
137
|
-
|
|
211
|
+
entry.resolve(parsed);
|
|
138
212
|
}
|
|
139
213
|
}
|
|
214
|
+
|
|
215
|
+
interface PendingCall {
|
|
216
|
+
resolve: (value: WrapperResponse) => void;
|
|
217
|
+
reject: (reason: Error) => void;
|
|
218
|
+
}
|
|
@@ -98,4 +98,47 @@ describe('handleStdioRequest', () => {
|
|
|
98
98
|
expect((all.result as unknown[]).length).toBeGreaterThan(0);
|
|
99
99
|
expect(filtered.result).toEqual(all.result);
|
|
100
100
|
});
|
|
101
|
+
|
|
102
|
+
it('filters diagnostics by flat kind and severity params', async () => {
|
|
103
|
+
const sessionTool = new RSToolAgent();
|
|
104
|
+
const created = await handleStdioRequest(sessionTool, { id: 30, method: 'createSession' });
|
|
105
|
+
const sessionId = (created.result as { sessionId: string }).sessionId;
|
|
106
|
+
// X1 without convention → schema diagnostic; D1 with broken formula → expression diagnostic
|
|
107
|
+
await handleStdioRequest(sessionTool, {
|
|
108
|
+
id: 31,
|
|
109
|
+
method: 'applySchemaPatch',
|
|
110
|
+
params: {
|
|
111
|
+
sessionId,
|
|
112
|
+
mode: 'best_effort',
|
|
113
|
+
items: [{ alias: 'X1' }, { alias: 'D1', definitionFormal: 'Pr1(' }]
|
|
114
|
+
}
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
const all = await handleStdioRequest(sessionTool, { id: 32, method: 'listDiagnostics', params: { sessionId } });
|
|
118
|
+
const kinds = new Set((all.result as { kind: string }[]).map(item => item.kind));
|
|
119
|
+
expect(kinds.has('schema')).toBe(true);
|
|
120
|
+
expect(kinds.has('expression')).toBe(true);
|
|
121
|
+
|
|
122
|
+
const schemaOnly = await handleStdioRequest(sessionTool, {
|
|
123
|
+
id: 33,
|
|
124
|
+
method: 'listDiagnostics',
|
|
125
|
+
params: { sessionId, kind: 'schema' }
|
|
126
|
+
});
|
|
127
|
+
expect((schemaOnly.result as { kind: string }[]).every(item => item.kind === 'schema')).toBe(true);
|
|
128
|
+
expect((schemaOnly.result as unknown[]).length).toBeLessThan((all.result as unknown[]).length);
|
|
129
|
+
|
|
130
|
+
const warningsOnly = await handleStdioRequest(sessionTool, {
|
|
131
|
+
id: 34,
|
|
132
|
+
method: 'listDiagnostics',
|
|
133
|
+
params: { sessionId, severity: 'warning' }
|
|
134
|
+
});
|
|
135
|
+
expect((warningsOnly.result as { severity: string }[]).every(item => item.severity === 'warning')).toBe(true);
|
|
136
|
+
|
|
137
|
+
const legacy = await handleStdioRequest(sessionTool, {
|
|
138
|
+
id: 35,
|
|
139
|
+
method: 'listDiagnostics',
|
|
140
|
+
params: { sessionId, filters: { kind: 'schema' } }
|
|
141
|
+
});
|
|
142
|
+
expect(legacy.result).toEqual(schemaOnly.result);
|
|
143
|
+
});
|
|
101
144
|
});
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { ListDiagnosticsFilters } from '../models/diagnostic';
|
|
1
2
|
import type { RSToolAgent } from '../models/rstool-agent';
|
|
2
3
|
|
|
3
4
|
export interface StdioRequest {
|
|
@@ -63,6 +64,22 @@ function omitSessionId(input: Record<string, unknown>): Record<string, unknown>
|
|
|
63
64
|
return rest;
|
|
64
65
|
}
|
|
65
66
|
|
|
67
|
+
/** Flat `constituentId` / `kind` / `severity` params; legacy nested `filters` object is still accepted. */
|
|
68
|
+
function diagnosticsFilters(input: Record<string, unknown>): ListDiagnosticsFilters | undefined {
|
|
69
|
+
const source = { ...asObject(input.filters), ...omitSessionId(input) };
|
|
70
|
+
const filters: ListDiagnosticsFilters = {};
|
|
71
|
+
if (typeof source.constituentId === 'number') {
|
|
72
|
+
filters.constituentId = source.constituentId;
|
|
73
|
+
}
|
|
74
|
+
if (source.kind === 'expression' || source.kind === 'schema' || source.kind === 'model') {
|
|
75
|
+
filters.kind = source.kind;
|
|
76
|
+
}
|
|
77
|
+
if (source.severity === 'error' || source.severity === 'warning') {
|
|
78
|
+
filters.severity = source.severity;
|
|
79
|
+
}
|
|
80
|
+
return Object.keys(filters).length > 0 ? filters : undefined;
|
|
81
|
+
}
|
|
82
|
+
|
|
66
83
|
export async function handleStdioRequest(tool: RSToolAgent, request: StdioRequest): Promise<StdioResponse> {
|
|
67
84
|
try {
|
|
68
85
|
const params = asObject(request.params);
|
|
@@ -110,15 +127,12 @@ export async function handleStdioRequest(tool: RSToolAgent, request: StdioReques
|
|
|
110
127
|
optionalSessionId(params)
|
|
111
128
|
)
|
|
112
129
|
};
|
|
113
|
-
case 'listDiagnostics':
|
|
114
|
-
const constituentId = params.constituentId;
|
|
115
|
-
const filters = typeof constituentId === 'number' ? { constituentId } : (params.filters as never);
|
|
130
|
+
case 'listDiagnostics':
|
|
116
131
|
return {
|
|
117
132
|
id: request.id,
|
|
118
133
|
ok: true,
|
|
119
|
-
result: tool.listDiagnostics(
|
|
134
|
+
result: tool.listDiagnostics(diagnosticsFilters(params), optionalSessionId(params))
|
|
120
135
|
};
|
|
121
|
-
}
|
|
122
136
|
case 'analyzeExpression':
|
|
123
137
|
return {
|
|
124
138
|
id: request.id,
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"diagnostic-assembly-B-6vIA5I.js","names":[],"sources":["../src/mappers/diagnostic-assembly.ts"],"sourcesContent":["import {\n type CstDiagnostic,\n DiagnosticKind,\n modelStatusCstDiagnostic,\n RSDiagnosticCode\n} from '@rsconcept/domain/library';\nimport { type DiagnosticSourceFields } from '@rsconcept/domain/library/rsform-api';\nimport { EvalStatus } from '@rsconcept/domain/library/rsmodel';\nimport { isCritical, RSErrorCode, type RSErrorDescription } from '@rsconcept/domain/rslang/error';\n\nexport { DiagnosticKind, RSDiagnosticCode };\n\n/** Severity of a {@link Diagnostic}. */\nexport type DiagnosticSeverity = 'error' | 'warning';\n\n/** Agent-facing diagnostic with redundant fields for autonomous repair. */\nexport interface Diagnostic extends CstDiagnostic {\n name: string;\n severity: DiagnosticSeverity;\n constituentId?: number;\n alias?: string;\n expression: string;\n from: number;\n to: number;\n}\n\n/** Context for attributing a {@link Diagnostic} to a constituent. */\nexport interface DiagnosticTarget {\n constituentId?: number;\n alias?: string;\n}\n\nconst DIAGNOSTIC_NAMES: ReadonlyMap<number, string> = new Map([\n ...Object.entries(RSErrorCode).map(([name, code]) => [code, name] as const),\n ...Object.entries(RSDiagnosticCode).map(([name, code]) => [code, name] as const)\n]);\n\nexport function getDiagnosticName(code: number): string {\n return DIAGNOSTIC_NAMES.get(code) ?? `0x${code.toString(16).toUpperCase()}`;\n}\n\nexport function getDiagnosticSeverity(code: number): DiagnosticSeverity {\n return isCritical(code as RSErrorCode) ? 'error' : 'warning';\n}\n\nexport function expandCstDiagnostic(cst: DiagnosticSourceFields, diagnostic: CstDiagnostic): Diagnostic {\n const expression = expressionForCstDiagnostic(cst, diagnostic);\n return {\n ...diagnostic,\n name: getDiagnosticName(diagnostic.code),\n severity: getDiagnosticSeverity(diagnostic.code),\n constituentId: cst.id,\n alias: cst.alias,\n expression,\n from: 0,\n to: diagnostic.kind === DiagnosticKind.MODEL ? 0 : Math.max(expression.length, 1)\n };\n}\n\nexport function expressionDiagnostic(\n error: RSErrorDescription,\n expression: string,\n target?: DiagnosticTarget\n): Diagnostic {\n return {\n kind: DiagnosticKind.EXPRESSION,\n code: error.code,\n name: getDiagnosticName(error.code),\n severity: getDiagnosticSeverity(error.code),\n constituentId: target?.constituentId,\n alias: target?.alias,\n expression,\n from: error.from,\n to: error.to,\n ...(error.params ? { params: error.params } : {})\n };\n}\n\nexport function modelStatusDiagnostic(status: EvalStatus, cst: DiagnosticSourceFields): Diagnostic | null {\n const base = modelStatusCstDiagnostic(status, cst.cst_type);\n return base ? expandCstDiagnostic(cst, base) : null;\n}\n\nfunction expressionForCstDiagnostic(cst: DiagnosticSourceFields, diagnostic: CstDiagnostic): string {\n switch (diagnostic.code) {\n case RSDiagnosticCode.schemaHomonym:\n case RSDiagnosticCode.schemaMissingTerm:\n return cst.term_resolved;\n case RSDiagnosticCode.schemaMissingConvention:\n return cst.convention;\n case RSDiagnosticCode.schemaTypeMismatch:\n return cst.typification_manual;\n case RSDiagnosticCode.schemaDependencyCycle:\n case RSDiagnosticCode.schemaFormalDuplicate:\n return cst.definition_formal;\n default:\n return cst.definition_formal;\n }\n}\n"],"mappings":";;;AAgCA,MAAM,mBAAgD,IAAI,IAAI,CAC5D,GAAG,OAAO,QAAQ,WAAW,CAAC,CAAC,KAAK,CAAC,MAAM,UAAU,CAAC,MAAM,IAAI,CAAU,GAC1E,GAAG,OAAO,QAAQ,gBAAgB,CAAC,CAAC,KAAK,CAAC,MAAM,UAAU,CAAC,MAAM,IAAI,CAAU,CACjF,CAAC;AAED,SAAgB,kBAAkB,MAAsB;CACtD,OAAO,iBAAiB,IAAI,IAAI,KAAK,KAAK,KAAK,SAAS,EAAE,CAAC,CAAC,YAAY;AAC1E;AAEA,SAAgB,sBAAsB,MAAkC;CACtE,OAAO,WAAW,IAAmB,IAAI,UAAU;AACrD;AAEA,SAAgB,oBAAoB,KAA6B,YAAuC;CACtG,MAAM,aAAa,2BAA2B,KAAK,UAAU;CAC7D,OAAO;EACL,GAAG;EACH,MAAM,kBAAkB,WAAW,IAAI;EACvC,UAAU,sBAAsB,WAAW,IAAI;EAC/C,eAAe,IAAI;EACnB,OAAO,IAAI;EACX;EACA,MAAM;EACN,IAAI,WAAW,SAAS,eAAe,QAAQ,IAAI,KAAK,IAAI,WAAW,QAAQ,CAAC;CAClF;AACF;AAEA,SAAgB,qBACd,OACA,YACA,QACY;CACZ,OAAO;EACL,MAAM,eAAe;EACrB,MAAM,MAAM;EACZ,MAAM,kBAAkB,MAAM,IAAI;EAClC,UAAU,sBAAsB,MAAM,IAAI;EAC1C,eAAe,QAAQ;EACvB,OAAO,QAAQ;EACf;EACA,MAAM,MAAM;EACZ,IAAI,MAAM;EACV,GAAI,MAAM,SAAS,EAAE,QAAQ,MAAM,OAAO,IAAI,CAAC;CACjD;AACF;AAEA,SAAgB,sBAAsB,QAAoB,KAAgD;CACxG,MAAM,OAAO,yBAAyB,QAAQ,IAAI,QAAQ;CAC1D,OAAO,OAAO,oBAAoB,KAAK,IAAI,IAAI;AACjD;AAEA,SAAS,2BAA2B,KAA6B,YAAmC;CAClG,QAAQ,WAAW,MAAnB;EACE,KAAK,iBAAiB;EACtB,KAAK,iBAAiB,mBACpB,OAAO,IAAI;EACb,KAAK,iBAAiB,yBACpB,OAAO,IAAI;EACb,KAAK,iBAAiB,oBACpB,OAAO,IAAI;EACb,KAAK,iBAAiB;EACtB,KAAK,iBAAiB,uBACpB,OAAO,IAAI;EACb,SACE,OAAO,IAAI;CACf;AACF"}
|