@hncode/hncode 0.5.6 → 0.6.0

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 (56) hide show
  1. package/package.json +37 -38
  2. package/src/agent.js +67 -2
  3. package/src/image-row.js +111 -0
  4. package/src/latex.js +1557 -0
  5. package/src/llm.js +19 -4
  6. package/src/mermaid/ansi.js +55 -0
  7. package/src/mermaid/canvas.js +449 -0
  8. package/src/mermaid/class-style.js +57 -0
  9. package/src/mermaid/css-colors.js +153 -0
  10. package/src/mermaid/diagrams/class.js +289 -0
  11. package/src/mermaid/diagrams/er.js +201 -0
  12. package/src/mermaid/diagrams/flowchart.js +488 -0
  13. package/src/mermaid/diagrams/gitgraph.js +205 -0
  14. package/src/mermaid/diagrams/mindmap.js +101 -0
  15. package/src/mermaid/diagrams/pie.js +104 -0
  16. package/src/mermaid/diagrams/sequence.js +282 -0
  17. package/src/mermaid/diagrams/state.js +292 -0
  18. package/src/mermaid/diagrams/timeline.js +99 -0
  19. package/src/mermaid/graph.js +138 -0
  20. package/src/mermaid/index.js +59 -0
  21. package/src/mermaid/labels.js +339 -0
  22. package/src/mermaid/layout-seq.js +225 -0
  23. package/src/mermaid/layout.js +1454 -0
  24. package/src/mermaid/registry.js +54 -0
  25. package/src/mermaid/source-box.js +102 -0
  26. package/src/mermaid/statements.js +235 -0
  27. package/src/mermaid/types.js +1 -0
  28. package/src/mermaid/width.js +29 -0
  29. package/src/screen-diff.js +512 -0
  30. package/src/screen-paint.js +128 -0
  31. package/src/screen.js +277 -0
  32. package/src/subagent-types.js +136 -147
  33. package/src/term-caps.js +10 -4
  34. package/src/tex/font.js +292 -0
  35. package/src/tex/fonts/LICENSE.txt +11 -0
  36. package/src/tex/fonts/STIX2Math.ttf +0 -0
  37. package/src/tex/index.js +293 -0
  38. package/src/tex/kitty.js +186 -0
  39. package/src/tex/layout.js +446 -0
  40. package/src/tex/parse.js +331 -0
  41. package/src/tex/raster.js +216 -0
  42. package/src/tex/render.js +167 -0
  43. package/src/tex/sixel.js +326 -0
  44. package/src/tex/svg.js +237 -0
  45. package/src/tool-result.js +2 -2
  46. package/src/tools/agent-swarm.js +3 -15
  47. package/src/tools/agent.js +454 -464
  48. package/src/tools/ask-user-question.js +195 -206
  49. package/src/tools/bash.js +39 -3
  50. package/src/tools/edit.js +10 -112
  51. package/src/tools/fetch-url.js +130 -120
  52. package/src/tools/memory.js +80 -88
  53. package/src/tools/read.js +27 -23
  54. package/src/tools/todo.js +8 -13
  55. package/src/tools/validate-args.js +78 -0
  56. package/src/tui.js +1346 -502
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
+ }
@@ -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: 'General software engineering agent — the only subagent type with file-editing tools; use it for any delegated task that must modify code.',
38
- whenToUse:
39
- 'Use this agent for non-trivial software engineering work that may require reading files, editing code, '
40
- + 'running commands, and returning a compact but technically complete summary to the parent agent.',
41
- // An explicit allowlist (not `null` = everything) so Agent/AgentSwarm are
42
- // excluded. Mirrors kimi's CODER_TOOLS.
43
- tools: [
44
- 'Read', 'Write', 'Edit', 'Glob', 'Grep', 'FileLines', 'Bash', 'Git',
45
- 'TodoList', 'FetchURL', 'WebSearch', 'ReadMediaFile',
46
- 'TaskList', 'TaskOutput', 'TaskStop', 'TaskWait',
47
- ],
48
- prompt:
49
- `${TASK_AGENT_ROLE_PREFIX}\n\n`
50
- + 'Your final message is the entire handoff — the parent sees nothing else from your run. '
51
- + 'Make it technically complete: what you changed and why, the path of every file you touched, '
52
- + 'how you verified the change (tests or commands run, with results), and anything left undone '
53
- + 'or worth follow-up. If you are stopped before finishing, the parent receives only what '
54
- + 'you have written so far, so keep the handoff current.',
55
- },
56
- explore: {
57
- label: 'Explore',
58
- description: 'Fast codebase exploration with prompt-enforced read-only behavior.',
59
- whenToUse:
60
- 'Fast agent specialized for exploring codebases. Use this when you need to quickly find files by patterns '
61
- + '(e.g. "src/**/*.yaml"), search code for keywords (e.g. "database connection"), or answer questions about '
62
- + 'the codebase (e.g. "how does the auth module work?"). When calling this agent, specify the desired '
63
- + 'thoroughness level: "quick" for basic searches, "medium" for moderate exploration, or "thorough" for '
64
- + 'comprehensive analysis across multiple locations and naming conventions. Use this agent for any read-only '
65
- + 'exploration that will clearly require more than 3 search queries. Prefer launching multiple explore agents '
66
- + 'concurrently when investigating independent questions.',
67
- // Bash is allowed for READ-ONLY commands (git log, ls, find) — the prompt
68
- // forbids using it to create or modify anything, exactly as kimi does.
69
- tools: ['Read', 'ReadMediaFile', 'Glob', 'Grep', 'Bash', 'WebSearch', 'FetchURL'],
70
- prompt:
71
- `${TASK_AGENT_ROLE_PREFIX}\n\n`
72
- + 'You are a codebase exploration specialist. Your role is EXCLUSIVELY to search, read, and analyze existing '
73
- + 'code and resources. You do NOT have access to file editing tools.\n\n'
74
- + 'Your strengths:\n'
75
- + '- Rapidly finding files using glob patterns\n'
76
- + '- Searching code and text with powerful regex patterns\n'
77
- + '- Reading and analyzing file contents\n'
78
- + '- Running read-only shell commands (git log, git diff, ls, find, etc.)\n\n'
79
- + 'Guidelines:\n'
80
- + '- Use Glob for broad file pattern matching. Prefer patterns with a literal anchor (extension or subdirectory); '
81
- + 'pure wildcards like `*` or `**/*` are allowed but usually truncate at the match cap.\n'
82
- + '- Use Grep for searching file contents with regex\n'
83
- + '- Use Read when you know the specific file path\n'
84
- + '- Use Bash ONLY for read-only operations (ls, git status, git log, git diff, find)\n'
85
- + '- NEVER use Bash for any file creation or modification commands\n'
86
- + '- Use WebSearch or FetchURL when a question needs external context; the local codebase remains your primary domain\n'
87
- + '- Adapt your search depth based on the thoroughness level specified by the caller\n'
88
- + '- Wherever possible, spawn multiple parallel tool calls for grepping and reading files to maximize speed\n\n'
89
- + 'You are meant to be a fast agent. Complete the search request efficiently and report your findings clearly '
90
- + 'in a structured format.',
91
- },
92
- plan: {
93
- label: 'Plan',
94
- description: 'Read-only implementation planning and architecture design.',
95
- whenToUse:
96
- 'Use this agent when the parent agent needs a step-by-step implementation plan, key file identification, '
97
- + 'and architectural trade-off analysis before code changes are made.',
98
- // No Bash at all — a planning agent has no shell.
99
- tools: ['Read', 'ReadMediaFile', 'Glob', 'Grep', 'WebSearch', 'FetchURL'],
100
- prompt:
101
- `${TASK_AGENT_ROLE_PREFIX}\n\n`
102
- + 'Before designing your implementation plan, consider whether you fully understand the codebase areas '
103
- + 'relevant to the task. If not, recommend the parent agent to use the explore agent '
104
- + '(subagent_type="explore") to investigate key questions first. In your response, clearly state:\n'
105
- + '1. What you already know from the information provided\n'
106
- + '2. What questions remain unanswered that would benefit from explore agent investigation\n'
107
- + '3. Your implementation plan (either preliminary if questions remain, or final if sufficient context exists)\n\n'
108
- + 'You are a read-only planning agent: you can read and search files and consult the web, but you have no '
109
- + 'shell and no file-editing tools. Where the general instructions tell you to make changes with tools, that '
110
- + 'does not apply to you — do not attempt to run commands or modify files. Your deliverable is the plan '
111
- + 'itself, returned as your final message.',
112
- },
113
- researcher: {
114
- label: 'Researcher',
115
- description: 'Read-only investigation; reports findings, changes nothing.',
116
- whenToUse:
117
- 'Use this agent for read-only investigation on the web or in the repository when you want findings '
118
- + 'reported back without any chance of local modification.',
119
- tools: ['Read', 'ReadMediaFile', 'Grep', 'Glob', 'WebSearch', 'FetchURL'],
120
- prompt:
121
- `${TASK_AGENT_ROLE_PREFIX}\n\n`
122
- + 'You are a research subagent. Investigate with the read-only tools and report findings. '
123
- + 'Do NOT modify anything.',
124
- },
125
- };
126
-
127
- // The catalog as the Agent tool describes it: one line per type, description and
128
- // the "when to use" guidance the model needs to pick correctly.
129
- export function availableTypes() {
130
- return Object.entries(SUBAGENT_TYPES)
131
- .map(([id, t]) => `- ${id}: ${t.description}${t.whenToUse ? `\n ${t.whenToUse}` : ''}`)
132
- .join('\n');
133
- }
134
-
135
- // The tool allowlist a subagent of `type` may run. Always excludes the delegation
136
- // tools, whatever the profile declares, so nesting is impossible.
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
- const w = Number(m[1]);
351
- const h = Number(m[2]);
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
- const w = Number(m[1]);
358
- const h = Number(m[2]);
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;