@hncode/hncode 0.5.6 → 0.6.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/package.json +2 -3
- package/src/agent.js +67 -2
- package/src/clipboard.js +221 -33
- package/src/image-row.js +111 -0
- package/src/latex.js +1557 -0
- package/src/llm.js +19 -4
- package/src/mermaid/ansi.js +55 -0
- package/src/mermaid/canvas.js +449 -0
- package/src/mermaid/class-style.js +57 -0
- package/src/mermaid/css-colors.js +153 -0
- package/src/mermaid/diagrams/class.js +289 -0
- package/src/mermaid/diagrams/er.js +201 -0
- package/src/mermaid/diagrams/flowchart.js +488 -0
- package/src/mermaid/diagrams/gitgraph.js +205 -0
- package/src/mermaid/diagrams/mindmap.js +101 -0
- package/src/mermaid/diagrams/pie.js +104 -0
- package/src/mermaid/diagrams/sequence.js +282 -0
- package/src/mermaid/diagrams/state.js +292 -0
- package/src/mermaid/diagrams/timeline.js +99 -0
- package/src/mermaid/graph.js +138 -0
- package/src/mermaid/index.js +59 -0
- package/src/mermaid/labels.js +339 -0
- package/src/mermaid/layout-seq.js +225 -0
- package/src/mermaid/layout.js +1454 -0
- package/src/mermaid/registry.js +54 -0
- package/src/mermaid/source-box.js +102 -0
- package/src/mermaid/statements.js +235 -0
- package/src/mermaid/types.js +1 -0
- package/src/mermaid/width.js +29 -0
- package/src/screen-diff.js +512 -0
- package/src/screen-paint.js +128 -0
- package/src/screen.js +277 -0
- package/src/subagent-types.js +136 -147
- package/src/term-caps.js +10 -4
- package/src/tex/font.js +292 -0
- package/src/tex/fonts/LICENSE.txt +11 -0
- package/src/tex/fonts/STIX2Math.ttf +0 -0
- package/src/tex/index.js +293 -0
- package/src/tex/kitty.js +186 -0
- package/src/tex/layout.js +446 -0
- package/src/tex/parse.js +331 -0
- package/src/tex/raster.js +216 -0
- package/src/tex/render.js +167 -0
- package/src/tex/sixel.js +326 -0
- package/src/tex/svg.js +237 -0
- package/src/tool-result.js +2 -2
- package/src/tools/agent-swarm.js +3 -15
- package/src/tools/agent.js +454 -464
- package/src/tools/ask-user-question.js +195 -206
- package/src/tools/bash.js +39 -3
- package/src/tools/edit.js +10 -112
- package/src/tools/fetch-url.js +130 -120
- package/src/tools/goal.js +93 -0
- package/src/tools/index.js +2 -0
- package/src/tools/memory.js +80 -88
- package/src/tools/read.js +27 -23
- package/src/tools/todo.js +8 -13
- package/src/tools/validate-args.js +78 -0
- package/src/tui.js +1718 -585
package/src/screen.js
ADDED
|
@@ -0,0 +1,277 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A screen buffer of individual cells, and the diff that turns two of them into terminal bytes.
|
|
3
|
+
*
|
|
4
|
+
* WHY CELLS AND NOT LINES
|
|
5
|
+
* -----------------------
|
|
6
|
+
* A frame used to be an array of strings, and the painter compared those strings. That worked while every row
|
|
7
|
+
* was text and every change was coarse, but it cannot express two things a terminal actually has: a bitmap,
|
|
8
|
+
* which occupies a RECTANGLE and no row of characters, and a style change, which alters what is drawn without
|
|
9
|
+
* altering which characters are drawn.
|
|
10
|
+
*
|
|
11
|
+
* Comparing whole rows also means a one-character change rewrites the entire row, and any consumer that
|
|
12
|
+
* wants to know "did anything under this rectangle change" has to re-derive it from string offsets.
|
|
13
|
+
*
|
|
14
|
+
* So a frame is a grid. Each cell is two integers — a pooled character id and a pooled style id — which makes
|
|
15
|
+
* a comparison an integer compare and makes a rectangle an index range. That is the same shape dsh-tui uses,
|
|
16
|
+
* and it is what lets an image be handled as a region rather than as a special row.
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
/** Cell width markers, matching the terminal's own notion. */
|
|
20
|
+
export const CELL_WIDTH = { narrow: 1, wide: 2, spacer: 0 };
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Character pool: interned strings so a cell comparison is an integer comparison.
|
|
24
|
+
*
|
|
25
|
+
* Index 0 is a space and index 1 is the empty "spacer" that follows a wide character, so both have stable ids
|
|
26
|
+
* that no ordinary character can take.
|
|
27
|
+
*/
|
|
28
|
+
export class CharPool {
|
|
29
|
+
constructor() {
|
|
30
|
+
this.strings = [' ', ''];
|
|
31
|
+
this.ids = new Map([[' ', 0], ['', 1]]);
|
|
32
|
+
this.ascii = new Int32Array(128).fill(-1);
|
|
33
|
+
this.ascii[32] = 0;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
intern(ch) {
|
|
37
|
+
if (ch.length === 1) {
|
|
38
|
+
const code = ch.charCodeAt(0);
|
|
39
|
+
if (code < 128) {
|
|
40
|
+
const hit = this.ascii[code];
|
|
41
|
+
if (hit !== -1) return hit;
|
|
42
|
+
const id = this.strings.length;
|
|
43
|
+
this.strings.push(ch);
|
|
44
|
+
this.ascii[code] = id;
|
|
45
|
+
return id;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
const hit = this.ids.get(ch);
|
|
49
|
+
if (hit !== undefined) return hit;
|
|
50
|
+
const id = this.strings.length;
|
|
51
|
+
this.strings.push(ch);
|
|
52
|
+
this.ids.set(ch, id);
|
|
53
|
+
return id;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
get(id) {
|
|
57
|
+
return this.strings[id] ?? ' ';
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Style pool: an SGR prefix per distinct style, so a cell stores an id rather than a string.
|
|
63
|
+
*
|
|
64
|
+
* The value is the SGR sequence that ENTERS the style; the diff writes `ESC[0m` plus the next cell's prefix
|
|
65
|
+
* when the style changes, which is simpler than a minimal-diff of two SGR strings and cannot be wrong.
|
|
66
|
+
*/
|
|
67
|
+
export class StylePool {
|
|
68
|
+
constructor() {
|
|
69
|
+
this.strings = [''];
|
|
70
|
+
this.ids = new Map([['', 0]]);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
intern(sgr) {
|
|
74
|
+
const s = sgr || '';
|
|
75
|
+
const hit = this.ids.get(s);
|
|
76
|
+
if (hit !== undefined) return hit;
|
|
77
|
+
const id = this.strings.length;
|
|
78
|
+
this.strings.push(s);
|
|
79
|
+
this.ids.set(s, id);
|
|
80
|
+
return id;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
get(id) {
|
|
84
|
+
return this.strings[id] ?? '';
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/** A rectangle in cell coordinates. */
|
|
89
|
+
export const rect = (x, y, width, height) => ({ x, y, width, height });
|
|
90
|
+
|
|
91
|
+
/** The union of two rectangles, or whichever is non-null. */
|
|
92
|
+
export function unionRect(a, b) {
|
|
93
|
+
if (!a) return b;
|
|
94
|
+
if (!b) return a;
|
|
95
|
+
const x = Math.min(a.x, b.x);
|
|
96
|
+
const y = Math.min(a.y, b.y);
|
|
97
|
+
const right = Math.max(a.x + a.width, b.x + b.width);
|
|
98
|
+
const bottom = Math.max(a.y + a.height, b.y + b.height);
|
|
99
|
+
return { x, y, width: right - x, height: bottom - y };
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/** True when the two rectangles share any cell. */
|
|
103
|
+
export function rectsOverlap(a, b) {
|
|
104
|
+
return a.x < b.x + b.width && a.x + a.width > b.x && a.y < b.y + b.height && a.y + a.height > b.y;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* A frame's painted surface.
|
|
109
|
+
*
|
|
110
|
+
* `cells` holds two Int32 per cell: `[charId, styleId]`. A wide character's second column holds the SPACER
|
|
111
|
+
* id so that a later write cannot split it, and the diff skips those columns when emitting.
|
|
112
|
+
*/
|
|
113
|
+
export class Screen {
|
|
114
|
+
constructor(width, height, charPool, stylePool) {
|
|
115
|
+
this.width = Math.max(0, width | 0);
|
|
116
|
+
this.height = Math.max(0, height | 0);
|
|
117
|
+
this.charPool = charPool;
|
|
118
|
+
this.stylePool = stylePool;
|
|
119
|
+
this.cells = new Int32Array(this.width * this.height * 2);
|
|
120
|
+
/** Bounding box of everything written since the last reset, or null when nothing was. */
|
|
121
|
+
this.damage = null;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/** Fill the buffer with spaces in the given style and clear the damage. */
|
|
125
|
+
reset(styleId = 0) {
|
|
126
|
+
this.cells.fill(0);
|
|
127
|
+
for (let i = 1; i < this.cells.length; i += 2) this.cells[i] = styleId;
|
|
128
|
+
this.damage = null;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
inBounds(x, y) {
|
|
132
|
+
return x >= 0 && y >= 0 && x < this.width && y < this.height;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* Write one character.
|
|
137
|
+
* @param {number} x
|
|
138
|
+
* @param {number} y
|
|
139
|
+
* @param {string} ch
|
|
140
|
+
* @param {number} styleId
|
|
141
|
+
* @param {number} [width] 1 for narrow, 2 for wide.
|
|
142
|
+
*/
|
|
143
|
+
set(x, y, ch, styleId, width = 1) {
|
|
144
|
+
if (!this.inBounds(x, y)) return;
|
|
145
|
+
const at = (y * this.width + x) * 2;
|
|
146
|
+
this.cells[at] = this.charPool.intern(ch);
|
|
147
|
+
this.cells[at + 1] = styleId;
|
|
148
|
+
|
|
149
|
+
if (width === 2) {
|
|
150
|
+
// The second column is a spacer, never a character of its own.
|
|
151
|
+
if (this.inBounds(x + 1, y)) {
|
|
152
|
+
const next = (y * this.width + x + 1) * 2;
|
|
153
|
+
this.cells[next] = 1;
|
|
154
|
+
this.cells[next + 1] = styleId;
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
this._mark(x, y, width);
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
/** Write a run of characters starting at x, returning the column after the last one. */
|
|
161
|
+
write(x, y, text, styleId) {
|
|
162
|
+
let col = x;
|
|
163
|
+
for (const ch of text) {
|
|
164
|
+
const w = charWidth(ch);
|
|
165
|
+
if (col + w > this.width) break;
|
|
166
|
+
this.set(col, y, ch, styleId, w);
|
|
167
|
+
col += w;
|
|
168
|
+
}
|
|
169
|
+
return col;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
/** Blank a rectangle in the given style. */
|
|
173
|
+
clear(x, y, width, height, styleId = 0) {
|
|
174
|
+
for (let row = y; row < Math.min(y + height, this.height); row++) {
|
|
175
|
+
for (let col = x; col < Math.min(x + width, this.width); col++) {
|
|
176
|
+
if (col < 0 || row < 0) continue;
|
|
177
|
+
const at = (row * this.width + col) * 2;
|
|
178
|
+
this.cells[at] = 0;
|
|
179
|
+
this.cells[at + 1] = styleId;
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
this._mark(x, y, Math.max(0, width), Math.max(0, height));
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
/** Rows this screen claims to have painted, so a later diff can tell a moved layout from an edited one. */
|
|
186
|
+
get rowCount() {
|
|
187
|
+
return this._rowCount === undefined ? -1 : this._rowCount;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
_mark(x, y, width, height = 1) {
|
|
191
|
+
const r = rect(Math.max(0, x), Math.max(0, y), Math.max(1, width), Math.max(1, height));
|
|
192
|
+
this.damage = unionRect(this.damage, r);
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
/** The character at a cell, and its style. */
|
|
196
|
+
at(x, y) {
|
|
197
|
+
const at = (y * this.width + x) * 2;
|
|
198
|
+
return { ch: this.charPool.get(this.cells[at]), styleId: this.cells[at + 1] };
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
/** True when this cell is the second half of a wide character. */
|
|
202
|
+
isSpacer(x, y) {
|
|
203
|
+
return this.cells[(y * this.width + x) * 2] === 1;
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
/**
|
|
207
|
+
* Every cell that differs from `other`, row by row.
|
|
208
|
+
*
|
|
209
|
+
* @param {Screen|null} other The previous frame, or null for a full paint.
|
|
210
|
+
* @param {(x:number, y:number, rowEnd:number)=>void} onRow Called once per row that has any change, with the
|
|
211
|
+
* first differing column and the column after the last one.
|
|
212
|
+
* @returns {boolean} Whether anything differed.
|
|
213
|
+
*/
|
|
214
|
+
diffRows(other, onRow) {
|
|
215
|
+
if (!other || other.width !== this.width || other.height !== this.height) {
|
|
216
|
+
for (let y = 0; y < this.height; y++) onRow(0, y, this.width);
|
|
217
|
+
return this.height > 0;
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
// `damage` may only narrow the scan when it already covers the whole grid: a row that lost its text is
|
|
221
|
+
// not written by anyone, so it never enters damage, and bounding by damage would skip exactly the rows
|
|
222
|
+
// that changed.
|
|
223
|
+
const covered = this.damage && this.damage.y <= 0 && this.damage.y + this.damage.height >= this.height;
|
|
224
|
+
const from = covered ? 0 : 0;
|
|
225
|
+
const to = this.height;
|
|
226
|
+
|
|
227
|
+
let any = false;
|
|
228
|
+
const a = other.cells;
|
|
229
|
+
const b = this.cells;
|
|
230
|
+
for (let y = from; y < to; y++) {
|
|
231
|
+
const base = y * this.width * 2;
|
|
232
|
+
let first = -1;
|
|
233
|
+
let last = -1;
|
|
234
|
+
for (let i = 0; i < this.width; i++) {
|
|
235
|
+
const at = base + i * 2;
|
|
236
|
+
if (a[at] !== b[at] || a[at + 1] !== b[at + 1]) {
|
|
237
|
+
if (first < 0) first = i;
|
|
238
|
+
last = i;
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
if (first >= 0) {
|
|
242
|
+
any = true;
|
|
243
|
+
onRow(first, y, last + 1);
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
return any;
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
/** Terminal columns a code point occupies. Narrow unless it is East Asian wide or an emoji. */
|
|
251
|
+
export function charWidth(ch) {
|
|
252
|
+
const cp = ch.codePointAt(0);
|
|
253
|
+
if (cp === undefined) return 1;
|
|
254
|
+
// Combining marks attach to the previous cell and take no column of their own.
|
|
255
|
+
if (cp >= 0x300 && cp <= 0x36f) return 0;
|
|
256
|
+
if (
|
|
257
|
+
(cp >= 0x1100 && cp <= 0x115f) // Hangul Jamo
|
|
258
|
+
|| (cp >= 0x2e80 && cp <= 0xa4cf) // CJK radicals … Yi
|
|
259
|
+
|| (cp >= 0xac00 && cp <= 0xd7a3) // Hangul syllables
|
|
260
|
+
|| (cp >= 0xf900 && cp <= 0xfaff) // CJK compatibility ideographs
|
|
261
|
+
|| (cp >= 0xfe30 && cp <= 0xfe6f) // CJK compatibility forms
|
|
262
|
+
|| (cp >= 0xff00 && cp <= 0xff60) // Fullwidth forms
|
|
263
|
+
|| (cp >= 0xffe0 && cp <= 0xffe6)
|
|
264
|
+
|| (cp >= 0x1f300 && cp <= 0x1f64f) // emoji
|
|
265
|
+
|| (cp >= 0x1f900 && cp <= 0x1f9ff)
|
|
266
|
+
|| (cp >= 0x20000 && cp <= 0x3fffd)
|
|
267
|
+
) return 2;
|
|
268
|
+
return 1;
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
/** A shareable pool pair, so screens in one session can be compared cheaply. */
|
|
272
|
+
export const pools = () => ({ charPool: new CharPool(), stylePool: new StylePool() });
|
|
273
|
+
|
|
274
|
+
/** Create a screen over the given pools. */
|
|
275
|
+
export function createScreen(width, height, charPool, stylePool) {
|
|
276
|
+
return new Screen(width, height, charPool, stylePool);
|
|
277
|
+
}
|
package/src/subagent-types.js
CHANGED
|
@@ -1,147 +1,136 @@
|
|
|
1
|
-
// Subagent type catalog, shared by the Agent tool and AgentSwarm.
|
|
2
|
-
//
|
|
3
|
-
// Mirrors kimi-code's builtin agent profiles (session/agentLifecycle/profile/profiles.ts):
|
|
4
|
-
// coder - general software engineering; the only type with editing tools
|
|
5
|
-
// explore - fast read-only codebase exploration (kimi calls it "explore")
|
|
6
|
-
// plan - read-only implementation planning; no shell, no editing
|
|
7
|
-
//
|
|
8
|
-
// Kept in its own module so agent.js and agent-swarm.js can both import it
|
|
9
|
-
// without a cycle (agent-swarm used to import it from agent.js, which then
|
|
10
|
-
// imported agent-swarm back — the constant was read before it was initialised).
|
|
11
|
-
|
|
12
|
-
export const DEFAULT_SUBAGENT_TYPE = 'coder';
|
|
13
|
-
|
|
14
|
-
// Shared prefix kimi puts on every task-agent role prompt. It is what stops a
|
|
15
|
-
// subagent from addressing the end user directly or assuming the parent can see
|
|
16
|
-
// its context.
|
|
17
|
-
const TASK_AGENT_ROLE_PREFIX =
|
|
18
|
-
'You are now running as a subagent. All the `user` messages are sent by the main agent. '
|
|
19
|
-
+ 'The main agent cannot see your context, it can only see your last message when you finish the task. '
|
|
20
|
-
+ 'You must treat the parent agent as your caller. Do not directly ask the end user questions. '
|
|
21
|
-
+ 'If something is unclear, explain the ambiguity in your final summary to the parent agent. '
|
|
22
|
-
// A subagent has no Agent/AgentSwarm in its toolbelt (see DELEGATION_TOOLS), so it
|
|
23
|
-
// must not try: a delegated delegation would have nobody to report to and would
|
|
24
|
-
// nest without bound.
|
|
25
|
-
+ 'You cannot delegate: you have no Agent or AgentSwarm tool. Do all of the work yourself.';
|
|
26
|
-
|
|
27
|
-
// Tools that let an agent hand work to ANOTHER agent. Subagents never get these:
|
|
28
|
-
// delegation belongs to the top-level turn the user is watching. kimi expresses
|
|
29
|
-
// this through each profile's explicit `tools` allowlist; we enforce it the same
|
|
30
|
-
// way plus a hard block, so a future profile that forgets to list its tools
|
|
31
|
-
// cannot silently re-enable nesting.
|
|
32
|
-
export const DELEGATION_TOOLS = ['Agent', 'AgentSwarm'];
|
|
33
|
-
|
|
34
|
-
export const SUBAGENT_TYPES = {
|
|
35
|
-
coder: {
|
|
36
|
-
label: 'Coder',
|
|
37
|
-
description: '
|
|
38
|
-
whenToUse:
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
'
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
+ '
|
|
51
|
-
+ '
|
|
52
|
-
+ '
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
+ '
|
|
66
|
-
+ '
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
+ '
|
|
73
|
-
+ '
|
|
74
|
-
+ '
|
|
75
|
-
+ '-
|
|
76
|
-
+ '-
|
|
77
|
-
+ '-
|
|
78
|
-
+ '-
|
|
79
|
-
+ '
|
|
80
|
-
+ '-
|
|
81
|
-
+ '
|
|
82
|
-
+ '
|
|
83
|
-
+ '
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
'
|
|
97
|
-
+ '
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
+ '
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
+ '
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
//
|
|
136
|
-
|
|
137
|
-
export function toolsForSubagent(type, parentFilter) {
|
|
138
|
-
const profile = SUBAGENT_TYPES[type] || SUBAGENT_TYPES[DEFAULT_SUBAGENT_TYPE];
|
|
139
|
-
const base = Array.isArray(profile.tools) ? profile.tools : null;
|
|
140
|
-
if (base) return base.filter((t) => !DELEGATION_TOOLS.includes(t));
|
|
141
|
-
// No explicit list: derive it from the parent's filter so a narrowed parent
|
|
142
|
-
// (Plan / Focus mode) stays narrowed in the child, minus delegation.
|
|
143
|
-
if (Array.isArray(parentFilter) && parentFilter.length) {
|
|
144
|
-
return parentFilter.filter((t) => !DELEGATION_TOOLS.includes(t));
|
|
145
|
-
}
|
|
146
|
-
return null; // null = agent.js exposes everything except DELEGATION_TOOLS
|
|
147
|
-
}
|
|
1
|
+
// Subagent type catalog, shared by the Agent tool and AgentSwarm.
|
|
2
|
+
//
|
|
3
|
+
// Mirrors kimi-code's builtin agent profiles (session/agentLifecycle/profile/profiles.ts):
|
|
4
|
+
// coder - general software engineering; the only type with editing tools
|
|
5
|
+
// explore - fast read-only codebase exploration (kimi calls it "explore")
|
|
6
|
+
// plan - read-only implementation planning; no shell, no editing
|
|
7
|
+
//
|
|
8
|
+
// Kept in its own module so agent.js and agent-swarm.js can both import it
|
|
9
|
+
// without a cycle (agent-swarm used to import it from agent.js, which then
|
|
10
|
+
// imported agent-swarm back — the constant was read before it was initialised).
|
|
11
|
+
|
|
12
|
+
export const DEFAULT_SUBAGENT_TYPE = 'coder';
|
|
13
|
+
|
|
14
|
+
// Shared prefix kimi puts on every task-agent role prompt. It is what stops a
|
|
15
|
+
// subagent from addressing the end user directly or assuming the parent can see
|
|
16
|
+
// its context.
|
|
17
|
+
const TASK_AGENT_ROLE_PREFIX =
|
|
18
|
+
'You are now running as a subagent. All the `user` messages are sent by the main agent. '
|
|
19
|
+
+ 'The main agent cannot see your context, it can only see your last message when you finish the task. '
|
|
20
|
+
+ 'You must treat the parent agent as your caller. Do not directly ask the end user questions. '
|
|
21
|
+
+ 'If something is unclear, explain the ambiguity in your final summary to the parent agent. '
|
|
22
|
+
// A subagent has no Agent/AgentSwarm in its toolbelt (see DELEGATION_TOOLS), so it
|
|
23
|
+
// must not try: a delegated delegation would have nobody to report to and would
|
|
24
|
+
// nest without bound.
|
|
25
|
+
+ 'You cannot delegate: you have no Agent or AgentSwarm tool. Do all of the work yourself.';
|
|
26
|
+
|
|
27
|
+
// Tools that let an agent hand work to ANOTHER agent. Subagents never get these:
|
|
28
|
+
// delegation belongs to the top-level turn the user is watching. kimi expresses
|
|
29
|
+
// this through each profile's explicit `tools` allowlist; we enforce it the same
|
|
30
|
+
// way plus a hard block, so a future profile that forgets to list its tools
|
|
31
|
+
// cannot silently re-enable nesting.
|
|
32
|
+
export const DELEGATION_TOOLS = ['Agent', 'AgentSwarm'];
|
|
33
|
+
|
|
34
|
+
export const SUBAGENT_TYPES = {
|
|
35
|
+
coder: {
|
|
36
|
+
label: 'Coder',
|
|
37
|
+
description: 'The only type that may edit files. For delegated work that changes code.',
|
|
38
|
+
whenToUse: 'Use for non-trivial engineering work: read, edit, run commands, report a compact but complete summary.',
|
|
39
|
+
// An explicit allowlist (not `null` = everything) so Agent/AgentSwarm are
|
|
40
|
+
// excluded. Mirrors kimi's CODER_TOOLS.
|
|
41
|
+
tools: [
|
|
42
|
+
'Read', 'Write', 'Edit', 'Glob', 'Grep', 'FileLines', 'Bash', 'Git',
|
|
43
|
+
'TodoList', 'FetchURL', 'WebSearch', 'ReadMediaFile',
|
|
44
|
+
'TaskList', 'TaskOutput', 'TaskStop', 'TaskWait',
|
|
45
|
+
],
|
|
46
|
+
prompt:
|
|
47
|
+
`${TASK_AGENT_ROLE_PREFIX}\n\n`
|
|
48
|
+
+ 'Your final message is the entire handoff — the parent sees nothing else from your run. '
|
|
49
|
+
+ 'Make it technically complete: what you changed and why, the path of every file you touched, '
|
|
50
|
+
+ 'how you verified the change (tests or commands run, with results), and anything left undone '
|
|
51
|
+
+ 'or worth follow-up. If you are stopped before finishing, the parent receives only what '
|
|
52
|
+
+ 'you have written so far, so keep the handoff current.',
|
|
53
|
+
},
|
|
54
|
+
explore: {
|
|
55
|
+
label: 'Explore',
|
|
56
|
+
description: 'Read-only codebase exploration.',
|
|
57
|
+
whenToUse: 'Use for read-only search that needs more than about three queries: find files, grep for a symbol, '
|
|
58
|
+
+ 'answer how a module works. Say "quick", "medium" or "thorough". Launch several in parallel when the '
|
|
59
|
+
+ 'questions are independent.',
|
|
60
|
+
// Bash is allowed for READ-ONLY commands (git log, ls, find) — the prompt
|
|
61
|
+
// forbids using it to create or modify anything, exactly as kimi does.
|
|
62
|
+
tools: ['Read', 'ReadMediaFile', 'Glob', 'Grep', 'Bash', 'WebSearch', 'FetchURL'],
|
|
63
|
+
prompt:
|
|
64
|
+
`${TASK_AGENT_ROLE_PREFIX}\n\n`
|
|
65
|
+
+ 'You are a codebase exploration specialist. Your role is EXCLUSIVELY to search, read, and analyze existing '
|
|
66
|
+
+ 'code and resources. You do NOT have access to file editing tools.\n\n'
|
|
67
|
+
+ 'Your strengths:\n'
|
|
68
|
+
+ '- Rapidly finding files using glob patterns\n'
|
|
69
|
+
+ '- Searching code and text with powerful regex patterns\n'
|
|
70
|
+
+ '- Reading and analyzing file contents\n'
|
|
71
|
+
+ '- Running read-only shell commands (git log, git diff, ls, find, etc.)\n\n'
|
|
72
|
+
+ 'Guidelines:\n'
|
|
73
|
+
+ '- Use Glob for broad file pattern matching. Prefer patterns with a literal anchor (extension or subdirectory); '
|
|
74
|
+
+ 'pure wildcards like `*` or `**/*` are allowed but usually truncate at the match cap.\n'
|
|
75
|
+
+ '- Use Grep for searching file contents with regex\n'
|
|
76
|
+
+ '- Use Read when you know the specific file path\n'
|
|
77
|
+
+ '- Use Bash ONLY for read-only operations (ls, git status, git log, git diff, find)\n'
|
|
78
|
+
+ '- NEVER use Bash for any file creation or modification commands\n'
|
|
79
|
+
+ '- Use WebSearch or FetchURL when a question needs external context; the local codebase remains your primary domain\n'
|
|
80
|
+
+ '- Adapt your search depth based on the thoroughness level specified by the caller\n'
|
|
81
|
+
+ '- Wherever possible, spawn multiple parallel tool calls for grepping and reading files to maximize speed\n\n'
|
|
82
|
+
+ 'You are meant to be a fast agent. Complete the search request efficiently and report your findings clearly '
|
|
83
|
+
+ 'in a structured format.',
|
|
84
|
+
},
|
|
85
|
+
plan: {
|
|
86
|
+
label: 'Plan',
|
|
87
|
+
description: 'Read-only implementation planning.',
|
|
88
|
+
whenToUse: 'Use when you need a step-by-step plan, the files involved, and the trade-offs, before editing.',
|
|
89
|
+
// No Bash at all — a planning agent has no shell.
|
|
90
|
+
tools: ['Read', 'ReadMediaFile', 'Glob', 'Grep', 'WebSearch', 'FetchURL'],
|
|
91
|
+
prompt:
|
|
92
|
+
`${TASK_AGENT_ROLE_PREFIX}\n\n`
|
|
93
|
+
+ 'Before designing your implementation plan, consider whether you fully understand the codebase areas '
|
|
94
|
+
+ 'relevant to the task. If not, recommend the parent agent to use the explore agent '
|
|
95
|
+
+ '(subagent_type="explore") to investigate key questions first. In your response, clearly state:\n'
|
|
96
|
+
+ '1. What you already know from the information provided\n'
|
|
97
|
+
+ '2. What questions remain unanswered that would benefit from explore agent investigation\n'
|
|
98
|
+
+ '3. Your implementation plan (either preliminary if questions remain, or final if sufficient context exists)\n\n'
|
|
99
|
+
+ 'You are a read-only planning agent: you can read and search files and consult the web, but you have no '
|
|
100
|
+
+ 'shell and no file-editing tools. Where the general instructions tell you to make changes with tools, that '
|
|
101
|
+
+ 'does not apply to you — do not attempt to run commands or modify files. Your deliverable is the plan '
|
|
102
|
+
+ 'itself, returned as your final message.',
|
|
103
|
+
},
|
|
104
|
+
researcher: {
|
|
105
|
+
label: 'Researcher',
|
|
106
|
+
description: 'Read-only investigation; reports findings, changes nothing.',
|
|
107
|
+
whenToUse: 'Use for read-only investigation on the web or in the repo when nothing local may be modified.',
|
|
108
|
+
tools: ['Read', 'ReadMediaFile', 'Grep', 'Glob', 'WebSearch', 'FetchURL'],
|
|
109
|
+
prompt:
|
|
110
|
+
`${TASK_AGENT_ROLE_PREFIX}\n\n`
|
|
111
|
+
+ 'You are a research subagent. Investigate with the read-only tools and report findings. '
|
|
112
|
+
+ 'Do NOT modify anything.',
|
|
113
|
+
},
|
|
114
|
+
};
|
|
115
|
+
|
|
116
|
+
// The catalog as the Agent tool describes it: one line per type, description and
|
|
117
|
+
// the "when to use" guidance the model needs to pick correctly.
|
|
118
|
+
export function availableTypes() {
|
|
119
|
+
return Object.entries(SUBAGENT_TYPES)
|
|
120
|
+
.map(([id, t]) => `- ${id}: ${t.description}${t.whenToUse ? `\n ${t.whenToUse}` : ''}`)
|
|
121
|
+
.join('\n');
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
// The tool allowlist a subagent of `type` may run. Always excludes the delegation
|
|
125
|
+
// tools, whatever the profile declares, so nesting is impossible.
|
|
126
|
+
export function toolsForSubagent(type, parentFilter) {
|
|
127
|
+
const profile = SUBAGENT_TYPES[type] || SUBAGENT_TYPES[DEFAULT_SUBAGENT_TYPE];
|
|
128
|
+
const base = Array.isArray(profile.tools) ? profile.tools : null;
|
|
129
|
+
if (base) return base.filter((t) => !DELEGATION_TOOLS.includes(t));
|
|
130
|
+
// No explicit list: derive it from the parent's filter so a narrowed parent
|
|
131
|
+
// (Plan / Focus mode) stays narrowed in the child, minus delegation.
|
|
132
|
+
if (Array.isArray(parentFilter) && parentFilter.length) {
|
|
133
|
+
return parentFilter.filter((t) => !DELEGATION_TOOLS.includes(t));
|
|
134
|
+
}
|
|
135
|
+
return null; // null = agent.js exposes everything except DELEGATION_TOOLS
|
|
136
|
+
}
|
package/src/term-caps.js
CHANGED
|
@@ -347,15 +347,21 @@ export function parseProbeReplies(buf) {
|
|
|
347
347
|
// are needed to place an image in cells rather than guessing — and a guess here is wrong
|
|
348
348
|
// on every display whose font is not square, which is most of them.
|
|
349
349
|
takeMatches(XT_CELL_RE, buf, spans, (m) => {
|
|
350
|
-
|
|
351
|
-
|
|
350
|
+
// THE REPLY PUTS HEIGHT FIRST: `CSI 6 ; height ; width t`.
|
|
351
|
+
//
|
|
352
|
+
// Reading it as width-then-height swapped the two on every terminal, which put the cell size at 20x10
|
|
353
|
+
// instead of 9x20 for a normal font. An image's row count is derived from the CELL height, so the bitmap
|
|
354
|
+
// claimed twice the rows it needed and pushed the interface down by exactly that much.
|
|
355
|
+
const h = Number(m[1]);
|
|
356
|
+
const w = Number(m[2]);
|
|
352
357
|
// A terminal that answers 0x0 has nothing to say — treat it as unknown rather than
|
|
353
358
|
// as a cell of zero width, which would divide by zero downstream.
|
|
354
359
|
if (w > 0 && h > 0) found.cell = { w, h };
|
|
355
360
|
});
|
|
356
361
|
takeMatches(XT_AREA_RE, buf, spans, (m) => {
|
|
357
|
-
|
|
358
|
-
const h = Number(m[
|
|
362
|
+
// The text area reply is `CSI 4 ; height ; width t` — same order.
|
|
363
|
+
const h = Number(m[1]);
|
|
364
|
+
const w = Number(m[2]);
|
|
359
365
|
if (w > 0 && h > 0) found.textArea = { w, h };
|
|
360
366
|
});
|
|
361
367
|
let complete = false;
|