@redaksjon/protokoll 1.0.13 → 1.0.14-dev.20260216152331.67acb08
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/.transcript/weight-model.json +12 -0
- package/dist/configDiscovery.js +952 -22
- package/dist/configDiscovery.js.map +1 -1
- package/dist/mcp/server-hono.js +667 -0
- package/dist/mcp/server-hono.js.map +1 -0
- package/dist/mcp/server-http.js +1 -1
- package/dist/mcp/server.js +4 -0
- package/dist/mcp/server.js.map +1 -1
- package/dist/weightModel.js +81 -0
- package/dist/weightModel.js.map +1 -0
- package/guide/audio-upload.md +324 -0
- package/guide/index.md +1 -0
- package/package.json +11 -6
- package/vite.config.ts +6 -0
package/dist/configDiscovery.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import { resolve,
|
|
1
|
+
import { resolve, relative, isAbsolute, extname, join, dirname, basename } from 'node:path';
|
|
2
2
|
import * as Metadata from '@redaksjon/protokoll-engine';
|
|
3
3
|
import { Transcript, Media, Util, Pipeline, findIgnoredResilient, findCompanyResilient, findTermResilient, findPersonResilient, findProjectResilient, Reasoning, VALID_STATUSES, isValidStatus } from '@redaksjon/protokoll-engine';
|
|
4
4
|
import * as yaml from 'js-yaml';
|
|
5
|
-
import { stat, readdir, mkdir } from 'node:fs/promises';
|
|
5
|
+
import { stat, readdir, mkdir, unlink } from 'node:fs/promises';
|
|
6
6
|
import { readFileSync } from 'node:fs';
|
|
7
7
|
import { fileURLToPath } from 'node:url';
|
|
8
8
|
import { create as create$1, parseEntityUri as parseEntityUri$1, createRelationship, createDocumentContent, createCodeContent, createMarkdownContent, createTextContent, createUrlContent } from '@redaksjon/context';
|
|
@@ -277,7 +277,7 @@ const resourceTemplates = [
|
|
|
277
277
|
}
|
|
278
278
|
];
|
|
279
279
|
|
|
280
|
-
const VERSION = "1.0.
|
|
280
|
+
const VERSION = "1.0.14-dev.20260216152331.67acb08 (working/67acb08 2026-02-16 07:22:44 -0800) linux arm64 v24.13.1";
|
|
281
281
|
const PROGRAM_NAME = "protokoll";
|
|
282
282
|
const DATE_FORMAT_YEAR_MONTH_DAY_HOURS_MINUTES_SECONDS = "YYYY-M-D-HHmmss";
|
|
283
283
|
const DEFAULT_AUDIO_EXTENSIONS = ["mp3", "mp4", "mpeg", "mpga", "m4a", "wav", "webm", "qta"];
|
|
@@ -668,7 +668,7 @@ const ensurePklExtension$1 = Transcript.ensurePklExtension;
|
|
|
668
668
|
function isUuidInput(input) {
|
|
669
669
|
return /^[a-f0-9]{8}/.test(input);
|
|
670
670
|
}
|
|
671
|
-
async function findTranscriptByUuid(uuid, searchDirectories) {
|
|
671
|
+
async function findTranscriptByUuid$1(uuid, searchDirectories) {
|
|
672
672
|
return Transcript.findTranscriptByUuid(uuid, searchDirectories);
|
|
673
673
|
}
|
|
674
674
|
const logger = getLogger();
|
|
@@ -821,7 +821,7 @@ async function resolveTranscriptPath$1(uriOrPathOrUuid, contextDirectory) {
|
|
|
821
821
|
}
|
|
822
822
|
const outputDirectory = await getConfiguredDirectory("outputDirectory");
|
|
823
823
|
if (isUuidInput(uriOrPathOrUuid)) {
|
|
824
|
-
const foundPath = await findTranscriptByUuid(uriOrPathOrUuid, [outputDirectory]);
|
|
824
|
+
const foundPath = await findTranscriptByUuid$1(uriOrPathOrUuid, [outputDirectory]);
|
|
825
825
|
if (!foundPath) {
|
|
826
826
|
throw new Error(`Transcript not found for UUID: ${uriOrPathOrUuid}`);
|
|
827
827
|
}
|
|
@@ -858,6 +858,25 @@ async function resolveTranscriptPath$1(uriOrPathOrUuid, contextDirectory) {
|
|
|
858
858
|
return existsResult.path;
|
|
859
859
|
}
|
|
860
860
|
|
|
861
|
+
const shared = /*#__PURE__*/Object.freeze(/*#__PURE__*/Object.defineProperty({
|
|
862
|
+
__proto__: null,
|
|
863
|
+
fileExists,
|
|
864
|
+
formatEntity,
|
|
865
|
+
getAudioMetadata,
|
|
866
|
+
getConfiguredDirectory,
|
|
867
|
+
getContextDirectories,
|
|
868
|
+
logger,
|
|
869
|
+
media,
|
|
870
|
+
mergeArray,
|
|
871
|
+
resolveTranscriptPath: resolveTranscriptPath$1,
|
|
872
|
+
sanitizePath,
|
|
873
|
+
slugify,
|
|
874
|
+
storage,
|
|
875
|
+
validateNotRemoteMode,
|
|
876
|
+
validatePathWithinDirectory,
|
|
877
|
+
validatePathWithinOutputDirectory
|
|
878
|
+
}, Symbol.toStringTag, { value: 'Module' }));
|
|
879
|
+
|
|
861
880
|
const { listTranscripts, resolveTranscriptPath, readTranscriptContent, stripTranscriptExtension } = Transcript;
|
|
862
881
|
async function readTranscriptResource(transcriptPath) {
|
|
863
882
|
if (!transcriptPath || typeof transcriptPath !== "string") {
|
|
@@ -2023,6 +2042,8 @@ async function handleProcessAudio(args) {
|
|
|
2023
2042
|
const outputFilenameOptions = contextConfig.outputFilenameOptions || DEFAULT_OUTPUT_FILENAME_OPTIONS;
|
|
2024
2043
|
const processedDirectory = config.processedDirectory ?? void 0;
|
|
2025
2044
|
const { creationTime, hash } = await getAudioMetadata(audioFile);
|
|
2045
|
+
const { getWeightModelService, updateTranscriptInWeightModel } = await import('./weightModel.js');
|
|
2046
|
+
const weightModelService = getWeightModelService();
|
|
2026
2047
|
const pipeline = await Pipeline.create({
|
|
2027
2048
|
model: args.model || DEFAULT_MODEL,
|
|
2028
2049
|
transcriptionModel: args.transcriptionModel || DEFAULT_TRANSCRIPTION_MODEL,
|
|
@@ -2041,7 +2062,11 @@ async function handleProcessAudio(args) {
|
|
|
2041
2062
|
outputFilenameOptions,
|
|
2042
2063
|
processedDirectory: processedDirectory || void 0,
|
|
2043
2064
|
maxAudioSize: DEFAULT_MAX_AUDIO_SIZE,
|
|
2044
|
-
tempDirectory: DEFAULT_TEMP_DIRECTORY
|
|
2065
|
+
tempDirectory: DEFAULT_TEMP_DIRECTORY,
|
|
2066
|
+
weightModelProvider: weightModelService?.provider,
|
|
2067
|
+
onTranscriptEntitiesUpdated: (uuid, entityIds, projectId) => {
|
|
2068
|
+
updateTranscriptInWeightModel(uuid, entityIds, projectId);
|
|
2069
|
+
}
|
|
2045
2070
|
});
|
|
2046
2071
|
const result = await pipeline.process({
|
|
2047
2072
|
audioFile,
|
|
@@ -2133,6 +2158,18 @@ const listProjectsTool = {
|
|
|
2133
2158
|
includeInactive: {
|
|
2134
2159
|
type: "boolean",
|
|
2135
2160
|
description: "Include inactive projects (default: false)"
|
|
2161
|
+
},
|
|
2162
|
+
limit: {
|
|
2163
|
+
type: "number",
|
|
2164
|
+
description: "Maximum number of results to return (default: 50)"
|
|
2165
|
+
},
|
|
2166
|
+
offset: {
|
|
2167
|
+
type: "number",
|
|
2168
|
+
description: "Number of results to skip for pagination (default: 0)"
|
|
2169
|
+
},
|
|
2170
|
+
search: {
|
|
2171
|
+
type: "string",
|
|
2172
|
+
description: "Filter by name/ID substring match (case-insensitive)"
|
|
2136
2173
|
}
|
|
2137
2174
|
},
|
|
2138
2175
|
required: []
|
|
@@ -2147,6 +2184,18 @@ const listPeopleTool = {
|
|
|
2147
2184
|
contextDirectory: {
|
|
2148
2185
|
type: "string",
|
|
2149
2186
|
description: "Path to the .protokoll context directory"
|
|
2187
|
+
},
|
|
2188
|
+
limit: {
|
|
2189
|
+
type: "number",
|
|
2190
|
+
description: "Maximum number of results to return (default: 50)"
|
|
2191
|
+
},
|
|
2192
|
+
offset: {
|
|
2193
|
+
type: "number",
|
|
2194
|
+
description: "Number of results to skip for pagination (default: 0)"
|
|
2195
|
+
},
|
|
2196
|
+
search: {
|
|
2197
|
+
type: "string",
|
|
2198
|
+
description: "Filter by name/ID substring match (case-insensitive)"
|
|
2150
2199
|
}
|
|
2151
2200
|
},
|
|
2152
2201
|
required: []
|
|
@@ -2161,6 +2210,18 @@ const listTermsTool = {
|
|
|
2161
2210
|
contextDirectory: {
|
|
2162
2211
|
type: "string",
|
|
2163
2212
|
description: "Path to the .protokoll context directory"
|
|
2213
|
+
},
|
|
2214
|
+
limit: {
|
|
2215
|
+
type: "number",
|
|
2216
|
+
description: "Maximum number of results to return (default: 50)"
|
|
2217
|
+
},
|
|
2218
|
+
offset: {
|
|
2219
|
+
type: "number",
|
|
2220
|
+
description: "Number of results to skip for pagination (default: 0)"
|
|
2221
|
+
},
|
|
2222
|
+
search: {
|
|
2223
|
+
type: "string",
|
|
2224
|
+
description: "Filter by name/ID substring match (case-insensitive)"
|
|
2164
2225
|
}
|
|
2165
2226
|
},
|
|
2166
2227
|
required: []
|
|
@@ -2175,6 +2236,18 @@ const listCompaniesTool = {
|
|
|
2175
2236
|
contextDirectory: {
|
|
2176
2237
|
type: "string",
|
|
2177
2238
|
description: "Path to the .protokoll context directory"
|
|
2239
|
+
},
|
|
2240
|
+
limit: {
|
|
2241
|
+
type: "number",
|
|
2242
|
+
description: "Maximum number of results to return (default: 50)"
|
|
2243
|
+
},
|
|
2244
|
+
offset: {
|
|
2245
|
+
type: "number",
|
|
2246
|
+
description: "Number of results to skip for pagination (default: 0)"
|
|
2247
|
+
},
|
|
2248
|
+
search: {
|
|
2249
|
+
type: "string",
|
|
2250
|
+
description: "Filter by name/ID substring match (case-insensitive)"
|
|
2178
2251
|
}
|
|
2179
2252
|
},
|
|
2180
2253
|
required: []
|
|
@@ -2193,6 +2266,14 @@ const searchContextTool = {
|
|
|
2193
2266
|
contextDirectory: {
|
|
2194
2267
|
type: "string",
|
|
2195
2268
|
description: "Path to the .protokoll context directory"
|
|
2269
|
+
},
|
|
2270
|
+
limit: {
|
|
2271
|
+
type: "number",
|
|
2272
|
+
description: "Maximum number of results to return (default: 50)"
|
|
2273
|
+
},
|
|
2274
|
+
offset: {
|
|
2275
|
+
type: "number",
|
|
2276
|
+
description: "Number of results to skip for pagination (default: 0)"
|
|
2196
2277
|
}
|
|
2197
2278
|
},
|
|
2198
2279
|
required: ["query"]
|
|
@@ -2221,6 +2302,35 @@ const getEntityTool = {
|
|
|
2221
2302
|
required: ["entityType", "entityId"]
|
|
2222
2303
|
}
|
|
2223
2304
|
};
|
|
2305
|
+
const predictEntitiesTool = {
|
|
2306
|
+
name: "protokoll_predict_entities",
|
|
2307
|
+
description: "Predict likely entities based on transcript context using weight model. Returns ranked entity suggestions based on co-occurrence patterns and project affinity. Useful for intelligent entity correction suggestions.",
|
|
2308
|
+
inputSchema: {
|
|
2309
|
+
type: "object",
|
|
2310
|
+
properties: {
|
|
2311
|
+
transcriptPath: {
|
|
2312
|
+
type: "string",
|
|
2313
|
+
description: "Path to the transcript file"
|
|
2314
|
+
},
|
|
2315
|
+
maxPredictions: {
|
|
2316
|
+
type: "number",
|
|
2317
|
+
description: "Maximum number of predictions to return (default: 10)",
|
|
2318
|
+
minimum: 1,
|
|
2319
|
+
maximum: 50
|
|
2320
|
+
},
|
|
2321
|
+
minScore: {
|
|
2322
|
+
type: "number",
|
|
2323
|
+
description: "Minimum prediction score (default: 1)",
|
|
2324
|
+
minimum: 0
|
|
2325
|
+
},
|
|
2326
|
+
contextDirectory: {
|
|
2327
|
+
type: "string",
|
|
2328
|
+
description: "Path to the .protokoll context directory"
|
|
2329
|
+
}
|
|
2330
|
+
},
|
|
2331
|
+
required: ["transcriptPath"]
|
|
2332
|
+
}
|
|
2333
|
+
};
|
|
2224
2334
|
async function handleContextStatus(args) {
|
|
2225
2335
|
const context = await getContextInstance(args.contextDirectory);
|
|
2226
2336
|
const dirs = context.getDiscoveredDirs();
|
|
@@ -2252,9 +2362,22 @@ async function handleListProjects(args) {
|
|
|
2252
2362
|
if (!args.includeInactive) {
|
|
2253
2363
|
projects = projects.filter((p) => p.active !== false);
|
|
2254
2364
|
}
|
|
2365
|
+
if (args.search) {
|
|
2366
|
+
const searchLower = args.search.toLowerCase();
|
|
2367
|
+
projects = projects.filter(
|
|
2368
|
+
(p) => p.name.toLowerCase().includes(searchLower) || p.id.toLowerCase().includes(searchLower)
|
|
2369
|
+
);
|
|
2370
|
+
}
|
|
2371
|
+
const total = projects.length;
|
|
2372
|
+
const limit = args.limit ?? 50;
|
|
2373
|
+
const offset = args.offset ?? 0;
|
|
2374
|
+
const paginatedProjects = projects.slice(offset, offset + limit);
|
|
2255
2375
|
return {
|
|
2256
|
-
|
|
2257
|
-
|
|
2376
|
+
total,
|
|
2377
|
+
limit,
|
|
2378
|
+
offset,
|
|
2379
|
+
count: paginatedProjects.length,
|
|
2380
|
+
projects: paginatedProjects.map((p) => ({
|
|
2258
2381
|
id: p.id,
|
|
2259
2382
|
name: p.name,
|
|
2260
2383
|
active: p.active !== false,
|
|
@@ -2267,10 +2390,23 @@ async function handleListProjects(args) {
|
|
|
2267
2390
|
}
|
|
2268
2391
|
async function handleListPeople(args) {
|
|
2269
2392
|
const context = await getContextInstance(args.contextDirectory);
|
|
2270
|
-
|
|
2393
|
+
let people = context.getAllPeople();
|
|
2394
|
+
if (args.search) {
|
|
2395
|
+
const searchLower = args.search.toLowerCase();
|
|
2396
|
+
people = people.filter(
|
|
2397
|
+
(p) => p.name.toLowerCase().includes(searchLower) || p.id.toLowerCase().includes(searchLower) || p.sounds_like?.some((s) => s.toLowerCase().includes(searchLower))
|
|
2398
|
+
);
|
|
2399
|
+
}
|
|
2400
|
+
const total = people.length;
|
|
2401
|
+
const limit = args.limit ?? 50;
|
|
2402
|
+
const offset = args.offset ?? 0;
|
|
2403
|
+
const paginatedPeople = people.slice(offset, offset + limit);
|
|
2271
2404
|
return {
|
|
2272
|
-
|
|
2273
|
-
|
|
2405
|
+
total,
|
|
2406
|
+
limit,
|
|
2407
|
+
offset,
|
|
2408
|
+
count: paginatedPeople.length,
|
|
2409
|
+
people: paginatedPeople.map((p) => ({
|
|
2274
2410
|
id: p.id,
|
|
2275
2411
|
name: p.name,
|
|
2276
2412
|
company: p.company,
|
|
@@ -2281,10 +2417,23 @@ async function handleListPeople(args) {
|
|
|
2281
2417
|
}
|
|
2282
2418
|
async function handleListTerms(args) {
|
|
2283
2419
|
const context = await getContextInstance(args.contextDirectory);
|
|
2284
|
-
|
|
2420
|
+
let terms = context.getAllTerms();
|
|
2421
|
+
if (args.search) {
|
|
2422
|
+
const searchLower = args.search.toLowerCase();
|
|
2423
|
+
terms = terms.filter(
|
|
2424
|
+
(t) => t.name.toLowerCase().includes(searchLower) || t.id.toLowerCase().includes(searchLower) || t.sounds_like?.some((s) => s.toLowerCase().includes(searchLower))
|
|
2425
|
+
);
|
|
2426
|
+
}
|
|
2427
|
+
const total = terms.length;
|
|
2428
|
+
const limit = args.limit ?? 50;
|
|
2429
|
+
const offset = args.offset ?? 0;
|
|
2430
|
+
const paginatedTerms = terms.slice(offset, offset + limit);
|
|
2285
2431
|
return {
|
|
2286
|
-
|
|
2287
|
-
|
|
2432
|
+
total,
|
|
2433
|
+
limit,
|
|
2434
|
+
offset,
|
|
2435
|
+
count: paginatedTerms.length,
|
|
2436
|
+
terms: paginatedTerms.map((t) => ({
|
|
2288
2437
|
id: t.id,
|
|
2289
2438
|
name: t.name,
|
|
2290
2439
|
expansion: t.expansion,
|
|
@@ -2295,10 +2444,23 @@ async function handleListTerms(args) {
|
|
|
2295
2444
|
}
|
|
2296
2445
|
async function handleListCompanies(args) {
|
|
2297
2446
|
const context = await getContextInstance(args.contextDirectory);
|
|
2298
|
-
|
|
2447
|
+
let companies = context.getAllCompanies();
|
|
2448
|
+
if (args.search) {
|
|
2449
|
+
const searchLower = args.search.toLowerCase();
|
|
2450
|
+
companies = companies.filter(
|
|
2451
|
+
(c) => c.name.toLowerCase().includes(searchLower) || c.id.toLowerCase().includes(searchLower) || c.sounds_like?.some((s) => s.toLowerCase().includes(searchLower))
|
|
2452
|
+
);
|
|
2453
|
+
}
|
|
2454
|
+
const total = companies.length;
|
|
2455
|
+
const limit = args.limit ?? 50;
|
|
2456
|
+
const offset = args.offset ?? 0;
|
|
2457
|
+
const paginatedCompanies = companies.slice(offset, offset + limit);
|
|
2299
2458
|
return {
|
|
2300
|
-
|
|
2301
|
-
|
|
2459
|
+
total,
|
|
2460
|
+
limit,
|
|
2461
|
+
offset,
|
|
2462
|
+
count: paginatedCompanies.length,
|
|
2463
|
+
companies: paginatedCompanies.map((c) => ({
|
|
2302
2464
|
id: c.id,
|
|
2303
2465
|
name: c.name,
|
|
2304
2466
|
fullName: c.fullName,
|
|
@@ -2310,10 +2472,17 @@ async function handleListCompanies(args) {
|
|
|
2310
2472
|
async function handleSearchContext(args) {
|
|
2311
2473
|
const context = await getContextInstance(args.contextDirectory);
|
|
2312
2474
|
const results = context.search(args.query);
|
|
2475
|
+
const total = results.length;
|
|
2476
|
+
const limit = args.limit ?? 50;
|
|
2477
|
+
const offset = args.offset ?? 0;
|
|
2478
|
+
const paginatedResults = results.slice(offset, offset + limit);
|
|
2313
2479
|
return {
|
|
2314
2480
|
query: args.query,
|
|
2315
|
-
|
|
2316
|
-
|
|
2481
|
+
total,
|
|
2482
|
+
limit,
|
|
2483
|
+
offset,
|
|
2484
|
+
count: paginatedResults.length,
|
|
2485
|
+
results: paginatedResults.map(formatEntity)
|
|
2317
2486
|
};
|
|
2318
2487
|
}
|
|
2319
2488
|
async function handleGetEntity(args) {
|
|
@@ -2344,6 +2513,36 @@ async function handleGetEntity(args) {
|
|
|
2344
2513
|
filePath
|
|
2345
2514
|
};
|
|
2346
2515
|
}
|
|
2516
|
+
async function handlePredictEntities(args) {
|
|
2517
|
+
const { resolveTranscriptPath } = await Promise.resolve().then(() => shared);
|
|
2518
|
+
const { Transcript: TranscriptUtils } = await import('@redaksjon/protokoll-engine');
|
|
2519
|
+
const { ensurePklExtension } = TranscriptUtils;
|
|
2520
|
+
const { PklTranscript } = await import('@redaksjon/protokoll-format');
|
|
2521
|
+
const absolutePath = await resolveTranscriptPath(args.transcriptPath, args.contextDirectory);
|
|
2522
|
+
const pklPath = ensurePklExtension(absolutePath);
|
|
2523
|
+
const transcript = PklTranscript.open(pklPath, { readOnly: true });
|
|
2524
|
+
const projectId = transcript.metadata.project;
|
|
2525
|
+
const entities = transcript.metadata.entities || {};
|
|
2526
|
+
const knownEntityIds = [
|
|
2527
|
+
...(entities.people || []).map((e) => e.id),
|
|
2528
|
+
...(entities.projects || []).map((e) => e.id),
|
|
2529
|
+
...(entities.terms || []).map((e) => e.id),
|
|
2530
|
+
...(entities.companies || []).map((e) => e.id)
|
|
2531
|
+
];
|
|
2532
|
+
transcript.close();
|
|
2533
|
+
const { getWeightModelService } = await import('./weightModel.js');
|
|
2534
|
+
const service = getWeightModelService();
|
|
2535
|
+
if (!service?.isReady || !service.provider) {
|
|
2536
|
+
return { success: true, predictions: [] };
|
|
2537
|
+
}
|
|
2538
|
+
const predictions = service.provider.predictLikelyEntities({
|
|
2539
|
+
knownEntityIds,
|
|
2540
|
+
projectId,
|
|
2541
|
+
maxPredictions: args.maxPredictions || 10,
|
|
2542
|
+
minScore: args.minScore ?? 1
|
|
2543
|
+
});
|
|
2544
|
+
return { success: true, predictions };
|
|
2545
|
+
}
|
|
2347
2546
|
|
|
2348
2547
|
const addPersonTool = {
|
|
2349
2548
|
name: "protokoll_add_person",
|
|
@@ -2898,6 +3097,51 @@ const addCompanyTool = {
|
|
|
2898
3097
|
required: ["name"]
|
|
2899
3098
|
}
|
|
2900
3099
|
};
|
|
3100
|
+
const editCompanyTool = {
|
|
3101
|
+
name: "protokoll_edit_company",
|
|
3102
|
+
description: "Edit an existing company with manual modifications. Can update company details and manage sounds_like variants.",
|
|
3103
|
+
inputSchema: {
|
|
3104
|
+
type: "object",
|
|
3105
|
+
properties: {
|
|
3106
|
+
id: {
|
|
3107
|
+
type: "string",
|
|
3108
|
+
description: "Company ID to edit"
|
|
3109
|
+
},
|
|
3110
|
+
name: {
|
|
3111
|
+
type: "string",
|
|
3112
|
+
description: "Update company name"
|
|
3113
|
+
},
|
|
3114
|
+
fullName: {
|
|
3115
|
+
type: "string",
|
|
3116
|
+
description: "Set full legal name"
|
|
3117
|
+
},
|
|
3118
|
+
industry: {
|
|
3119
|
+
type: "string",
|
|
3120
|
+
description: "Set industry"
|
|
3121
|
+
},
|
|
3122
|
+
sounds_like: {
|
|
3123
|
+
type: "array",
|
|
3124
|
+
items: { type: "string" },
|
|
3125
|
+
description: "Replace entire sounds_like array"
|
|
3126
|
+
},
|
|
3127
|
+
add_sounds_like: {
|
|
3128
|
+
type: "array",
|
|
3129
|
+
items: { type: "string" },
|
|
3130
|
+
description: "Add sounds_like variants"
|
|
3131
|
+
},
|
|
3132
|
+
remove_sounds_like: {
|
|
3133
|
+
type: "array",
|
|
3134
|
+
items: { type: "string" },
|
|
3135
|
+
description: "Remove sounds_like variants"
|
|
3136
|
+
},
|
|
3137
|
+
contextDirectory: {
|
|
3138
|
+
type: "string",
|
|
3139
|
+
description: "Path to the .protokoll context directory"
|
|
3140
|
+
}
|
|
3141
|
+
},
|
|
3142
|
+
required: ["id"]
|
|
3143
|
+
}
|
|
3144
|
+
};
|
|
2901
3145
|
const deleteEntityTool = {
|
|
2902
3146
|
name: "protokoll_delete_entity",
|
|
2903
3147
|
description: "Delete an entity from the context. Removes the entity's YAML file from the context directory.",
|
|
@@ -3426,6 +3670,47 @@ async function handleAddCompany(args) {
|
|
|
3426
3670
|
entity: formatEntity(company)
|
|
3427
3671
|
};
|
|
3428
3672
|
}
|
|
3673
|
+
async function handleEditCompany(args) {
|
|
3674
|
+
const context = await create({
|
|
3675
|
+
startingDir: args.contextDirectory || process.cwd()
|
|
3676
|
+
});
|
|
3677
|
+
if (!context.hasContext()) {
|
|
3678
|
+
throw new Error("No .protokoll directory found. Initialize context first.");
|
|
3679
|
+
}
|
|
3680
|
+
const existingCompany = findCompanyResilient(context, args.id);
|
|
3681
|
+
const updatedSoundsLike = mergeArray(
|
|
3682
|
+
existingCompany.sounds_like,
|
|
3683
|
+
args.sounds_like,
|
|
3684
|
+
args.add_sounds_like,
|
|
3685
|
+
args.remove_sounds_like
|
|
3686
|
+
);
|
|
3687
|
+
const updatedCompany = {
|
|
3688
|
+
...existingCompany,
|
|
3689
|
+
...args.name !== void 0 && { name: args.name },
|
|
3690
|
+
...args.fullName !== void 0 && { fullName: args.fullName },
|
|
3691
|
+
...args.industry !== void 0 && { industry: args.industry },
|
|
3692
|
+
updatedAt: /* @__PURE__ */ new Date()
|
|
3693
|
+
};
|
|
3694
|
+
if (updatedSoundsLike !== void 0) {
|
|
3695
|
+
updatedCompany.sounds_like = updatedSoundsLike;
|
|
3696
|
+
} else if (existingCompany.sounds_like && (args.sounds_like !== void 0 || args.remove_sounds_like)) {
|
|
3697
|
+
delete updatedCompany.sounds_like;
|
|
3698
|
+
}
|
|
3699
|
+
await context.saveEntity(updatedCompany, true);
|
|
3700
|
+
const changes = [];
|
|
3701
|
+
if (args.name !== void 0) changes.push(`name: "${args.name}"`);
|
|
3702
|
+
if (args.fullName !== void 0) changes.push(`fullName: "${args.fullName}"`);
|
|
3703
|
+
if (args.industry !== void 0) changes.push(`industry: "${args.industry}"`);
|
|
3704
|
+
if (args.sounds_like !== void 0) changes.push(`sounds_like replaced with ${args.sounds_like.length} items`);
|
|
3705
|
+
if (args.add_sounds_like?.length) changes.push(`added ${args.add_sounds_like.length} sounds_like variants`);
|
|
3706
|
+
if (args.remove_sounds_like?.length) changes.push(`removed ${args.remove_sounds_like.length} sounds_like variants`);
|
|
3707
|
+
return {
|
|
3708
|
+
success: true,
|
|
3709
|
+
message: `Updated company "${existingCompany.name}"`,
|
|
3710
|
+
changes,
|
|
3711
|
+
company: formatEntity(updatedCompany)
|
|
3712
|
+
};
|
|
3713
|
+
}
|
|
3429
3714
|
async function handleDeleteEntity(args) {
|
|
3430
3715
|
const context = await create({
|
|
3431
3716
|
startingDir: args.contextDirectory || process.cwd()
|
|
@@ -3460,6 +3745,34 @@ async function handleDeleteEntity(args) {
|
|
|
3460
3745
|
};
|
|
3461
3746
|
}
|
|
3462
3747
|
|
|
3748
|
+
const entityTools = /*#__PURE__*/Object.freeze(/*#__PURE__*/Object.defineProperty({
|
|
3749
|
+
__proto__: null,
|
|
3750
|
+
addCompanyTool,
|
|
3751
|
+
addPersonTool,
|
|
3752
|
+
addProjectTool,
|
|
3753
|
+
addTermTool,
|
|
3754
|
+
deleteEntityTool,
|
|
3755
|
+
editCompanyTool,
|
|
3756
|
+
editPersonTool,
|
|
3757
|
+
editProjectTool,
|
|
3758
|
+
editTermTool,
|
|
3759
|
+
handleAddCompany,
|
|
3760
|
+
handleAddPerson,
|
|
3761
|
+
handleAddProject,
|
|
3762
|
+
handleAddTerm,
|
|
3763
|
+
handleDeleteEntity,
|
|
3764
|
+
handleEditCompany,
|
|
3765
|
+
handleEditPerson,
|
|
3766
|
+
handleEditProject,
|
|
3767
|
+
handleEditTerm,
|
|
3768
|
+
handleMergeTerms,
|
|
3769
|
+
handleUpdateProject,
|
|
3770
|
+
handleUpdateTerm,
|
|
3771
|
+
mergeTermsTool,
|
|
3772
|
+
updateProjectTool,
|
|
3773
|
+
updateTermTool
|
|
3774
|
+
}, Symbol.toStringTag, { value: 'Module' }));
|
|
3775
|
+
|
|
3463
3776
|
const suggestProjectMetadataTool = {
|
|
3464
3777
|
name: "protokoll_suggest_project_metadata",
|
|
3465
3778
|
description: "Generate project metadata suggestions without creating the project. Returns sounds_like (phonetic variants for transcription), explicit_phrases (content-matching trigger phrases), topics and description from source content. Useful for interactive workflows where AI assistant presents suggestions for user review before creating the project.",
|
|
@@ -3585,6 +3898,15 @@ const listTranscriptsTool = {
|
|
|
3585
3898
|
type: "string",
|
|
3586
3899
|
description: "Search for transcripts containing this text (searches filename and content)"
|
|
3587
3900
|
},
|
|
3901
|
+
entityId: {
|
|
3902
|
+
type: "string",
|
|
3903
|
+
description: "Filter to transcripts that reference this entity ID"
|
|
3904
|
+
},
|
|
3905
|
+
entityType: {
|
|
3906
|
+
type: "string",
|
|
3907
|
+
enum: ["person", "project", "term", "company"],
|
|
3908
|
+
description: "Entity type to filter by (used with entityId to narrow search)"
|
|
3909
|
+
},
|
|
3588
3910
|
contextDirectory: {
|
|
3589
3911
|
type: "string",
|
|
3590
3912
|
description: "Optional: Path to the .protokoll context directory"
|
|
@@ -3835,6 +4157,72 @@ const createNoteTool = {
|
|
|
3835
4157
|
required: ["title"]
|
|
3836
4158
|
}
|
|
3837
4159
|
};
|
|
4160
|
+
const getEnhancementLogTool = {
|
|
4161
|
+
name: "protokoll_get_enhancement_log",
|
|
4162
|
+
description: "Get the enhancement log for a transcript. Returns a timestamped audit trail of enhancement pipeline steps (transcribe, enhance, simple-replace phases). Shows what happened during processing: entities found, corrections applied, tools called, etc.",
|
|
4163
|
+
inputSchema: {
|
|
4164
|
+
type: "object",
|
|
4165
|
+
properties: {
|
|
4166
|
+
transcriptPath: {
|
|
4167
|
+
type: "string",
|
|
4168
|
+
description: 'Transcript URI (preferred) or relative path from output directory. URI format: "protokoll://transcript/2026/2/12-1606-meeting" (no file extension). Path format: "2026/2/12-1606-meeting" or "2026/2/12-1606-meeting.pkl"'
|
|
4169
|
+
},
|
|
4170
|
+
phase: {
|
|
4171
|
+
type: "string",
|
|
4172
|
+
enum: ["transcribe", "enhance", "simple-replace"],
|
|
4173
|
+
description: "Optional: Filter to a specific phase"
|
|
4174
|
+
},
|
|
4175
|
+
limit: {
|
|
4176
|
+
type: "number",
|
|
4177
|
+
description: "Maximum number of entries to return (default: 100)"
|
|
4178
|
+
},
|
|
4179
|
+
offset: {
|
|
4180
|
+
type: "number",
|
|
4181
|
+
description: "Number of entries to skip for pagination (default: 0)"
|
|
4182
|
+
},
|
|
4183
|
+
contextDirectory: {
|
|
4184
|
+
type: "string",
|
|
4185
|
+
description: "Optional: Path to the .protokoll context directory"
|
|
4186
|
+
}
|
|
4187
|
+
},
|
|
4188
|
+
required: ["transcriptPath"]
|
|
4189
|
+
}
|
|
4190
|
+
};
|
|
4191
|
+
const correctToEntityTool = {
|
|
4192
|
+
name: "protokoll_correct_to_entity",
|
|
4193
|
+
description: "Correct misheard text in transcript by mapping to existing or new entity. Atomically updates transcript content, adds misspelling to entity sounds_like array, updates entity references, and logs the correction to enhancement_log. This is the primary mechanism for training the transcription system.",
|
|
4194
|
+
inputSchema: {
|
|
4195
|
+
type: "object",
|
|
4196
|
+
properties: {
|
|
4197
|
+
transcriptPath: {
|
|
4198
|
+
type: "string",
|
|
4199
|
+
description: "Path to transcript file"
|
|
4200
|
+
},
|
|
4201
|
+
selectedText: {
|
|
4202
|
+
type: "string",
|
|
4203
|
+
description: "The misheard text to correct"
|
|
4204
|
+
},
|
|
4205
|
+
entityType: {
|
|
4206
|
+
type: "string",
|
|
4207
|
+
enum: ["person", "project", "term", "company"],
|
|
4208
|
+
description: "Type of entity"
|
|
4209
|
+
},
|
|
4210
|
+
entityId: {
|
|
4211
|
+
type: "string",
|
|
4212
|
+
description: "ID of existing entity (for map-to-existing flow)"
|
|
4213
|
+
},
|
|
4214
|
+
entityName: {
|
|
4215
|
+
type: "string",
|
|
4216
|
+
description: "Name of new entity to create (for create-new flow)"
|
|
4217
|
+
},
|
|
4218
|
+
contextDirectory: {
|
|
4219
|
+
type: "string",
|
|
4220
|
+
description: "Path to .protokoll context directory"
|
|
4221
|
+
}
|
|
4222
|
+
},
|
|
4223
|
+
required: ["transcriptPath", "selectedText", "entityType"]
|
|
4224
|
+
}
|
|
4225
|
+
};
|
|
3838
4226
|
async function handleReadTranscript(args) {
|
|
3839
4227
|
const absolutePath = await resolveTranscriptPath$1(args.transcriptPath, args.contextDirectory);
|
|
3840
4228
|
const transcriptData = await readTranscript(absolutePath);
|
|
@@ -3875,6 +4263,8 @@ async function handleListTranscripts(args) {
|
|
|
3875
4263
|
startDate: args.startDate,
|
|
3876
4264
|
endDate: args.endDate,
|
|
3877
4265
|
search: args.search
|
|
4266
|
+
// entityId: args.entityId,
|
|
4267
|
+
// entityType: args.entityType,
|
|
3878
4268
|
});
|
|
3879
4269
|
const outputDirectory = await getConfiguredDirectory("outputDirectory", args.contextDirectory);
|
|
3880
4270
|
const relativeTranscripts = await Promise.all(
|
|
@@ -3904,7 +4294,9 @@ async function handleListTranscripts(args) {
|
|
|
3904
4294
|
sortBy: args.sortBy ?? "date",
|
|
3905
4295
|
startDate: args.startDate,
|
|
3906
4296
|
endDate: args.endDate,
|
|
3907
|
-
search: args.search
|
|
4297
|
+
search: args.search,
|
|
4298
|
+
entityId: args.entityId,
|
|
4299
|
+
entityType: args.entityType
|
|
3908
4300
|
}
|
|
3909
4301
|
};
|
|
3910
4302
|
}
|
|
@@ -4155,11 +4547,16 @@ async function handleUpdateTranscriptEntityReferences(args) {
|
|
|
4155
4547
|
};
|
|
4156
4548
|
const pklPath = ensurePklExtension(absolutePath);
|
|
4157
4549
|
const transcript = PklTranscript.open(pklPath, { readOnly: false });
|
|
4550
|
+
const transcriptUuid = transcript.metadata.id;
|
|
4551
|
+
const projectId = transcript.metadata.project;
|
|
4158
4552
|
try {
|
|
4159
4553
|
transcript.updateMetadata({ entities });
|
|
4160
4554
|
} finally {
|
|
4161
4555
|
transcript.close();
|
|
4162
4556
|
}
|
|
4557
|
+
const { updateTranscriptInWeightModel } = await import('./weightModel.js');
|
|
4558
|
+
const allEntityIds = entityReferences.map((e) => e.id);
|
|
4559
|
+
updateTranscriptInWeightModel(transcriptUuid, allEntityIds, projectId);
|
|
4163
4560
|
const outputDirectory = await getConfiguredDirectory("outputDirectory", args.contextDirectory);
|
|
4164
4561
|
const relativePath = await sanitizePath(pklPath, outputDirectory);
|
|
4165
4562
|
return {
|
|
@@ -4291,6 +4688,206 @@ async function handleCreateNote(args) {
|
|
|
4291
4688
|
message: `Note "${args.title}" created successfully`
|
|
4292
4689
|
};
|
|
4293
4690
|
}
|
|
4691
|
+
async function handleGetEnhancementLog(args) {
|
|
4692
|
+
const absolutePath = await resolveTranscriptPath$1(args.transcriptPath, args.contextDirectory);
|
|
4693
|
+
const transcript = PklTranscript.open(absolutePath, { readOnly: true });
|
|
4694
|
+
try {
|
|
4695
|
+
const allEntries = [];
|
|
4696
|
+
const limit = args.limit ?? 100;
|
|
4697
|
+
const offset = args.offset ?? 0;
|
|
4698
|
+
const total = allEntries.length;
|
|
4699
|
+
const entries = allEntries.slice(offset, offset + limit);
|
|
4700
|
+
const serializedEntries = entries.map((entry) => ({
|
|
4701
|
+
id: entry.id,
|
|
4702
|
+
timestamp: entry.timestamp.toISOString(),
|
|
4703
|
+
phase: entry.phase,
|
|
4704
|
+
action: entry.action,
|
|
4705
|
+
details: entry.details,
|
|
4706
|
+
entities: entry.entities
|
|
4707
|
+
}));
|
|
4708
|
+
return {
|
|
4709
|
+
entries: serializedEntries,
|
|
4710
|
+
total,
|
|
4711
|
+
limit,
|
|
4712
|
+
offset,
|
|
4713
|
+
hasMore: offset + limit < total
|
|
4714
|
+
};
|
|
4715
|
+
} finally {
|
|
4716
|
+
transcript.close();
|
|
4717
|
+
}
|
|
4718
|
+
}
|
|
4719
|
+
function applyCorrections(transcriptText, corrections) {
|
|
4720
|
+
let correctedText = transcriptText;
|
|
4721
|
+
for (const [original, corrected] of corrections) {
|
|
4722
|
+
if (original !== corrected && corrected.trim() !== "") {
|
|
4723
|
+
const regex = new RegExp(original.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"), "gi");
|
|
4724
|
+
correctedText = correctedText.replace(regex, corrected);
|
|
4725
|
+
}
|
|
4726
|
+
}
|
|
4727
|
+
return correctedText;
|
|
4728
|
+
}
|
|
4729
|
+
async function handleCorrectToEntity(args) {
|
|
4730
|
+
const context = await create({ startingDir: args.contextDirectory || process.cwd() });
|
|
4731
|
+
const absolutePath = await resolveTranscriptPath$1(args.transcriptPath, args.contextDirectory);
|
|
4732
|
+
const pklPath = ensurePklExtension(absolutePath);
|
|
4733
|
+
let finalEntityId;
|
|
4734
|
+
let finalEntityName;
|
|
4735
|
+
let isNewEntity = false;
|
|
4736
|
+
if (args.entityName) {
|
|
4737
|
+
const EntityTools2 = await Promise.resolve().then(() => entityTools);
|
|
4738
|
+
const createArgs = { name: args.entityName, contextDirectory: args.contextDirectory };
|
|
4739
|
+
try {
|
|
4740
|
+
let result;
|
|
4741
|
+
switch (args.entityType) {
|
|
4742
|
+
case "person":
|
|
4743
|
+
result = await EntityTools2.handleAddPerson(createArgs);
|
|
4744
|
+
break;
|
|
4745
|
+
case "project":
|
|
4746
|
+
result = await EntityTools2.handleAddProject(createArgs);
|
|
4747
|
+
break;
|
|
4748
|
+
case "term":
|
|
4749
|
+
result = await EntityTools2.handleAddTerm({ term: args.entityName, contextDirectory: args.contextDirectory });
|
|
4750
|
+
break;
|
|
4751
|
+
case "company":
|
|
4752
|
+
result = await EntityTools2.handleAddCompany(createArgs);
|
|
4753
|
+
break;
|
|
4754
|
+
}
|
|
4755
|
+
finalEntityId = result.entity.id;
|
|
4756
|
+
finalEntityName = result.entity.name;
|
|
4757
|
+
isNewEntity = true;
|
|
4758
|
+
} catch (error) {
|
|
4759
|
+
const match = error.message?.match(/ID\s+["']([^"']+)["']\s+already exists/i);
|
|
4760
|
+
if (match) {
|
|
4761
|
+
finalEntityId = match[1];
|
|
4762
|
+
const { findPersonResilient, findProjectResilient, findTermResilient, findCompanyResilient } = await import('@redaksjon/protokoll-engine');
|
|
4763
|
+
let entity;
|
|
4764
|
+
switch (args.entityType) {
|
|
4765
|
+
case "person":
|
|
4766
|
+
entity = findPersonResilient(context, finalEntityId);
|
|
4767
|
+
break;
|
|
4768
|
+
case "project":
|
|
4769
|
+
entity = findProjectResilient(context, finalEntityId);
|
|
4770
|
+
break;
|
|
4771
|
+
case "term":
|
|
4772
|
+
entity = findTermResilient(context, finalEntityId);
|
|
4773
|
+
break;
|
|
4774
|
+
case "company":
|
|
4775
|
+
entity = findCompanyResilient(context, finalEntityId);
|
|
4776
|
+
break;
|
|
4777
|
+
}
|
|
4778
|
+
finalEntityName = entity.name;
|
|
4779
|
+
} else {
|
|
4780
|
+
throw error;
|
|
4781
|
+
}
|
|
4782
|
+
}
|
|
4783
|
+
} else if (args.entityId) {
|
|
4784
|
+
finalEntityId = args.entityId;
|
|
4785
|
+
const { findPersonResilient, findProjectResilient, findTermResilient, findCompanyResilient } = await import('@redaksjon/protokoll-engine');
|
|
4786
|
+
let entity;
|
|
4787
|
+
switch (args.entityType) {
|
|
4788
|
+
case "person":
|
|
4789
|
+
entity = findPersonResilient(context, finalEntityId);
|
|
4790
|
+
break;
|
|
4791
|
+
case "project":
|
|
4792
|
+
entity = findProjectResilient(context, finalEntityId);
|
|
4793
|
+
break;
|
|
4794
|
+
case "term":
|
|
4795
|
+
entity = findTermResilient(context, finalEntityId);
|
|
4796
|
+
break;
|
|
4797
|
+
case "company":
|
|
4798
|
+
entity = findCompanyResilient(context, finalEntityId);
|
|
4799
|
+
break;
|
|
4800
|
+
}
|
|
4801
|
+
finalEntityName = entity.name;
|
|
4802
|
+
} else {
|
|
4803
|
+
throw new Error("Either entityId or entityName must be provided");
|
|
4804
|
+
}
|
|
4805
|
+
const transcript = PklTranscript.open(pklPath, { readOnly: false });
|
|
4806
|
+
const originalContent = transcript.content;
|
|
4807
|
+
const corrections = /* @__PURE__ */ new Map([[args.selectedText, finalEntityName]]);
|
|
4808
|
+
const correctedContent = applyCorrections(originalContent, corrections);
|
|
4809
|
+
transcript.updateContent(correctedContent);
|
|
4810
|
+
const EntityTools = await Promise.resolve().then(() => entityTools);
|
|
4811
|
+
const editArgs = {
|
|
4812
|
+
id: finalEntityId,
|
|
4813
|
+
add_sounds_like: [args.selectedText],
|
|
4814
|
+
contextDirectory: args.contextDirectory
|
|
4815
|
+
};
|
|
4816
|
+
switch (args.entityType) {
|
|
4817
|
+
case "person":
|
|
4818
|
+
await EntityTools.handleEditPerson(editArgs);
|
|
4819
|
+
break;
|
|
4820
|
+
case "project":
|
|
4821
|
+
await EntityTools.handleEditProject(editArgs);
|
|
4822
|
+
break;
|
|
4823
|
+
case "term":
|
|
4824
|
+
await EntityTools.handleEditTerm(editArgs);
|
|
4825
|
+
break;
|
|
4826
|
+
case "company":
|
|
4827
|
+
await EntityTools.handleEditCompany(editArgs);
|
|
4828
|
+
break;
|
|
4829
|
+
}
|
|
4830
|
+
const entities = transcript.metadata.entities || { people: [], projects: [], terms: [], companies: [] };
|
|
4831
|
+
const entityRef = { id: finalEntityId, name: finalEntityName, type: args.entityType };
|
|
4832
|
+
let entityArray;
|
|
4833
|
+
switch (args.entityType) {
|
|
4834
|
+
case "person":
|
|
4835
|
+
entityArray = entities.people || [];
|
|
4836
|
+
if (!entityArray.some((e) => e.id === finalEntityId)) {
|
|
4837
|
+
entities.people = [...entityArray, entityRef];
|
|
4838
|
+
}
|
|
4839
|
+
break;
|
|
4840
|
+
case "project":
|
|
4841
|
+
entityArray = entities.projects || [];
|
|
4842
|
+
if (!entityArray.some((e) => e.id === finalEntityId)) {
|
|
4843
|
+
entities.projects = [...entityArray, entityRef];
|
|
4844
|
+
}
|
|
4845
|
+
break;
|
|
4846
|
+
case "term":
|
|
4847
|
+
entityArray = entities.terms || [];
|
|
4848
|
+
if (!entityArray.some((e) => e.id === finalEntityId)) {
|
|
4849
|
+
entities.terms = [...entityArray, entityRef];
|
|
4850
|
+
}
|
|
4851
|
+
break;
|
|
4852
|
+
case "company":
|
|
4853
|
+
entityArray = entities.companies || [];
|
|
4854
|
+
if (!entityArray.some((e) => e.id === finalEntityId)) {
|
|
4855
|
+
entities.companies = [...entityArray, entityRef];
|
|
4856
|
+
}
|
|
4857
|
+
break;
|
|
4858
|
+
}
|
|
4859
|
+
transcript.updateMetadata({ entities });
|
|
4860
|
+
transcript.enhancementLog.logStep(
|
|
4861
|
+
/* @__PURE__ */ new Date(),
|
|
4862
|
+
"user-correction",
|
|
4863
|
+
// Type will be correct after protokoll-format link
|
|
4864
|
+
"correction_applied",
|
|
4865
|
+
{
|
|
4866
|
+
original: args.selectedText,
|
|
4867
|
+
replacement: finalEntityName,
|
|
4868
|
+
entityId: finalEntityId,
|
|
4869
|
+
entityType: args.entityType,
|
|
4870
|
+
isNewEntity
|
|
4871
|
+
},
|
|
4872
|
+
[{ id: finalEntityId, name: finalEntityName, type: args.entityType }]
|
|
4873
|
+
);
|
|
4874
|
+
transcript.close();
|
|
4875
|
+
const allEntityIds = [
|
|
4876
|
+
...(entities.people || []).map((e) => e.id),
|
|
4877
|
+
...(entities.projects || []).map((e) => e.id),
|
|
4878
|
+
...(entities.terms || []).map((e) => e.id),
|
|
4879
|
+
...(entities.companies || []).map((e) => e.id)
|
|
4880
|
+
];
|
|
4881
|
+
const { updateTranscriptInWeightModel } = await import('./weightModel.js');
|
|
4882
|
+
updateTranscriptInWeightModel(transcript.metadata.id, allEntityIds, transcript.metadata.project);
|
|
4883
|
+
return {
|
|
4884
|
+
success: true,
|
|
4885
|
+
message: `Corrected "${args.selectedText}" to "${finalEntityName}"`,
|
|
4886
|
+
correction: { original: args.selectedText, replacement: finalEntityName },
|
|
4887
|
+
entity: { id: finalEntityId, name: finalEntityName, type: args.entityType },
|
|
4888
|
+
isNewEntity
|
|
4889
|
+
};
|
|
4890
|
+
}
|
|
4294
4891
|
|
|
4295
4892
|
const getVersionTool = {
|
|
4296
4893
|
name: "protokoll_get_version",
|
|
@@ -5127,6 +5724,307 @@ async function handleDeleteTask(args) {
|
|
|
5127
5724
|
}
|
|
5128
5725
|
}
|
|
5129
5726
|
|
|
5727
|
+
const {
|
|
5728
|
+
findUploadedTranscripts,
|
|
5729
|
+
findTranscribingTranscripts,
|
|
5730
|
+
resetTranscriptToUploaded,
|
|
5731
|
+
findTranscriptByUuid
|
|
5732
|
+
} = Transcript;
|
|
5733
|
+
let workerInstance = null;
|
|
5734
|
+
function setWorkerInstance(worker) {
|
|
5735
|
+
workerInstance = worker;
|
|
5736
|
+
}
|
|
5737
|
+
const queueStatusTool = {
|
|
5738
|
+
name: "protokoll_queue_status",
|
|
5739
|
+
description: "Get current upload queue status - pending transcriptions, processing, and recent completions. Shows what audio files are waiting to be transcribed and which are currently being processed.",
|
|
5740
|
+
inputSchema: {
|
|
5741
|
+
type: "object",
|
|
5742
|
+
properties: {}
|
|
5743
|
+
}
|
|
5744
|
+
};
|
|
5745
|
+
const getTranscriptByUuidTool = {
|
|
5746
|
+
name: "protokoll_get_transcript_by_uuid",
|
|
5747
|
+
description: "Get transcript metadata and status by UUID. Accepts either full UUID or 8-character prefix. Useful for checking upload status after receiving UUID from upload endpoint.",
|
|
5748
|
+
inputSchema: {
|
|
5749
|
+
type: "object",
|
|
5750
|
+
properties: {
|
|
5751
|
+
uuid: {
|
|
5752
|
+
type: "string",
|
|
5753
|
+
description: "UUID or 8-character UUID prefix"
|
|
5754
|
+
},
|
|
5755
|
+
includeContent: {
|
|
5756
|
+
type: "boolean",
|
|
5757
|
+
description: "Include transcript content (default: false). Only works for transcripts in initial/enhanced/reviewed status."
|
|
5758
|
+
}
|
|
5759
|
+
},
|
|
5760
|
+
required: ["uuid"]
|
|
5761
|
+
}
|
|
5762
|
+
};
|
|
5763
|
+
const retryTranscriptionTool = {
|
|
5764
|
+
name: "protokoll_retry_transcription",
|
|
5765
|
+
description: "Retry a failed transcription by resetting status from error to uploaded. The transcript will be re-queued for processing.",
|
|
5766
|
+
inputSchema: {
|
|
5767
|
+
type: "object",
|
|
5768
|
+
properties: {
|
|
5769
|
+
uuid: {
|
|
5770
|
+
type: "string",
|
|
5771
|
+
description: "UUID of transcript to retry"
|
|
5772
|
+
}
|
|
5773
|
+
},
|
|
5774
|
+
required: ["uuid"]
|
|
5775
|
+
}
|
|
5776
|
+
};
|
|
5777
|
+
const cancelTranscriptionTool = {
|
|
5778
|
+
name: "protokoll_cancel_transcription",
|
|
5779
|
+
description: "Cancel a pending or processing transcription. Optionally delete the PKL file.",
|
|
5780
|
+
inputSchema: {
|
|
5781
|
+
type: "object",
|
|
5782
|
+
properties: {
|
|
5783
|
+
uuid: {
|
|
5784
|
+
type: "string",
|
|
5785
|
+
description: "UUID of transcript to cancel"
|
|
5786
|
+
},
|
|
5787
|
+
deleteFile: {
|
|
5788
|
+
type: "boolean",
|
|
5789
|
+
description: "Also delete the PKL file (default: false)"
|
|
5790
|
+
}
|
|
5791
|
+
},
|
|
5792
|
+
required: ["uuid"]
|
|
5793
|
+
}
|
|
5794
|
+
};
|
|
5795
|
+
const workerStatusTool = {
|
|
5796
|
+
name: "protokoll_worker_status",
|
|
5797
|
+
description: "Get background transcription worker status and statistics. Shows if worker is running, current task, total processed, and uptime.",
|
|
5798
|
+
inputSchema: {
|
|
5799
|
+
type: "object",
|
|
5800
|
+
properties: {}
|
|
5801
|
+
}
|
|
5802
|
+
};
|
|
5803
|
+
const restartWorkerTool = {
|
|
5804
|
+
name: "protokoll_restart_worker",
|
|
5805
|
+
description: "Restart the background transcription worker. Useful if worker is stuck or needs to reload configuration.",
|
|
5806
|
+
inputSchema: {
|
|
5807
|
+
type: "object",
|
|
5808
|
+
properties: {}
|
|
5809
|
+
}
|
|
5810
|
+
};
|
|
5811
|
+
function getTranscribingStartTime(metadata) {
|
|
5812
|
+
if (!metadata.history || metadata.history.length === 0) {
|
|
5813
|
+
return metadata.date?.toISOString() || "";
|
|
5814
|
+
}
|
|
5815
|
+
const transcribingTransition = metadata.history.filter((h) => h.to === "transcribing").sort((a, b) => b.at.getTime() - a.at.getTime())[0];
|
|
5816
|
+
return transcribingTransition?.at.toISOString() || metadata.date?.toISOString() || "";
|
|
5817
|
+
}
|
|
5818
|
+
function getCompletionTime(metadata) {
|
|
5819
|
+
if (!metadata.history || metadata.history.length === 0) {
|
|
5820
|
+
return "";
|
|
5821
|
+
}
|
|
5822
|
+
const lastTransition = metadata.history.sort((a, b) => b.at.getTime() - a.at.getTime())[0];
|
|
5823
|
+
return lastTransition?.at.toISOString() || "";
|
|
5824
|
+
}
|
|
5825
|
+
async function findRecentTranscripts(searchDirectories, limit) {
|
|
5826
|
+
const results = [];
|
|
5827
|
+
const oneDayAgo = new Date(Date.now() - 24 * 60 * 60 * 1e3);
|
|
5828
|
+
const { glob } = await import('glob');
|
|
5829
|
+
for (const dir of searchDirectories) {
|
|
5830
|
+
const files = await glob("????????-*.pkl", { cwd: dir, absolute: true });
|
|
5831
|
+
for (const file of files) {
|
|
5832
|
+
try {
|
|
5833
|
+
const transcript = PklTranscript.open(file, { readOnly: true });
|
|
5834
|
+
const metadata = transcript.metadata;
|
|
5835
|
+
const recentlyCompleted = metadata.history?.some(
|
|
5836
|
+
(h) => h.at >= oneDayAgo && ["initial", "enhanced", "reviewed", "error"].includes(h.to)
|
|
5837
|
+
);
|
|
5838
|
+
if (recentlyCompleted) {
|
|
5839
|
+
results.push({
|
|
5840
|
+
uuid: metadata.id,
|
|
5841
|
+
filePath: file,
|
|
5842
|
+
metadata
|
|
5843
|
+
});
|
|
5844
|
+
}
|
|
5845
|
+
await transcript.close();
|
|
5846
|
+
} catch (error) {
|
|
5847
|
+
console.warn(`Failed to open transcript ${file}:`, error);
|
|
5848
|
+
}
|
|
5849
|
+
}
|
|
5850
|
+
}
|
|
5851
|
+
results.sort((a, b) => {
|
|
5852
|
+
const aTime = a.metadata.history?.[a.metadata.history.length - 1]?.at.getTime() || 0;
|
|
5853
|
+
const bTime = b.metadata.history?.[b.metadata.history.length - 1]?.at.getTime() || 0;
|
|
5854
|
+
return bTime - aTime;
|
|
5855
|
+
});
|
|
5856
|
+
return results.slice(0, limit);
|
|
5857
|
+
}
|
|
5858
|
+
async function handleQueueStatus() {
|
|
5859
|
+
const outputDir = getOutputDirectory();
|
|
5860
|
+
const uploaded = await findUploadedTranscripts([outputDir]);
|
|
5861
|
+
const transcribing = await findTranscribingTranscripts([outputDir]);
|
|
5862
|
+
const recent = await findRecentTranscripts([outputDir], 10);
|
|
5863
|
+
return {
|
|
5864
|
+
pending: uploaded.map((t) => ({
|
|
5865
|
+
uuid: t.uuid,
|
|
5866
|
+
filename: t.metadata.audioFile || "unknown",
|
|
5867
|
+
uploadedAt: t.metadata.date?.toISOString() || ""
|
|
5868
|
+
})),
|
|
5869
|
+
processing: transcribing.map((t) => ({
|
|
5870
|
+
uuid: t.uuid,
|
|
5871
|
+
filename: t.metadata.audioFile || "unknown",
|
|
5872
|
+
startedAt: getTranscribingStartTime(t.metadata)
|
|
5873
|
+
})),
|
|
5874
|
+
recent: recent.map((t) => ({
|
|
5875
|
+
uuid: t.uuid,
|
|
5876
|
+
filename: t.metadata.audioFile || t.metadata.title || "unknown",
|
|
5877
|
+
completedAt: getCompletionTime(t.metadata),
|
|
5878
|
+
status: t.metadata.status || "unknown"
|
|
5879
|
+
})),
|
|
5880
|
+
totalPending: uploaded.length
|
|
5881
|
+
};
|
|
5882
|
+
}
|
|
5883
|
+
async function handleGetTranscriptByUuid(args) {
|
|
5884
|
+
try {
|
|
5885
|
+
const outputDir = getOutputDirectory();
|
|
5886
|
+
const filePath = await findTranscriptByUuid(args.uuid, [outputDir]);
|
|
5887
|
+
if (!filePath) {
|
|
5888
|
+
return { found: false, error: `No transcript found for UUID: ${args.uuid}` };
|
|
5889
|
+
}
|
|
5890
|
+
const transcript = PklTranscript.open(filePath, { readOnly: true });
|
|
5891
|
+
const metadata = transcript.metadata;
|
|
5892
|
+
const result = {
|
|
5893
|
+
found: true,
|
|
5894
|
+
uuid: metadata.id,
|
|
5895
|
+
filePath: await sanitizePath(filePath, outputDir),
|
|
5896
|
+
metadata
|
|
5897
|
+
};
|
|
5898
|
+
if (args.includeContent && ["initial", "enhanced", "reviewed"].includes(metadata.status || "")) {
|
|
5899
|
+
result.content = transcript.content || "";
|
|
5900
|
+
}
|
|
5901
|
+
await transcript.close();
|
|
5902
|
+
return result;
|
|
5903
|
+
} catch (error) {
|
|
5904
|
+
return { found: false, error: error instanceof Error ? error.message : String(error) };
|
|
5905
|
+
}
|
|
5906
|
+
}
|
|
5907
|
+
async function handleRetryTranscription(args) {
|
|
5908
|
+
try {
|
|
5909
|
+
const outputDir = getOutputDirectory();
|
|
5910
|
+
const filePath = await findTranscriptByUuid(args.uuid, [outputDir]);
|
|
5911
|
+
if (!filePath) {
|
|
5912
|
+
return {
|
|
5913
|
+
success: false,
|
|
5914
|
+
message: "",
|
|
5915
|
+
error: `No transcript found for UUID: ${args.uuid}`
|
|
5916
|
+
};
|
|
5917
|
+
}
|
|
5918
|
+
const transcript = PklTranscript.open(filePath, { readOnly: true });
|
|
5919
|
+
const metadata = transcript.metadata;
|
|
5920
|
+
await transcript.close();
|
|
5921
|
+
if (metadata.status !== "error") {
|
|
5922
|
+
return {
|
|
5923
|
+
success: false,
|
|
5924
|
+
message: "",
|
|
5925
|
+
error: `Transcript is not in error status (current: ${metadata.status})`
|
|
5926
|
+
};
|
|
5927
|
+
}
|
|
5928
|
+
await resetTranscriptToUploaded(filePath);
|
|
5929
|
+
return {
|
|
5930
|
+
success: true,
|
|
5931
|
+
message: `Transcript ${args.uuid} reset to uploaded status and re-queued`
|
|
5932
|
+
};
|
|
5933
|
+
} catch (error) {
|
|
5934
|
+
return {
|
|
5935
|
+
success: false,
|
|
5936
|
+
message: "",
|
|
5937
|
+
error: error instanceof Error ? error.message : String(error)
|
|
5938
|
+
};
|
|
5939
|
+
}
|
|
5940
|
+
}
|
|
5941
|
+
async function handleCancelTranscription(args) {
|
|
5942
|
+
try {
|
|
5943
|
+
const outputDir = getOutputDirectory();
|
|
5944
|
+
const filePath = await findTranscriptByUuid(args.uuid, [outputDir]);
|
|
5945
|
+
if (!filePath) {
|
|
5946
|
+
return {
|
|
5947
|
+
success: false,
|
|
5948
|
+
message: "",
|
|
5949
|
+
error: `No transcript found for UUID: ${args.uuid}`
|
|
5950
|
+
};
|
|
5951
|
+
}
|
|
5952
|
+
const transcript = PklTranscript.open(filePath, { readOnly: true });
|
|
5953
|
+
const metadata = transcript.metadata;
|
|
5954
|
+
await transcript.close();
|
|
5955
|
+
if (!["uploaded", "transcribing"].includes(metadata.status || "")) {
|
|
5956
|
+
return {
|
|
5957
|
+
success: false,
|
|
5958
|
+
message: "",
|
|
5959
|
+
error: `Cannot cancel transcript in ${metadata.status} status. Only uploaded/transcribing can be cancelled.`
|
|
5960
|
+
};
|
|
5961
|
+
}
|
|
5962
|
+
if (args.deleteFile) {
|
|
5963
|
+
await unlink(filePath);
|
|
5964
|
+
return {
|
|
5965
|
+
success: true,
|
|
5966
|
+
message: `Transcript ${args.uuid} cancelled and file deleted`
|
|
5967
|
+
};
|
|
5968
|
+
} else {
|
|
5969
|
+
const transcriptToUpdate = PklTranscript.open(filePath);
|
|
5970
|
+
transcriptToUpdate.updateMetadata({
|
|
5971
|
+
status: "error",
|
|
5972
|
+
errorDetails: "Cancelled by user"
|
|
5973
|
+
});
|
|
5974
|
+
await transcriptToUpdate.close();
|
|
5975
|
+
return {
|
|
5976
|
+
success: true,
|
|
5977
|
+
message: `Transcript ${args.uuid} cancelled (marked as error)`
|
|
5978
|
+
};
|
|
5979
|
+
}
|
|
5980
|
+
} catch (error) {
|
|
5981
|
+
return {
|
|
5982
|
+
success: false,
|
|
5983
|
+
message: "",
|
|
5984
|
+
error: error instanceof Error ? error.message : String(error)
|
|
5985
|
+
};
|
|
5986
|
+
}
|
|
5987
|
+
}
|
|
5988
|
+
async function handleWorkerStatus() {
|
|
5989
|
+
if (!workerInstance) {
|
|
5990
|
+
return {
|
|
5991
|
+
isRunning: false,
|
|
5992
|
+
totalProcessed: 0,
|
|
5993
|
+
uptime: 0
|
|
5994
|
+
};
|
|
5995
|
+
}
|
|
5996
|
+
return {
|
|
5997
|
+
isRunning: workerInstance.isActive(),
|
|
5998
|
+
currentTask: workerInstance.getCurrentTask(),
|
|
5999
|
+
totalProcessed: workerInstance.getProcessedCount(),
|
|
6000
|
+
lastProcessed: workerInstance.getLastProcessedTime(),
|
|
6001
|
+
uptime: workerInstance.getUptime()
|
|
6002
|
+
};
|
|
6003
|
+
}
|
|
6004
|
+
async function handleRestartWorker() {
|
|
6005
|
+
if (!workerInstance) {
|
|
6006
|
+
return {
|
|
6007
|
+
success: false,
|
|
6008
|
+
message: "",
|
|
6009
|
+
error: "Worker not initialized"
|
|
6010
|
+
};
|
|
6011
|
+
}
|
|
6012
|
+
try {
|
|
6013
|
+
await workerInstance.stop();
|
|
6014
|
+
await workerInstance.start();
|
|
6015
|
+
return {
|
|
6016
|
+
success: true,
|
|
6017
|
+
message: "Worker restarted successfully"
|
|
6018
|
+
};
|
|
6019
|
+
} catch (error) {
|
|
6020
|
+
return {
|
|
6021
|
+
success: false,
|
|
6022
|
+
message: "",
|
|
6023
|
+
error: error instanceof Error ? error.message : String(error)
|
|
6024
|
+
};
|
|
6025
|
+
}
|
|
6026
|
+
}
|
|
6027
|
+
|
|
5130
6028
|
const tools = [
|
|
5131
6029
|
// System Information
|
|
5132
6030
|
getVersionTool,
|
|
@@ -5145,6 +6043,7 @@ const tools = [
|
|
|
5145
6043
|
listCompaniesTool,
|
|
5146
6044
|
searchContextTool,
|
|
5147
6045
|
getEntityTool,
|
|
6046
|
+
predictEntitiesTool,
|
|
5148
6047
|
// Entity CRUD
|
|
5149
6048
|
addPersonTool,
|
|
5150
6049
|
editPersonTool,
|
|
@@ -5156,6 +6055,7 @@ const tools = [
|
|
|
5156
6055
|
updateTermTool,
|
|
5157
6056
|
mergeTermsTool,
|
|
5158
6057
|
addCompanyTool,
|
|
6058
|
+
editCompanyTool,
|
|
5159
6059
|
deleteEntityTool,
|
|
5160
6060
|
// Relationship Management
|
|
5161
6061
|
addRelationshipTool,
|
|
@@ -5180,11 +6080,20 @@ const tools = [
|
|
|
5180
6080
|
updateTranscriptContentTool,
|
|
5181
6081
|
updateTranscriptEntityReferencesTool,
|
|
5182
6082
|
createNoteTool,
|
|
6083
|
+
getEnhancementLogTool,
|
|
6084
|
+
correctToEntityTool,
|
|
5183
6085
|
// Lifecycle Status & Tasks
|
|
5184
6086
|
setStatusTool,
|
|
5185
6087
|
createTaskTool,
|
|
5186
6088
|
completeTaskTool,
|
|
5187
|
-
deleteTaskTool
|
|
6089
|
+
deleteTaskTool,
|
|
6090
|
+
// Queue Management
|
|
6091
|
+
queueStatusTool,
|
|
6092
|
+
getTranscriptByUuidTool,
|
|
6093
|
+
retryTranscriptionTool,
|
|
6094
|
+
cancelTranscriptionTool,
|
|
6095
|
+
workerStatusTool,
|
|
6096
|
+
restartWorkerTool
|
|
5188
6097
|
];
|
|
5189
6098
|
async function handleToolCall(name, args) {
|
|
5190
6099
|
switch (name) {
|
|
@@ -5218,6 +6127,8 @@ async function handleToolCall(name, args) {
|
|
|
5218
6127
|
return handleSearchContext(args);
|
|
5219
6128
|
case "protokoll_get_entity":
|
|
5220
6129
|
return handleGetEntity(args);
|
|
6130
|
+
case "protokoll_predict_entities":
|
|
6131
|
+
return handlePredictEntities(args);
|
|
5221
6132
|
// Entity CRUD
|
|
5222
6133
|
case "protokoll_add_person":
|
|
5223
6134
|
return handleAddPerson(args);
|
|
@@ -5239,6 +6150,8 @@ async function handleToolCall(name, args) {
|
|
|
5239
6150
|
return handleMergeTerms(args);
|
|
5240
6151
|
case "protokoll_add_company":
|
|
5241
6152
|
return handleAddCompany(args);
|
|
6153
|
+
case "protokoll_edit_company":
|
|
6154
|
+
return handleEditCompany(args);
|
|
5242
6155
|
case "protokoll_delete_entity":
|
|
5243
6156
|
return handleDeleteEntity(args);
|
|
5244
6157
|
// Smart Assistance
|
|
@@ -5283,6 +6196,10 @@ async function handleToolCall(name, args) {
|
|
|
5283
6196
|
return handleUpdateTranscriptEntityReferences(args);
|
|
5284
6197
|
case "protokoll_create_note":
|
|
5285
6198
|
return handleCreateNote(args);
|
|
6199
|
+
case "protokoll_get_enhancement_log":
|
|
6200
|
+
return handleGetEnhancementLog(args);
|
|
6201
|
+
case "protokoll_correct_to_entity":
|
|
6202
|
+
return handleCorrectToEntity(args);
|
|
5286
6203
|
// Lifecycle Status & Tasks
|
|
5287
6204
|
case "protokoll_set_status":
|
|
5288
6205
|
return handleSetStatus(args);
|
|
@@ -5292,6 +6209,19 @@ async function handleToolCall(name, args) {
|
|
|
5292
6209
|
return handleCompleteTask(args);
|
|
5293
6210
|
case "protokoll_delete_task":
|
|
5294
6211
|
return handleDeleteTask(args);
|
|
6212
|
+
// Queue Management
|
|
6213
|
+
case "protokoll_queue_status":
|
|
6214
|
+
return handleQueueStatus();
|
|
6215
|
+
case "protokoll_get_transcript_by_uuid":
|
|
6216
|
+
return handleGetTranscriptByUuid(args);
|
|
6217
|
+
case "protokoll_retry_transcription":
|
|
6218
|
+
return handleRetryTranscription(args);
|
|
6219
|
+
case "protokoll_cancel_transcription":
|
|
6220
|
+
return handleCancelTranscription(args);
|
|
6221
|
+
case "protokoll_worker_status":
|
|
6222
|
+
return handleWorkerStatus();
|
|
6223
|
+
case "protokoll_restart_worker":
|
|
6224
|
+
return handleRestartWorker();
|
|
5295
6225
|
default:
|
|
5296
6226
|
throw new Error(`Unknown tool: ${name}`);
|
|
5297
6227
|
}
|
|
@@ -5391,5 +6321,5 @@ async function initializeWorkingDirectoryFromArgsAndConfig() {
|
|
|
5391
6321
|
}
|
|
5392
6322
|
}
|
|
5393
6323
|
|
|
5394
|
-
export { DEFAULT_CONFIG_FILE as D, handleListResources as a, handleReadResource as b, getPrompt as c, initializeServerConfig as d, getCachedRoots as e, getServerConfig as f, getPrompts as g, handleToolCall as h, initializeWorkingDirectoryFromArgsAndConfig as i, getContext as j,
|
|
6324
|
+
export { DEFAULT_CONFIG_FILE as D, handleListResources as a, handleReadResource as b, getPrompt as c, initializeServerConfig as d, getCachedRoots as e, getServerConfig as f, getPrompts as g, handleToolCall as h, initializeWorkingDirectoryFromArgsAndConfig as i, getContext as j, getOutputDirectory as k, loadCardigantimeConfig as l, setWorkerInstance as m, serverConfig$1 as n, setRoots as s, tools as t, uri as u };
|
|
5395
6325
|
//# sourceMappingURL=configDiscovery.js.map
|