@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.
Files changed (39) hide show
  1. package/README.md +20 -12
  2. package/dist/src/agent/config.js +14 -2
  3. package/dist/src/cli/args.js +31 -0
  4. package/dist/src/index.js +21 -2
  5. package/dist/src/indexer/code-map.js +52 -5
  6. package/dist/src/indexer/focus-tracker.js +63 -0
  7. package/dist/src/indexer/project-index.js +2 -2
  8. package/dist/src/serve/agent-bridge.js +233 -0
  9. package/dist/src/serve/openai-compat.js +144 -0
  10. package/dist/src/serve/server.js +251 -0
  11. package/dist/src/serve/types.js +2 -0
  12. package/dist/src/serve/websocket.js +28 -0
  13. package/dist/src/ui/cli-ink.js +22 -2
  14. package/dist/src/web/app.js +350 -0
  15. package/dist/src/web/index.html +49 -0
  16. package/dist/src/web/style.css +422 -0
  17. package/dist/tests/agent.test.js +62 -0
  18. package/dist/tests/cli-args.test.js +4 -2
  19. package/dist/tests/serve.integration.test.js +534 -0
  20. package/node_modules/@minicode/agent-sdk/dist/src/agent/agent.d.ts +30 -1
  21. package/node_modules/@minicode/agent-sdk/dist/src/agent/agent.d.ts.map +1 -1
  22. package/node_modules/@minicode/agent-sdk/dist/src/agent/agent.js +212 -8
  23. package/node_modules/@minicode/agent-sdk/dist/src/agent/agent.js.map +1 -1
  24. package/node_modules/@minicode/agent-sdk/dist/src/agent/types.d.ts +10 -0
  25. package/node_modules/@minicode/agent-sdk/dist/src/agent/types.d.ts.map +1 -1
  26. package/node_modules/@minicode/agent-sdk/dist/src/index.d.ts +1 -1
  27. package/node_modules/@minicode/agent-sdk/dist/src/index.d.ts.map +1 -1
  28. package/node_modules/@minicode/agent-sdk/dist/src/index.js.map +1 -1
  29. package/node_modules/@minicode/agent-sdk/dist/src/model/client.d.ts.map +1 -1
  30. package/node_modules/@minicode/agent-sdk/dist/src/model/client.js +2 -0
  31. package/node_modules/@minicode/agent-sdk/dist/src/model/client.js.map +1 -1
  32. package/node_modules/@minicode/agent-sdk/dist/src/session/session.d.ts +51 -1
  33. package/node_modules/@minicode/agent-sdk/dist/src/session/session.d.ts.map +1 -1
  34. package/node_modules/@minicode/agent-sdk/dist/src/session/session.js +210 -2
  35. package/node_modules/@minicode/agent-sdk/dist/src/session/session.js.map +1 -1
  36. package/node_modules/@minicode/agent-sdk/dist/tests/session.test.js +75 -0
  37. package/node_modules/@minicode/agent-sdk/dist/tests/session.test.js.map +1 -1
  38. package/node_modules/@minicode/agent-sdk/dist/tsconfig.tsbuildinfo +1 -1
  39. package/package.json +5 -3
@@ -0,0 +1,422 @@
1
+ *, *::before, *::after {
2
+ margin: 0;
3
+ padding: 0;
4
+ box-sizing: border-box;
5
+ }
6
+
7
+ :root {
8
+ --bg: #1a1b26;
9
+ --bg-surface: #222336;
10
+ --bg-hover: #2a2b3d;
11
+ --text: #c0caf5;
12
+ --text-dim: #565f89;
13
+ --accent: #7aa2f7;
14
+ --accent-dim: #3d59a1;
15
+ --green: #9ece6a;
16
+ --yellow: #e0af68;
17
+ --red: #f7768e;
18
+ --border: #33354a;
19
+ --font-mono: 'JetBrains Mono', 'Fira Code', 'SF Mono', Menlo, monospace;
20
+ --font-sans: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
21
+ }
22
+
23
+ html, body {
24
+ height: 100%;
25
+ background: var(--bg);
26
+ color: var(--text);
27
+ font-family: var(--font-mono);
28
+ font-size: 14px;
29
+ line-height: 1.6;
30
+ }
31
+
32
+ #app {
33
+ display: flex;
34
+ flex-direction: column;
35
+ height: 100vh;
36
+ max-width: 900px;
37
+ margin: 0 auto;
38
+ }
39
+
40
+ /* Header */
41
+ header {
42
+ display: flex;
43
+ align-items: center;
44
+ justify-content: space-between;
45
+ padding: 12px 16px;
46
+ border-bottom: 1px solid var(--border);
47
+ flex-shrink: 0;
48
+ }
49
+
50
+ .header-left {
51
+ display: flex;
52
+ align-items: center;
53
+ gap: 12px;
54
+ }
55
+
56
+ h1 {
57
+ font-size: 16px;
58
+ font-weight: 600;
59
+ color: var(--accent);
60
+ }
61
+
62
+ .header-right {
63
+ display: flex;
64
+ align-items: center;
65
+ gap: 12px;
66
+ }
67
+
68
+ #model-info {
69
+ font-size: 12px;
70
+ color: var(--text-dim);
71
+ }
72
+
73
+ /* Session menu */
74
+ .session-menu {
75
+ position: relative;
76
+ }
77
+
78
+ .header-btn {
79
+ background: var(--bg-surface);
80
+ border: 1px solid var(--border);
81
+ color: var(--text-dim);
82
+ font-family: var(--font-mono);
83
+ font-size: 12px;
84
+ padding: 4px 10px;
85
+ border-radius: 4px;
86
+ cursor: pointer;
87
+ transition: color 0.15s, border-color 0.15s;
88
+ }
89
+
90
+ .header-btn:hover {
91
+ color: var(--text);
92
+ border-color: var(--accent-dim);
93
+ }
94
+
95
+ .dropdown {
96
+ position: absolute;
97
+ top: calc(100% + 6px);
98
+ right: 0;
99
+ min-width: 260px;
100
+ background: var(--bg-surface);
101
+ border: 1px solid var(--border);
102
+ border-radius: 6px;
103
+ box-shadow: 0 4px 16px rgba(0, 0, 0, 0.4);
104
+ z-index: 100;
105
+ overflow: hidden;
106
+ }
107
+
108
+ .dropdown-section {
109
+ padding: 8px;
110
+ }
111
+
112
+ .dropdown-divider {
113
+ height: 1px;
114
+ background: var(--border);
115
+ }
116
+
117
+ .dropdown-row {
118
+ display: flex;
119
+ gap: 6px;
120
+ }
121
+
122
+ .dropdown-row input {
123
+ flex: 1;
124
+ background: var(--bg);
125
+ border: 1px solid var(--border);
126
+ border-radius: 4px;
127
+ color: var(--text);
128
+ font-family: var(--font-mono);
129
+ font-size: 12px;
130
+ padding: 5px 8px;
131
+ }
132
+
133
+ .dropdown-row input:focus {
134
+ outline: none;
135
+ border-color: var(--accent-dim);
136
+ }
137
+
138
+ .dropdown-action {
139
+ background: var(--accent-dim);
140
+ border: none;
141
+ color: var(--text);
142
+ font-family: var(--font-mono);
143
+ font-size: 12px;
144
+ padding: 5px 12px;
145
+ border-radius: 4px;
146
+ cursor: pointer;
147
+ flex-shrink: 0;
148
+ }
149
+
150
+ .dropdown-action:hover {
151
+ background: var(--accent);
152
+ color: var(--bg);
153
+ }
154
+
155
+ .dropdown-empty {
156
+ font-size: 12px;
157
+ color: var(--text-dim);
158
+ text-align: center;
159
+ padding: 8px;
160
+ }
161
+
162
+ .session-item {
163
+ display: flex;
164
+ align-items: center;
165
+ justify-content: space-between;
166
+ padding: 6px 8px;
167
+ border-radius: 4px;
168
+ cursor: pointer;
169
+ font-size: 12px;
170
+ transition: background 0.15s;
171
+ }
172
+
173
+ .session-item:hover {
174
+ background: var(--bg-hover);
175
+ }
176
+
177
+ .session-label {
178
+ color: var(--text);
179
+ overflow: hidden;
180
+ text-overflow: ellipsis;
181
+ white-space: nowrap;
182
+ }
183
+
184
+ .session-meta {
185
+ color: var(--text-dim);
186
+ font-size: 11px;
187
+ flex-shrink: 0;
188
+ margin-left: 8px;
189
+ }
190
+
191
+ .badge {
192
+ font-size: 11px;
193
+ padding: 2px 8px;
194
+ border-radius: 10px;
195
+ font-weight: 500;
196
+ }
197
+
198
+ .badge.ready {
199
+ background: rgba(158, 206, 106, 0.15);
200
+ color: var(--green);
201
+ }
202
+
203
+ .badge.busy {
204
+ background: rgba(224, 175, 104, 0.15);
205
+ color: var(--yellow);
206
+ }
207
+
208
+ .badge.error {
209
+ background: rgba(247, 118, 142, 0.15);
210
+ color: var(--red);
211
+ }
212
+
213
+ /* Messages */
214
+ main {
215
+ flex: 1;
216
+ overflow-y: auto;
217
+ padding: 16px;
218
+ display: flex;
219
+ flex-direction: column;
220
+ gap: 10px;
221
+ }
222
+
223
+ .message {
224
+ padding: 10px 14px;
225
+ border-radius: 8px;
226
+ max-width: 100%;
227
+ word-wrap: break-word;
228
+ white-space: pre-wrap;
229
+ }
230
+
231
+ .message.user {
232
+ background: var(--accent-dim);
233
+ color: #fff;
234
+ align-self: flex-end;
235
+ max-width: 80%;
236
+ border-radius: 8px 8px 2px 8px;
237
+ }
238
+
239
+ .message.assistant {
240
+ background: var(--bg-surface);
241
+ border: 1px solid var(--border);
242
+ border-radius: 8px 8px 8px 2px;
243
+ }
244
+
245
+ .message.error {
246
+ background: rgba(247, 118, 142, 0.1);
247
+ border: 1px solid rgba(247, 118, 142, 0.3);
248
+ color: var(--red);
249
+ }
250
+
251
+ .message.thinking {
252
+ color: var(--text-dim);
253
+ font-style: italic;
254
+ font-size: 13px;
255
+ padding: 6px 14px;
256
+ }
257
+
258
+ /* Tool call group — clusters consecutive tool calls */
259
+ .tool-group {
260
+ display: flex;
261
+ flex-direction: column;
262
+ gap: 2px;
263
+ margin: 2px 0;
264
+ }
265
+
266
+ /* Tool calls — compact inline pills */
267
+ .tool-call {
268
+ font-size: 12px;
269
+ padding: 4px 10px;
270
+ border-radius: 4px;
271
+ color: var(--text-dim);
272
+ cursor: pointer;
273
+ transition: background 0.15s;
274
+ line-height: 1.4;
275
+ }
276
+
277
+ .tool-call:hover {
278
+ background: rgba(122, 162, 247, 0.08);
279
+ }
280
+
281
+ .tool-call .tool-header {
282
+ display: flex;
283
+ align-items: baseline;
284
+ gap: 6px;
285
+ }
286
+
287
+ .tool-call .tool-name {
288
+ color: var(--accent);
289
+ font-weight: 600;
290
+ flex-shrink: 0;
291
+ }
292
+
293
+ .tool-call .tool-arg {
294
+ color: var(--text-dim);
295
+ overflow: hidden;
296
+ text-overflow: ellipsis;
297
+ white-space: nowrap;
298
+ }
299
+
300
+ .tool-call .tool-time {
301
+ color: var(--text-dim);
302
+ margin-left: auto;
303
+ flex-shrink: 0;
304
+ font-size: 11px;
305
+ opacity: 0.7;
306
+ }
307
+
308
+ .tool-call .tool-result {
309
+ display: none;
310
+ margin-top: 6px;
311
+ padding: 6px 8px;
312
+ border-radius: 3px;
313
+ background: rgba(0, 0, 0, 0.2);
314
+ white-space: pre-wrap;
315
+ max-height: 200px;
316
+ overflow-y: auto;
317
+ font-size: 11px;
318
+ color: var(--text-dim);
319
+ }
320
+
321
+ .tool-call.expanded .tool-result {
322
+ display: block;
323
+ }
324
+
325
+ /* Streaming cursor */
326
+ .streaming-cursor::after {
327
+ content: '\25CC';
328
+ animation: blink 0.8s step-end infinite;
329
+ color: var(--accent);
330
+ }
331
+
332
+ @keyframes blink {
333
+ 50% { opacity: 0; }
334
+ }
335
+
336
+ /* Usage info */
337
+ .usage-info {
338
+ font-size: 11px;
339
+ color: var(--text-dim);
340
+ opacity: 0.6;
341
+ text-align: right;
342
+ padding: 2px 4px;
343
+ }
344
+
345
+ /* Footer / Input */
346
+ footer {
347
+ padding: 12px 16px;
348
+ border-top: 1px solid var(--border);
349
+ flex-shrink: 0;
350
+ }
351
+
352
+ #chat-form {
353
+ display: flex;
354
+ gap: 8px;
355
+ align-items: flex-end;
356
+ }
357
+
358
+ #chat-input {
359
+ flex: 1;
360
+ background: var(--bg-surface);
361
+ border: 1px solid var(--border);
362
+ border-radius: 6px;
363
+ color: var(--text);
364
+ font-family: var(--font-mono);
365
+ font-size: 14px;
366
+ padding: 10px 12px;
367
+ resize: none;
368
+ max-height: 150px;
369
+ line-height: 1.5;
370
+ }
371
+
372
+ #chat-input:focus {
373
+ outline: none;
374
+ border-color: var(--accent);
375
+ }
376
+
377
+ #send-btn, #cancel-btn {
378
+ padding: 10px 18px;
379
+ border: none;
380
+ border-radius: 6px;
381
+ font-family: var(--font-mono);
382
+ font-size: 13px;
383
+ font-weight: 600;
384
+ cursor: pointer;
385
+ }
386
+
387
+ #send-btn {
388
+ background: var(--accent);
389
+ color: var(--bg);
390
+ }
391
+
392
+ #send-btn:hover {
393
+ opacity: 0.9;
394
+ }
395
+
396
+ #send-btn:disabled {
397
+ opacity: 0.4;
398
+ cursor: not-allowed;
399
+ }
400
+
401
+ #cancel-btn {
402
+ background: var(--red);
403
+ color: #fff;
404
+ }
405
+
406
+ .hidden {
407
+ display: none !important;
408
+ }
409
+
410
+ /* Scrollbar */
411
+ main::-webkit-scrollbar {
412
+ width: 6px;
413
+ }
414
+
415
+ main::-webkit-scrollbar-track {
416
+ background: transparent;
417
+ }
418
+
419
+ main::-webkit-scrollbar-thumb {
420
+ background: var(--border);
421
+ border-radius: 3px;
422
+ }
@@ -102,6 +102,68 @@ test("agent omits code map when projectIndex is not provided", async () => {
102
102
  await agent.runTurn("Hello");
103
103
  assert.ok(!capturedSystem.includes("[Project Code Map]"));
104
104
  });
105
+ test("agent caps thinking text in session but preserves final response", async () => {
106
+ const longThinking = "I need to analyze this carefully. ".repeat(20); // ~660 chars, well over 200
107
+ const finalResponse = "Here is the complete and detailed answer that should not be truncated at all. ".repeat(10); // ~780 chars
108
+ const responses = [
109
+ {
110
+ text: longThinking,
111
+ toolCalls: [{ id: "tool-1", name: "echo_tool", input: { value: "ok" } }],
112
+ stopReason: "tool_use",
113
+ usage: { inputTokens: 10, outputTokens: 8 },
114
+ },
115
+ {
116
+ text: finalResponse,
117
+ toolCalls: [],
118
+ stopReason: "end_turn",
119
+ usage: { inputTokens: 12, outputTokens: 6 },
120
+ },
121
+ ];
122
+ const agent = new CodingAgent({
123
+ config: createTestAgentConfig("/tmp"),
124
+ modelClient: new SequenceModelClient(responses),
125
+ toolRegistry: new ToolRegistry([createEchoTool()]),
126
+ });
127
+ const { text } = await agent.runTurn("Do something complex");
128
+ const messages = agent.getSession().getMessages();
129
+ // Thinking message (assistant with toolCalls) should be capped at ~200 chars + "..."
130
+ const thinkingMsg = messages[1];
131
+ assert.equal(thinkingMsg?.role, "assistant");
132
+ assert.ok(thinkingMsg?.role === "assistant" && thinkingMsg.content.length <= 204, `Thinking should be capped but was ${thinkingMsg?.role === "assistant" ? thinkingMsg.content.length : "?"} chars`);
133
+ assert.ok(thinkingMsg?.role === "assistant" && thinkingMsg.content.endsWith("..."), "Capped thinking should end with ellipsis");
134
+ // Final response (assistant without toolCalls) should be preserved in full
135
+ const finalMsg = messages[3];
136
+ assert.equal(finalMsg?.role, "assistant");
137
+ assert.equal(text, finalResponse);
138
+ assert.ok(finalMsg?.role === "assistant" && finalMsg.content === finalResponse, "Final response should not be truncated");
139
+ });
140
+ test("agent preserves short thinking text without capping", async () => {
141
+ const shortThinking = "Let me check.";
142
+ const responses = [
143
+ {
144
+ text: shortThinking,
145
+ toolCalls: [{ id: "tool-1", name: "echo_tool", input: { value: "ok" } }],
146
+ stopReason: "tool_use",
147
+ usage: { inputTokens: 10, outputTokens: 8 },
148
+ },
149
+ {
150
+ text: "Done.",
151
+ toolCalls: [],
152
+ stopReason: "end_turn",
153
+ usage: { inputTokens: 12, outputTokens: 6 },
154
+ },
155
+ ];
156
+ const agent = new CodingAgent({
157
+ config: createTestAgentConfig("/tmp"),
158
+ modelClient: new SequenceModelClient(responses),
159
+ toolRegistry: new ToolRegistry([createEchoTool()]),
160
+ });
161
+ await agent.runTurn("Quick task");
162
+ const messages = agent.getSession().getMessages();
163
+ const thinkingMsg = messages[1];
164
+ assert.equal(thinkingMsg?.role, "assistant");
165
+ assert.ok(thinkingMsg?.role === "assistant" && thinkingMsg.content === shortThinking, "Short thinking should be preserved verbatim");
166
+ });
105
167
  test("agent includes code map in system prompt when projectIndex is provided", async () => {
106
168
  const root = path.resolve(import.meta.dirname, "..");
107
169
  const projectIndex = await buildProjectIndex(root);
@@ -58,16 +58,18 @@ test("parseCliArgs rejects --out without value", () => {
58
58
  assert.throws(() => parseCliArgs(["node", "src/index.ts", "--oneshot", "--out"]), CliUsageError);
59
59
  });
60
60
  test("validateCliArgs rejects oneshot without task", () => {
61
- assert.throws(() => validateCliArgs({ verbose: false, oneshot: true, json: false, task: "" }), /--oneshot requires a task prompt/);
61
+ assert.throws(() => validateCliArgs({ verbose: false, oneshot: true, json: false, serve: false, port: 4567, task: "" }), /--oneshot requires a task prompt/);
62
62
  });
63
63
  test("validateCliArgs rejects json without oneshot", () => {
64
64
  assert.throws(() => validateCliArgs({
65
65
  verbose: false,
66
66
  oneshot: false,
67
67
  json: true,
68
+ serve: false,
69
+ port: 4567,
68
70
  task: "hello",
69
71
  }), /only supported with --oneshot/);
70
72
  });
71
73
  test("validateCliArgs allows non-oneshot empty task", () => {
72
- assert.doesNotThrow(() => validateCliArgs({ verbose: false, oneshot: false, json: false, task: "" }));
74
+ assert.doesNotThrow(() => validateCliArgs({ verbose: false, oneshot: false, json: false, serve: false, port: 4567, task: "" }));
73
75
  });