@redaksjon/protokoll 0.0.12 → 0.0.13
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/.cursor/rules/definition-of-done.md +1 -0
- package/.cursor/rules/no-emoticons.md +26 -12
- package/README.md +483 -69
- package/dist/agentic/executor.js +473 -41
- package/dist/agentic/executor.js.map +1 -1
- package/dist/agentic/index.js.map +1 -1
- package/dist/agentic/tools/lookup-person.js +123 -4
- package/dist/agentic/tools/lookup-person.js.map +1 -1
- package/dist/agentic/tools/lookup-project.js +139 -22
- package/dist/agentic/tools/lookup-project.js.map +1 -1
- package/dist/agentic/tools/route-note.js +5 -1
- package/dist/agentic/tools/route-note.js.map +1 -1
- package/dist/arguments.js +6 -3
- package/dist/arguments.js.map +1 -1
- package/dist/cli/action.js +704 -0
- package/dist/cli/action.js.map +1 -0
- package/dist/cli/config.js +482 -0
- package/dist/cli/config.js.map +1 -0
- package/dist/cli/context.js +466 -0
- package/dist/cli/context.js.map +1 -0
- package/dist/cli/feedback.js +858 -0
- package/dist/cli/feedback.js.map +1 -0
- package/dist/cli/index.js +103 -0
- package/dist/cli/index.js.map +1 -0
- package/dist/cli/install.js +572 -0
- package/dist/cli/install.js.map +1 -0
- package/dist/cli/transcript.js +199 -0
- package/dist/cli/transcript.js.map +1 -0
- package/dist/constants.js +11 -4
- package/dist/constants.js.map +1 -1
- package/dist/context/index.js +25 -1
- package/dist/context/index.js.map +1 -1
- package/dist/context/storage.js +56 -3
- package/dist/context/storage.js.map +1 -1
- package/dist/interactive/handler.js +310 -9
- package/dist/interactive/handler.js.map +1 -1
- package/dist/main.js +11 -1
- package/dist/main.js.map +1 -1
- package/dist/output/index.js.map +1 -1
- package/dist/output/manager.js +46 -1
- package/dist/output/manager.js.map +1 -1
- package/dist/phases/complete.js +37 -2
- package/dist/phases/complete.js.map +1 -1
- package/dist/pipeline/orchestrator.js +104 -31
- package/dist/pipeline/orchestrator.js.map +1 -1
- package/dist/protokoll.js +68 -2
- package/dist/protokoll.js.map +1 -1
- package/dist/reasoning/client.js +83 -0
- package/dist/reasoning/client.js.map +1 -1
- package/dist/reasoning/index.js +1 -0
- package/dist/reasoning/index.js.map +1 -1
- package/dist/util/metadata.js.map +1 -1
- package/dist/util/sound.js +116 -0
- package/dist/util/sound.js.map +1 -0
- package/docs/duplicate-question-prevention.md +117 -0
- package/docs/examples.md +152 -0
- package/docs/interactive-context-example.md +92 -0
- package/docs/package-lock.json +6 -0
- package/docs/package.json +3 -1
- package/guide/action.md +375 -0
- package/guide/config.md +207 -0
- package/guide/configuration.md +82 -67
- package/guide/context-commands.md +574 -0
- package/guide/context-system.md +20 -7
- package/guide/development.md +106 -4
- package/guide/feedback.md +335 -0
- package/guide/index.md +100 -4
- package/guide/interactive.md +15 -14
- package/guide/quickstart.md +21 -7
- package/guide/reasoning.md +18 -4
- package/guide/routing.md +192 -97
- package/package.json +1 -1
- package/scripts/coverage-priority.mjs +323 -0
- package/tsconfig.tsbuildinfo +1 -1
- package/vitest.config.ts +5 -1
|
@@ -0,0 +1,858 @@
|
|
|
1
|
+
import { Command } from 'commander';
|
|
2
|
+
import * as readline from 'readline';
|
|
3
|
+
import * as fs from 'fs/promises';
|
|
4
|
+
import * as path from 'node:path';
|
|
5
|
+
import { create } from '../context/index.js';
|
|
6
|
+
import { create as create$1 } from '../reasoning/index.js';
|
|
7
|
+
import { getLogger } from '../logging.js';
|
|
8
|
+
import { extractTimestampFromFilename, slugifyTitle } from './action.js';
|
|
9
|
+
import { DEFAULT_MODEL } from '../constants.js';
|
|
10
|
+
|
|
11
|
+
// CLI output helper
|
|
12
|
+
const print = (text)=>process.stdout.write(text + '\n');
|
|
13
|
+
const FEEDBACK_TOOLS = [
|
|
14
|
+
{
|
|
15
|
+
name: 'correct_text',
|
|
16
|
+
description: 'Replace text in the transcript. Use this to fix misspellings, wrong terms, or incorrect names.',
|
|
17
|
+
parameters: {
|
|
18
|
+
find: {
|
|
19
|
+
type: 'string',
|
|
20
|
+
description: 'The text to find in the transcript',
|
|
21
|
+
required: true
|
|
22
|
+
},
|
|
23
|
+
replace: {
|
|
24
|
+
type: 'string',
|
|
25
|
+
description: 'The text to replace it with',
|
|
26
|
+
required: true
|
|
27
|
+
},
|
|
28
|
+
replace_all: {
|
|
29
|
+
type: 'boolean',
|
|
30
|
+
description: 'Replace all occurrences (default: true)'
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
},
|
|
34
|
+
{
|
|
35
|
+
name: 'add_term',
|
|
36
|
+
description: 'Add a new term to the context so it will be recognized in future transcripts. Use this when you learn about abbreviations, acronyms, or technical terms.',
|
|
37
|
+
parameters: {
|
|
38
|
+
term: {
|
|
39
|
+
type: 'string',
|
|
40
|
+
description: 'The correct term/abbreviation',
|
|
41
|
+
required: true
|
|
42
|
+
},
|
|
43
|
+
definition: {
|
|
44
|
+
type: 'string',
|
|
45
|
+
description: 'What the term means',
|
|
46
|
+
required: true
|
|
47
|
+
},
|
|
48
|
+
sounds_like: {
|
|
49
|
+
type: 'array',
|
|
50
|
+
items: {
|
|
51
|
+
type: 'string'
|
|
52
|
+
},
|
|
53
|
+
description: 'Phonetic variations that might be transcribed incorrectly (e.g., ["W C M P", "double u see em pee"])'
|
|
54
|
+
},
|
|
55
|
+
context: {
|
|
56
|
+
type: 'string',
|
|
57
|
+
description: 'Additional context about when this term is used'
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
},
|
|
61
|
+
{
|
|
62
|
+
name: 'add_person',
|
|
63
|
+
description: 'Add a new person to the context for future name recognition. Use this when you learn about people whose names were transcribed incorrectly.',
|
|
64
|
+
parameters: {
|
|
65
|
+
name: {
|
|
66
|
+
type: 'string',
|
|
67
|
+
description: 'The correct full name',
|
|
68
|
+
required: true
|
|
69
|
+
},
|
|
70
|
+
sounds_like: {
|
|
71
|
+
type: 'array',
|
|
72
|
+
items: {
|
|
73
|
+
type: 'string'
|
|
74
|
+
},
|
|
75
|
+
description: 'Phonetic variations (e.g., ["San Jay", "Sanjai", "Sanjey"])',
|
|
76
|
+
required: true
|
|
77
|
+
},
|
|
78
|
+
role: {
|
|
79
|
+
type: 'string',
|
|
80
|
+
description: 'Their role or title'
|
|
81
|
+
},
|
|
82
|
+
company: {
|
|
83
|
+
type: 'string',
|
|
84
|
+
description: 'Company they work for'
|
|
85
|
+
},
|
|
86
|
+
context: {
|
|
87
|
+
type: 'string',
|
|
88
|
+
description: 'Additional context about this person'
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
},
|
|
92
|
+
{
|
|
93
|
+
name: 'change_project',
|
|
94
|
+
description: 'Change the project assignment of this transcript. This updates metadata and may move the file to a new location based on project routing.',
|
|
95
|
+
parameters: {
|
|
96
|
+
project_id: {
|
|
97
|
+
type: 'string',
|
|
98
|
+
description: 'The project ID to assign',
|
|
99
|
+
required: true
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
},
|
|
103
|
+
{
|
|
104
|
+
name: 'change_title',
|
|
105
|
+
description: 'Change the title of this transcript. This updates the document heading and renames the file.',
|
|
106
|
+
parameters: {
|
|
107
|
+
new_title: {
|
|
108
|
+
type: 'string',
|
|
109
|
+
description: 'The new title for the transcript',
|
|
110
|
+
required: true
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
},
|
|
114
|
+
{
|
|
115
|
+
name: 'provide_help',
|
|
116
|
+
description: 'Provide helpful information to the user about what kinds of feedback they can give.',
|
|
117
|
+
parameters: {
|
|
118
|
+
topic: {
|
|
119
|
+
type: 'string',
|
|
120
|
+
description: 'The topic to help with',
|
|
121
|
+
enum: [
|
|
122
|
+
'terms',
|
|
123
|
+
'people',
|
|
124
|
+
'projects',
|
|
125
|
+
'corrections',
|
|
126
|
+
'general'
|
|
127
|
+
]
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
},
|
|
131
|
+
{
|
|
132
|
+
name: 'complete',
|
|
133
|
+
description: 'Call this when you have finished processing all the feedback and applied all necessary changes.',
|
|
134
|
+
parameters: {
|
|
135
|
+
summary: {
|
|
136
|
+
type: 'string',
|
|
137
|
+
description: 'A summary of what was done',
|
|
138
|
+
required: true
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
];
|
|
143
|
+
/**
|
|
144
|
+
* Create a readline interface for user input
|
|
145
|
+
*/ const createReadlineInterface = ()=>{
|
|
146
|
+
return readline.createInterface({
|
|
147
|
+
input: process.stdin,
|
|
148
|
+
output: process.stdout
|
|
149
|
+
});
|
|
150
|
+
};
|
|
151
|
+
/**
|
|
152
|
+
* Ask a question and get user input
|
|
153
|
+
*/ const askQuestion = (rl, question)=>{
|
|
154
|
+
return new Promise((resolve)=>{
|
|
155
|
+
rl.question(question, (answer)=>{
|
|
156
|
+
resolve(answer.trim());
|
|
157
|
+
});
|
|
158
|
+
});
|
|
159
|
+
};
|
|
160
|
+
/**
|
|
161
|
+
* Execute a feedback tool
|
|
162
|
+
*/ const executeTool = async (toolName, args, feedbackCtx)=>{
|
|
163
|
+
const logger = getLogger();
|
|
164
|
+
switch(toolName){
|
|
165
|
+
case 'correct_text':
|
|
166
|
+
{
|
|
167
|
+
const find = String(args.find);
|
|
168
|
+
const replace = String(args.replace);
|
|
169
|
+
const replaceAll = args.replace_all !== false;
|
|
170
|
+
if (!feedbackCtx.transcriptContent.includes(find)) {
|
|
171
|
+
return {
|
|
172
|
+
success: false,
|
|
173
|
+
message: `Text "${find}" not found in transcript.`
|
|
174
|
+
};
|
|
175
|
+
}
|
|
176
|
+
const occurrences = feedbackCtx.transcriptContent.split(find).length - 1;
|
|
177
|
+
if (replaceAll) {
|
|
178
|
+
feedbackCtx.transcriptContent = feedbackCtx.transcriptContent.split(find).join(replace);
|
|
179
|
+
} else {
|
|
180
|
+
feedbackCtx.transcriptContent = feedbackCtx.transcriptContent.replace(find, replace);
|
|
181
|
+
}
|
|
182
|
+
const changeCount = replaceAll ? occurrences : 1;
|
|
183
|
+
feedbackCtx.changes.push({
|
|
184
|
+
type: 'text_correction',
|
|
185
|
+
description: `Replaced "${find}" with "${replace}" (${changeCount} occurrence${changeCount > 1 ? 's' : ''})`,
|
|
186
|
+
details: {
|
|
187
|
+
find,
|
|
188
|
+
replace,
|
|
189
|
+
count: changeCount
|
|
190
|
+
}
|
|
191
|
+
});
|
|
192
|
+
if (feedbackCtx.verbose) {
|
|
193
|
+
print(` ✓ Replaced "${find}" → "${replace}" (${changeCount}x)`);
|
|
194
|
+
}
|
|
195
|
+
return {
|
|
196
|
+
success: true,
|
|
197
|
+
message: `Replaced ${changeCount} occurrence(s) of "${find}" with "${replace}".`
|
|
198
|
+
};
|
|
199
|
+
}
|
|
200
|
+
case 'add_term':
|
|
201
|
+
{
|
|
202
|
+
const term = String(args.term);
|
|
203
|
+
const definition = String(args.definition);
|
|
204
|
+
const soundsLike = args.sounds_like;
|
|
205
|
+
const termContext = args.context;
|
|
206
|
+
// Generate ID from term
|
|
207
|
+
const id = term.toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/^-|-$/g, '');
|
|
208
|
+
// Check if term already exists
|
|
209
|
+
const existing = feedbackCtx.context.getTerm(id);
|
|
210
|
+
if (existing) {
|
|
211
|
+
return {
|
|
212
|
+
success: false,
|
|
213
|
+
message: `Term "${term}" already exists in context.`
|
|
214
|
+
};
|
|
215
|
+
}
|
|
216
|
+
const newTerm = {
|
|
217
|
+
id,
|
|
218
|
+
name: term,
|
|
219
|
+
type: 'term',
|
|
220
|
+
expansion: definition,
|
|
221
|
+
sounds_like: soundsLike,
|
|
222
|
+
domain: termContext
|
|
223
|
+
};
|
|
224
|
+
if (!feedbackCtx.dryRun) {
|
|
225
|
+
await feedbackCtx.context.saveEntity(newTerm);
|
|
226
|
+
}
|
|
227
|
+
feedbackCtx.changes.push({
|
|
228
|
+
type: 'term_added',
|
|
229
|
+
description: `Added term "${term}" to context`,
|
|
230
|
+
details: {
|
|
231
|
+
term,
|
|
232
|
+
definition,
|
|
233
|
+
sounds_like: soundsLike
|
|
234
|
+
}
|
|
235
|
+
});
|
|
236
|
+
if (feedbackCtx.verbose) {
|
|
237
|
+
print(` ✓ Added term: ${term} = "${definition}"`);
|
|
238
|
+
if (soundsLike === null || soundsLike === void 0 ? void 0 : soundsLike.length) {
|
|
239
|
+
print(` sounds_like: ${soundsLike.join(', ')}`);
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
return {
|
|
243
|
+
success: true,
|
|
244
|
+
message: `Added term "${term}" to context. It will be recognized in future transcripts.`,
|
|
245
|
+
data: {
|
|
246
|
+
id,
|
|
247
|
+
term,
|
|
248
|
+
definition
|
|
249
|
+
}
|
|
250
|
+
};
|
|
251
|
+
}
|
|
252
|
+
case 'add_person':
|
|
253
|
+
{
|
|
254
|
+
const name = String(args.name);
|
|
255
|
+
const soundsLike = args.sounds_like;
|
|
256
|
+
const role = args.role;
|
|
257
|
+
const company = args.company;
|
|
258
|
+
const personContext = args.context;
|
|
259
|
+
// Generate ID from name
|
|
260
|
+
const id = name.toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/^-|-$/g, '');
|
|
261
|
+
// Check if person already exists
|
|
262
|
+
const existing = feedbackCtx.context.getPerson(id);
|
|
263
|
+
if (existing) {
|
|
264
|
+
return {
|
|
265
|
+
success: false,
|
|
266
|
+
message: `Person "${name}" already exists in context.`
|
|
267
|
+
};
|
|
268
|
+
}
|
|
269
|
+
const newPerson = {
|
|
270
|
+
id,
|
|
271
|
+
name,
|
|
272
|
+
type: 'person',
|
|
273
|
+
sounds_like: soundsLike,
|
|
274
|
+
role,
|
|
275
|
+
company,
|
|
276
|
+
context: personContext
|
|
277
|
+
};
|
|
278
|
+
if (!feedbackCtx.dryRun) {
|
|
279
|
+
await feedbackCtx.context.saveEntity(newPerson);
|
|
280
|
+
}
|
|
281
|
+
feedbackCtx.changes.push({
|
|
282
|
+
type: 'person_added',
|
|
283
|
+
description: `Added person "${name}" to context`,
|
|
284
|
+
details: {
|
|
285
|
+
name,
|
|
286
|
+
sounds_like: soundsLike,
|
|
287
|
+
role,
|
|
288
|
+
company
|
|
289
|
+
}
|
|
290
|
+
});
|
|
291
|
+
if (feedbackCtx.verbose) {
|
|
292
|
+
print(` ✓ Added person: ${name}`);
|
|
293
|
+
print(` sounds_like: ${soundsLike.join(', ')}`);
|
|
294
|
+
if (role) print(` role: ${role}`);
|
|
295
|
+
if (company) print(` company: ${company}`);
|
|
296
|
+
}
|
|
297
|
+
return {
|
|
298
|
+
success: true,
|
|
299
|
+
message: `Added person "${name}" to context. Their name will be recognized in future transcripts.`,
|
|
300
|
+
data: {
|
|
301
|
+
id,
|
|
302
|
+
name,
|
|
303
|
+
sounds_like: soundsLike
|
|
304
|
+
}
|
|
305
|
+
};
|
|
306
|
+
}
|
|
307
|
+
case 'change_project':
|
|
308
|
+
{
|
|
309
|
+
var _project_routing;
|
|
310
|
+
const projectId = String(args.project_id);
|
|
311
|
+
// Find the project
|
|
312
|
+
const project = feedbackCtx.context.getProject(projectId);
|
|
313
|
+
if (!project) {
|
|
314
|
+
// List available projects
|
|
315
|
+
const available = feedbackCtx.context.getAllProjects().map((p)=>p.id);
|
|
316
|
+
return {
|
|
317
|
+
success: false,
|
|
318
|
+
message: `Project "${projectId}" not found. Available projects: ${available.join(', ')}`
|
|
319
|
+
};
|
|
320
|
+
}
|
|
321
|
+
// Update metadata in transcript
|
|
322
|
+
const metadataRegex = /\*\*Project\*\*: .+/;
|
|
323
|
+
const projectIdRegex = /\*\*Project ID\*\*: `.+`/;
|
|
324
|
+
if (metadataRegex.test(feedbackCtx.transcriptContent)) {
|
|
325
|
+
feedbackCtx.transcriptContent = feedbackCtx.transcriptContent.replace(metadataRegex, `**Project**: ${project.name}`);
|
|
326
|
+
}
|
|
327
|
+
if (projectIdRegex.test(feedbackCtx.transcriptContent)) {
|
|
328
|
+
feedbackCtx.transcriptContent = feedbackCtx.transcriptContent.replace(projectIdRegex, `**Project ID**: \`${project.id}\``);
|
|
329
|
+
}
|
|
330
|
+
feedbackCtx.changes.push({
|
|
331
|
+
type: 'project_changed',
|
|
332
|
+
description: `Changed project to "${project.name}" (${project.id})`,
|
|
333
|
+
details: {
|
|
334
|
+
project_id: projectId,
|
|
335
|
+
project_name: project.name,
|
|
336
|
+
routing: project.routing
|
|
337
|
+
}
|
|
338
|
+
});
|
|
339
|
+
if (feedbackCtx.verbose) {
|
|
340
|
+
var _project_routing1;
|
|
341
|
+
print(` ✓ Changed project to: ${project.name} (${project.id})`);
|
|
342
|
+
if ((_project_routing1 = project.routing) === null || _project_routing1 === void 0 ? void 0 : _project_routing1.destination) {
|
|
343
|
+
print(` New destination: ${project.routing.destination}`);
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
return {
|
|
347
|
+
success: true,
|
|
348
|
+
message: `Changed project to "${project.name}". The transcript metadata has been updated.`,
|
|
349
|
+
data: {
|
|
350
|
+
project_id: projectId,
|
|
351
|
+
destination: (_project_routing = project.routing) === null || _project_routing === void 0 ? void 0 : _project_routing.destination
|
|
352
|
+
}
|
|
353
|
+
};
|
|
354
|
+
}
|
|
355
|
+
case 'change_title':
|
|
356
|
+
{
|
|
357
|
+
const newTitle = String(args.new_title);
|
|
358
|
+
// Update title in transcript (first # heading)
|
|
359
|
+
const titleRegex = /^# .+$/m;
|
|
360
|
+
if (titleRegex.test(feedbackCtx.transcriptContent)) {
|
|
361
|
+
feedbackCtx.transcriptContent = feedbackCtx.transcriptContent.replace(titleRegex, `# ${newTitle}`);
|
|
362
|
+
}
|
|
363
|
+
feedbackCtx.changes.push({
|
|
364
|
+
type: 'title_changed',
|
|
365
|
+
description: `Changed title to "${newTitle}"`,
|
|
366
|
+
details: {
|
|
367
|
+
new_title: newTitle,
|
|
368
|
+
slug: slugifyTitle(newTitle)
|
|
369
|
+
}
|
|
370
|
+
});
|
|
371
|
+
if (feedbackCtx.verbose) {
|
|
372
|
+
print(` ✓ Changed title to: ${newTitle}`);
|
|
373
|
+
}
|
|
374
|
+
return {
|
|
375
|
+
success: true,
|
|
376
|
+
message: `Changed title to "${newTitle}". The file will be renamed accordingly.`,
|
|
377
|
+
data: {
|
|
378
|
+
new_title: newTitle
|
|
379
|
+
}
|
|
380
|
+
};
|
|
381
|
+
}
|
|
382
|
+
case 'provide_help':
|
|
383
|
+
{
|
|
384
|
+
const topic = String(args.topic || 'general');
|
|
385
|
+
let helpText = '';
|
|
386
|
+
switch(topic){
|
|
387
|
+
case 'terms':
|
|
388
|
+
helpText = `
|
|
389
|
+
**Term Corrections**
|
|
390
|
+
|
|
391
|
+
You can teach me about abbreviations, acronyms, and technical terms:
|
|
392
|
+
|
|
393
|
+
- "Everywhere it says WCMP, that should be WCNP - Walmart's Native Cloud Platform"
|
|
394
|
+
- "YB should be spelled Wibey"
|
|
395
|
+
- "API should be written as A.P.I. in this context"
|
|
396
|
+
|
|
397
|
+
I'll:
|
|
398
|
+
1. Fix the term in this transcript
|
|
399
|
+
2. Add it to my vocabulary for future transcripts
|
|
400
|
+
`;
|
|
401
|
+
break;
|
|
402
|
+
case 'people':
|
|
403
|
+
helpText = `
|
|
404
|
+
**Name Corrections**
|
|
405
|
+
|
|
406
|
+
You can teach me about people whose names were transcribed incorrectly:
|
|
407
|
+
|
|
408
|
+
- "San Jay Grouper is actually Sanjay Gupta"
|
|
409
|
+
- "Priya was transcribed as 'pre a' - please fix"
|
|
410
|
+
- "Marie should be spelled Mari (without the e)"
|
|
411
|
+
|
|
412
|
+
I'll:
|
|
413
|
+
1. Fix the name everywhere in this transcript
|
|
414
|
+
2. Remember how the name sounds for future transcripts
|
|
415
|
+
`;
|
|
416
|
+
break;
|
|
417
|
+
case 'projects':
|
|
418
|
+
helpText = `
|
|
419
|
+
**Project Assignment**
|
|
420
|
+
|
|
421
|
+
You can tell me if a transcript belongs to a different project:
|
|
422
|
+
|
|
423
|
+
- "This should be in the Quantum Readiness project"
|
|
424
|
+
- "Move this to the client-alpha project"
|
|
425
|
+
- "This was misclassified - it's a personal note, not work"
|
|
426
|
+
|
|
427
|
+
I'll:
|
|
428
|
+
1. Update the project metadata
|
|
429
|
+
2. Move the file to the project's configured location
|
|
430
|
+
`;
|
|
431
|
+
break;
|
|
432
|
+
case 'corrections':
|
|
433
|
+
helpText = `
|
|
434
|
+
**General Corrections**
|
|
435
|
+
|
|
436
|
+
You can ask me to fix any text in the transcript:
|
|
437
|
+
|
|
438
|
+
- "Change 'gonna' to 'going to' everywhere"
|
|
439
|
+
- "The date mentioned should be January 15th, not January 5th"
|
|
440
|
+
- "Remove the paragraph about lunch - that was a tangent"
|
|
441
|
+
|
|
442
|
+
I'll make the corrections while preserving the rest of the transcript.
|
|
443
|
+
`;
|
|
444
|
+
break;
|
|
445
|
+
default:
|
|
446
|
+
helpText = `
|
|
447
|
+
**What I Can Help With**
|
|
448
|
+
|
|
449
|
+
I can process your feedback to:
|
|
450
|
+
|
|
451
|
+
1. **Fix Terms & Abbreviations**: "WCMP should be WCNP"
|
|
452
|
+
2. **Correct Names**: "San Jay Grouper is Sanjay Gupta"
|
|
453
|
+
3. **Change Projects**: "This belongs in the Quantum project"
|
|
454
|
+
4. **Update Title**: "Change the title to 'Q1 Planning Session'"
|
|
455
|
+
5. **General Corrections**: "Replace X with Y everywhere"
|
|
456
|
+
|
|
457
|
+
Just describe what's wrong in natural language, and I'll figure out what to do!
|
|
458
|
+
|
|
459
|
+
Ask about specific topics:
|
|
460
|
+
- "How do I correct terms?"
|
|
461
|
+
- "How do I fix names?"
|
|
462
|
+
- "How do I change the project?"
|
|
463
|
+
`;
|
|
464
|
+
}
|
|
465
|
+
print(helpText);
|
|
466
|
+
return {
|
|
467
|
+
success: true,
|
|
468
|
+
message: helpText
|
|
469
|
+
};
|
|
470
|
+
}
|
|
471
|
+
case 'complete':
|
|
472
|
+
{
|
|
473
|
+
const summary = String(args.summary);
|
|
474
|
+
return {
|
|
475
|
+
success: true,
|
|
476
|
+
message: summary,
|
|
477
|
+
data: {
|
|
478
|
+
complete: true
|
|
479
|
+
}
|
|
480
|
+
};
|
|
481
|
+
}
|
|
482
|
+
default:
|
|
483
|
+
logger.warn('Unknown tool: %s', toolName);
|
|
484
|
+
return {
|
|
485
|
+
success: false,
|
|
486
|
+
message: `Unknown tool: ${toolName}`
|
|
487
|
+
};
|
|
488
|
+
}
|
|
489
|
+
};
|
|
490
|
+
/**
|
|
491
|
+
* Build the system prompt for the feedback agent
|
|
492
|
+
*/ const buildFeedbackSystemPrompt = (transcriptPreview, availableProjects)=>{
|
|
493
|
+
const toolDescriptions = FEEDBACK_TOOLS.map((t)=>`- ${t.name}: ${t.description}`).join('\n');
|
|
494
|
+
return `You are an intelligent feedback processor for a transcription system. Your job is to understand user feedback about transcripts and take appropriate actions.
|
|
495
|
+
|
|
496
|
+
## Current Transcript Preview
|
|
497
|
+
${transcriptPreview.substring(0, 1000)}${transcriptPreview.length > 1000 ? '...' : ''}
|
|
498
|
+
|
|
499
|
+
## Available Projects
|
|
500
|
+
${availableProjects.length > 0 ? availableProjects.join(', ') : '(no projects configured)'}
|
|
501
|
+
|
|
502
|
+
## Available Tools
|
|
503
|
+
${toolDescriptions}
|
|
504
|
+
|
|
505
|
+
## How to Process Feedback
|
|
506
|
+
|
|
507
|
+
1. **Understand the feedback**: What is the user asking for?
|
|
508
|
+
2. **Identify actions**: What tools do you need to use?
|
|
509
|
+
3. **Execute in order**:
|
|
510
|
+
- First, make text corrections (correct_text)
|
|
511
|
+
- Then, add context entities (add_term, add_person)
|
|
512
|
+
- Finally, change metadata if needed (change_project, change_title)
|
|
513
|
+
4. **Summarize**: Call 'complete' with a summary when done
|
|
514
|
+
|
|
515
|
+
## Important Rules
|
|
516
|
+
|
|
517
|
+
- If the user asks for help or seems unsure, use provide_help first
|
|
518
|
+
- For name corrections: BOTH fix the text AND add the person to context
|
|
519
|
+
- For term corrections: BOTH fix the text AND add the term to context
|
|
520
|
+
- When fixing names/terms, use correct_text with replace_all=true
|
|
521
|
+
- Be thorough - if "San Jay Grouper" should be "Sanjay Gupta", also consider variations like "San jay", "Sanjay Grouper", etc.
|
|
522
|
+
- Always call 'complete' when finished, with a summary of what you did
|
|
523
|
+
|
|
524
|
+
## Example Interactions
|
|
525
|
+
|
|
526
|
+
User: "YB should be Wibey"
|
|
527
|
+
→ Use correct_text to replace "YB" with "Wibey"
|
|
528
|
+
→ Use add_term to add "Wibey" with sounds_like ["YB", "Y B"]
|
|
529
|
+
→ Use complete to summarize
|
|
530
|
+
|
|
531
|
+
User: "San Jay Grouper is actually Sanjay Gupta"
|
|
532
|
+
→ Use correct_text to replace "San Jay Grouper" with "Sanjay Gupta"
|
|
533
|
+
→ Use correct_text to replace any other variations like "San jay Grouper"
|
|
534
|
+
→ Use add_person to add "Sanjay Gupta" with sounds_like ["San Jay Grouper", "Sanjay Grouper"]
|
|
535
|
+
→ Use complete to summarize
|
|
536
|
+
|
|
537
|
+
User: "This should be in the Quantum Readiness project"
|
|
538
|
+
→ Use change_project with project_id matching "Quantum Readiness" or similar
|
|
539
|
+
→ Use complete to summarize
|
|
540
|
+
|
|
541
|
+
Respond with tool calls to process the feedback.`;
|
|
542
|
+
};
|
|
543
|
+
/**
|
|
544
|
+
* Process feedback using the agentic model
|
|
545
|
+
*/ const processFeedback = async (feedback, feedbackCtx, reasoning)=>{
|
|
546
|
+
const logger = getLogger();
|
|
547
|
+
// Get available projects
|
|
548
|
+
const projects = feedbackCtx.context.getAllProjects().map((p)=>`${p.id} (${p.name})`);
|
|
549
|
+
// Build the prompt
|
|
550
|
+
const systemPrompt = buildFeedbackSystemPrompt(feedbackCtx.transcriptContent, projects);
|
|
551
|
+
// Convert tools to OpenAI format
|
|
552
|
+
const tools = FEEDBACK_TOOLS.map((t)=>({
|
|
553
|
+
type: 'function',
|
|
554
|
+
function: {
|
|
555
|
+
name: t.name,
|
|
556
|
+
description: t.description,
|
|
557
|
+
parameters: {
|
|
558
|
+
type: 'object',
|
|
559
|
+
properties: Object.fromEntries(Object.entries(t.parameters).map(([key, param])=>[
|
|
560
|
+
key,
|
|
561
|
+
{
|
|
562
|
+
type: param.type,
|
|
563
|
+
description: param.description,
|
|
564
|
+
...param.enum ? {
|
|
565
|
+
enum: param.enum
|
|
566
|
+
} : {},
|
|
567
|
+
...param.items ? {
|
|
568
|
+
items: param.items
|
|
569
|
+
} : {}
|
|
570
|
+
}
|
|
571
|
+
])),
|
|
572
|
+
required: Object.entries(t.parameters).filter(([_, p])=>p.required).map(([key])=>key)
|
|
573
|
+
}
|
|
574
|
+
}
|
|
575
|
+
}));
|
|
576
|
+
// Process with reasoning model
|
|
577
|
+
let iterations = 0;
|
|
578
|
+
const maxIterations = 10;
|
|
579
|
+
const conversationHistory = [
|
|
580
|
+
{
|
|
581
|
+
role: 'system',
|
|
582
|
+
content: systemPrompt
|
|
583
|
+
},
|
|
584
|
+
{
|
|
585
|
+
role: 'user',
|
|
586
|
+
content: feedback
|
|
587
|
+
}
|
|
588
|
+
];
|
|
589
|
+
while(iterations < maxIterations){
|
|
590
|
+
iterations++;
|
|
591
|
+
logger.debug('Feedback processing iteration %d', iterations);
|
|
592
|
+
try {
|
|
593
|
+
const response = await reasoning.completeWithTools({
|
|
594
|
+
messages: conversationHistory,
|
|
595
|
+
tools
|
|
596
|
+
});
|
|
597
|
+
// Check for tool calls
|
|
598
|
+
if (response.tool_calls && response.tool_calls.length > 0) {
|
|
599
|
+
// Add assistant message with tool calls
|
|
600
|
+
conversationHistory.push({
|
|
601
|
+
role: 'assistant',
|
|
602
|
+
content: response.content || '',
|
|
603
|
+
tool_calls: response.tool_calls.map((tc)=>({
|
|
604
|
+
id: tc.id,
|
|
605
|
+
function: {
|
|
606
|
+
name: tc.function.name,
|
|
607
|
+
arguments: tc.function.arguments
|
|
608
|
+
}
|
|
609
|
+
}))
|
|
610
|
+
});
|
|
611
|
+
// Execute each tool call
|
|
612
|
+
for (const toolCall of response.tool_calls){
|
|
613
|
+
const toolName = toolCall.function.name;
|
|
614
|
+
let args;
|
|
615
|
+
try {
|
|
616
|
+
args = JSON.parse(toolCall.function.arguments);
|
|
617
|
+
} catch {
|
|
618
|
+
args = {};
|
|
619
|
+
}
|
|
620
|
+
if (feedbackCtx.verbose) {
|
|
621
|
+
print(`\n[Executing: ${toolName}]`);
|
|
622
|
+
}
|
|
623
|
+
const result = await executeTool(toolName, args, feedbackCtx);
|
|
624
|
+
// Add tool result to conversation
|
|
625
|
+
conversationHistory.push({
|
|
626
|
+
role: 'tool',
|
|
627
|
+
content: JSON.stringify(result),
|
|
628
|
+
tool_call_id: toolCall.id
|
|
629
|
+
});
|
|
630
|
+
// Check if complete
|
|
631
|
+
if (toolName === 'complete') {
|
|
632
|
+
if (feedbackCtx.verbose) {
|
|
633
|
+
print(`\n${result.message}`);
|
|
634
|
+
}
|
|
635
|
+
return;
|
|
636
|
+
}
|
|
637
|
+
}
|
|
638
|
+
} else {
|
|
639
|
+
// No tool calls - model is done or confused
|
|
640
|
+
if (response.content) {
|
|
641
|
+
print(`\n${response.content}`);
|
|
642
|
+
}
|
|
643
|
+
return;
|
|
644
|
+
}
|
|
645
|
+
} catch (error) {
|
|
646
|
+
logger.error('Error during feedback processing', {
|
|
647
|
+
error
|
|
648
|
+
});
|
|
649
|
+
throw error;
|
|
650
|
+
}
|
|
651
|
+
}
|
|
652
|
+
logger.warn('Feedback processing reached max iterations');
|
|
653
|
+
};
|
|
654
|
+
/**
|
|
655
|
+
* Apply changes and save the transcript
|
|
656
|
+
*/ const applyChanges = async (feedbackCtx)=>{
|
|
657
|
+
const logger = getLogger();
|
|
658
|
+
let newPath = feedbackCtx.transcriptPath;
|
|
659
|
+
let moved = false;
|
|
660
|
+
// Check if we need to rename the file (title changed)
|
|
661
|
+
const titleChange = feedbackCtx.changes.find((c)=>c.type === 'title_changed');
|
|
662
|
+
if (titleChange) {
|
|
663
|
+
const slug = titleChange.details.slug;
|
|
664
|
+
const timestamp = extractTimestampFromFilename(feedbackCtx.transcriptPath);
|
|
665
|
+
const dir = path.dirname(feedbackCtx.transcriptPath);
|
|
666
|
+
if (timestamp) {
|
|
667
|
+
const timeStr = `${timestamp.hour.toString().padStart(2, '0')}${timestamp.minute.toString().padStart(2, '0')}`;
|
|
668
|
+
newPath = path.join(dir, `${timestamp.day}-${timeStr}-${slug}.md`);
|
|
669
|
+
} else {
|
|
670
|
+
newPath = path.join(dir, `${slug}.md`);
|
|
671
|
+
}
|
|
672
|
+
}
|
|
673
|
+
// Check if we need to move the file (project changed)
|
|
674
|
+
const projectChange = feedbackCtx.changes.find((c)=>c.type === 'project_changed');
|
|
675
|
+
if (projectChange && projectChange.details.routing) {
|
|
676
|
+
const routing = projectChange.details.routing;
|
|
677
|
+
if (routing.destination) {
|
|
678
|
+
// Expand ~ to home directory
|
|
679
|
+
let dest = routing.destination;
|
|
680
|
+
if (dest.startsWith('~')) {
|
|
681
|
+
dest = path.join(process.env.HOME || '', dest.slice(1));
|
|
682
|
+
}
|
|
683
|
+
// Get date from transcript metadata or use current date
|
|
684
|
+
const now = new Date();
|
|
685
|
+
const year = now.getFullYear().toString();
|
|
686
|
+
const month = (now.getMonth() + 1).toString().padStart(2, '0');
|
|
687
|
+
// Build path based on structure
|
|
688
|
+
let structuredPath = dest;
|
|
689
|
+
const structure = routing.structure || 'month';
|
|
690
|
+
if (structure === 'year') {
|
|
691
|
+
structuredPath = path.join(dest, year);
|
|
692
|
+
} else if (structure === 'month') {
|
|
693
|
+
structuredPath = path.join(dest, year, month);
|
|
694
|
+
} else if (structure === 'day') {
|
|
695
|
+
const day = now.getDate().toString().padStart(2, '0');
|
|
696
|
+
structuredPath = path.join(dest, year, month, day);
|
|
697
|
+
}
|
|
698
|
+
// Update path
|
|
699
|
+
const filename = path.basename(newPath);
|
|
700
|
+
newPath = path.join(structuredPath, filename);
|
|
701
|
+
moved = true;
|
|
702
|
+
}
|
|
703
|
+
}
|
|
704
|
+
// Ensure directory exists
|
|
705
|
+
await fs.mkdir(path.dirname(newPath), {
|
|
706
|
+
recursive: true
|
|
707
|
+
});
|
|
708
|
+
// Write the updated content
|
|
709
|
+
if (!feedbackCtx.dryRun) {
|
|
710
|
+
await fs.writeFile(newPath, feedbackCtx.transcriptContent, 'utf-8');
|
|
711
|
+
// Delete original if moved/renamed
|
|
712
|
+
if (newPath !== feedbackCtx.transcriptPath) {
|
|
713
|
+
await fs.unlink(feedbackCtx.transcriptPath);
|
|
714
|
+
}
|
|
715
|
+
}
|
|
716
|
+
logger.info('Applied %d changes to transcript', feedbackCtx.changes.length);
|
|
717
|
+
return {
|
|
718
|
+
newPath,
|
|
719
|
+
moved
|
|
720
|
+
};
|
|
721
|
+
};
|
|
722
|
+
/**
|
|
723
|
+
* Run the feedback command
|
|
724
|
+
*/ const runFeedback = async (transcriptPath, options)=>{
|
|
725
|
+
// Verify file exists
|
|
726
|
+
try {
|
|
727
|
+
await fs.access(transcriptPath);
|
|
728
|
+
} catch {
|
|
729
|
+
print(`Error: File not found: ${transcriptPath}`);
|
|
730
|
+
process.exit(1);
|
|
731
|
+
}
|
|
732
|
+
// Read transcript
|
|
733
|
+
const transcriptContent = await fs.readFile(transcriptPath, 'utf-8');
|
|
734
|
+
// Initialize context
|
|
735
|
+
const context = await create();
|
|
736
|
+
// Initialize reasoning
|
|
737
|
+
const reasoning = create$1({
|
|
738
|
+
model: options.model || DEFAULT_MODEL
|
|
739
|
+
});
|
|
740
|
+
// Create feedback context
|
|
741
|
+
const feedbackCtx = {
|
|
742
|
+
transcriptPath,
|
|
743
|
+
transcriptContent,
|
|
744
|
+
originalContent: transcriptContent,
|
|
745
|
+
context,
|
|
746
|
+
changes: [],
|
|
747
|
+
verbose: options.verbose || false,
|
|
748
|
+
dryRun: options.dryRun || false
|
|
749
|
+
};
|
|
750
|
+
// Get feedback from user if not provided
|
|
751
|
+
let feedback = options.feedback;
|
|
752
|
+
if (!feedback) {
|
|
753
|
+
const rl = createReadlineInterface();
|
|
754
|
+
print('\n' + '─'.repeat(60));
|
|
755
|
+
print(`[Feedback for: ${path.basename(transcriptPath)}]`);
|
|
756
|
+
print('─'.repeat(60));
|
|
757
|
+
print('\nDescribe what needs to be corrected in natural language.');
|
|
758
|
+
print('Examples:');
|
|
759
|
+
print(' - "YB should be Wibey"');
|
|
760
|
+
print(' - "San Jay Grouper is actually Sanjay Gupta"');
|
|
761
|
+
print(' - "This should be in the Quantum Readiness project"');
|
|
762
|
+
print(' - "What feedback can I give?" (for help)\n');
|
|
763
|
+
feedback = await askQuestion(rl, 'What is your feedback? ');
|
|
764
|
+
rl.close();
|
|
765
|
+
if (!feedback) {
|
|
766
|
+
print('No feedback provided.');
|
|
767
|
+
return;
|
|
768
|
+
}
|
|
769
|
+
}
|
|
770
|
+
if (options.verbose) {
|
|
771
|
+
print('\n[Processing feedback...]');
|
|
772
|
+
}
|
|
773
|
+
// Process feedback with agentic model
|
|
774
|
+
await processFeedback(feedback, feedbackCtx, reasoning);
|
|
775
|
+
// Apply changes
|
|
776
|
+
if (feedbackCtx.changes.length > 0) {
|
|
777
|
+
if (options.dryRun) {
|
|
778
|
+
print('\n[Dry Run] Would apply the following changes:');
|
|
779
|
+
for (const change of feedbackCtx.changes){
|
|
780
|
+
print(` - ${change.description}`);
|
|
781
|
+
}
|
|
782
|
+
} else {
|
|
783
|
+
const { newPath, moved } = await applyChanges(feedbackCtx);
|
|
784
|
+
print('\n' + '─'.repeat(60));
|
|
785
|
+
print('[Changes Applied]');
|
|
786
|
+
print('─'.repeat(60));
|
|
787
|
+
for (const change of feedbackCtx.changes){
|
|
788
|
+
print(` ✓ ${change.description}`);
|
|
789
|
+
}
|
|
790
|
+
if (newPath !== feedbackCtx.transcriptPath) {
|
|
791
|
+
if (moved) {
|
|
792
|
+
print(`\nFile moved to: ${newPath}`);
|
|
793
|
+
} else {
|
|
794
|
+
print(`\nFile renamed to: ${path.basename(newPath)}`);
|
|
795
|
+
}
|
|
796
|
+
} else {
|
|
797
|
+
print(`\nFile updated: ${transcriptPath}`);
|
|
798
|
+
}
|
|
799
|
+
}
|
|
800
|
+
} else {
|
|
801
|
+
print('\nNo changes were made.');
|
|
802
|
+
}
|
|
803
|
+
};
|
|
804
|
+
/**
|
|
805
|
+
* Register the feedback command
|
|
806
|
+
*/ const registerFeedbackCommands = (program)=>{
|
|
807
|
+
const feedbackCmd = new Command('feedback').description('Provide natural language feedback to correct transcripts and improve context').argument('[file]', 'Transcript file to provide feedback on').option('-f, --feedback <text>', 'Feedback text (if not provided, will prompt interactively)').option('-m, --model <model>', 'Reasoning model to use', DEFAULT_MODEL).option('--dry-run', 'Show what would happen without making changes').option('-v, --verbose', 'Show detailed output').option('--help-me', 'Show examples of feedback you can provide').action(async (file, options)=>{
|
|
808
|
+
if (options.helpMe) {
|
|
809
|
+
print(`
|
|
810
|
+
╔════════════════════════════════════════════════════════════╗
|
|
811
|
+
║ PROTOKOLL FEEDBACK - EXAMPLES ║
|
|
812
|
+
╠════════════════════════════════════════════════════════════╣
|
|
813
|
+
║ ║
|
|
814
|
+
║ CORRECTING TERMS & ABBREVIATIONS ║
|
|
815
|
+
║ ───────────────────────────────── ║
|
|
816
|
+
║ "Everywhere it says WCMP, that should be WCNP" ║
|
|
817
|
+
║ "YB should be spelled Wibey" ║
|
|
818
|
+
║ "API should be written as A-P-I" ║
|
|
819
|
+
║ ║
|
|
820
|
+
║ FIXING NAMES ║
|
|
821
|
+
║ ──────────── ║
|
|
822
|
+
║ "San Jay Grouper is actually Sanjay Gupta" ║
|
|
823
|
+
║ "Priya was transcribed as 'pre a' - please fix" ║
|
|
824
|
+
║ "Marie should be spelled Mari" ║
|
|
825
|
+
║ ║
|
|
826
|
+
║ CHANGING PROJECT ASSIGNMENT ║
|
|
827
|
+
║ ─────────────────────────── ║
|
|
828
|
+
║ "This should be in the Quantum Readiness project" ║
|
|
829
|
+
║ "Move this to client-alpha" ║
|
|
830
|
+
║ "This was misclassified - should be personal" ║
|
|
831
|
+
║ ║
|
|
832
|
+
║ GENERAL CORRECTIONS ║
|
|
833
|
+
║ ─────────────────── ║
|
|
834
|
+
║ "Change the title to 'Q1 Planning Session'" ║
|
|
835
|
+
║ "Replace 'gonna' with 'going to' everywhere" ║
|
|
836
|
+
║ ║
|
|
837
|
+
╚════════════════════════════════════════════════════════════╝
|
|
838
|
+
|
|
839
|
+
Usage:
|
|
840
|
+
protokoll feedback /path/to/transcript.md
|
|
841
|
+
protokoll feedback /path/to/transcript.md -f "YB should be Wibey"
|
|
842
|
+
protokoll feedback /path/to/transcript.md --dry-run -v
|
|
843
|
+
`);
|
|
844
|
+
return;
|
|
845
|
+
}
|
|
846
|
+
if (!file) {
|
|
847
|
+
print('Error: A transcript file is required.');
|
|
848
|
+
print('Usage: protokoll feedback /path/to/transcript.md');
|
|
849
|
+
print('Run "protokoll feedback --help-me" for examples.');
|
|
850
|
+
process.exit(1);
|
|
851
|
+
}
|
|
852
|
+
await runFeedback(file, options);
|
|
853
|
+
});
|
|
854
|
+
program.addCommand(feedbackCmd);
|
|
855
|
+
};
|
|
856
|
+
|
|
857
|
+
export { FEEDBACK_TOOLS, applyChanges, buildFeedbackSystemPrompt, executeTool, processFeedback, registerFeedbackCommands, runFeedback };
|
|
858
|
+
//# sourceMappingURL=feedback.js.map
|