@sean.holung/minicode 0.2.4 → 0.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/src/indexer/code-map.js +2 -1
- package/dist/src/indexer/plugins/typescript.js +63 -34
- package/dist/src/serve/agent-bridge.js +127 -4
- package/dist/src/serve/server.js +101 -3
- package/dist/src/session/session-store.js +3 -1
- package/dist/src/ui/cli-ink.js +1 -0
- package/dist/src/web/app.js +2037 -117
- package/dist/src/web/index.html +28 -9
- package/dist/src/web/style.css +503 -3
- package/dist/tests/benchmark-index.test.js +152 -0
- package/dist/tests/indexer.test.js +62 -0
- package/dist/tests/serve.integration.test.js +225 -0
- package/node_modules/@minicode/agent-sdk/dist/src/agent/agent.d.ts +6 -1
- package/node_modules/@minicode/agent-sdk/dist/src/agent/agent.d.ts.map +1 -1
- package/node_modules/@minicode/agent-sdk/dist/src/agent/agent.js +35 -20
- package/node_modules/@minicode/agent-sdk/dist/src/agent/agent.js.map +1 -1
- package/node_modules/@minicode/agent-sdk/dist/src/index.d.ts +2 -1
- package/node_modules/@minicode/agent-sdk/dist/src/index.d.ts.map +1 -1
- package/node_modules/@minicode/agent-sdk/dist/src/index.js.map +1 -1
- package/node_modules/@minicode/agent-sdk/dist/src/indexer/types.d.ts +68 -0
- package/node_modules/@minicode/agent-sdk/dist/src/indexer/types.d.ts.map +1 -0
- package/node_modules/@minicode/agent-sdk/dist/src/indexer/types.js +2 -0
- package/node_modules/@minicode/agent-sdk/dist/src/indexer/types.js.map +1 -0
- package/node_modules/@minicode/agent-sdk/dist/src/prompt/system-prompt.d.ts +1 -5
- package/node_modules/@minicode/agent-sdk/dist/src/prompt/system-prompt.d.ts.map +1 -1
- package/node_modules/@minicode/agent-sdk/dist/src/prompt/system-prompt.js.map +1 -1
- package/node_modules/@minicode/agent-sdk/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +7 -4
|
@@ -10,7 +10,8 @@ function formatSymbol(symbol, indent, isMethod) {
|
|
|
10
10
|
return `${indent}${symbol.kind} ${symbol.qualifiedName}\n${indent} ${symbol.signature}`;
|
|
11
11
|
}
|
|
12
12
|
function isEntryPointFile(filePath) {
|
|
13
|
-
|
|
13
|
+
const name = filePath.replace(/\\/g, "/");
|
|
14
|
+
return /(?:^|\/)index\.[jt]sx?$/.test(name);
|
|
14
15
|
}
|
|
15
16
|
/**
|
|
16
17
|
* Build the set of symbols related to focus symbols via dependency edges.
|
|
@@ -17,7 +17,7 @@ function extractSignature(node, sourceFile) {
|
|
|
17
17
|
if (ts.isArrowFunction(node) && ts.isBlock(node.body)) {
|
|
18
18
|
return node.body.getStart(sourceFile);
|
|
19
19
|
}
|
|
20
|
-
if (ts.isClassDeclaration(node)) {
|
|
20
|
+
if (ts.isClassDeclaration(node) || ts.isClassExpression(node)) {
|
|
21
21
|
const text = sourceText.slice(node.getStart(), node.getEnd());
|
|
22
22
|
const braceIdx = text.indexOf("{");
|
|
23
23
|
return braceIdx >= 0 ? node.getStart() + braceIdx : null;
|
|
@@ -84,7 +84,8 @@ function createPlugin() {
|
|
|
84
84
|
});
|
|
85
85
|
return;
|
|
86
86
|
}
|
|
87
|
-
if (ts.isClassDeclaration(node)
|
|
87
|
+
if ((ts.isClassDeclaration(node) || ts.isClassExpression(node)) &&
|
|
88
|
+
node.name) {
|
|
88
89
|
const name = node.name.getText(sourceFile);
|
|
89
90
|
const prevClass = currentClass;
|
|
90
91
|
currentClass = name;
|
|
@@ -97,7 +98,7 @@ function createPlugin() {
|
|
|
97
98
|
startLine: getLine(sourceFile, node.getStart(sourceFile)),
|
|
98
99
|
endLine: getLine(sourceFile, node.getEnd()),
|
|
99
100
|
signature: extractSignature(node, sourceFile),
|
|
100
|
-
exported: isExported(node),
|
|
101
|
+
exported: ts.isClassDeclaration(node) ? isExported(node) : false,
|
|
101
102
|
dependencies: [],
|
|
102
103
|
...(doc && { docComment: doc }),
|
|
103
104
|
});
|
|
@@ -183,9 +184,9 @@ function createPlugin() {
|
|
|
183
184
|
const doc = extractJSDoc(node, sourceFile);
|
|
184
185
|
for (const decl of node.declarationList.declarations) {
|
|
185
186
|
const init = decl.initializer;
|
|
186
|
-
if (ts.isIdentifier(decl.name)
|
|
187
|
-
|
|
188
|
-
|
|
187
|
+
if (!ts.isIdentifier(decl.name) || !init)
|
|
188
|
+
continue;
|
|
189
|
+
if (ts.isArrowFunction(init) || ts.isFunctionExpression(init)) {
|
|
189
190
|
const name = decl.name.getText(sourceFile);
|
|
190
191
|
symbols.push({
|
|
191
192
|
name,
|
|
@@ -200,6 +201,25 @@ function createPlugin() {
|
|
|
200
201
|
...(doc && { docComment: doc }),
|
|
201
202
|
});
|
|
202
203
|
}
|
|
204
|
+
else if (ts.isClassExpression(init)) {
|
|
205
|
+
const name = decl.name.getText(sourceFile);
|
|
206
|
+
const prevClass = currentClass;
|
|
207
|
+
currentClass = name;
|
|
208
|
+
symbols.push({
|
|
209
|
+
name,
|
|
210
|
+
qualifiedName: name,
|
|
211
|
+
kind: "class",
|
|
212
|
+
filePath,
|
|
213
|
+
startLine: getLine(sourceFile, decl.getStart(sourceFile)),
|
|
214
|
+
endLine: getLine(sourceFile, decl.getEnd()),
|
|
215
|
+
signature: extractSignature(init, sourceFile),
|
|
216
|
+
exported,
|
|
217
|
+
dependencies: [],
|
|
218
|
+
...(doc && { docComment: doc }),
|
|
219
|
+
});
|
|
220
|
+
ts.forEachChild(init, visit);
|
|
221
|
+
currentClass = prevClass;
|
|
222
|
+
}
|
|
203
223
|
}
|
|
204
224
|
return;
|
|
205
225
|
}
|
|
@@ -236,10 +256,11 @@ function createPlugin() {
|
|
|
236
256
|
if (ts.isIdentifier(expr)) {
|
|
237
257
|
addEdge(from, expr.getText(), "calls");
|
|
238
258
|
}
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
259
|
+
}
|
|
260
|
+
if (ts.isNewExpression(node)) {
|
|
261
|
+
const expr = node.expression;
|
|
262
|
+
if (ts.isIdentifier(expr)) {
|
|
263
|
+
addEdge(from, expr.getText(), "calls");
|
|
243
264
|
}
|
|
244
265
|
}
|
|
245
266
|
ts.forEachChild(node, (n) => collectCalls(n, from));
|
|
@@ -248,29 +269,33 @@ function createPlugin() {
|
|
|
248
269
|
const fullPath = path.join(rootDir, filePath);
|
|
249
270
|
const sourceFile = ts.createSourceFile(fullPath, content, ts.ScriptTarget.Latest, true);
|
|
250
271
|
let currentClass = null;
|
|
251
|
-
function
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
? expr.getText()
|
|
262
|
-
:
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
: "implements";
|
|
268
|
-
addEdge(name, target, kind);
|
|
269
|
-
}
|
|
272
|
+
function visitClassNode(node, name) {
|
|
273
|
+
const prevClass = currentClass;
|
|
274
|
+
currentClass = name;
|
|
275
|
+
if (symbolSet.has(name)) {
|
|
276
|
+
for (const clause of node.heritageClauses ?? []) {
|
|
277
|
+
for (const type of clause.types) {
|
|
278
|
+
const expr = type.expression;
|
|
279
|
+
const target = ts.isIdentifier(expr)
|
|
280
|
+
? expr.getText()
|
|
281
|
+
: ts.isPropertyAccessExpression(expr)
|
|
282
|
+
? expr.expression.getText()
|
|
283
|
+
: expr.getText();
|
|
284
|
+
const kind = clause.token === ts.SyntaxKind.ExtendsKeyword
|
|
285
|
+
? "extends"
|
|
286
|
+
: "implements";
|
|
287
|
+
addEdge(name, target, kind);
|
|
270
288
|
}
|
|
271
289
|
}
|
|
272
|
-
|
|
273
|
-
|
|
290
|
+
}
|
|
291
|
+
ts.forEachChild(node, visit);
|
|
292
|
+
currentClass = prevClass;
|
|
293
|
+
}
|
|
294
|
+
function visit(node) {
|
|
295
|
+
if ((ts.isClassDeclaration(node) || ts.isClassExpression(node)) &&
|
|
296
|
+
node.name) {
|
|
297
|
+
const name = node.name.getText(sourceFile);
|
|
298
|
+
visitClassNode(node, name);
|
|
274
299
|
return;
|
|
275
300
|
}
|
|
276
301
|
if (ts.isConstructorDeclaration(node)) {
|
|
@@ -304,15 +329,19 @@ function createPlugin() {
|
|
|
304
329
|
if (ts.isVariableStatement(node)) {
|
|
305
330
|
for (const decl of node.declarationList.declarations) {
|
|
306
331
|
const init = decl.initializer;
|
|
307
|
-
if (ts.isIdentifier(decl.name)
|
|
308
|
-
|
|
309
|
-
|
|
332
|
+
if (!ts.isIdentifier(decl.name) || !init)
|
|
333
|
+
continue;
|
|
334
|
+
if (ts.isArrowFunction(init) || ts.isFunctionExpression(init)) {
|
|
310
335
|
const name = decl.name.getText(sourceFile);
|
|
311
336
|
if (symbolSet.has(name)) {
|
|
312
337
|
collectTypeRefs(decl, name);
|
|
313
338
|
collectCalls(decl, name);
|
|
314
339
|
}
|
|
315
340
|
}
|
|
341
|
+
else if (ts.isClassExpression(init)) {
|
|
342
|
+
const name = decl.name.getText(sourceFile);
|
|
343
|
+
visitClassNode(init, name);
|
|
344
|
+
}
|
|
316
345
|
}
|
|
317
346
|
return;
|
|
318
347
|
}
|
|
@@ -15,6 +15,7 @@ export class AgentBridge {
|
|
|
15
15
|
verbose;
|
|
16
16
|
listeners = new Set();
|
|
17
17
|
pinnedSymbols = new Set();
|
|
18
|
+
annotations = new Map();
|
|
18
19
|
constructor(broadcast, verbose) {
|
|
19
20
|
this.broadcast = broadcast;
|
|
20
21
|
this.verbose = verbose;
|
|
@@ -51,9 +52,15 @@ export class AgentBridge {
|
|
|
51
52
|
projectIndex = undefined;
|
|
52
53
|
}
|
|
53
54
|
const toolRegistry = createToolRegistry(config, projectIndex);
|
|
55
|
+
// Wrap tool registry execute to inject annotations into tool results
|
|
56
|
+
const originalExecute = toolRegistry.execute.bind(toolRegistry);
|
|
57
|
+
toolRegistry.execute = async (name, input) => {
|
|
58
|
+
const result = await originalExecute(name, input);
|
|
59
|
+
return this.appendAnnotationsToResult(name, input, result);
|
|
60
|
+
};
|
|
54
61
|
this.config = config;
|
|
55
62
|
this.projectIndex = projectIndex;
|
|
56
|
-
this.buildAgent = (session) => {
|
|
63
|
+
this.buildAgent = (session, onUiUpdate) => {
|
|
57
64
|
return new CodingAgent({
|
|
58
65
|
config,
|
|
59
66
|
modelClient,
|
|
@@ -63,9 +70,10 @@ export class AgentBridge {
|
|
|
63
70
|
...(projectIndex !== undefined
|
|
64
71
|
? { getCodeMap: (focusSymbols) => projectIndex.getCodeMap(undefined, focusSymbols) }
|
|
65
72
|
: {}),
|
|
66
|
-
onUiUpdate: (event) => {
|
|
73
|
+
onUiUpdate: onUiUpdate ?? ((event) => {
|
|
67
74
|
this.emit(event);
|
|
68
|
-
},
|
|
75
|
+
}),
|
|
76
|
+
getSystemPromptSuffix: () => this.buildAnnotationSuffix(),
|
|
69
77
|
});
|
|
70
78
|
};
|
|
71
79
|
this.agent = this.buildAgent();
|
|
@@ -118,13 +126,23 @@ export class AgentBridge {
|
|
|
118
126
|
}
|
|
119
127
|
// Session operations
|
|
120
128
|
async saveSess(label) {
|
|
121
|
-
|
|
129
|
+
const annotationsObj = this.annotations.size > 0
|
|
130
|
+
? Object.fromEntries(this.annotations)
|
|
131
|
+
: undefined;
|
|
132
|
+
return saveSession(this.agent.getSession(), label, annotationsObj);
|
|
122
133
|
}
|
|
123
134
|
async loadSess(label) {
|
|
124
135
|
const result = (await loadSessionByLabel(label)) ?? (await loadSession(label));
|
|
125
136
|
if (!result)
|
|
126
137
|
return null;
|
|
127
138
|
this.agent = this.buildAgent(result.session);
|
|
139
|
+
// Restore annotations from saved session
|
|
140
|
+
this.annotations.clear();
|
|
141
|
+
if (result.annotations) {
|
|
142
|
+
for (const [name, notes] of Object.entries(result.annotations)) {
|
|
143
|
+
this.annotations.set(name, notes);
|
|
144
|
+
}
|
|
145
|
+
}
|
|
128
146
|
return result;
|
|
129
147
|
}
|
|
130
148
|
async listSess() {
|
|
@@ -230,4 +248,109 @@ export class AgentBridge {
|
|
|
230
248
|
this.pinnedSymbols.delete(sym.qualifiedName);
|
|
231
249
|
return true;
|
|
232
250
|
}
|
|
251
|
+
// ── Annotations ──
|
|
252
|
+
getAnnotations() {
|
|
253
|
+
this.evictStaleAnnotations();
|
|
254
|
+
return Object.fromEntries(this.annotations);
|
|
255
|
+
}
|
|
256
|
+
getAnnotationsForSymbol(name) {
|
|
257
|
+
return this.annotations.get(name) ?? [];
|
|
258
|
+
}
|
|
259
|
+
addAnnotation(name, text) {
|
|
260
|
+
if (!this.projectIndex)
|
|
261
|
+
return false;
|
|
262
|
+
const sym = this.projectIndex.getSymbol(name);
|
|
263
|
+
if (!sym)
|
|
264
|
+
return false;
|
|
265
|
+
const trimmed = text.slice(0, 500).trim();
|
|
266
|
+
if (trimmed.length === 0)
|
|
267
|
+
return false;
|
|
268
|
+
const key = sym.qualifiedName;
|
|
269
|
+
const existing = this.annotations.get(key) ?? [];
|
|
270
|
+
existing.push(trimmed);
|
|
271
|
+
this.annotations.set(key, existing);
|
|
272
|
+
return true;
|
|
273
|
+
}
|
|
274
|
+
removeAnnotation(name, index) {
|
|
275
|
+
const notes = this.annotations.get(name);
|
|
276
|
+
if (!notes || index < 0 || index >= notes.length)
|
|
277
|
+
return false;
|
|
278
|
+
notes.splice(index, 1);
|
|
279
|
+
if (notes.length === 0) {
|
|
280
|
+
this.annotations.delete(name);
|
|
281
|
+
}
|
|
282
|
+
return true;
|
|
283
|
+
}
|
|
284
|
+
clearAnnotations(name) {
|
|
285
|
+
this.annotations.delete(name);
|
|
286
|
+
}
|
|
287
|
+
evictStaleAnnotations() {
|
|
288
|
+
if (!this.projectIndex)
|
|
289
|
+
return;
|
|
290
|
+
for (const name of [...this.annotations.keys()]) {
|
|
291
|
+
if (!this.projectIndex.getSymbol(name)) {
|
|
292
|
+
this.annotations.delete(name);
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
buildAnnotationSuffix() {
|
|
297
|
+
this.evictStaleAnnotations();
|
|
298
|
+
if (this.annotations.size === 0)
|
|
299
|
+
return undefined;
|
|
300
|
+
return `[Annotated symbols: ${[...this.annotations.keys()].join(", ")}]`;
|
|
301
|
+
}
|
|
302
|
+
appendAnnotationsToResult(toolName, input, result) {
|
|
303
|
+
if (this.annotations.size === 0)
|
|
304
|
+
return result;
|
|
305
|
+
const inp = input;
|
|
306
|
+
if (toolName === "read_symbol" || toolName === "find_references" || toolName === "get_dependencies") {
|
|
307
|
+
const symName = (inp.name ?? inp.symbol ?? inp.query);
|
|
308
|
+
if (!symName)
|
|
309
|
+
return result;
|
|
310
|
+
// Try direct match, then resolve via index
|
|
311
|
+
let notes = this.annotations.get(symName);
|
|
312
|
+
if (!notes && this.projectIndex) {
|
|
313
|
+
const sym = this.projectIndex.getSymbol(symName);
|
|
314
|
+
if (sym)
|
|
315
|
+
notes = this.annotations.get(sym.qualifiedName);
|
|
316
|
+
}
|
|
317
|
+
if (notes && notes.length > 0) {
|
|
318
|
+
return result + `\n[User annotation: ${notes.join("; ")}]`;
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
if (toolName === "read_file") {
|
|
322
|
+
const filePath = inp.path;
|
|
323
|
+
if (!filePath)
|
|
324
|
+
return result;
|
|
325
|
+
const fileAnnotations = [];
|
|
326
|
+
for (const [name, notes] of this.annotations) {
|
|
327
|
+
if (!this.projectIndex)
|
|
328
|
+
continue;
|
|
329
|
+
const sym = this.projectIndex.getSymbol(name);
|
|
330
|
+
if (sym && (sym.filePath === filePath || filePath.endsWith(sym.filePath))) {
|
|
331
|
+
fileAnnotations.push(`- ${sym.name}: ${notes.join("; ")}`);
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
if (fileAnnotations.length > 0) {
|
|
335
|
+
return result + `\n[User annotations for symbols in this file:]\n${fileAnnotations.join("\n")}`;
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
return result;
|
|
339
|
+
}
|
|
340
|
+
// ── Explain ──
|
|
341
|
+
async explainSymbol(name, onEvent, signal) {
|
|
342
|
+
if (!this.projectIndex)
|
|
343
|
+
throw new Error("No project index");
|
|
344
|
+
const sym = this.projectIndex.getSymbol(name);
|
|
345
|
+
if (!sym)
|
|
346
|
+
throw new Error(`Symbol "${name}" not found`);
|
|
347
|
+
const explainAgent = this.buildAgent(undefined, onEvent);
|
|
348
|
+
const prompt = `Explain "${sym.name}" (${sym.kind} in ${sym.filePath}).
|
|
349
|
+
Use read_symbol, get_dependencies, find_references to gather context.
|
|
350
|
+
Explain what it does, how it works, what depends on it, and key design decisions.
|
|
351
|
+
Be concise but thorough.`;
|
|
352
|
+
const opts = signal ? { signal } : undefined;
|
|
353
|
+
const result = await explainAgent.runTurn(prompt, opts);
|
|
354
|
+
return result.text;
|
|
355
|
+
}
|
|
233
356
|
}
|
package/dist/src/serve/server.js
CHANGED
|
@@ -7,10 +7,12 @@ import { createWebSocketServer } from "./websocket.js";
|
|
|
7
7
|
import { handleChatCompletions, handleModels } from "./openai-compat.js";
|
|
8
8
|
import { formatConfigForDisplay } from "../agent/config.js";
|
|
9
9
|
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
10
|
-
// Resolve web dir:
|
|
10
|
+
// Resolve web dir: always serve from dist/src/web (built by scripts/build-web.mjs)
|
|
11
|
+
// In dev (tsx): __dirname = src/serve → go up to project root, then dist/src/web
|
|
12
|
+
// In prod (dist): __dirname = dist/src/serve → sibling dir dist/src/web
|
|
11
13
|
const webDir = __dirname.includes(`${path.sep}dist${path.sep}`)
|
|
12
|
-
? path.resolve(__dirname, "
|
|
13
|
-
: path.resolve(__dirname, "
|
|
14
|
+
? path.resolve(__dirname, "../web")
|
|
15
|
+
: path.resolve(__dirname, "../../dist/src/web");
|
|
14
16
|
const MIME_TYPES = {
|
|
15
17
|
".html": "text/html",
|
|
16
18
|
".css": "text/css",
|
|
@@ -133,6 +135,26 @@ export function createRequestHandler(bridge) {
|
|
|
133
135
|
sendJson(res, 200, { symbol: name, references: result });
|
|
134
136
|
return;
|
|
135
137
|
}
|
|
138
|
+
if (pathname.startsWith("/api/symbols/") && pathname.endsWith("/source") && method === "GET") {
|
|
139
|
+
const name = decodeURIComponent(pathname.slice("/api/symbols/".length, -"/source".length));
|
|
140
|
+
const sym = bridge.getSymbol(name);
|
|
141
|
+
if (!sym) {
|
|
142
|
+
sendJson(res, 404, { error: `Symbol "${name}" not found` });
|
|
143
|
+
return;
|
|
144
|
+
}
|
|
145
|
+
try {
|
|
146
|
+
const fileContent = await readFile(path.resolve(config.workspaceRoot, sym.filePath), "utf8");
|
|
147
|
+
const lines = fileContent.split(/\r?\n/);
|
|
148
|
+
const start = Math.max(0, sym.startLine - 1);
|
|
149
|
+
const end = Math.min(lines.length, sym.endLine);
|
|
150
|
+
const source = lines.slice(start, end).join("\n");
|
|
151
|
+
sendJson(res, 200, { symbol: name, filePath: sym.filePath, startLine: sym.startLine, endLine: sym.endLine, source });
|
|
152
|
+
}
|
|
153
|
+
catch {
|
|
154
|
+
sendJson(res, 500, { error: `Could not read file: ${sym.filePath}` });
|
|
155
|
+
}
|
|
156
|
+
return;
|
|
157
|
+
}
|
|
136
158
|
if (pathname === "/api/code-map" && method === "GET") {
|
|
137
159
|
const budgetParam = url.searchParams.get("budget");
|
|
138
160
|
const budget = budgetParam ? Number(budgetParam) : undefined;
|
|
@@ -180,6 +202,82 @@ export function createRequestHandler(bridge) {
|
|
|
180
202
|
sendJson(res, 400, { error: `Unknown action "${body.action}". Use "pin" or "unpin".` });
|
|
181
203
|
return;
|
|
182
204
|
}
|
|
205
|
+
// ── Annotations API ──
|
|
206
|
+
if (pathname === "/api/annotations" && method === "GET") {
|
|
207
|
+
sendJson(res, 200, { annotations: bridge.getAnnotations() });
|
|
208
|
+
return;
|
|
209
|
+
}
|
|
210
|
+
if (pathname.startsWith("/api/symbols/") && pathname.endsWith("/annotations") && method === "GET") {
|
|
211
|
+
const name = decodeURIComponent(pathname.slice("/api/symbols/".length, -"/annotations".length));
|
|
212
|
+
const notes = bridge.getAnnotationsForSymbol(name);
|
|
213
|
+
sendJson(res, 200, { symbol: name, annotations: notes });
|
|
214
|
+
return;
|
|
215
|
+
}
|
|
216
|
+
if (pathname.startsWith("/api/symbols/") && pathname.endsWith("/annotations") && method === "POST") {
|
|
217
|
+
const name = decodeURIComponent(pathname.slice("/api/symbols/".length, -"/annotations".length));
|
|
218
|
+
const body = JSON.parse(await readBody(req));
|
|
219
|
+
if (!body.text) {
|
|
220
|
+
sendJson(res, 400, { error: "text is required" });
|
|
221
|
+
return;
|
|
222
|
+
}
|
|
223
|
+
const ok = bridge.addAnnotation(name, body.text);
|
|
224
|
+
if (!ok) {
|
|
225
|
+
sendJson(res, 404, { error: `Symbol "${name}" not found or text empty` });
|
|
226
|
+
return;
|
|
227
|
+
}
|
|
228
|
+
sendJson(res, 200, { symbol: name, annotations: bridge.getAnnotationsForSymbol(name) });
|
|
229
|
+
return;
|
|
230
|
+
}
|
|
231
|
+
// DELETE /api/symbols/:name/annotations/:index
|
|
232
|
+
{
|
|
233
|
+
const annoDeleteMatch = pathname.match(/^\/api\/symbols\/(.+)\/annotations\/(\d+)$/);
|
|
234
|
+
if (annoDeleteMatch && method === "DELETE") {
|
|
235
|
+
const name = decodeURIComponent(annoDeleteMatch[1]);
|
|
236
|
+
const index = Number(annoDeleteMatch[2]);
|
|
237
|
+
const ok = bridge.removeAnnotation(name, index);
|
|
238
|
+
if (!ok) {
|
|
239
|
+
sendJson(res, 404, { error: "Annotation not found" });
|
|
240
|
+
return;
|
|
241
|
+
}
|
|
242
|
+
sendJson(res, 200, { symbol: name, annotations: bridge.getAnnotationsForSymbol(name) });
|
|
243
|
+
return;
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
if (pathname.startsWith("/api/symbols/") && pathname.endsWith("/annotations") && method === "DELETE") {
|
|
247
|
+
const name = decodeURIComponent(pathname.slice("/api/symbols/".length, -"/annotations".length));
|
|
248
|
+
bridge.clearAnnotations(name);
|
|
249
|
+
sendJson(res, 200, { symbol: name, annotations: [] });
|
|
250
|
+
return;
|
|
251
|
+
}
|
|
252
|
+
// ── Explain SSE ──
|
|
253
|
+
if (pathname.startsWith("/api/symbols/") && pathname.endsWith("/explain") && method === "GET") {
|
|
254
|
+
const name = decodeURIComponent(pathname.slice("/api/symbols/".length, -"/explain".length));
|
|
255
|
+
res.writeHead(200, {
|
|
256
|
+
"Content-Type": "text/event-stream",
|
|
257
|
+
"Cache-Control": "no-cache",
|
|
258
|
+
Connection: "keep-alive",
|
|
259
|
+
});
|
|
260
|
+
const abortController = new AbortController();
|
|
261
|
+
req.on("close", () => abortController.abort());
|
|
262
|
+
try {
|
|
263
|
+
await bridge.explainSymbol(name, (event) => {
|
|
264
|
+
if (!res.writableEnded) {
|
|
265
|
+
res.write(`data: ${JSON.stringify(event)}\n\n`);
|
|
266
|
+
}
|
|
267
|
+
}, abortController.signal);
|
|
268
|
+
}
|
|
269
|
+
catch (error) {
|
|
270
|
+
if (!res.writableEnded) {
|
|
271
|
+
const msg = error instanceof Error ? error.message : "Unknown error";
|
|
272
|
+
res.write(`data: ${JSON.stringify({ type: "error", message: msg })}\n\n`);
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
if (!res.writableEnded) {
|
|
276
|
+
res.write("data: [DONE]\n\n");
|
|
277
|
+
res.end();
|
|
278
|
+
}
|
|
279
|
+
return;
|
|
280
|
+
}
|
|
183
281
|
if (pathname === "/api/chat" && method === "POST") {
|
|
184
282
|
const body = JSON.parse(await readBody(req));
|
|
185
283
|
if (!body.message) {
|
|
@@ -7,7 +7,7 @@ let sessionsDir = path.join(os.homedir(), ".minicode", "sessions");
|
|
|
7
7
|
export function setSessionsDir(dir) {
|
|
8
8
|
sessionsDir = dir;
|
|
9
9
|
}
|
|
10
|
-
export async function saveSession(session, label) {
|
|
10
|
+
export async function saveSession(session, label, annotations) {
|
|
11
11
|
await mkdir(sessionsDir, { recursive: true });
|
|
12
12
|
const savedAt = new Date().toISOString();
|
|
13
13
|
const snapshot = session.toJSON();
|
|
@@ -18,6 +18,7 @@ export async function saveSession(session, label) {
|
|
|
18
18
|
label: resolvedLabel,
|
|
19
19
|
savedAt,
|
|
20
20
|
session: snapshot,
|
|
21
|
+
...(annotations && Object.keys(annotations).length > 0 ? { annotations } : {}),
|
|
21
22
|
};
|
|
22
23
|
const filePath = path.join(sessionsDir, `${snapshot.id}.json`);
|
|
23
24
|
await writeFile(filePath, JSON.stringify(data, null, 2), "utf8");
|
|
@@ -67,6 +68,7 @@ export async function loadSession(sessionId) {
|
|
|
67
68
|
return {
|
|
68
69
|
session: Session.fromJSON(data.session),
|
|
69
70
|
label: data.label,
|
|
71
|
+
...(data.annotations ? { annotations: data.annotations } : {}),
|
|
70
72
|
};
|
|
71
73
|
}
|
|
72
74
|
catch {
|
package/dist/src/ui/cli-ink.js
CHANGED
|
@@ -90,6 +90,7 @@ export async function runInkCli(verbose, initialTask) {
|
|
|
90
90
|
...(verbose
|
|
91
91
|
? {
|
|
92
92
|
onProgress: (msg) => store.addItem({ type: "system", content: msg }),
|
|
93
|
+
onVerbose: (msg) => store.addItem({ type: "system", content: msg }),
|
|
93
94
|
}
|
|
94
95
|
: {}),
|
|
95
96
|
onUiUpdate: createUiUpdateHandler(),
|