@sean.holung/minicode 0.2.2 → 0.2.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +20 -12
- package/dist/src/agent/config.js +14 -2
- package/dist/src/cli/args.js +31 -0
- package/dist/src/index.js +21 -2
- package/dist/src/indexer/code-map.js +52 -5
- package/dist/src/indexer/focus-tracker.js +63 -0
- package/dist/src/indexer/project-index.js +2 -2
- package/dist/src/serve/agent-bridge.js +233 -0
- package/dist/src/serve/openai-compat.js +144 -0
- package/dist/src/serve/server.js +251 -0
- package/dist/src/serve/types.js +2 -0
- package/dist/src/serve/websocket.js +28 -0
- package/dist/src/ui/cli-ink.js +22 -2
- package/dist/src/web/app.js +350 -0
- package/dist/src/web/index.html +49 -0
- package/dist/src/web/style.css +422 -0
- package/dist/tests/agent.test.js +62 -0
- package/dist/tests/cli-args.test.js +4 -2
- package/dist/tests/serve.integration.test.js +534 -0
- package/node_modules/@minicode/agent-sdk/dist/src/agent/agent.d.ts +30 -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 +212 -8
- package/node_modules/@minicode/agent-sdk/dist/src/agent/agent.js.map +1 -1
- package/node_modules/@minicode/agent-sdk/dist/src/agent/types.d.ts +10 -0
- package/node_modules/@minicode/agent-sdk/dist/src/agent/types.d.ts.map +1 -1
- package/node_modules/@minicode/agent-sdk/dist/src/index.d.ts +1 -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/model/client.d.ts.map +1 -1
- package/node_modules/@minicode/agent-sdk/dist/src/model/client.js +2 -0
- package/node_modules/@minicode/agent-sdk/dist/src/model/client.js.map +1 -1
- package/node_modules/@minicode/agent-sdk/dist/src/session/session.d.ts +51 -1
- package/node_modules/@minicode/agent-sdk/dist/src/session/session.d.ts.map +1 -1
- package/node_modules/@minicode/agent-sdk/dist/src/session/session.js +210 -2
- package/node_modules/@minicode/agent-sdk/dist/src/session/session.js.map +1 -1
- package/node_modules/@minicode/agent-sdk/dist/tests/session.test.js +75 -0
- package/node_modules/@minicode/agent-sdk/dist/tests/session.test.js.map +1 -1
- package/node_modules/@minicode/agent-sdk/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +5 -3
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
import { randomUUID } from "node:crypto";
|
|
2
|
+
function sendJson(res, status, body) {
|
|
3
|
+
res.writeHead(status, { "Content-Type": "application/json" });
|
|
4
|
+
res.end(JSON.stringify(body));
|
|
5
|
+
}
|
|
6
|
+
function readBody(req) {
|
|
7
|
+
return new Promise((resolve, reject) => {
|
|
8
|
+
const chunks = [];
|
|
9
|
+
req.on("data", (chunk) => chunks.push(chunk));
|
|
10
|
+
req.on("end", () => resolve(Buffer.concat(chunks).toString()));
|
|
11
|
+
req.on("error", reject);
|
|
12
|
+
});
|
|
13
|
+
}
|
|
14
|
+
export function handleModels(_req, res) {
|
|
15
|
+
sendJson(res, 200, {
|
|
16
|
+
object: "list",
|
|
17
|
+
data: [
|
|
18
|
+
{
|
|
19
|
+
id: "minicode-agent",
|
|
20
|
+
object: "model",
|
|
21
|
+
created: Math.floor(Date.now() / 1000),
|
|
22
|
+
owned_by: "minicode",
|
|
23
|
+
},
|
|
24
|
+
],
|
|
25
|
+
});
|
|
26
|
+
}
|
|
27
|
+
export async function handleChatCompletions(req, res, bridge) {
|
|
28
|
+
let body;
|
|
29
|
+
try {
|
|
30
|
+
const raw = await readBody(req);
|
|
31
|
+
body = JSON.parse(raw);
|
|
32
|
+
}
|
|
33
|
+
catch {
|
|
34
|
+
sendJson(res, 400, { error: { message: "Invalid JSON body", type: "invalid_request_error" } });
|
|
35
|
+
return;
|
|
36
|
+
}
|
|
37
|
+
const messages = body.messages;
|
|
38
|
+
if (!messages || messages.length === 0) {
|
|
39
|
+
sendJson(res, 400, { error: { message: "messages array is required", type: "invalid_request_error" } });
|
|
40
|
+
return;
|
|
41
|
+
}
|
|
42
|
+
// Extract last user message
|
|
43
|
+
let userMessage;
|
|
44
|
+
for (let i = messages.length - 1; i >= 0; i--) {
|
|
45
|
+
if (messages[i].role === "user") {
|
|
46
|
+
userMessage = messages[i].content;
|
|
47
|
+
break;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
if (!userMessage) {
|
|
51
|
+
sendJson(res, 400, { error: { message: "No user message found", type: "invalid_request_error" } });
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
if (bridge.isBusy()) {
|
|
55
|
+
sendJson(res, 429, { error: { message: "Agent is busy with another request. Try again later.", type: "rate_limit_error" } });
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
58
|
+
const completionId = `chatcmpl-${randomUUID()}`;
|
|
59
|
+
const created = Math.floor(Date.now() / 1000);
|
|
60
|
+
if (body.stream) {
|
|
61
|
+
await handleStreaming(res, bridge, userMessage, completionId, created);
|
|
62
|
+
}
|
|
63
|
+
else {
|
|
64
|
+
await handleNonStreaming(res, bridge, userMessage, completionId, created);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
async function handleNonStreaming(res, bridge, message, completionId, created) {
|
|
68
|
+
try {
|
|
69
|
+
const result = await bridge.runTurn(message);
|
|
70
|
+
sendJson(res, 200, {
|
|
71
|
+
id: completionId,
|
|
72
|
+
object: "chat.completion",
|
|
73
|
+
created,
|
|
74
|
+
model: "minicode-agent",
|
|
75
|
+
choices: [
|
|
76
|
+
{
|
|
77
|
+
index: 0,
|
|
78
|
+
message: { role: "assistant", content: result.text },
|
|
79
|
+
finish_reason: "stop",
|
|
80
|
+
},
|
|
81
|
+
],
|
|
82
|
+
usage: result.usage
|
|
83
|
+
? {
|
|
84
|
+
prompt_tokens: result.usage.inputTokens,
|
|
85
|
+
completion_tokens: result.usage.outputTokens,
|
|
86
|
+
total_tokens: result.usage.inputTokens + result.usage.outputTokens,
|
|
87
|
+
}
|
|
88
|
+
: undefined,
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
catch (error) {
|
|
92
|
+
const msg = error instanceof Error ? error.message : "Unknown error";
|
|
93
|
+
sendJson(res, 500, { error: { message: msg, type: "server_error" } });
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
async function handleStreaming(res, bridge, message, completionId, created) {
|
|
97
|
+
res.writeHead(200, {
|
|
98
|
+
"Content-Type": "text/event-stream",
|
|
99
|
+
"Cache-Control": "no-cache",
|
|
100
|
+
Connection: "keep-alive",
|
|
101
|
+
});
|
|
102
|
+
function sendSSE(data) {
|
|
103
|
+
res.write(`data: ${JSON.stringify(data)}\n\n`);
|
|
104
|
+
}
|
|
105
|
+
// Send initial role chunk
|
|
106
|
+
sendSSE({
|
|
107
|
+
id: completionId,
|
|
108
|
+
object: "chat.completion.chunk",
|
|
109
|
+
created,
|
|
110
|
+
model: "minicode-agent",
|
|
111
|
+
choices: [{ index: 0, delta: { role: "assistant" }, finish_reason: null }],
|
|
112
|
+
});
|
|
113
|
+
const listener = (msg) => {
|
|
114
|
+
if (msg.type === "streaming_chunk" && msg.content) {
|
|
115
|
+
sendSSE({
|
|
116
|
+
id: completionId,
|
|
117
|
+
object: "chat.completion.chunk",
|
|
118
|
+
created,
|
|
119
|
+
model: "minicode-agent",
|
|
120
|
+
choices: [{ index: 0, delta: { content: msg.content }, finish_reason: null }],
|
|
121
|
+
});
|
|
122
|
+
}
|
|
123
|
+
};
|
|
124
|
+
bridge.addListener(listener);
|
|
125
|
+
try {
|
|
126
|
+
await bridge.runTurn(message);
|
|
127
|
+
sendSSE({
|
|
128
|
+
id: completionId,
|
|
129
|
+
object: "chat.completion.chunk",
|
|
130
|
+
created,
|
|
131
|
+
model: "minicode-agent",
|
|
132
|
+
choices: [{ index: 0, delta: {}, finish_reason: "stop" }],
|
|
133
|
+
});
|
|
134
|
+
res.write("data: [DONE]\n\n");
|
|
135
|
+
}
|
|
136
|
+
catch (error) {
|
|
137
|
+
const msg = error instanceof Error ? error.message : "Unknown error";
|
|
138
|
+
sendSSE({ error: { message: msg, type: "server_error" } });
|
|
139
|
+
}
|
|
140
|
+
finally {
|
|
141
|
+
bridge.removeListener(listener);
|
|
142
|
+
res.end();
|
|
143
|
+
}
|
|
144
|
+
}
|
|
@@ -0,0 +1,251 @@
|
|
|
1
|
+
import { createServer } from "node:http";
|
|
2
|
+
import { readFile } from "node:fs/promises";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import { fileURLToPath } from "node:url";
|
|
5
|
+
import { AgentBridge } from "./agent-bridge.js";
|
|
6
|
+
import { createWebSocketServer } from "./websocket.js";
|
|
7
|
+
import { handleChatCompletions, handleModels } from "./openai-compat.js";
|
|
8
|
+
import { formatConfigForDisplay } from "../agent/config.js";
|
|
9
|
+
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
10
|
+
// Resolve web dir: works in both dev (src/serve/) and dist (dist/src/serve/)
|
|
11
|
+
const webDir = __dirname.includes(`${path.sep}dist${path.sep}`)
|
|
12
|
+
? path.resolve(__dirname, "../../src/web")
|
|
13
|
+
: path.resolve(__dirname, "../web");
|
|
14
|
+
const MIME_TYPES = {
|
|
15
|
+
".html": "text/html",
|
|
16
|
+
".css": "text/css",
|
|
17
|
+
".js": "application/javascript",
|
|
18
|
+
".json": "application/json",
|
|
19
|
+
};
|
|
20
|
+
function sendJson(res, status, body) {
|
|
21
|
+
res.writeHead(status, { "Content-Type": "application/json" });
|
|
22
|
+
res.end(JSON.stringify(body));
|
|
23
|
+
}
|
|
24
|
+
function readBody(req) {
|
|
25
|
+
return new Promise((resolve, reject) => {
|
|
26
|
+
const chunks = [];
|
|
27
|
+
req.on("data", (chunk) => chunks.push(chunk));
|
|
28
|
+
req.on("end", () => resolve(Buffer.concat(chunks).toString()));
|
|
29
|
+
req.on("error", reject);
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
async function serveStatic(res, urlPath) {
|
|
33
|
+
const fileName = urlPath === "/" ? "index.html" : urlPath.slice(1);
|
|
34
|
+
const filePath = path.join(webDir, fileName);
|
|
35
|
+
// Prevent path traversal
|
|
36
|
+
if (!filePath.startsWith(webDir)) {
|
|
37
|
+
res.writeHead(403);
|
|
38
|
+
res.end("Forbidden");
|
|
39
|
+
return;
|
|
40
|
+
}
|
|
41
|
+
try {
|
|
42
|
+
const content = await readFile(filePath);
|
|
43
|
+
const ext = path.extname(filePath);
|
|
44
|
+
const contentType = MIME_TYPES[ext] ?? "application/octet-stream";
|
|
45
|
+
res.writeHead(200, { "Content-Type": contentType });
|
|
46
|
+
res.end(content);
|
|
47
|
+
}
|
|
48
|
+
catch {
|
|
49
|
+
res.writeHead(404);
|
|
50
|
+
res.end("Not Found");
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
/** Create the HTTP request handler. Exported for testing. */
|
|
54
|
+
export function createRequestHandler(bridge) {
|
|
55
|
+
const config = bridge.getConfig();
|
|
56
|
+
return (req, res) => {
|
|
57
|
+
const url = new URL(req.url ?? "/", `http://${req.headers.host ?? "localhost"}`);
|
|
58
|
+
const method = req.method ?? "GET";
|
|
59
|
+
const pathname = url.pathname;
|
|
60
|
+
const handle = async () => {
|
|
61
|
+
// OpenAI-compatible routes
|
|
62
|
+
if (pathname === "/v1/models" && method === "GET") {
|
|
63
|
+
handleModels(req, res);
|
|
64
|
+
return;
|
|
65
|
+
}
|
|
66
|
+
if (pathname === "/v1/chat/completions" && method === "POST") {
|
|
67
|
+
await handleChatCompletions(req, res, bridge);
|
|
68
|
+
return;
|
|
69
|
+
}
|
|
70
|
+
// Minicode REST API
|
|
71
|
+
if (pathname === "/api/status" && method === "GET") {
|
|
72
|
+
sendJson(res, 200, {
|
|
73
|
+
status: bridge.isBusy() ? "busy" : "ready",
|
|
74
|
+
workspace: config.workspaceRoot,
|
|
75
|
+
model: config.model,
|
|
76
|
+
provider: config.modelProvider,
|
|
77
|
+
});
|
|
78
|
+
return;
|
|
79
|
+
}
|
|
80
|
+
if (pathname === "/api/config" && method === "GET") {
|
|
81
|
+
sendJson(res, 200, { config: formatConfigForDisplay(config) });
|
|
82
|
+
return;
|
|
83
|
+
}
|
|
84
|
+
if (pathname === "/api/sessions" && method === "GET") {
|
|
85
|
+
const sessions = await bridge.listSess();
|
|
86
|
+
sendJson(res, 200, { sessions });
|
|
87
|
+
return;
|
|
88
|
+
}
|
|
89
|
+
if (pathname === "/api/sessions/save" && method === "POST") {
|
|
90
|
+
const body = JSON.parse(await readBody(req));
|
|
91
|
+
const meta = await bridge.saveSess(body.label);
|
|
92
|
+
sendJson(res, 200, meta);
|
|
93
|
+
return;
|
|
94
|
+
}
|
|
95
|
+
if (pathname === "/api/sessions/load" && method === "POST") {
|
|
96
|
+
const body = JSON.parse(await readBody(req));
|
|
97
|
+
const result = await bridge.loadSess(body.label);
|
|
98
|
+
if (!result) {
|
|
99
|
+
sendJson(res, 404, { error: "Session not found" });
|
|
100
|
+
return;
|
|
101
|
+
}
|
|
102
|
+
sendJson(res, 200, { label: result.label });
|
|
103
|
+
return;
|
|
104
|
+
}
|
|
105
|
+
// ── Graph / Index API ──
|
|
106
|
+
if (pathname === "/api/symbols" && method === "GET") {
|
|
107
|
+
if (!bridge.hasIndex()) {
|
|
108
|
+
sendJson(res, 404, { error: "No project index available" });
|
|
109
|
+
return;
|
|
110
|
+
}
|
|
111
|
+
sendJson(res, 200, { symbols: bridge.getSymbols() });
|
|
112
|
+
return;
|
|
113
|
+
}
|
|
114
|
+
if (pathname.startsWith("/api/symbols/") && pathname.endsWith("/dependencies") && method === "GET") {
|
|
115
|
+
const name = decodeURIComponent(pathname.slice("/api/symbols/".length, -"/dependencies".length));
|
|
116
|
+
const depthParam = url.searchParams.get("depth");
|
|
117
|
+
const depth = depthParam ? Number(depthParam) : undefined;
|
|
118
|
+
const result = bridge.getDependencies(name, depth);
|
|
119
|
+
if (!result) {
|
|
120
|
+
sendJson(res, 404, { error: `Symbol "${name}" not found` });
|
|
121
|
+
return;
|
|
122
|
+
}
|
|
123
|
+
sendJson(res, 200, { symbol: name, dependencies: result });
|
|
124
|
+
return;
|
|
125
|
+
}
|
|
126
|
+
if (pathname.startsWith("/api/symbols/") && pathname.endsWith("/references") && method === "GET") {
|
|
127
|
+
const name = decodeURIComponent(pathname.slice("/api/symbols/".length, -"/references".length));
|
|
128
|
+
const result = bridge.getReferences(name);
|
|
129
|
+
if (!result) {
|
|
130
|
+
sendJson(res, 404, { error: `Symbol "${name}" not found` });
|
|
131
|
+
return;
|
|
132
|
+
}
|
|
133
|
+
sendJson(res, 200, { symbol: name, references: result });
|
|
134
|
+
return;
|
|
135
|
+
}
|
|
136
|
+
if (pathname === "/api/code-map" && method === "GET") {
|
|
137
|
+
const budgetParam = url.searchParams.get("budget");
|
|
138
|
+
const budget = budgetParam ? Number(budgetParam) : undefined;
|
|
139
|
+
const result = bridge.getCodeMap(budget);
|
|
140
|
+
if (!result) {
|
|
141
|
+
sendJson(res, 404, { error: "No project index available" });
|
|
142
|
+
return;
|
|
143
|
+
}
|
|
144
|
+
sendJson(res, 200, result);
|
|
145
|
+
return;
|
|
146
|
+
}
|
|
147
|
+
if (pathname === "/api/graph" && method === "GET") {
|
|
148
|
+
const result = bridge.getGraph();
|
|
149
|
+
if (!result) {
|
|
150
|
+
sendJson(res, 404, { error: "No project index available" });
|
|
151
|
+
return;
|
|
152
|
+
}
|
|
153
|
+
sendJson(res, 200, result);
|
|
154
|
+
return;
|
|
155
|
+
}
|
|
156
|
+
if (pathname === "/api/focus" && method === "GET") {
|
|
157
|
+
sendJson(res, 200, { pinned: bridge.getPinnedSymbols() });
|
|
158
|
+
return;
|
|
159
|
+
}
|
|
160
|
+
if (pathname === "/api/focus" && method === "POST") {
|
|
161
|
+
const body = JSON.parse(await readBody(req));
|
|
162
|
+
if (!body.symbol || !body.action) {
|
|
163
|
+
sendJson(res, 400, { error: "action and symbol are required" });
|
|
164
|
+
return;
|
|
165
|
+
}
|
|
166
|
+
if (body.action === "pin") {
|
|
167
|
+
const ok = bridge.pinSymbol(body.symbol);
|
|
168
|
+
if (!ok) {
|
|
169
|
+
sendJson(res, 404, { error: `Symbol "${body.symbol}" not found` });
|
|
170
|
+
return;
|
|
171
|
+
}
|
|
172
|
+
sendJson(res, 200, { pinned: bridge.getPinnedSymbols() });
|
|
173
|
+
return;
|
|
174
|
+
}
|
|
175
|
+
if (body.action === "unpin") {
|
|
176
|
+
bridge.unpinSymbol(body.symbol);
|
|
177
|
+
sendJson(res, 200, { pinned: bridge.getPinnedSymbols() });
|
|
178
|
+
return;
|
|
179
|
+
}
|
|
180
|
+
sendJson(res, 400, { error: `Unknown action "${body.action}". Use "pin" or "unpin".` });
|
|
181
|
+
return;
|
|
182
|
+
}
|
|
183
|
+
if (pathname === "/api/chat" && method === "POST") {
|
|
184
|
+
const body = JSON.parse(await readBody(req));
|
|
185
|
+
if (!body.message) {
|
|
186
|
+
sendJson(res, 400, { error: "message is required" });
|
|
187
|
+
return;
|
|
188
|
+
}
|
|
189
|
+
if (bridge.isBusy()) {
|
|
190
|
+
sendJson(res, 429, { error: "Agent is busy" });
|
|
191
|
+
return;
|
|
192
|
+
}
|
|
193
|
+
try {
|
|
194
|
+
const result = await bridge.runTurn(body.message);
|
|
195
|
+
sendJson(res, 200, { text: result.text, usage: result.usage });
|
|
196
|
+
}
|
|
197
|
+
catch (error) {
|
|
198
|
+
const msg = error instanceof Error ? error.message : "Unknown error";
|
|
199
|
+
sendJson(res, 500, { error: msg });
|
|
200
|
+
}
|
|
201
|
+
return;
|
|
202
|
+
}
|
|
203
|
+
// Static files
|
|
204
|
+
await serveStatic(res, pathname);
|
|
205
|
+
};
|
|
206
|
+
handle().catch((error) => {
|
|
207
|
+
const msg = error instanceof Error ? error.message : "Unknown error";
|
|
208
|
+
sendJson(res, 500, { error: msg });
|
|
209
|
+
});
|
|
210
|
+
};
|
|
211
|
+
}
|
|
212
|
+
export async function runServe(verbose, port) {
|
|
213
|
+
console.log("Initializing agent...");
|
|
214
|
+
// Set up broadcast plumbing
|
|
215
|
+
let broadcastFn = () => { };
|
|
216
|
+
const bridge = new AgentBridge((msg) => broadcastFn(msg), verbose);
|
|
217
|
+
await bridge.init();
|
|
218
|
+
const config = bridge.getConfig();
|
|
219
|
+
const handler = createRequestHandler(bridge);
|
|
220
|
+
const server = createServer(handler);
|
|
221
|
+
// WebSocket server — captures the real broadcast function
|
|
222
|
+
const wss = createWebSocketServer(server, bridge);
|
|
223
|
+
// Wire up the broadcast: WS clients receive all agent events
|
|
224
|
+
const { WebSocket } = await import("ws");
|
|
225
|
+
broadcastFn = (msg) => {
|
|
226
|
+
const data = JSON.stringify(msg);
|
|
227
|
+
for (const client of wss.clients) {
|
|
228
|
+
if (client.readyState === WebSocket.OPEN) {
|
|
229
|
+
client.send(data);
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
};
|
|
233
|
+
// Graceful shutdown
|
|
234
|
+
process.on("SIGINT", () => {
|
|
235
|
+
console.log("\nShutting down...");
|
|
236
|
+
wss.close();
|
|
237
|
+
server.close(() => {
|
|
238
|
+
process.exit(0);
|
|
239
|
+
});
|
|
240
|
+
});
|
|
241
|
+
server.listen(port, "127.0.0.1", () => {
|
|
242
|
+
console.log(`\nminicode serve`);
|
|
243
|
+
console.log(` Workspace: ${config.workspaceRoot}`);
|
|
244
|
+
console.log(` Model: ${config.model} (${config.modelProvider})`);
|
|
245
|
+
console.log(` Web UI: http://localhost:${port}`);
|
|
246
|
+
console.log(` OpenAI: http://localhost:${port}/v1`);
|
|
247
|
+
console.log(`\nPress Ctrl+C to stop.\n`);
|
|
248
|
+
});
|
|
249
|
+
// Keep alive
|
|
250
|
+
await new Promise(() => { });
|
|
251
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { WebSocketServer } from "ws";
|
|
2
|
+
export function createWebSocketServer(httpServer, bridge) {
|
|
3
|
+
const wss = new WebSocketServer({ server: httpServer });
|
|
4
|
+
wss.on("connection", (ws) => {
|
|
5
|
+
ws.on("message", (raw) => {
|
|
6
|
+
let msg;
|
|
7
|
+
try {
|
|
8
|
+
msg = JSON.parse(String(raw));
|
|
9
|
+
}
|
|
10
|
+
catch {
|
|
11
|
+
return;
|
|
12
|
+
}
|
|
13
|
+
if (msg.type === "chat") {
|
|
14
|
+
if (bridge.isBusy()) {
|
|
15
|
+
ws.send(JSON.stringify({ type: "busy" }));
|
|
16
|
+
return;
|
|
17
|
+
}
|
|
18
|
+
bridge.runTurn(msg.message).catch(() => {
|
|
19
|
+
// errors already broadcast via agent-bridge
|
|
20
|
+
});
|
|
21
|
+
}
|
|
22
|
+
else if (msg.type === "cancel") {
|
|
23
|
+
bridge.cancel();
|
|
24
|
+
}
|
|
25
|
+
});
|
|
26
|
+
});
|
|
27
|
+
return wss;
|
|
28
|
+
}
|
package/dist/src/ui/cli-ink.js
CHANGED
|
@@ -85,7 +85,7 @@ export async function runInkCli(verbose, initialTask) {
|
|
|
85
85
|
verbose,
|
|
86
86
|
...(session ? { session } : {}),
|
|
87
87
|
...(projectIndex !== undefined
|
|
88
|
-
? { getCodeMap: () => projectIndex.getCodeMap() }
|
|
88
|
+
? { getCodeMap: (focusSymbols) => projectIndex.getCodeMap(undefined, focusSymbols) }
|
|
89
89
|
: {}),
|
|
90
90
|
...(verbose
|
|
91
91
|
? {
|
|
@@ -121,7 +121,7 @@ export async function runInkCli(verbose, initialTask) {
|
|
|
121
121
|
if (trimmed === "/help") {
|
|
122
122
|
store.addItem({
|
|
123
123
|
type: "system",
|
|
124
|
-
content: 'Commands: "/help", "/config", "/save [label]", "/load [label]", "/sessions", "/exit".',
|
|
124
|
+
content: 'Commands: "/help", "/config", "/compact", "/save [label]", "/load [label]", "/sessions", "/exit".',
|
|
125
125
|
});
|
|
126
126
|
return;
|
|
127
127
|
}
|
|
@@ -132,6 +132,26 @@ export async function runInkCli(verbose, initialTask) {
|
|
|
132
132
|
});
|
|
133
133
|
return;
|
|
134
134
|
}
|
|
135
|
+
if (trimmed === "/compact") {
|
|
136
|
+
const session = agent.getSession();
|
|
137
|
+
const result = await agent.compactContext();
|
|
138
|
+
if (result) {
|
|
139
|
+
const method = config.compactionModel ? "LLM" : "mechanical";
|
|
140
|
+
store.addItem({
|
|
141
|
+
type: "system",
|
|
142
|
+
content: `Compacted (${method}): ${result.removedMessages} messages summarized, ` +
|
|
143
|
+
`${result.previousTokens} → ${result.newTokens} tokens ` +
|
|
144
|
+
`(saved ${result.previousTokens - result.newTokens} tokens)`,
|
|
145
|
+
});
|
|
146
|
+
}
|
|
147
|
+
else {
|
|
148
|
+
store.addItem({
|
|
149
|
+
type: "system",
|
|
150
|
+
content: `Nothing to compact (${session.getTokenEstimate()} tokens, ${session.getMessages().length} messages).`,
|
|
151
|
+
});
|
|
152
|
+
}
|
|
153
|
+
return;
|
|
154
|
+
}
|
|
135
155
|
if (trimmed === "/save" || trimmed.startsWith("/save ")) {
|
|
136
156
|
const label = trimmed.slice("/save".length).trim() || undefined;
|
|
137
157
|
try {
|