mu-harness 0.16.9 → 0.16.11

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.
@@ -1,5 +1,5 @@
1
1
  import { runSubAgent } from './runner.js';
2
- const BASE_PROMPT = 'Delegate an isolated, fully-specifiable task to a named sub-agent instead of doing it inline; it returns only its final answer. Pick the matching sub-agent, brief it from scratch, then verify its answer before relying on it.';
2
+ const BASE_PROMPT = 'Delegate isolated, fully-specifiable tasks to named sub-agents instead of doing them inline; each returns only its final answer. List several tasks in one call to run them concurrently — put all independent work in a single call rather than one at a time. Brief each sub-agent from scratch, then verify its answer before relying on it.';
3
3
  export const createSubAgentTool = (deps) => {
4
4
  const roster = deps.registry.list()
5
5
  .filter((agent) => agent.name !== 'title')
@@ -11,26 +11,54 @@ export const createSubAgentTool = (deps) => {
11
11
  parameters: {
12
12
  type: 'object',
13
13
  properties: {
14
- agent: { type: 'string', description: 'Sub-agent name.' },
15
- task: { type: 'string', description: 'The task to delegate.' },
14
+ tasks: {
15
+ type: 'array',
16
+ description: 'One or more tasks to delegate; multiple entries run in parallel.',
17
+ minItems: 1,
18
+ items: {
19
+ type: 'object',
20
+ properties: {
21
+ agent: { type: 'string', description: 'Sub-agent name.' },
22
+ task: { type: 'string', description: 'The task to delegate.' },
23
+ },
24
+ required: ['agent', 'task'],
25
+ additionalProperties: false,
26
+ },
27
+ },
16
28
  },
17
- required: ['agent', 'task'],
29
+ required: ['tasks'],
18
30
  additionalProperties: false,
19
31
  },
20
32
  run: async (input, ctx) => {
21
- const { agent, task } = (input ?? {});
22
- if (!agent || !task)
23
- return [{ type: 'text', text: 'Error: subagent requires `agent` and `task`.' }];
24
- const def = deps.registry.get(agent);
25
- if (!def)
26
- return [{ type: 'text', text: `Error: unknown sub-agent "${agent}".` }];
27
- const result = await runSubAgent(def, task, {
28
- spawn: deps.spawn,
29
- runs: deps.runs,
30
- parentId: deps.parentId,
31
- signal: ctx.signal,
32
- });
33
- return [{ type: 'text', text: result.text }];
33
+ const { tasks } = (input ?? {});
34
+ if (!Array.isArray(tasks) || tasks.length === 0) {
35
+ return [{ type: 'text', text: 'Error: subagent requires a non-empty `tasks` array.' }];
36
+ }
37
+ const results = await Promise.all(tasks.map(({ agent, task }) => runOne(agent, task, deps, ctx.signal)));
38
+ const labeled = results.length > 1;
39
+ return results.map(({ agent, text }) => ({
40
+ type: 'text',
41
+ text: labeled ? `[${agent ?? 'unknown'}]\n${text}` : text,
42
+ }));
34
43
  },
35
44
  };
36
45
  };
46
+ const runOne = async (agent, task, deps, signal) => {
47
+ if (!agent || !task)
48
+ return { agent, text: 'Error: each task requires `agent` and `task`.' };
49
+ const def = deps.registry.get(agent);
50
+ if (!def)
51
+ return { agent, text: `Error: unknown sub-agent "${agent}".` };
52
+ try {
53
+ const result = await runSubAgent(def, task, {
54
+ spawn: deps.spawn,
55
+ runs: deps.runs,
56
+ parentId: deps.parentId,
57
+ signal,
58
+ });
59
+ return { agent, text: result.text };
60
+ }
61
+ catch (err) {
62
+ return { agent, text: `Error: ${err instanceof Error ? err.message : String(err)}` };
63
+ }
64
+ };
@@ -1,14 +1,26 @@
1
1
  import type { InputEvent } from '../events';
2
2
  import type { Component, Surface } from '../surface';
3
+ export interface ScrollViewOptions {
4
+ wheelStep?: number;
5
+ stickyHeader?: (info: {
6
+ scrollY: number;
7
+ width: number;
8
+ }) => Component | undefined;
9
+ footer?: () => Component | undefined;
10
+ }
3
11
  export declare class ScrollView implements Component {
4
12
  private content;
5
13
  private scrollY;
6
14
  private stick;
7
- constructor(content: Component);
15
+ private readonly wheelStep;
16
+ private readonly stickyHeader?;
17
+ private readonly footer?;
18
+ constructor(content: Component, opts?: ScrollViewOptions);
19
+ atBottom(): boolean;
8
20
  setContent(content: Component): void;
9
21
  scrollToBottom(): void;
10
22
  render(s: Surface): void;
11
23
  handleInput(event: InputEvent): void;
12
24
  private scrollBy;
13
25
  }
14
- export declare const scrollView: (content: Component) => ScrollView;
26
+ export declare const scrollView: (content: Component, opts?: ScrollViewOptions) => ScrollView;
@@ -2,8 +2,17 @@ export class ScrollView {
2
2
  content;
3
3
  scrollY = 0;
4
4
  stick = true;
5
- constructor(content) {
5
+ wheelStep;
6
+ stickyHeader;
7
+ footer;
8
+ constructor(content, opts = {}) {
6
9
  this.content = content;
10
+ this.wheelStep = Math.max(1, opts.wheelStep ?? 3);
11
+ this.stickyHeader = opts.stickyHeader;
12
+ this.footer = opts.footer;
13
+ }
14
+ atBottom() {
15
+ return this.stick;
7
16
  }
8
17
  setContent(content) {
9
18
  this.content = content;
@@ -21,13 +30,29 @@ export class ScrollView {
21
30
  }
22
31
  const scroll = Number.isFinite(s.height) ? this.scrollY : 0;
23
32
  s.child(this.content, { x: 0, y: -scroll, width: s.width, height: contentHeight });
33
+ if (this.stickyHeader && Number.isFinite(s.height) && !this.stick) {
34
+ const header = this.stickyHeader({ scrollY: this.scrollY, width: s.width });
35
+ if (header) {
36
+ const h = Math.min(s.measure(header, s.width), s.height);
37
+ if (h > 0)
38
+ s.child(header, { x: 0, y: 0, width: s.width, height: h });
39
+ }
40
+ }
41
+ if (this.footer && Number.isFinite(s.height) && !this.stick) {
42
+ const footer = this.footer();
43
+ if (footer) {
44
+ const h = Math.min(s.measure(footer, s.width), s.height);
45
+ if (h > 0)
46
+ s.child(footer, { x: 0, y: s.height - h, width: s.width, height: h });
47
+ }
48
+ }
24
49
  }
25
50
  handleInput(event) {
26
51
  if (event.type === 'mouse' && event.kind === 'wheel') {
27
52
  if (event.button === 'wheelUp')
28
- this.scrollBy(-1);
53
+ this.scrollBy(-this.wheelStep);
29
54
  else if (event.button === 'wheelDown')
30
- this.scrollBy(1);
55
+ this.scrollBy(this.wheelStep);
31
56
  return;
32
57
  }
33
58
  if (event.type !== 'key' || event.kind === 'release')
@@ -42,4 +67,4 @@ export class ScrollView {
42
67
  this.scrollY = Math.max(0, this.scrollY + delta);
43
68
  }
44
69
  }
45
- export const scrollView = (content) => new ScrollView(content);
70
+ export const scrollView = (content, opts) => new ScrollView(content, opts);
@@ -17,7 +17,7 @@ export declare class SelectList<T> implements Component {
17
17
  selectedItem(): SelectItem<T> | undefined;
18
18
  move(delta: number): void;
19
19
  render(s: Surface): void;
20
- handleInput(event: InputEvent): void;
20
+ handleInput(event: InputEvent): boolean | void;
21
21
  }
22
22
  export declare const selectList: <T>(items?: SelectItem<T>[], opts?: {
23
23
  maxRows?: number;
@@ -45,13 +45,15 @@ export class SelectList {
45
45
  }
46
46
  }
47
47
  handleInput(event) {
48
- if (event.type === 'mouse' && event.kind === 'press' && event.button === 'left') {
48
+ if (event.type === 'mouse') {
49
+ if (event.kind !== 'press' || event.button !== 'left')
50
+ return false;
49
51
  const index = this.top + (event.localY ?? 0);
50
52
  if (index >= 0 && index < this.items.length) {
51
53
  this.selected = index;
52
54
  this.onSelect?.(this.items[index]);
53
55
  }
54
- return;
56
+ return true;
55
57
  }
56
58
  if (event.type !== 'key' || event.kind === 'release')
57
59
  return;
@@ -2,7 +2,7 @@ export type { Component, Surface, SurfaceEntry } from './surface';
2
2
  export { measure, measureWidth, renderToBuffer } from './surface';
3
3
  export { box, type BoxOptions, column, flex, type FlexItem, modal, type ModalOptions, overlay, type OverlayOptions, row, text, toast, type ToastKind, type ToastOptions, } from './views';
4
4
  export { Editor, editor, type EditorOptions } from './components/editor';
5
- export { ScrollView, scrollView } from './components/scroll-view';
5
+ export { ScrollView, type ScrollViewOptions, scrollView } from './components/scroll-view';
6
6
  export { type SelectItem, SelectList, selectList } from './components/select-list';
7
7
  export { type Command, CommandPalette, commandPalette, type CommandPaletteOptions } from './components/command-palette';
8
8
  export { type LayerHandle, type ToastHandle, TUI, type TuiOptions } from './tui';
@@ -98,7 +98,13 @@ export class InputRouter {
98
98
  continue;
99
99
  if (!containsPoint(entry.rect, event.x, event.y))
100
100
  continue;
101
- entry.component.handleInput({ ...event, localX: event.x - entry.rect.x, localY: event.y - entry.rect.y });
101
+ const handled = entry.component.handleInput({
102
+ ...event,
103
+ localX: event.x - entry.rect.x,
104
+ localY: event.y - entry.rect.y,
105
+ });
106
+ if (handled === false)
107
+ continue;
102
108
  this.host.requestRender();
103
109
  return;
104
110
  }
@@ -257,6 +257,20 @@ function codepointToKey(code) {
257
257
  13: 'enter',
258
258
  27: 'escape',
259
259
  127: 'backspace',
260
+ 57344: 'escape',
261
+ 57345: 'enter',
262
+ 57346: 'tab',
263
+ 57347: 'backspace',
264
+ 57348: 'insert',
265
+ 57349: 'delete',
266
+ 57350: 'left',
267
+ 57351: 'right',
268
+ 57352: 'up',
269
+ 57353: 'down',
270
+ 57354: 'pageUp',
271
+ 57355: 'pageDown',
272
+ 57356: 'home',
273
+ 57357: 'end',
260
274
  };
261
275
  return named[code] ?? codepointToText(code) ?? `unknown:${code}`;
262
276
  }
@@ -3,7 +3,7 @@ import { type CellBuffer } from './layout/cellbuffer';
3
3
  import type { Color, Rect } from './layout/types';
4
4
  export interface Component {
5
5
  render(surface: Surface): void;
6
- handleInput?(event: InputEvent): void;
6
+ handleInput?(event: InputEvent): boolean | void;
7
7
  wantsKeyRelease?: boolean;
8
8
  }
9
9
  export interface Surface {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mu-harness",
3
- "version": "0.16.9",
3
+ "version": "0.16.11",
4
4
  "description": "Agent harness: createHarness wires mu-core into a host — XDG paths, model registry, plugins, disk-loaded agents & skills, sub-agents, sessions (JSONL + SQLite catalog), slash commands, permission/approval hooks, an optional scheduler, and a composable TUI chat app",
5
5
  "license": "MIT",
6
6
  "main": "./script/index.js",
@@ -16,8 +16,8 @@
16
16
  "dependencies": {
17
17
  "@swc/wasm-typescript": "^1.15.0",
18
18
  "croner": "^9.0.0",
19
- "mu-core": "^0.16.9",
20
- "mu-tui": "^0.16.9"
19
+ "mu-core": "^0.16.11",
20
+ "mu-tui": "^0.16.11"
21
21
  },
22
22
  "_generatedBy": "dnt@dev"
23
23
  }
@@ -2,7 +2,7 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.createSubAgentTool = void 0;
4
4
  const runner_js_1 = require("./runner.js");
5
- const BASE_PROMPT = 'Delegate an isolated, fully-specifiable task to a named sub-agent instead of doing it inline; it returns only its final answer. Pick the matching sub-agent, brief it from scratch, then verify its answer before relying on it.';
5
+ const BASE_PROMPT = 'Delegate isolated, fully-specifiable tasks to named sub-agents instead of doing them inline; each returns only its final answer. List several tasks in one call to run them concurrently — put all independent work in a single call rather than one at a time. Brief each sub-agent from scratch, then verify its answer before relying on it.';
6
6
  const createSubAgentTool = (deps) => {
7
7
  const roster = deps.registry.list()
8
8
  .filter((agent) => agent.name !== 'title')
@@ -14,27 +14,55 @@ const createSubAgentTool = (deps) => {
14
14
  parameters: {
15
15
  type: 'object',
16
16
  properties: {
17
- agent: { type: 'string', description: 'Sub-agent name.' },
18
- task: { type: 'string', description: 'The task to delegate.' },
17
+ tasks: {
18
+ type: 'array',
19
+ description: 'One or more tasks to delegate; multiple entries run in parallel.',
20
+ minItems: 1,
21
+ items: {
22
+ type: 'object',
23
+ properties: {
24
+ agent: { type: 'string', description: 'Sub-agent name.' },
25
+ task: { type: 'string', description: 'The task to delegate.' },
26
+ },
27
+ required: ['agent', 'task'],
28
+ additionalProperties: false,
29
+ },
30
+ },
19
31
  },
20
- required: ['agent', 'task'],
32
+ required: ['tasks'],
21
33
  additionalProperties: false,
22
34
  },
23
35
  run: async (input, ctx) => {
24
- const { agent, task } = (input ?? {});
25
- if (!agent || !task)
26
- return [{ type: 'text', text: 'Error: subagent requires `agent` and `task`.' }];
27
- const def = deps.registry.get(agent);
28
- if (!def)
29
- return [{ type: 'text', text: `Error: unknown sub-agent "${agent}".` }];
30
- const result = await (0, runner_js_1.runSubAgent)(def, task, {
31
- spawn: deps.spawn,
32
- runs: deps.runs,
33
- parentId: deps.parentId,
34
- signal: ctx.signal,
35
- });
36
- return [{ type: 'text', text: result.text }];
36
+ const { tasks } = (input ?? {});
37
+ if (!Array.isArray(tasks) || tasks.length === 0) {
38
+ return [{ type: 'text', text: 'Error: subagent requires a non-empty `tasks` array.' }];
39
+ }
40
+ const results = await Promise.all(tasks.map(({ agent, task }) => runOne(agent, task, deps, ctx.signal)));
41
+ const labeled = results.length > 1;
42
+ return results.map(({ agent, text }) => ({
43
+ type: 'text',
44
+ text: labeled ? `[${agent ?? 'unknown'}]\n${text}` : text,
45
+ }));
37
46
  },
38
47
  };
39
48
  };
40
49
  exports.createSubAgentTool = createSubAgentTool;
50
+ const runOne = async (agent, task, deps, signal) => {
51
+ if (!agent || !task)
52
+ return { agent, text: 'Error: each task requires `agent` and `task`.' };
53
+ const def = deps.registry.get(agent);
54
+ if (!def)
55
+ return { agent, text: `Error: unknown sub-agent "${agent}".` };
56
+ try {
57
+ const result = await (0, runner_js_1.runSubAgent)(def, task, {
58
+ spawn: deps.spawn,
59
+ runs: deps.runs,
60
+ parentId: deps.parentId,
61
+ signal,
62
+ });
63
+ return { agent, text: result.text };
64
+ }
65
+ catch (err) {
66
+ return { agent, text: `Error: ${err instanceof Error ? err.message : String(err)}` };
67
+ }
68
+ };
@@ -1,14 +1,26 @@
1
1
  import type { InputEvent } from '../events';
2
2
  import type { Component, Surface } from '../surface';
3
+ export interface ScrollViewOptions {
4
+ wheelStep?: number;
5
+ stickyHeader?: (info: {
6
+ scrollY: number;
7
+ width: number;
8
+ }) => Component | undefined;
9
+ footer?: () => Component | undefined;
10
+ }
3
11
  export declare class ScrollView implements Component {
4
12
  private content;
5
13
  private scrollY;
6
14
  private stick;
7
- constructor(content: Component);
15
+ private readonly wheelStep;
16
+ private readonly stickyHeader?;
17
+ private readonly footer?;
18
+ constructor(content: Component, opts?: ScrollViewOptions);
19
+ atBottom(): boolean;
8
20
  setContent(content: Component): void;
9
21
  scrollToBottom(): void;
10
22
  render(s: Surface): void;
11
23
  handleInput(event: InputEvent): void;
12
24
  private scrollBy;
13
25
  }
14
- export declare const scrollView: (content: Component) => ScrollView;
26
+ export declare const scrollView: (content: Component, opts?: ScrollViewOptions) => ScrollView;
@@ -5,8 +5,17 @@ class ScrollView {
5
5
  content;
6
6
  scrollY = 0;
7
7
  stick = true;
8
- constructor(content) {
8
+ wheelStep;
9
+ stickyHeader;
10
+ footer;
11
+ constructor(content, opts = {}) {
9
12
  this.content = content;
13
+ this.wheelStep = Math.max(1, opts.wheelStep ?? 3);
14
+ this.stickyHeader = opts.stickyHeader;
15
+ this.footer = opts.footer;
16
+ }
17
+ atBottom() {
18
+ return this.stick;
10
19
  }
11
20
  setContent(content) {
12
21
  this.content = content;
@@ -24,13 +33,29 @@ class ScrollView {
24
33
  }
25
34
  const scroll = Number.isFinite(s.height) ? this.scrollY : 0;
26
35
  s.child(this.content, { x: 0, y: -scroll, width: s.width, height: contentHeight });
36
+ if (this.stickyHeader && Number.isFinite(s.height) && !this.stick) {
37
+ const header = this.stickyHeader({ scrollY: this.scrollY, width: s.width });
38
+ if (header) {
39
+ const h = Math.min(s.measure(header, s.width), s.height);
40
+ if (h > 0)
41
+ s.child(header, { x: 0, y: 0, width: s.width, height: h });
42
+ }
43
+ }
44
+ if (this.footer && Number.isFinite(s.height) && !this.stick) {
45
+ const footer = this.footer();
46
+ if (footer) {
47
+ const h = Math.min(s.measure(footer, s.width), s.height);
48
+ if (h > 0)
49
+ s.child(footer, { x: 0, y: s.height - h, width: s.width, height: h });
50
+ }
51
+ }
27
52
  }
28
53
  handleInput(event) {
29
54
  if (event.type === 'mouse' && event.kind === 'wheel') {
30
55
  if (event.button === 'wheelUp')
31
- this.scrollBy(-1);
56
+ this.scrollBy(-this.wheelStep);
32
57
  else if (event.button === 'wheelDown')
33
- this.scrollBy(1);
58
+ this.scrollBy(this.wheelStep);
34
59
  return;
35
60
  }
36
61
  if (event.type !== 'key' || event.kind === 'release')
@@ -46,5 +71,5 @@ class ScrollView {
46
71
  }
47
72
  }
48
73
  exports.ScrollView = ScrollView;
49
- const scrollView = (content) => new ScrollView(content);
74
+ const scrollView = (content, opts) => new ScrollView(content, opts);
50
75
  exports.scrollView = scrollView;
@@ -17,7 +17,7 @@ export declare class SelectList<T> implements Component {
17
17
  selectedItem(): SelectItem<T> | undefined;
18
18
  move(delta: number): void;
19
19
  render(s: Surface): void;
20
- handleInput(event: InputEvent): void;
20
+ handleInput(event: InputEvent): boolean | void;
21
21
  }
22
22
  export declare const selectList: <T>(items?: SelectItem<T>[], opts?: {
23
23
  maxRows?: number;
@@ -48,13 +48,15 @@ class SelectList {
48
48
  }
49
49
  }
50
50
  handleInput(event) {
51
- if (event.type === 'mouse' && event.kind === 'press' && event.button === 'left') {
51
+ if (event.type === 'mouse') {
52
+ if (event.kind !== 'press' || event.button !== 'left')
53
+ return false;
52
54
  const index = this.top + (event.localY ?? 0);
53
55
  if (index >= 0 && index < this.items.length) {
54
56
  this.selected = index;
55
57
  this.onSelect?.(this.items[index]);
56
58
  }
57
- return;
59
+ return true;
58
60
  }
59
61
  if (event.type !== 'key' || event.kind === 'release')
60
62
  return;
@@ -2,7 +2,7 @@ export type { Component, Surface, SurfaceEntry } from './surface';
2
2
  export { measure, measureWidth, renderToBuffer } from './surface';
3
3
  export { box, type BoxOptions, column, flex, type FlexItem, modal, type ModalOptions, overlay, type OverlayOptions, row, text, toast, type ToastKind, type ToastOptions, } from './views';
4
4
  export { Editor, editor, type EditorOptions } from './components/editor';
5
- export { ScrollView, scrollView } from './components/scroll-view';
5
+ export { ScrollView, type ScrollViewOptions, scrollView } from './components/scroll-view';
6
6
  export { type SelectItem, SelectList, selectList } from './components/select-list';
7
7
  export { type Command, CommandPalette, commandPalette, type CommandPaletteOptions } from './components/command-palette';
8
8
  export { type LayerHandle, type ToastHandle, TUI, type TuiOptions } from './tui';
@@ -101,7 +101,13 @@ class InputRouter {
101
101
  continue;
102
102
  if (!(0, insets_1.containsPoint)(entry.rect, event.x, event.y))
103
103
  continue;
104
- entry.component.handleInput({ ...event, localX: event.x - entry.rect.x, localY: event.y - entry.rect.y });
104
+ const handled = entry.component.handleInput({
105
+ ...event,
106
+ localX: event.x - entry.rect.x,
107
+ localY: event.y - entry.rect.y,
108
+ });
109
+ if (handled === false)
110
+ continue;
105
111
  this.host.requestRender();
106
112
  return;
107
113
  }
@@ -260,6 +260,20 @@ function codepointToKey(code) {
260
260
  13: 'enter',
261
261
  27: 'escape',
262
262
  127: 'backspace',
263
+ 57344: 'escape',
264
+ 57345: 'enter',
265
+ 57346: 'tab',
266
+ 57347: 'backspace',
267
+ 57348: 'insert',
268
+ 57349: 'delete',
269
+ 57350: 'left',
270
+ 57351: 'right',
271
+ 57352: 'up',
272
+ 57353: 'down',
273
+ 57354: 'pageUp',
274
+ 57355: 'pageDown',
275
+ 57356: 'home',
276
+ 57357: 'end',
263
277
  };
264
278
  return named[code] ?? codepointToText(code) ?? `unknown:${code}`;
265
279
  }
@@ -3,7 +3,7 @@ import { type CellBuffer } from './layout/cellbuffer';
3
3
  import type { Color, Rect } from './layout/types';
4
4
  export interface Component {
5
5
  render(surface: Surface): void;
6
- handleInput?(event: InputEvent): void;
6
+ handleInput?(event: InputEvent): boolean | void;
7
7
  wantsKeyRelease?: boolean;
8
8
  }
9
9
  export interface Surface {