@geotechcli/core 0.2.0 → 0.4.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/dist/agents/brain.d.ts +1 -0
- package/dist/agents/brain.d.ts.map +1 -1
- package/dist/agents/brain.js +39 -38
- package/dist/agents/brain.js.map +1 -1
- package/dist/agents/bridge-tools.js +15 -3
- package/dist/agents/bridge-tools.js.map +1 -1
- package/dist/agents/case-file.d.ts +52 -0
- package/dist/agents/case-file.d.ts.map +1 -0
- package/dist/agents/case-file.js +657 -0
- package/dist/agents/case-file.js.map +1 -0
- package/dist/agents/contracts.d.ts +232 -0
- package/dist/agents/contracts.d.ts.map +1 -0
- package/dist/agents/contracts.js +14 -0
- package/dist/agents/contracts.js.map +1 -0
- package/dist/agents/data-tools.js +11 -3
- package/dist/agents/data-tools.js.map +1 -1
- package/dist/agents/deliverable-tools.d.ts +2 -0
- package/dist/agents/deliverable-tools.d.ts.map +1 -0
- package/dist/agents/deliverable-tools.js +497 -0
- package/dist/agents/deliverable-tools.js.map +1 -0
- package/dist/agents/evidence.d.ts +21 -0
- package/dist/agents/evidence.d.ts.map +1 -0
- package/dist/agents/evidence.js +302 -0
- package/dist/agents/evidence.js.map +1 -0
- package/dist/agents/orchestrator.js +13 -13
- package/dist/agents/sandbox.d.ts.map +1 -1
- package/dist/agents/sandbox.js +84 -31
- package/dist/agents/sandbox.js.map +1 -1
- package/dist/agents/swarm.d.ts +2 -0
- package/dist/agents/swarm.d.ts.map +1 -1
- package/dist/agents/swarm.js +170 -149
- package/dist/agents/swarm.js.map +1 -1
- package/dist/bridge/index.js +87 -87
- package/dist/geo/index.d.ts +2 -1
- package/dist/geo/index.d.ts.map +1 -1
- package/dist/geo/index.js +1 -0
- package/dist/geo/index.js.map +1 -1
- package/dist/geo/pile-capacity.d.ts.map +1 -1
- package/dist/geo/pile-capacity.js +47 -8
- package/dist/geo/pile-capacity.js.map +1 -1
- package/dist/geo/seepage.d.ts +62 -0
- package/dist/geo/seepage.d.ts.map +1 -0
- package/dist/geo/seepage.js +130 -0
- package/dist/geo/seepage.js.map +1 -0
- package/dist/geo/slope-stability.d.ts.map +1 -1
- package/dist/geo/slope-stability.js +9 -6
- package/dist/geo/slope-stability.js.map +1 -1
- package/dist/index.d.ts +5 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +6 -1
- package/dist/index.js.map +1 -1
- package/dist/llm/middleware/metering.js +14 -14
- package/dist/llm/providers/hosted-beta.d.ts.map +1 -1
- package/dist/llm/providers/hosted-beta.js +5 -2
- package/dist/llm/providers/hosted-beta.js.map +1 -1
- package/dist/llm/providers/zhipu.d.ts.map +1 -1
- package/dist/llm/providers/zhipu.js +3 -1
- package/dist/llm/providers/zhipu.js.map +1 -1
- package/dist/llm/util.d.ts +10 -0
- package/dist/llm/util.d.ts.map +1 -0
- package/dist/llm/util.js +54 -0
- package/dist/llm/util.js.map +1 -0
- package/dist/meta/metadata.json +4 -4
- package/dist/report/casefile.d.ts +207 -0
- package/dist/report/casefile.d.ts.map +1 -0
- package/dist/report/casefile.js +438 -0
- package/dist/report/casefile.js.map +1 -0
- package/dist/report/docx.d.ts +3 -0
- package/dist/report/docx.d.ts.map +1 -0
- package/dist/report/docx.js +178 -0
- package/dist/report/docx.js.map +1 -0
- package/dist/report/index.d.ts +3 -0
- package/dist/report/index.d.ts.map +1 -1
- package/dist/report/index.js +23 -20
- package/dist/report/index.js.map +1 -1
- package/dist/report/pdf.d.ts +3 -0
- package/dist/report/pdf.d.ts.map +1 -0
- package/dist/report/pdf.js +195 -0
- package/dist/report/pdf.js.map +1 -0
- package/dist/vision/index.d.ts.map +1 -1
- package/dist/vision/index.js +163 -84
- package/dist/vision/index.js.map +1 -1
- package/package.json +58 -55
|
@@ -0,0 +1,657 @@
|
|
|
1
|
+
import { randomUUID } from 'node:crypto';
|
|
2
|
+
import { loadProject, saveNamedDataset, } from '../storage/index.js';
|
|
3
|
+
import { AGENT_STAGES, SCENARIO_ARTIFACT_TYPES, } from './contracts.js';
|
|
4
|
+
const CASE_FILE_KIND = 'scenario-case-file';
|
|
5
|
+
const ARTIFACT_KIND = 'scenario-artifact';
|
|
6
|
+
function nowIso() {
|
|
7
|
+
return new Date().toISOString();
|
|
8
|
+
}
|
|
9
|
+
function isRecord(value) {
|
|
10
|
+
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
11
|
+
}
|
|
12
|
+
function asString(value) {
|
|
13
|
+
return typeof value === 'string' && value.trim() ? value.trim() : undefined;
|
|
14
|
+
}
|
|
15
|
+
function asNumber(value) {
|
|
16
|
+
return typeof value === 'number' && Number.isFinite(value) ? value : undefined;
|
|
17
|
+
}
|
|
18
|
+
function isScenarioArtifactType(value) {
|
|
19
|
+
return typeof value === 'string' && SCENARIO_ARTIFACT_TYPES.includes(value);
|
|
20
|
+
}
|
|
21
|
+
function isAgentStage(value) {
|
|
22
|
+
return typeof value === 'string' && AGENT_STAGES.includes(value);
|
|
23
|
+
}
|
|
24
|
+
function toScenarioId(task) {
|
|
25
|
+
const normalized = task
|
|
26
|
+
.trim()
|
|
27
|
+
.toLowerCase()
|
|
28
|
+
.replace(/[^a-z0-9]+/g, '-')
|
|
29
|
+
.replace(/^-|-$/g, '');
|
|
30
|
+
return normalized || 'baseline';
|
|
31
|
+
}
|
|
32
|
+
function titleFromTask(task) {
|
|
33
|
+
const trimmed = task.trim();
|
|
34
|
+
return trimmed.length > 120 ? `${trimmed.slice(0, 117)}...` : trimmed;
|
|
35
|
+
}
|
|
36
|
+
function humanizeToolName(toolName) {
|
|
37
|
+
return toolName.replace(/^calculate_/, '').replace(/_/g, ' ');
|
|
38
|
+
}
|
|
39
|
+
function firstSentence(text, fallback) {
|
|
40
|
+
const trimmed = text.trim();
|
|
41
|
+
if (!trimmed)
|
|
42
|
+
return fallback;
|
|
43
|
+
const sentence = trimmed.split(/(?<=[.!?])\s+/)[0]?.trim();
|
|
44
|
+
return sentence || fallback;
|
|
45
|
+
}
|
|
46
|
+
function extractConfidenceFromSteps(session) {
|
|
47
|
+
for (const step of [...session.steps].reverse()) {
|
|
48
|
+
const match = step.content.match(/confidence:\s*(\d{1,3})%/i);
|
|
49
|
+
if (match) {
|
|
50
|
+
const confidence = Number.parseInt(match[1], 10);
|
|
51
|
+
if (Number.isFinite(confidence)) {
|
|
52
|
+
return Math.max(0, Math.min(100, confidence));
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
if (!session.reviewPassed)
|
|
57
|
+
return 45;
|
|
58
|
+
if (session.corrections.length > 0)
|
|
59
|
+
return 70;
|
|
60
|
+
return 85;
|
|
61
|
+
}
|
|
62
|
+
function extractAnswer(session) {
|
|
63
|
+
return [...session.steps]
|
|
64
|
+
.reverse()
|
|
65
|
+
.find((step) => step.type === 'answer')
|
|
66
|
+
?.content
|
|
67
|
+
?.trim();
|
|
68
|
+
}
|
|
69
|
+
function toMetricRows(value) {
|
|
70
|
+
if (!isRecord(value)) {
|
|
71
|
+
if (value === null || value === undefined) {
|
|
72
|
+
return [];
|
|
73
|
+
}
|
|
74
|
+
return [
|
|
75
|
+
{
|
|
76
|
+
name: 'value',
|
|
77
|
+
value: typeof value === 'string' || typeof value === 'number' || typeof value === 'boolean'
|
|
78
|
+
? value
|
|
79
|
+
: JSON.stringify(value),
|
|
80
|
+
},
|
|
81
|
+
];
|
|
82
|
+
}
|
|
83
|
+
return Object.entries(value)
|
|
84
|
+
.filter(([, item]) => ['string', 'number', 'boolean'].includes(typeof item) || item === null)
|
|
85
|
+
.map(([name, item]) => ({
|
|
86
|
+
name,
|
|
87
|
+
value: item,
|
|
88
|
+
}));
|
|
89
|
+
}
|
|
90
|
+
function buildGroundModelPayload(session) {
|
|
91
|
+
const interpretation = isRecord(session.context.interpretation) ? session.context.interpretation : null;
|
|
92
|
+
if (!interpretation)
|
|
93
|
+
return null;
|
|
94
|
+
const derivedParameters = Object.fromEntries(Object.entries(interpretation).filter(([, value]) => ['string', 'number', 'boolean'].includes(typeof value) || value === null));
|
|
95
|
+
const groundwaterDepth = asNumber(interpretation.waterTableDepth ??
|
|
96
|
+
interpretation.groundwaterDepth ??
|
|
97
|
+
interpretation.gwlDepth ??
|
|
98
|
+
interpretation.groundWaterDepth);
|
|
99
|
+
return {
|
|
100
|
+
summary: 'Ground model snapshot captured from the interpretation stage.',
|
|
101
|
+
units: 'SI',
|
|
102
|
+
strata: [],
|
|
103
|
+
groundwater: {
|
|
104
|
+
detected: groundwaterDepth !== undefined,
|
|
105
|
+
...(groundwaterDepth !== undefined ? { depthM: groundwaterDepth } : {}),
|
|
106
|
+
},
|
|
107
|
+
derivedParameters,
|
|
108
|
+
missingInputs: [],
|
|
109
|
+
blockedInputs: [],
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
function buildAssumptionsPayload(projectId) {
|
|
113
|
+
const project = loadProject(projectId);
|
|
114
|
+
if (project.assumptions.length === 0)
|
|
115
|
+
return null;
|
|
116
|
+
return {
|
|
117
|
+
summary: 'Active project assumptions captured in project memory.',
|
|
118
|
+
assumptions: project.assumptions.map((assumption, index) => ({
|
|
119
|
+
assumptionId: assumption.id || `assumption-${index + 1}`,
|
|
120
|
+
category: 'other',
|
|
121
|
+
statement: assumption.text,
|
|
122
|
+
basis: assumption.source,
|
|
123
|
+
impact: 'medium',
|
|
124
|
+
evidenceRefs: [],
|
|
125
|
+
})),
|
|
126
|
+
criticalAssumptions: project.assumptions.slice(0, 5).map((assumption) => assumption.text),
|
|
127
|
+
};
|
|
128
|
+
}
|
|
129
|
+
function buildAnalysisPlanPayload(task, session) {
|
|
130
|
+
const simulationCalls = session.steps.filter((step) => step.agent === 'simulation' && step.type === 'tool_call' && typeof step.toolName === 'string');
|
|
131
|
+
if (simulationCalls.length === 0)
|
|
132
|
+
return null;
|
|
133
|
+
return {
|
|
134
|
+
summary: 'Analysis plan reconstructed from simulation-stage tool calls.',
|
|
135
|
+
scenarioObjective: task,
|
|
136
|
+
analyses: simulationCalls.map((step, index) => ({
|
|
137
|
+
analysisId: `analysis-${index + 1}`,
|
|
138
|
+
method: humanizeToolName(step.toolName ?? `analysis-${index + 1}`),
|
|
139
|
+
purpose: `Run ${humanizeToolName(step.toolName ?? `analysis-${index + 1}`)} for the active scenario.`,
|
|
140
|
+
toolName: step.toolName ?? `analysis-${index + 1}`,
|
|
141
|
+
requiredInputs: isRecord(step.toolArgs) ? Object.keys(step.toolArgs) : [],
|
|
142
|
+
outputArtifactTypes: ['results'],
|
|
143
|
+
})),
|
|
144
|
+
acceptanceCriteria: session.reviewPassed
|
|
145
|
+
? ['Reviewer approval achieved or acceptable with notes.']
|
|
146
|
+
: ['Reviewer issues must be resolved before final approval.'],
|
|
147
|
+
};
|
|
148
|
+
}
|
|
149
|
+
function buildResultsPayload(scenarioId, session) {
|
|
150
|
+
const simulation = isRecord(session.context.simulation) ? session.context.simulation : null;
|
|
151
|
+
if (!simulation)
|
|
152
|
+
return null;
|
|
153
|
+
const records = Object.entries(simulation).map(([toolName, value], index) => ({
|
|
154
|
+
resultId: `result-${index + 1}`,
|
|
155
|
+
label: humanizeToolName(toolName),
|
|
156
|
+
method: humanizeToolName(toolName),
|
|
157
|
+
toolName,
|
|
158
|
+
metrics: toMetricRows(value),
|
|
159
|
+
interpretation: isRecord(value) && typeof value.summary === 'string'
|
|
160
|
+
? value.summary
|
|
161
|
+
: `Stored output from ${toolName}.`,
|
|
162
|
+
evidenceRefs: [],
|
|
163
|
+
warnings: isRecord(value) && Array.isArray(value.warnings)
|
|
164
|
+
? value.warnings.filter((item) => typeof item === 'string')
|
|
165
|
+
: [],
|
|
166
|
+
}));
|
|
167
|
+
return {
|
|
168
|
+
summary: `Stored ${records.length} simulation result${records.length === 1 ? '' : 's'} from the swarm session.`,
|
|
169
|
+
scenarioLabel: scenarioId,
|
|
170
|
+
analysesRun: Object.keys(simulation),
|
|
171
|
+
records,
|
|
172
|
+
};
|
|
173
|
+
}
|
|
174
|
+
function buildReviewChecklistPayload(session) {
|
|
175
|
+
const reviewerSteps = session.steps.filter((step) => step.agent === 'reviewer' && (step.type === 'review' || step.type === 'correction'));
|
|
176
|
+
return {
|
|
177
|
+
summary: 'Reviewer-stage summary reconstructed from swarm output.',
|
|
178
|
+
items: reviewerSteps.length > 0
|
|
179
|
+
? reviewerSteps.map((step, index) => ({
|
|
180
|
+
itemId: `review-${index + 1}`,
|
|
181
|
+
category: 'traceability',
|
|
182
|
+
question: step.type === 'review' ? 'Did the reviewer approve the submitted results?' : 'Did the reviewer request corrections?',
|
|
183
|
+
status: step.type === 'review' ? 'pass' : 'warning',
|
|
184
|
+
detail: step.content,
|
|
185
|
+
}))
|
|
186
|
+
: [
|
|
187
|
+
{
|
|
188
|
+
itemId: 'review-1',
|
|
189
|
+
category: 'traceability',
|
|
190
|
+
question: 'Was reviewer output captured?',
|
|
191
|
+
status: session.reviewPassed ? 'pass' : 'warning',
|
|
192
|
+
detail: session.reviewPassed
|
|
193
|
+
? 'Reviewer approved the session without explicit checklist items.'
|
|
194
|
+
: 'Reviewer output was limited; corrections remain recorded in session metadata.',
|
|
195
|
+
},
|
|
196
|
+
],
|
|
197
|
+
};
|
|
198
|
+
}
|
|
199
|
+
function buildAcceptanceStatusPayload(session) {
|
|
200
|
+
const confidence = extractConfidenceFromSteps(session);
|
|
201
|
+
const verdict = session.reviewPassed
|
|
202
|
+
? (session.corrections.length > 0 ? 'CONDITIONAL' : 'APPROVED')
|
|
203
|
+
: 'REJECTED';
|
|
204
|
+
return {
|
|
205
|
+
summary: session.reviewPassed
|
|
206
|
+
? (session.corrections.length > 0
|
|
207
|
+
? 'Reviewer completed the session with follow-up notes.'
|
|
208
|
+
: 'Reviewer approved the session results.')
|
|
209
|
+
: 'Reviewer identified unresolved issues that still require correction.',
|
|
210
|
+
verdict,
|
|
211
|
+
confidence,
|
|
212
|
+
reasons: session.corrections.length > 0
|
|
213
|
+
? [...session.corrections]
|
|
214
|
+
: [session.reviewPassed ? 'Reviewer approval recorded.' : 'Outstanding reviewer issues remain.'],
|
|
215
|
+
};
|
|
216
|
+
}
|
|
217
|
+
function buildIssuesPayload(session) {
|
|
218
|
+
if (session.corrections.length === 0)
|
|
219
|
+
return null;
|
|
220
|
+
return {
|
|
221
|
+
summary: 'Reviewer corrections captured from the swarm session.',
|
|
222
|
+
issues: session.corrections.map((correction, index) => ({
|
|
223
|
+
issueId: `issue-${index + 1}`,
|
|
224
|
+
severity: session.reviewPassed ? 'minor' : 'major',
|
|
225
|
+
issue: correction,
|
|
226
|
+
correction,
|
|
227
|
+
affectedArtifacts: ['results'],
|
|
228
|
+
})),
|
|
229
|
+
};
|
|
230
|
+
}
|
|
231
|
+
function buildFinalReportPayload(projectId, answer) {
|
|
232
|
+
const project = loadProject(projectId);
|
|
233
|
+
return {
|
|
234
|
+
summary: firstSentence(answer, 'Final report generated from the swarm session.'),
|
|
235
|
+
markdown: answer,
|
|
236
|
+
recommendation: firstSentence(answer, 'Review the final markdown report for the recommendation.'),
|
|
237
|
+
assumptionTable: project.assumptions.map((assumption) => ({
|
|
238
|
+
assumption: assumption.text,
|
|
239
|
+
impact: 'medium',
|
|
240
|
+
basis: assumption.source,
|
|
241
|
+
})),
|
|
242
|
+
evidenceTable: [],
|
|
243
|
+
};
|
|
244
|
+
}
|
|
245
|
+
function caseFileDatasetName(scenarioId) {
|
|
246
|
+
return `scenario-case-file:${scenarioId}`;
|
|
247
|
+
}
|
|
248
|
+
function artifactDatasetName(scenarioId, artifactType, version) {
|
|
249
|
+
return `scenario-artifact:${scenarioId}:${artifactType}:v${version}`;
|
|
250
|
+
}
|
|
251
|
+
function parseArtifactDatasetName(datasetName, scenarioId) {
|
|
252
|
+
const prefix = `scenario-artifact:${scenarioId}:`;
|
|
253
|
+
if (!datasetName.startsWith(prefix))
|
|
254
|
+
return null;
|
|
255
|
+
const remainder = datasetName.slice(prefix.length);
|
|
256
|
+
const versionMarker = remainder.lastIndexOf(':v');
|
|
257
|
+
if (versionMarker <= 0)
|
|
258
|
+
return null;
|
|
259
|
+
const artifactType = remainder.slice(0, versionMarker);
|
|
260
|
+
const version = Number.parseInt(remainder.slice(versionMarker + 2), 10);
|
|
261
|
+
if (!isScenarioArtifactType(artifactType) || !Number.isFinite(version) || version <= 0) {
|
|
262
|
+
return null;
|
|
263
|
+
}
|
|
264
|
+
return { artifactType, version };
|
|
265
|
+
}
|
|
266
|
+
function normalizeCaseFile(value) {
|
|
267
|
+
if (!isRecord(value))
|
|
268
|
+
return null;
|
|
269
|
+
const caseFileId = asString(value.caseFileId);
|
|
270
|
+
const projectId = asString(value.projectId);
|
|
271
|
+
const scenarioId = asString(value.scenarioId);
|
|
272
|
+
const title = asString(value.title);
|
|
273
|
+
const createdAt = asString(value.createdAt);
|
|
274
|
+
const updatedAt = asString(value.updatedAt);
|
|
275
|
+
if (!caseFileId || !projectId || !scenarioId || !title || !createdAt || !updatedAt) {
|
|
276
|
+
return null;
|
|
277
|
+
}
|
|
278
|
+
const artifactRefs = {};
|
|
279
|
+
if (isRecord(value.artifactRefs)) {
|
|
280
|
+
for (const [key, ref] of Object.entries(value.artifactRefs)) {
|
|
281
|
+
if (isScenarioArtifactType(key) && typeof ref === 'string' && ref.trim()) {
|
|
282
|
+
artifactRefs[key] = ref.trim();
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
const latestVersions = {};
|
|
287
|
+
if (isRecord(value.latestVersions)) {
|
|
288
|
+
for (const [key, version] of Object.entries(value.latestVersions)) {
|
|
289
|
+
if (isScenarioArtifactType(key)) {
|
|
290
|
+
const parsed = asNumber(version);
|
|
291
|
+
if (parsed != null) {
|
|
292
|
+
latestVersions[key] = Math.floor(parsed);
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
const evidenceDatasetRefs = Array.isArray(value.evidenceDatasetRefs)
|
|
298
|
+
? value.evidenceDatasetRefs.filter((item) => typeof item === 'string' && item.trim().length > 0)
|
|
299
|
+
: [];
|
|
300
|
+
const evidenceIndex = {};
|
|
301
|
+
if (isRecord(value.evidenceIndex)) {
|
|
302
|
+
for (const [key, datasetName] of Object.entries(value.evidenceIndex)) {
|
|
303
|
+
if (typeof datasetName === 'string' && datasetName.trim()) {
|
|
304
|
+
evidenceIndex[key] = datasetName.trim();
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
return {
|
|
309
|
+
caseFileId,
|
|
310
|
+
projectId,
|
|
311
|
+
scenarioId,
|
|
312
|
+
title,
|
|
313
|
+
createdAt,
|
|
314
|
+
updatedAt,
|
|
315
|
+
artifactRefs,
|
|
316
|
+
latestVersions,
|
|
317
|
+
evidenceDatasetRefs,
|
|
318
|
+
evidenceIndex,
|
|
319
|
+
finalRecommendationRef: asString(value.finalRecommendationRef),
|
|
320
|
+
acceptanceStatusRef: asString(value.acceptanceStatusRef),
|
|
321
|
+
};
|
|
322
|
+
}
|
|
323
|
+
function normalizeArtifact(value, fallbackType) {
|
|
324
|
+
if (!isRecord(value))
|
|
325
|
+
return null;
|
|
326
|
+
const artifactType = asString(value.artifactType);
|
|
327
|
+
const version = asNumber(value.version);
|
|
328
|
+
const projectId = asString(value.projectId);
|
|
329
|
+
const scenarioId = asString(value.scenarioId);
|
|
330
|
+
const artifactId = asString(value.artifactId);
|
|
331
|
+
const title = asString(value.title);
|
|
332
|
+
const provenance = isRecord(value.provenance) ? value.provenance : undefined;
|
|
333
|
+
const provenanceSource = provenance && isRecord(provenance.source) ? provenance.source : undefined;
|
|
334
|
+
const createdAt = asString(provenance?.createdAt);
|
|
335
|
+
const resolvedArtifactType = (fallbackType ?? artifactType);
|
|
336
|
+
if (!resolvedArtifactType ||
|
|
337
|
+
!isScenarioArtifactType(resolvedArtifactType) ||
|
|
338
|
+
!projectId ||
|
|
339
|
+
!scenarioId ||
|
|
340
|
+
!artifactId ||
|
|
341
|
+
!title ||
|
|
342
|
+
!createdAt ||
|
|
343
|
+
version == null ||
|
|
344
|
+
version <= 0) {
|
|
345
|
+
return null;
|
|
346
|
+
}
|
|
347
|
+
return {
|
|
348
|
+
artifactId,
|
|
349
|
+
projectId,
|
|
350
|
+
scenarioId,
|
|
351
|
+
artifactType: resolvedArtifactType,
|
|
352
|
+
version: Math.floor(version),
|
|
353
|
+
title,
|
|
354
|
+
confidence: asNumber(value.confidence),
|
|
355
|
+
warnings: Array.isArray(value.warnings)
|
|
356
|
+
? value.warnings.filter((warning) => typeof warning === 'string')
|
|
357
|
+
: [],
|
|
358
|
+
assumptionsUsed: Array.isArray(value.assumptionsUsed)
|
|
359
|
+
? value.assumptionsUsed.filter((item) => typeof item === 'string')
|
|
360
|
+
: [],
|
|
361
|
+
evidenceRefs: Array.isArray(value.evidenceRefs)
|
|
362
|
+
? value.evidenceRefs.filter((item) => isRecord(item) &&
|
|
363
|
+
typeof item.evidenceId === 'string' &&
|
|
364
|
+
typeof item.class === 'string' &&
|
|
365
|
+
typeof item.label === 'string' &&
|
|
366
|
+
typeof item.source === 'string')
|
|
367
|
+
: [],
|
|
368
|
+
provenance: {
|
|
369
|
+
createdAt,
|
|
370
|
+
derivedFrom: Array.isArray(provenance?.derivedFrom)
|
|
371
|
+
? provenance.derivedFrom.filter((item) => typeof item === 'string')
|
|
372
|
+
: [],
|
|
373
|
+
source: provenanceSource
|
|
374
|
+
? {
|
|
375
|
+
agent: isAgentStage(provenanceSource.agent) ? provenanceSource.agent : 'orchestrator',
|
|
376
|
+
toolName: asString(provenanceSource.toolName),
|
|
377
|
+
stepId: asString(provenanceSource.stepId),
|
|
378
|
+
model: asString(provenanceSource.model),
|
|
379
|
+
}
|
|
380
|
+
: { agent: 'orchestrator' },
|
|
381
|
+
},
|
|
382
|
+
payload: value.payload,
|
|
383
|
+
};
|
|
384
|
+
}
|
|
385
|
+
function saveCaseFile(caseFile, source) {
|
|
386
|
+
saveNamedDataset(caseFile.projectId, {
|
|
387
|
+
name: caseFileDatasetName(caseFile.scenarioId),
|
|
388
|
+
kind: CASE_FILE_KIND,
|
|
389
|
+
data: caseFile,
|
|
390
|
+
source,
|
|
391
|
+
});
|
|
392
|
+
}
|
|
393
|
+
export function saveScenarioCaseFile(projectId, scenarioId, caseFile, source = 'case-file') {
|
|
394
|
+
const normalized = {
|
|
395
|
+
...caseFile,
|
|
396
|
+
projectId,
|
|
397
|
+
scenarioId,
|
|
398
|
+
updatedAt: nowIso(),
|
|
399
|
+
evidenceDatasetRefs: [...(caseFile.evidenceDatasetRefs ?? [])],
|
|
400
|
+
evidenceIndex: { ...(caseFile.evidenceIndex ?? {}) },
|
|
401
|
+
};
|
|
402
|
+
saveCaseFile(normalized, source);
|
|
403
|
+
return normalized;
|
|
404
|
+
}
|
|
405
|
+
export function loadScenarioCaseFile(projectId, scenarioId) {
|
|
406
|
+
const project = loadProject(projectId);
|
|
407
|
+
const dataset = project.namedDatasets[caseFileDatasetName(scenarioId)];
|
|
408
|
+
return normalizeCaseFile(dataset?.data);
|
|
409
|
+
}
|
|
410
|
+
export function ensureScenarioCaseFile(projectId, scenarioId, title = scenarioId) {
|
|
411
|
+
const existing = loadScenarioCaseFile(projectId, scenarioId);
|
|
412
|
+
if (existing)
|
|
413
|
+
return existing;
|
|
414
|
+
const timestamp = nowIso();
|
|
415
|
+
const caseFile = {
|
|
416
|
+
caseFileId: randomUUID(),
|
|
417
|
+
projectId,
|
|
418
|
+
scenarioId,
|
|
419
|
+
title,
|
|
420
|
+
createdAt: timestamp,
|
|
421
|
+
updatedAt: timestamp,
|
|
422
|
+
artifactRefs: {},
|
|
423
|
+
latestVersions: {},
|
|
424
|
+
evidenceDatasetRefs: [],
|
|
425
|
+
evidenceIndex: {},
|
|
426
|
+
};
|
|
427
|
+
saveCaseFile(caseFile, 'case-file');
|
|
428
|
+
return caseFile;
|
|
429
|
+
}
|
|
430
|
+
export function persistScenarioArtifact(options) {
|
|
431
|
+
const caseFile = ensureScenarioCaseFile(options.projectId, options.scenarioId);
|
|
432
|
+
const version = (caseFile.latestVersions[options.artifactType] ?? 0) + 1;
|
|
433
|
+
const createdAt = nowIso();
|
|
434
|
+
const artifact = {
|
|
435
|
+
artifactId: randomUUID(),
|
|
436
|
+
projectId: options.projectId,
|
|
437
|
+
scenarioId: options.scenarioId,
|
|
438
|
+
artifactType: options.artifactType,
|
|
439
|
+
version,
|
|
440
|
+
title: options.title ?? `${options.artifactType} v${version}`,
|
|
441
|
+
confidence: options.confidence,
|
|
442
|
+
warnings: [...(options.warnings ?? [])],
|
|
443
|
+
assumptionsUsed: [...(options.assumptionsUsed ?? [])],
|
|
444
|
+
evidenceRefs: [...(options.evidenceRefs ?? [])],
|
|
445
|
+
provenance: {
|
|
446
|
+
createdAt,
|
|
447
|
+
derivedFrom: [...(options.derivedFrom ?? [])],
|
|
448
|
+
source: options.source,
|
|
449
|
+
},
|
|
450
|
+
payload: options.payload,
|
|
451
|
+
};
|
|
452
|
+
const datasetName = artifactDatasetName(options.scenarioId, options.artifactType, version);
|
|
453
|
+
saveNamedDataset(options.projectId, {
|
|
454
|
+
name: datasetName,
|
|
455
|
+
kind: ARTIFACT_KIND,
|
|
456
|
+
data: artifact,
|
|
457
|
+
source: options.source.agent,
|
|
458
|
+
});
|
|
459
|
+
saveCaseFile({
|
|
460
|
+
...caseFile,
|
|
461
|
+
updatedAt: createdAt,
|
|
462
|
+
artifactRefs: {
|
|
463
|
+
...caseFile.artifactRefs,
|
|
464
|
+
[options.artifactType]: datasetName,
|
|
465
|
+
},
|
|
466
|
+
latestVersions: {
|
|
467
|
+
...caseFile.latestVersions,
|
|
468
|
+
[options.artifactType]: version,
|
|
469
|
+
},
|
|
470
|
+
...(options.artifactType === 'final-report' ? { finalRecommendationRef: datasetName } : {}),
|
|
471
|
+
...(options.artifactType === 'acceptance-status' ? { acceptanceStatusRef: datasetName } : {}),
|
|
472
|
+
}, options.source.agent);
|
|
473
|
+
return artifact;
|
|
474
|
+
}
|
|
475
|
+
export function listScenarioArtifacts(projectId, scenarioId, artifactType) {
|
|
476
|
+
const project = loadProject(projectId);
|
|
477
|
+
const prefix = `scenario-artifact:${scenarioId}:`;
|
|
478
|
+
return Object.entries(project.namedDatasets)
|
|
479
|
+
.filter(([name]) => name.startsWith(prefix))
|
|
480
|
+
.map(([name, dataset]) => {
|
|
481
|
+
const parsed = parseArtifactDatasetName(name, scenarioId);
|
|
482
|
+
if (!parsed)
|
|
483
|
+
return null;
|
|
484
|
+
if (artifactType && parsed.artifactType !== artifactType)
|
|
485
|
+
return null;
|
|
486
|
+
return normalizeArtifact(dataset.data, parsed.artifactType);
|
|
487
|
+
})
|
|
488
|
+
.filter((artifact) => Boolean(artifact))
|
|
489
|
+
.sort((a, b) => {
|
|
490
|
+
const typeRank = SCENARIO_ARTIFACT_TYPES.indexOf(a.artifactType) - SCENARIO_ARTIFACT_TYPES.indexOf(b.artifactType);
|
|
491
|
+
if (typeRank !== 0)
|
|
492
|
+
return typeRank;
|
|
493
|
+
if (a.version !== b.version)
|
|
494
|
+
return a.version - b.version;
|
|
495
|
+
return a.artifactId.localeCompare(b.artifactId);
|
|
496
|
+
});
|
|
497
|
+
}
|
|
498
|
+
export function loadLatestScenarioArtifact(projectId, scenarioId, artifactType) {
|
|
499
|
+
const caseFile = loadScenarioCaseFile(projectId, scenarioId);
|
|
500
|
+
const datasetName = caseFile?.artifactRefs[artifactType];
|
|
501
|
+
if (datasetName) {
|
|
502
|
+
const project = loadProject(projectId);
|
|
503
|
+
const artifact = normalizeArtifact(project.namedDatasets[datasetName]?.data, artifactType);
|
|
504
|
+
if (artifact)
|
|
505
|
+
return artifact;
|
|
506
|
+
}
|
|
507
|
+
const artifacts = listScenarioArtifacts(projectId, scenarioId, artifactType);
|
|
508
|
+
return (artifacts.length > 0 ? artifacts[artifacts.length - 1] : null);
|
|
509
|
+
}
|
|
510
|
+
export function loadLatestScenarioArtifacts(projectId, scenarioId) {
|
|
511
|
+
const result = {};
|
|
512
|
+
for (const artifactType of SCENARIO_ARTIFACT_TYPES) {
|
|
513
|
+
const latest = loadLatestScenarioArtifact(projectId, scenarioId, artifactType);
|
|
514
|
+
if (latest) {
|
|
515
|
+
result[artifactType] = latest;
|
|
516
|
+
}
|
|
517
|
+
}
|
|
518
|
+
return result;
|
|
519
|
+
}
|
|
520
|
+
export function buildSwarmSessionProjectRecord(query, session, options = {}) {
|
|
521
|
+
const stepCount = session.steps.length;
|
|
522
|
+
const stepAgents = Array.from(new Set(session.steps.map((step) => step.agent)));
|
|
523
|
+
const stepTypes = Array.from(new Set(session.steps.map((step) => step.type)));
|
|
524
|
+
return {
|
|
525
|
+
mode: options.mode ?? 'swarm',
|
|
526
|
+
query,
|
|
527
|
+
answer: options.answer,
|
|
528
|
+
summary: options.summary ?? 'Swarm session',
|
|
529
|
+
stepCount,
|
|
530
|
+
tokens: session.totalTokens,
|
|
531
|
+
latencyMs: session.totalLatencyMs,
|
|
532
|
+
context: {
|
|
533
|
+
...session.context,
|
|
534
|
+
swarmSession: {
|
|
535
|
+
stepCount,
|
|
536
|
+
reviewPassed: session.reviewPassed,
|
|
537
|
+
corrections: [...session.corrections],
|
|
538
|
+
totalTokens: session.totalTokens,
|
|
539
|
+
totalLatencyMs: session.totalLatencyMs,
|
|
540
|
+
agents: stepAgents,
|
|
541
|
+
stepTypes,
|
|
542
|
+
firstStepAt: session.steps[0]?.timestamp,
|
|
543
|
+
lastStepAt: stepCount > 0 ? session.steps[stepCount - 1].timestamp : undefined,
|
|
544
|
+
},
|
|
545
|
+
},
|
|
546
|
+
metadata: {
|
|
547
|
+
...(options.metadata ?? {}),
|
|
548
|
+
reviewPassed: session.reviewPassed,
|
|
549
|
+
corrections: [...session.corrections],
|
|
550
|
+
stepCount,
|
|
551
|
+
stepAgents,
|
|
552
|
+
},
|
|
553
|
+
};
|
|
554
|
+
}
|
|
555
|
+
export function persistSwarmCaseFile(projectId, query, session) {
|
|
556
|
+
const scenarioId = asString(session.context.scenarioId) ?? toScenarioId(query);
|
|
557
|
+
const caseFile = ensureScenarioCaseFile(projectId, scenarioId, titleFromTask(query));
|
|
558
|
+
const artifactRefs = {};
|
|
559
|
+
const groundModel = buildGroundModelPayload(session);
|
|
560
|
+
if (groundModel) {
|
|
561
|
+
const artifact = persistScenarioArtifact({
|
|
562
|
+
projectId,
|
|
563
|
+
scenarioId,
|
|
564
|
+
artifactType: 'ground-model',
|
|
565
|
+
payload: groundModel,
|
|
566
|
+
source: { agent: 'interpretation' },
|
|
567
|
+
title: 'Ground model snapshot',
|
|
568
|
+
});
|
|
569
|
+
artifactRefs['ground-model'] = artifact.artifactId;
|
|
570
|
+
}
|
|
571
|
+
const assumptions = buildAssumptionsPayload(projectId);
|
|
572
|
+
if (assumptions) {
|
|
573
|
+
const artifact = persistScenarioArtifact({
|
|
574
|
+
projectId,
|
|
575
|
+
scenarioId,
|
|
576
|
+
artifactType: 'assumptions',
|
|
577
|
+
payload: assumptions,
|
|
578
|
+
source: { agent: 'interpretation' },
|
|
579
|
+
title: 'Project assumptions snapshot',
|
|
580
|
+
});
|
|
581
|
+
artifactRefs.assumptions = artifact.artifactId;
|
|
582
|
+
}
|
|
583
|
+
const analysisPlan = buildAnalysisPlanPayload(query, session);
|
|
584
|
+
if (analysisPlan) {
|
|
585
|
+
const artifact = persistScenarioArtifact({
|
|
586
|
+
projectId,
|
|
587
|
+
scenarioId,
|
|
588
|
+
artifactType: 'analysis-plan',
|
|
589
|
+
payload: analysisPlan,
|
|
590
|
+
source: { agent: 'simulation' },
|
|
591
|
+
title: 'Swarm analysis plan',
|
|
592
|
+
});
|
|
593
|
+
artifactRefs['analysis-plan'] = artifact.artifactId;
|
|
594
|
+
}
|
|
595
|
+
const results = buildResultsPayload(scenarioId, session);
|
|
596
|
+
if (results) {
|
|
597
|
+
const artifact = persistScenarioArtifact({
|
|
598
|
+
projectId,
|
|
599
|
+
scenarioId,
|
|
600
|
+
artifactType: 'results',
|
|
601
|
+
payload: results,
|
|
602
|
+
source: { agent: 'simulation' },
|
|
603
|
+
title: 'Swarm simulation results',
|
|
604
|
+
});
|
|
605
|
+
artifactRefs.results = artifact.artifactId;
|
|
606
|
+
}
|
|
607
|
+
const checklist = buildReviewChecklistPayload(session);
|
|
608
|
+
const checklistArtifact = persistScenarioArtifact({
|
|
609
|
+
projectId,
|
|
610
|
+
scenarioId,
|
|
611
|
+
artifactType: 'review-checklist',
|
|
612
|
+
payload: checklist,
|
|
613
|
+
source: { agent: 'reviewer' },
|
|
614
|
+
title: 'Reviewer checklist snapshot',
|
|
615
|
+
});
|
|
616
|
+
artifactRefs['review-checklist'] = checklistArtifact.artifactId;
|
|
617
|
+
const acceptance = persistScenarioArtifact({
|
|
618
|
+
projectId,
|
|
619
|
+
scenarioId,
|
|
620
|
+
artifactType: 'acceptance-status',
|
|
621
|
+
payload: buildAcceptanceStatusPayload(session),
|
|
622
|
+
source: { agent: 'reviewer' },
|
|
623
|
+
title: 'Reviewer acceptance status',
|
|
624
|
+
});
|
|
625
|
+
artifactRefs['acceptance-status'] = acceptance.artifactId;
|
|
626
|
+
const issues = buildIssuesPayload(session);
|
|
627
|
+
if (issues) {
|
|
628
|
+
const artifact = persistScenarioArtifact({
|
|
629
|
+
projectId,
|
|
630
|
+
scenarioId,
|
|
631
|
+
artifactType: 'issues-and-corrections',
|
|
632
|
+
payload: issues,
|
|
633
|
+
source: { agent: 'reviewer' },
|
|
634
|
+
title: 'Reviewer issues and corrections',
|
|
635
|
+
});
|
|
636
|
+
artifactRefs['issues-and-corrections'] = artifact.artifactId;
|
|
637
|
+
}
|
|
638
|
+
const answer = extractAnswer(session);
|
|
639
|
+
if (answer) {
|
|
640
|
+
const artifact = persistScenarioArtifact({
|
|
641
|
+
projectId,
|
|
642
|
+
scenarioId,
|
|
643
|
+
artifactType: 'final-report',
|
|
644
|
+
payload: buildFinalReportPayload(projectId, answer),
|
|
645
|
+
source: { agent: 'orchestrator' },
|
|
646
|
+
title: 'Swarm final report',
|
|
647
|
+
});
|
|
648
|
+
artifactRefs['final-report'] = artifact.artifactId;
|
|
649
|
+
}
|
|
650
|
+
const latest = loadScenarioCaseFile(projectId, scenarioId);
|
|
651
|
+
return {
|
|
652
|
+
scenarioId,
|
|
653
|
+
caseFileId: latest?.caseFileId ?? caseFile.caseFileId,
|
|
654
|
+
artifactRefs,
|
|
655
|
+
};
|
|
656
|
+
}
|
|
657
|
+
//# sourceMappingURL=case-file.js.map
|