@stratta/mcp 0.10.0 → 0.12.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 +37 -3
- package/dist/confirm.d.ts +15 -0
- package/dist/confirm.js +54 -0
- package/dist/errors.js +27 -0
- package/dist/index.js +27 -2
- package/dist/prompts.d.ts +4 -0
- package/dist/prompts.js +40 -0
- package/dist/resources.d.ts +3 -0
- package/dist/resources.js +35 -0
- package/dist/tools/catalog.gen.d.ts +34 -0
- package/dist/tools/catalog.gen.js +724 -0
- package/dist/tools/dossier.d.ts +48 -0
- package/dist/tools/dossier.js +249 -127
- package/dist/tools/ingest.d.ts +8 -0
- package/dist/tools/ingest.js +116 -98
- package/dist/tools/read.d.ts +42 -17
- package/dist/tools/read.js +160 -84
- package/dist/usage.d.ts +3 -0
- package/dist/usage.js +50 -0
- package/package.json +1 -1
- package/scripts/ingest-prepass.py +499 -54
- package/scripts/tests/test_prepass.py +211 -0
- package/skills/consult-stratta/SKILL.md +73 -0
- package/skills/ingest-norm/SKILL.md +99 -44
- package/skills/verification-note/SKILL.md +54 -0
package/dist/tools/dossier.d.ts
CHANGED
|
@@ -11,6 +11,8 @@ export declare const openQuestion: import("./define.js").ToolDef<{
|
|
|
11
11
|
options: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
12
12
|
}>;
|
|
13
13
|
export declare const saveFinding: import("./define.js").ToolDef<{
|
|
14
|
+
attachmentId: z.ZodOptional<z.ZodString>;
|
|
15
|
+
attachmentPage: z.ZodOptional<z.ZodNumber>;
|
|
14
16
|
title: z.ZodString;
|
|
15
17
|
detail: z.ZodOptional<z.ZodString>;
|
|
16
18
|
value: z.ZodOptional<z.ZodString>;
|
|
@@ -32,6 +34,7 @@ export declare const saveFinding: import("./define.js").ToolDef<{
|
|
|
32
34
|
}>;
|
|
33
35
|
}>;
|
|
34
36
|
export declare const recordDecision: import("./define.js").ToolDef<{
|
|
37
|
+
confirmed: z.ZodOptional<z.ZodBoolean>;
|
|
35
38
|
title: z.ZodString;
|
|
36
39
|
detail: z.ZodOptional<z.ZodString>;
|
|
37
40
|
value: z.ZodOptional<z.ZodString>;
|
|
@@ -54,6 +57,30 @@ export declare const loadDossier: import("./define.js").ToolDef<{
|
|
|
54
57
|
export declare const resolveQuestion: import("./define.js").ToolDef<{
|
|
55
58
|
questionId: z.ZodString;
|
|
56
59
|
resolved: z.ZodOptional<z.ZodBoolean>;
|
|
60
|
+
confirmed: z.ZodOptional<z.ZodBoolean>;
|
|
61
|
+
}>;
|
|
62
|
+
export declare const listAttachments: import("./define.js").ToolDef<{
|
|
63
|
+
dossierId: z.ZodString;
|
|
64
|
+
}>;
|
|
65
|
+
export declare const readAttachment: import("./define.js").ToolDef<{
|
|
66
|
+
attachmentId: z.ZodString;
|
|
67
|
+
from: z.ZodOptional<z.ZodNumber>;
|
|
68
|
+
to: z.ZodOptional<z.ZodNumber>;
|
|
69
|
+
maxChars: z.ZodOptional<z.ZodNumber>;
|
|
70
|
+
}>;
|
|
71
|
+
export declare const searchInDossier: import("./define.js").ToolDef<{
|
|
72
|
+
dossierId: z.ZodString;
|
|
73
|
+
keyword: z.ZodString;
|
|
74
|
+
limit: z.ZodOptional<z.ZodNumber>;
|
|
75
|
+
}>;
|
|
76
|
+
export declare const addAttachment: import("./define.js").ToolDef<{
|
|
77
|
+
dossierId: z.ZodString;
|
|
78
|
+
filePath: z.ZodString;
|
|
79
|
+
}>;
|
|
80
|
+
export declare const listTemplates: import("./define.js").ToolDef<{}>;
|
|
81
|
+
export declare const applyTemplate: import("./define.js").ToolDef<{
|
|
82
|
+
dossierId: z.ZodString;
|
|
83
|
+
templateId: z.ZodString;
|
|
57
84
|
}>;
|
|
58
85
|
export declare const dossierTools: (import("./define.js").ToolDef<{}> | import("./define.js").ToolDef<{
|
|
59
86
|
name: z.ZodString;
|
|
@@ -64,6 +91,8 @@ export declare const dossierTools: (import("./define.js").ToolDef<{}> | import("
|
|
|
64
91
|
body: z.ZodOptional<z.ZodString>;
|
|
65
92
|
options: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
66
93
|
}> | import("./define.js").ToolDef<{
|
|
94
|
+
attachmentId: z.ZodOptional<z.ZodString>;
|
|
95
|
+
attachmentPage: z.ZodOptional<z.ZodNumber>;
|
|
67
96
|
title: z.ZodString;
|
|
68
97
|
detail: z.ZodOptional<z.ZodString>;
|
|
69
98
|
value: z.ZodOptional<z.ZodString>;
|
|
@@ -84,6 +113,7 @@ export declare const dossierTools: (import("./define.js").ToolDef<{}> | import("
|
|
|
84
113
|
observation: "observation";
|
|
85
114
|
}>;
|
|
86
115
|
}> | import("./define.js").ToolDef<{
|
|
116
|
+
confirmed: z.ZodOptional<z.ZodBoolean>;
|
|
87
117
|
title: z.ZodString;
|
|
88
118
|
detail: z.ZodOptional<z.ZodString>;
|
|
89
119
|
value: z.ZodOptional<z.ZodString>;
|
|
@@ -104,4 +134,22 @@ export declare const dossierTools: (import("./define.js").ToolDef<{}> | import("
|
|
|
104
134
|
}> | import("./define.js").ToolDef<{
|
|
105
135
|
questionId: z.ZodString;
|
|
106
136
|
resolved: z.ZodOptional<z.ZodBoolean>;
|
|
137
|
+
confirmed: z.ZodOptional<z.ZodBoolean>;
|
|
138
|
+
}> | import("./define.js").ToolDef<{
|
|
139
|
+
dossierId: z.ZodString;
|
|
140
|
+
}> | import("./define.js").ToolDef<{
|
|
141
|
+
attachmentId: z.ZodString;
|
|
142
|
+
from: z.ZodOptional<z.ZodNumber>;
|
|
143
|
+
to: z.ZodOptional<z.ZodNumber>;
|
|
144
|
+
maxChars: z.ZodOptional<z.ZodNumber>;
|
|
145
|
+
}> | import("./define.js").ToolDef<{
|
|
146
|
+
dossierId: z.ZodString;
|
|
147
|
+
keyword: z.ZodString;
|
|
148
|
+
limit: z.ZodOptional<z.ZodNumber>;
|
|
149
|
+
}> | import("./define.js").ToolDef<{
|
|
150
|
+
dossierId: z.ZodString;
|
|
151
|
+
filePath: z.ZodString;
|
|
152
|
+
}> | import("./define.js").ToolDef<{
|
|
153
|
+
dossierId: z.ZodString;
|
|
154
|
+
templateId: z.ZodString;
|
|
107
155
|
}>)[];
|
package/dist/tools/dossier.js
CHANGED
|
@@ -1,86 +1,58 @@
|
|
|
1
|
+
import { readFileSync } from 'node:fs';
|
|
2
|
+
import { basename, extname } from 'node:path';
|
|
1
3
|
import { z } from 'zod';
|
|
2
4
|
import { api } from '../client.js';
|
|
3
5
|
import { requireApiKey } from '../auth.js';
|
|
6
|
+
import { catalogEntry, param } from './catalog.gen.js';
|
|
4
7
|
import { defineTool } from './define.js';
|
|
5
8
|
/**
|
|
6
9
|
* The seven dossier tools: how the work survives the conversation.
|
|
7
10
|
*
|
|
8
|
-
*
|
|
9
|
-
* it, so the next conversation starts where the last one stopped and a human
|
|
10
|
-
* can review it in the app.
|
|
11
|
-
*
|
|
12
|
-
* A dossier is a set of QUESTIONS. Each question holds the evidence gathered
|
|
11
|
+
* A dossier is a set of questions. Each question holds the evidence gathered
|
|
13
12
|
* for it, the options on the table and, once the engineer has confirmed it, a
|
|
14
13
|
* decision. The question is the unit of work: it is what gets assigned,
|
|
15
|
-
* reviewed and closed.
|
|
16
|
-
* dossier it produced held a single "question" with eight sub-questions in its
|
|
17
|
-
* body — unassignable, unanswerable one at a time, unreadable.
|
|
14
|
+
* reviewed and closed.
|
|
18
15
|
*
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
-
*
|
|
22
|
-
* that mattered. `get_methodology` tells it when to open a dossier; these tell
|
|
23
|
-
* it what is worth putting in one, and in what shape.
|
|
16
|
+
* Names, descriptions and annotations come from the generated catalogue,
|
|
17
|
+
* shared with the remote transport. `get_methodology` tells the agent when to
|
|
18
|
+
* open a dossier; the descriptions tell it what is worth putting in one.
|
|
24
19
|
*/
|
|
25
|
-
const
|
|
26
|
-
const
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
20
|
+
const headOf = (name) => {
|
|
21
|
+
const entry = catalogEntry(name);
|
|
22
|
+
return {
|
|
23
|
+
name,
|
|
24
|
+
title: entry.title,
|
|
25
|
+
description: entry.description,
|
|
26
|
+
annotations: entry.annotations,
|
|
27
|
+
};
|
|
28
|
+
};
|
|
29
|
+
const dossierId = (tool) => z.string().min(1).describe(param(tool, 'dossierId'));
|
|
30
|
+
const questionId = (tool) => z.string().min(1).describe(param(tool, 'questionId'));
|
|
35
31
|
/** The fields a piece of evidence or a decision carries. */
|
|
36
|
-
const entryFields = {
|
|
37
|
-
title: z
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
.max(200)
|
|
41
|
-
.describe('The fact in one line, as an engineer would write it in a report. One clause, one value or one observation; not a theme.'),
|
|
42
|
-
detail: z
|
|
43
|
-
.string()
|
|
44
|
-
.max(4000)
|
|
45
|
-
.optional()
|
|
46
|
-
.describe('The reasoning, in one or two sentences: why this value, what it rests on, what disagreed. Required in practice for a hypothesis.'),
|
|
47
|
-
value: z
|
|
48
|
-
.string()
|
|
49
|
-
.max(120)
|
|
50
|
-
.optional()
|
|
51
|
-
.describe('The retained value WITH its unit, e.g. "30°", "1,25 kN/m²".'),
|
|
32
|
+
const entryFields = (tool) => ({
|
|
33
|
+
title: z.string().min(1).max(200).describe(param(tool, 'title')),
|
|
34
|
+
detail: z.string().max(4000).optional().describe(param(tool, 'detail')),
|
|
35
|
+
value: z.string().max(120).optional().describe(param(tool, 'value')),
|
|
52
36
|
confidence: z
|
|
53
37
|
.enum(['established', 'judgement', 'to_confirm'])
|
|
54
38
|
.optional()
|
|
55
|
-
.describe(
|
|
56
|
-
normCode: z
|
|
57
|
-
.string()
|
|
58
|
-
.max(40)
|
|
59
|
-
.optional()
|
|
60
|
-
.describe('Norm this comes from, e.g. "SIA 267".'),
|
|
39
|
+
.describe(param(tool, 'confidence')),
|
|
40
|
+
normCode: z.string().max(40).optional().describe(param(tool, 'normCode')),
|
|
61
41
|
sectionPath: z
|
|
62
42
|
.string()
|
|
63
43
|
.max(60)
|
|
64
44
|
.optional()
|
|
65
|
-
.describe(
|
|
66
|
-
page: z
|
|
67
|
-
.number()
|
|
68
|
-
.int()
|
|
69
|
-
.positive()
|
|
70
|
-
.optional()
|
|
71
|
-
.describe('Page in the norm, so a human can verify it in their PDF.'),
|
|
45
|
+
.describe(param(tool, 'sectionPath')),
|
|
46
|
+
page: z.number().int().positive().optional().describe(param(tool, 'page')),
|
|
72
47
|
normEdition: z
|
|
73
48
|
.string()
|
|
74
49
|
.max(40)
|
|
75
50
|
.optional()
|
|
76
|
-
.describe(
|
|
77
|
-
};
|
|
51
|
+
.describe(param(tool, 'normEdition')),
|
|
52
|
+
});
|
|
78
53
|
export const listDossiers = defineTool({
|
|
79
|
-
|
|
80
|
-
title: 'List dossiers',
|
|
81
|
-
description: 'List the dossiers of YOUR organisation, most recently touched first. A dossier is a project record: the questions to settle, what was decided, what it rests on. Call this when the user mentions a project by name, or asks what they were working on. Each row carries the count of entries and of OPEN questions — a dossier with open questions is the one to resume.',
|
|
54
|
+
...headOf('list_dossiers'),
|
|
82
55
|
inputSchema: {},
|
|
83
|
-
annotations: readOnly,
|
|
84
56
|
run: async (client) => ({
|
|
85
57
|
dossiers: await client.action(api.dossiersApi.listDossiers, {
|
|
86
58
|
apiKey: requireApiKey(),
|
|
@@ -88,22 +60,15 @@ export const listDossiers = defineTool({
|
|
|
88
60
|
}),
|
|
89
61
|
});
|
|
90
62
|
export const openDossier = defineTool({
|
|
91
|
-
|
|
92
|
-
title: 'Open a dossier',
|
|
93
|
-
description: "Open the dossier for a project, creating it if it does not exist yet. IDEMPOTENT on the name: calling it twice with the same name returns the same dossier rather than splitting a project's history in two. Call this when the user starts working on a named project and there is something worth keeping — a question to settle, a retained value, an assumption, a site observation. Do not open one for a passing lookup. Returns { dossierId, created }.",
|
|
63
|
+
...headOf('open_dossier'),
|
|
94
64
|
inputSchema: {
|
|
95
|
-
name: z
|
|
96
|
-
.string()
|
|
97
|
-
.min(1)
|
|
98
|
-
.max(120)
|
|
99
|
-
.describe('How the user refers to the project, e.g. "Villa Morges — toiture". Reuse their words: this is the name they will search for.'),
|
|
65
|
+
name: z.string().min(1).max(120).describe(param('open_dossier', 'name')),
|
|
100
66
|
reference: z
|
|
101
67
|
.string()
|
|
102
68
|
.max(60)
|
|
103
69
|
.optional()
|
|
104
|
-
.describe(
|
|
70
|
+
.describe(param('open_dossier', 'reference')),
|
|
105
71
|
},
|
|
106
|
-
annotations: { ...write, idempotentHint: true },
|
|
107
72
|
run: (client, args) => client.action(api.dossiersApi.openDossier, {
|
|
108
73
|
apiKey: requireApiKey(),
|
|
109
74
|
name: args.name,
|
|
@@ -111,32 +76,21 @@ export const openDossier = defineTool({
|
|
|
111
76
|
}),
|
|
112
77
|
});
|
|
113
78
|
export const openQuestion = defineTool({
|
|
114
|
-
|
|
115
|
-
title: 'Open a question',
|
|
116
|
-
description: `Open ONE question to settle in a dossier, BEFORE gathering evidence for it. A question is the unit of work: "Can pile P38 be kept?", "Which φ'k do we retain for the fill?", "Is kinematic interaction to be checked?". Evidence saved with save_finding and the eventual record_decision hang off it.
|
|
117
|
-
|
|
118
|
-
ONE QUESTION PER THING TO SETTLE. Never put a list of sub-questions in the body: a question that bundles eight cannot be assigned, answered or closed one at a time. Open eight questions.
|
|
119
|
-
|
|
120
|
-
IDEMPOTENT on the title: reopening the same wording returns the question already open, with its evidence. Name the options on the table in \`options\` when the user or the norm puts several forward ("abandon the pile", "recover it by re-concreting plus integrity tests", "redistribute onto P37/P39"). Returns { questionId, created }.`,
|
|
79
|
+
...headOf('open_question'),
|
|
121
80
|
inputSchema: {
|
|
122
|
-
dossierId,
|
|
123
|
-
title: z
|
|
124
|
-
.string()
|
|
125
|
-
.min(1)
|
|
126
|
-
.max(200)
|
|
127
|
-
.describe('The question in one line, phrased as a question.'),
|
|
81
|
+
dossierId: dossierId('open_question'),
|
|
82
|
+
title: z.string().min(1).max(200).describe(param('open_question', 'title')),
|
|
128
83
|
body: z
|
|
129
84
|
.string()
|
|
130
85
|
.max(4000)
|
|
131
86
|
.optional()
|
|
132
|
-
.describe('
|
|
87
|
+
.describe(param('open_question', 'body')),
|
|
133
88
|
options: z
|
|
134
89
|
.array(z.string().min(1).max(120))
|
|
135
90
|
.max(10)
|
|
136
91
|
.optional()
|
|
137
|
-
.describe('
|
|
92
|
+
.describe(param('open_question', 'options')),
|
|
138
93
|
},
|
|
139
|
-
annotations: { ...write, idempotentHint: true },
|
|
140
94
|
run: (client, args) => client.action(api.dossiersApi.openQuestion, {
|
|
141
95
|
apiKey: requireApiKey(),
|
|
142
96
|
dossierId: args.dossierId,
|
|
@@ -146,75 +100,66 @@ IDEMPOTENT on the title: reopening the same wording returns the question already
|
|
|
146
100
|
}),
|
|
147
101
|
});
|
|
148
102
|
export const saveFinding = defineTool({
|
|
149
|
-
|
|
150
|
-
title: 'Record evidence in a dossier',
|
|
151
|
-
description: `Record ONE piece of evidence in a dossier. Call it as you work, not in a batch at the end — a finding saved when it is made carries the reasoning that produced it.
|
|
152
|
-
|
|
153
|
-
WHAT TO RECORD, by kind:
|
|
154
|
-
- reference: what a norm says, that the project relies on. ALWAYS fill normCode + sectionPath (+ page). If you cannot cite it, it is not a reference.
|
|
155
|
-
- hypothesis: a value the engineer RETAINS, and why. This is the one that matters most in geotechnics, where a retained value is a judgement between disagreeing measurements rather than the output of a formula. Put the number in \`value\` and the reasoning in \`detail\`.
|
|
156
|
-
- observation: what the site, a borehole, or a survey showed. Include the date in \`detail\` when known.
|
|
157
|
-
|
|
158
|
-
KEEP IT SHORT: one clause, one value or one fact per call, in one or two sentences. Three short entries beat one paragraph that mixes the citation, the reasoning and the consequence. A question is NOT recorded here: call open_question. A decision is NOT recorded here: call record_decision.
|
|
159
|
-
|
|
160
|
-
FILE IT: pass \`questionId\` so the entry lands under the question it serves. If you do not yet know which question it serves, save it anyway without one — the engineer files it later.
|
|
161
|
-
|
|
162
|
-
Set \`confidence\` whenever the entry is a value: established (computed or read directly), judgement (the engineer chose it), to_confirm (provisional, needs a test or a check). Confusing those three is the professional fault this field exists to prevent.`,
|
|
103
|
+
...headOf('save_finding'),
|
|
163
104
|
inputSchema: {
|
|
164
|
-
dossierId,
|
|
165
|
-
questionId: questionId
|
|
166
|
-
.optional()
|
|
167
|
-
.describe('The question this evidence serves, from open_question or load_dossier. Omit only when you do not know yet.'),
|
|
105
|
+
dossierId: dossierId('save_finding'),
|
|
106
|
+
questionId: questionId('save_finding').optional(),
|
|
168
107
|
kind: z
|
|
169
108
|
.enum(['reference', 'hypothesis', 'observation'])
|
|
170
|
-
.describe('
|
|
171
|
-
...entryFields,
|
|
109
|
+
.describe(param('save_finding', 'kind')),
|
|
110
|
+
...entryFields('save_finding'),
|
|
111
|
+
attachmentId: z
|
|
112
|
+
.string()
|
|
113
|
+
.optional()
|
|
114
|
+
.describe(param('save_finding', 'attachmentId')),
|
|
115
|
+
attachmentPage: z
|
|
116
|
+
.number()
|
|
117
|
+
.int()
|
|
118
|
+
.min(1)
|
|
119
|
+
.optional()
|
|
120
|
+
.describe(param('save_finding', 'attachmentPage')),
|
|
172
121
|
},
|
|
173
|
-
annotations: write,
|
|
174
122
|
run: (client, args) => client.action(api.dossiersApi.saveFinding, {
|
|
175
123
|
apiKey: requireApiKey(),
|
|
176
124
|
...args,
|
|
177
125
|
}),
|
|
178
126
|
});
|
|
179
127
|
export const recordDecision = defineTool({
|
|
180
|
-
|
|
181
|
-
title: 'Record a decision',
|
|
182
|
-
description: `Settle a question with a decision the engineer has CONFIRMED: what was decided, why, and the clause it rests on. The question leaves the open items and keeps its evidence; the decision becomes the entry a reviewer reads first.
|
|
183
|
-
|
|
184
|
-
Only call this once the user has confirmed the decision, never on your own reasoning. Name the retained option in \`retainedOption\` when the question listed some (the name as it was given; an option not listed yet is created). Put the number in \`value\` when the decision is a value, and set \`confidence\`. Returns { entryId }.`,
|
|
128
|
+
...headOf('record_decision'),
|
|
185
129
|
inputSchema: {
|
|
186
|
-
questionId,
|
|
130
|
+
questionId: questionId('record_decision'),
|
|
187
131
|
retainedOption: z
|
|
188
132
|
.string()
|
|
189
133
|
.max(120)
|
|
190
134
|
.optional()
|
|
191
|
-
.describe('
|
|
192
|
-
...entryFields,
|
|
135
|
+
.describe(param('record_decision', 'retainedOption')),
|
|
136
|
+
...entryFields('record_decision'),
|
|
137
|
+
confirmed: z
|
|
138
|
+
.boolean()
|
|
139
|
+
.optional()
|
|
140
|
+
.describe(param('record_decision', 'confirmed')),
|
|
193
141
|
},
|
|
194
|
-
|
|
195
|
-
run: (client, args) => client.action(api.dossiersApi.recordDecision, {
|
|
142
|
+
// `confirmed` is read by the confirmation gate in `index.ts`, never sent.
|
|
143
|
+
run: (client, { confirmed: _confirmed, ...args }) => client.action(api.dossiersApi.recordDecision, {
|
|
196
144
|
apiKey: requireApiKey(),
|
|
197
145
|
...args,
|
|
198
146
|
}),
|
|
199
147
|
});
|
|
200
148
|
export const loadDossier = defineTool({
|
|
201
|
-
|
|
202
|
-
title: 'Load a dossier',
|
|
203
|
-
description: 'Reload everything a dossier holds: its questions with their evidence, options and decisions, then the evidence not yet filed under a question, then the comments a colleague left. OPEN QUESTIONS COME FIRST — read them before anything else and tell the user what is still open. Call this whenever the user returns to a project ("reprends le dossier X", "on en était où sur Y") BEFORE answering anything about it, so you build on what was decided instead of deciding it again. Accepts either the id or the name the user uses.',
|
|
149
|
+
...headOf('load_dossier'),
|
|
204
150
|
inputSchema: {
|
|
205
151
|
dossierId: z
|
|
206
152
|
.string()
|
|
207
153
|
.min(1)
|
|
208
154
|
.optional()
|
|
209
|
-
.describe('
|
|
155
|
+
.describe(param('load_dossier', 'dossierId')),
|
|
210
156
|
name: z
|
|
211
157
|
.string()
|
|
212
158
|
.min(1)
|
|
213
159
|
.max(120)
|
|
214
160
|
.optional()
|
|
215
|
-
.describe('
|
|
161
|
+
.describe(param('load_dossier', 'name')),
|
|
216
162
|
},
|
|
217
|
-
annotations: readOnly,
|
|
218
163
|
run: (client, args) => client.action(api.dossiersApi.loadDossier, {
|
|
219
164
|
apiKey: requireApiKey(),
|
|
220
165
|
dossierId: args.dossierId,
|
|
@@ -222,17 +167,18 @@ export const loadDossier = defineTool({
|
|
|
222
167
|
}),
|
|
223
168
|
});
|
|
224
169
|
export const resolveQuestion = defineTool({
|
|
225
|
-
|
|
226
|
-
title: 'Close a question',
|
|
227
|
-
description: 'Close a question that stopped mattering WITHOUT a decision: the variant was abandoned, the client withdrew the request, the point became moot. The question stays in the dossier, marked closed, with its evidence. To settle a question with an answer, call record_decision instead. Pass resolved: false to reopen a question. Only call this when the user says so, never on your own reasoning.',
|
|
170
|
+
...headOf('resolve_question'),
|
|
228
171
|
inputSchema: {
|
|
229
|
-
questionId,
|
|
172
|
+
questionId: questionId('resolve_question'),
|
|
230
173
|
resolved: z
|
|
231
174
|
.boolean()
|
|
232
175
|
.optional()
|
|
233
|
-
.describe('
|
|
176
|
+
.describe(param('resolve_question', 'resolved')),
|
|
177
|
+
confirmed: z
|
|
178
|
+
.boolean()
|
|
179
|
+
.optional()
|
|
180
|
+
.describe(param('resolve_question', 'confirmed')),
|
|
234
181
|
},
|
|
235
|
-
annotations: { ...write, idempotentHint: true },
|
|
236
182
|
run: async (client, args) => {
|
|
237
183
|
await client.action(api.dossiersApi.resolveQuestion, {
|
|
238
184
|
apiKey: requireApiKey(),
|
|
@@ -242,6 +188,176 @@ export const resolveQuestion = defineTool({
|
|
|
242
188
|
return { ok: true };
|
|
243
189
|
},
|
|
244
190
|
});
|
|
191
|
+
const MIME_BY_EXT = {
|
|
192
|
+
'.pdf': 'application/pdf',
|
|
193
|
+
'.docx': 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
|
|
194
|
+
'.xlsx': 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
|
|
195
|
+
'.png': 'image/png',
|
|
196
|
+
'.jpg': 'image/jpeg',
|
|
197
|
+
'.jpeg': 'image/jpeg',
|
|
198
|
+
'.txt': 'text/plain',
|
|
199
|
+
'.md': 'text/markdown',
|
|
200
|
+
'.csv': 'text/csv',
|
|
201
|
+
};
|
|
202
|
+
export const listAttachments = defineTool({
|
|
203
|
+
...headOf('list_attachments'),
|
|
204
|
+
inputSchema: {
|
|
205
|
+
dossierId: z.string().describe(param('list_attachments', 'dossierId')),
|
|
206
|
+
},
|
|
207
|
+
run: async (client, args) => {
|
|
208
|
+
const attachments = await client.action(api.dossiersApi.listAttachments, {
|
|
209
|
+
apiKey: requireApiKey(),
|
|
210
|
+
dossierId: args.dossierId,
|
|
211
|
+
});
|
|
212
|
+
if (attachments === null)
|
|
213
|
+
return { error: 'Dossier not found in your workspace.' };
|
|
214
|
+
return { dossierId: args.dossierId, attachments };
|
|
215
|
+
},
|
|
216
|
+
});
|
|
217
|
+
export const readAttachment = defineTool({
|
|
218
|
+
...headOf('read_attachment'),
|
|
219
|
+
inputSchema: {
|
|
220
|
+
attachmentId: z.string().describe(param('read_attachment', 'attachmentId')),
|
|
221
|
+
from: z
|
|
222
|
+
.number()
|
|
223
|
+
.int()
|
|
224
|
+
.min(1)
|
|
225
|
+
.optional()
|
|
226
|
+
.describe(param('read_attachment', 'from')),
|
|
227
|
+
to: z
|
|
228
|
+
.number()
|
|
229
|
+
.int()
|
|
230
|
+
.min(1)
|
|
231
|
+
.optional()
|
|
232
|
+
.describe(param('read_attachment', 'to')),
|
|
233
|
+
maxChars: z
|
|
234
|
+
.number()
|
|
235
|
+
.int()
|
|
236
|
+
.min(500)
|
|
237
|
+
.max(40_000)
|
|
238
|
+
.optional()
|
|
239
|
+
.describe(param('read_attachment', 'maxChars')),
|
|
240
|
+
},
|
|
241
|
+
run: async (client, args) => {
|
|
242
|
+
const read = (await client.action(api.dossiersApi.readAttachment, {
|
|
243
|
+
apiKey: requireApiKey(),
|
|
244
|
+
attachmentId: args.attachmentId,
|
|
245
|
+
from: args.from,
|
|
246
|
+
to: args.to,
|
|
247
|
+
maxChars: args.maxChars,
|
|
248
|
+
}));
|
|
249
|
+
if (read === null) {
|
|
250
|
+
return {
|
|
251
|
+
error: 'Attachment not found in your workspace. Call list_attachments on the dossier.',
|
|
252
|
+
};
|
|
253
|
+
}
|
|
254
|
+
return read;
|
|
255
|
+
},
|
|
256
|
+
toContent: (result) => {
|
|
257
|
+
const r = result;
|
|
258
|
+
if (r.error || !r.markdown) {
|
|
259
|
+
return {
|
|
260
|
+
content: [{ type: 'text', text: r.error ?? 'Attachment unavailable.' }],
|
|
261
|
+
isError: true,
|
|
262
|
+
};
|
|
263
|
+
}
|
|
264
|
+
const { markdown, ...structured } = r;
|
|
265
|
+
return {
|
|
266
|
+
content: [{ type: 'text', text: markdown }],
|
|
267
|
+
structuredContent: { ...structured, markdown },
|
|
268
|
+
};
|
|
269
|
+
},
|
|
270
|
+
});
|
|
271
|
+
export const searchInDossier = defineTool({
|
|
272
|
+
...headOf('search_in_dossier'),
|
|
273
|
+
inputSchema: {
|
|
274
|
+
dossierId: z.string().describe(param('search_in_dossier', 'dossierId')),
|
|
275
|
+
keyword: z
|
|
276
|
+
.string()
|
|
277
|
+
.min(1)
|
|
278
|
+
.max(200)
|
|
279
|
+
.describe(param('search_in_dossier', 'keyword')),
|
|
280
|
+
limit: z
|
|
281
|
+
.number()
|
|
282
|
+
.int()
|
|
283
|
+
.min(1)
|
|
284
|
+
.max(30)
|
|
285
|
+
.optional()
|
|
286
|
+
.describe(param('search_in_dossier', 'limit')),
|
|
287
|
+
},
|
|
288
|
+
run: async (client, args) => {
|
|
289
|
+
const hits = await client.action(api.dossiersApi.searchInDossier, {
|
|
290
|
+
apiKey: requireApiKey(),
|
|
291
|
+
dossierId: args.dossierId,
|
|
292
|
+
keyword: args.keyword,
|
|
293
|
+
limit: args.limit,
|
|
294
|
+
});
|
|
295
|
+
if (hits === null)
|
|
296
|
+
return { error: 'Dossier not found in your workspace.' };
|
|
297
|
+
return { keyword: args.keyword, hits };
|
|
298
|
+
},
|
|
299
|
+
});
|
|
300
|
+
export const addAttachment = defineTool({
|
|
301
|
+
...headOf('add_attachment'),
|
|
302
|
+
inputSchema: {
|
|
303
|
+
dossierId: z.string().describe(param('add_attachment', 'dossierId')),
|
|
304
|
+
filePath: z.string().min(1).describe(param('add_attachment', 'filePath')),
|
|
305
|
+
},
|
|
306
|
+
run: async (client, args) => {
|
|
307
|
+
const mimeType = MIME_BY_EXT[extname(args.filePath).toLowerCase()];
|
|
308
|
+
if (!mimeType) {
|
|
309
|
+
return {
|
|
310
|
+
error: `Unsupported file type "${extname(args.filePath)}". Allowed: ${Object.keys(MIME_BY_EXT).join(', ')}.`,
|
|
311
|
+
};
|
|
312
|
+
}
|
|
313
|
+
let bytes;
|
|
314
|
+
try {
|
|
315
|
+
bytes = readFileSync(args.filePath);
|
|
316
|
+
}
|
|
317
|
+
catch {
|
|
318
|
+
return {
|
|
319
|
+
error: `Cannot read ${args.filePath}. Check the path with the user.`,
|
|
320
|
+
};
|
|
321
|
+
}
|
|
322
|
+
if (bytes.byteLength > 25 * 1024 * 1024) {
|
|
323
|
+
return { error: 'A file is limited to 25 MB.' };
|
|
324
|
+
}
|
|
325
|
+
const { attachmentId } = await client.action(api.dossiersApi.uploadAttachment, {
|
|
326
|
+
apiKey: requireApiKey(),
|
|
327
|
+
dossierId: args.dossierId,
|
|
328
|
+
name: basename(args.filePath),
|
|
329
|
+
mimeType,
|
|
330
|
+
base64: bytes.toString('base64'),
|
|
331
|
+
});
|
|
332
|
+
return {
|
|
333
|
+
attachmentId,
|
|
334
|
+
name: basename(args.filePath),
|
|
335
|
+
bytes: bytes.byteLength,
|
|
336
|
+
note: 'Text extraction runs in the background; list_attachments shows when it is ready.',
|
|
337
|
+
};
|
|
338
|
+
},
|
|
339
|
+
});
|
|
340
|
+
export const listTemplates = defineTool({
|
|
341
|
+
...headOf('list_templates'),
|
|
342
|
+
inputSchema: {},
|
|
343
|
+
run: async (client) => ({
|
|
344
|
+
templates: await client.action(api.dossiersApi.listTemplates, {
|
|
345
|
+
apiKey: requireApiKey(),
|
|
346
|
+
}),
|
|
347
|
+
}),
|
|
348
|
+
});
|
|
349
|
+
export const applyTemplate = defineTool({
|
|
350
|
+
...headOf('apply_template'),
|
|
351
|
+
inputSchema: {
|
|
352
|
+
dossierId: z.string().describe(param('apply_template', 'dossierId')),
|
|
353
|
+
templateId: z.string().describe(param('apply_template', 'templateId')),
|
|
354
|
+
},
|
|
355
|
+
run: (client, args) => client.action(api.dossiersApi.applyTemplate, {
|
|
356
|
+
apiKey: requireApiKey(),
|
|
357
|
+
dossierId: args.dossierId,
|
|
358
|
+
templateId: args.templateId,
|
|
359
|
+
}),
|
|
360
|
+
});
|
|
245
361
|
export const dossierTools = [
|
|
246
362
|
listDossiers,
|
|
247
363
|
openDossier,
|
|
@@ -250,4 +366,10 @@ export const dossierTools = [
|
|
|
250
366
|
recordDecision,
|
|
251
367
|
loadDossier,
|
|
252
368
|
resolveQuestion,
|
|
369
|
+
listAttachments,
|
|
370
|
+
readAttachment,
|
|
371
|
+
searchInDossier,
|
|
372
|
+
addAttachment,
|
|
373
|
+
listTemplates,
|
|
374
|
+
applyTemplate,
|
|
253
375
|
];
|
package/dist/tools/ingest.d.ts
CHANGED
|
@@ -13,6 +13,8 @@ export declare const ingestCreateDocument: import("./define.js").ToolDef<{
|
|
|
13
13
|
en: "en";
|
|
14
14
|
}>;
|
|
15
15
|
totalPages: z.ZodNumber;
|
|
16
|
+
scope: z.ZodOptional<z.ZodString>;
|
|
17
|
+
edition: z.ZodOptional<z.ZodString>;
|
|
16
18
|
}>;
|
|
17
19
|
export declare const ingestCreateSections: import("./define.js").ToolDef<{
|
|
18
20
|
documentId: z.ZodString;
|
|
@@ -71,6 +73,7 @@ export declare const ingestNormalizeCrossRefs: import("./define.js").ToolDef<{
|
|
|
71
73
|
}>;
|
|
72
74
|
export declare const ingestPublish: import("./define.js").ToolDef<{
|
|
73
75
|
documentId: z.ZodString;
|
|
76
|
+
force: z.ZodOptional<z.ZodBoolean>;
|
|
74
77
|
}>;
|
|
75
78
|
export declare const ingestDelete: import("./define.js").ToolDef<{
|
|
76
79
|
documentId: z.ZodString;
|
|
@@ -88,6 +91,8 @@ export declare const ingestTools: (import("./define.js").ToolDef<{
|
|
|
88
91
|
en: "en";
|
|
89
92
|
}>;
|
|
90
93
|
totalPages: z.ZodNumber;
|
|
94
|
+
scope: z.ZodOptional<z.ZodString>;
|
|
95
|
+
edition: z.ZodOptional<z.ZodString>;
|
|
91
96
|
}> | import("./define.js").ToolDef<{
|
|
92
97
|
documentId: z.ZodString;
|
|
93
98
|
sections: z.ZodArray<z.ZodObject<{
|
|
@@ -137,4 +142,7 @@ export declare const ingestTools: (import("./define.js").ToolDef<{
|
|
|
137
142
|
figureNumber: z.ZodString;
|
|
138
143
|
}> | import("./define.js").ToolDef<{
|
|
139
144
|
documentId: z.ZodString;
|
|
145
|
+
}> | import("./define.js").ToolDef<{
|
|
146
|
+
documentId: z.ZodString;
|
|
147
|
+
force: z.ZodOptional<z.ZodBoolean>;
|
|
140
148
|
}>)[];
|