mu-harness 0.16.8 → 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.
- package/esm/harness/npm/src/scheduler/tool.js +1 -2
- package/esm/harness/npm/src/session/agent-session.js +1 -0
- package/esm/harness/npm/src/session/manager.js +3 -0
- package/esm/harness/npm/src/session/persist.js +3 -0
- package/esm/harness/npm/src/session/types.d.ts +2 -1
- package/esm/harness/npm/src/skills/run.js +1 -2
- package/esm/harness/npm/src/skills/tool.js +4 -4
- package/esm/harness/npm/src/skills/writer.js +1 -2
- package/esm/harness/npm/src/subAgents/tool.js +46 -19
- package/esm/tui/src/components/scroll-view.d.ts +14 -2
- package/esm/tui/src/components/scroll-view.js +29 -4
- package/esm/tui/src/components/select-list.d.ts +1 -1
- package/esm/tui/src/components/select-list.js +4 -2
- package/esm/tui/src/index.d.ts +1 -1
- package/esm/tui/src/inputRouter.js +7 -1
- package/esm/tui/src/keyboard.js +14 -0
- package/esm/tui/src/surface.d.ts +1 -1
- package/package.json +3 -3
- package/script/harness/npm/src/scheduler/tool.js +1 -2
- package/script/harness/npm/src/session/agent-session.js +1 -0
- package/script/harness/npm/src/session/manager.js +3 -0
- package/script/harness/npm/src/session/persist.js +3 -0
- package/script/harness/npm/src/session/types.d.ts +2 -1
- package/script/harness/npm/src/skills/run.js +1 -2
- package/script/harness/npm/src/skills/tool.js +4 -4
- package/script/harness/npm/src/skills/writer.js +1 -2
- package/script/harness/npm/src/subAgents/tool.js +46 -19
- package/script/tui/src/components/scroll-view.d.ts +14 -2
- package/script/tui/src/components/scroll-view.js +29 -4
- package/script/tui/src/components/select-list.d.ts +1 -1
- package/script/tui/src/components/select-list.js +4 -2
- package/script/tui/src/index.d.ts +1 -1
- package/script/tui/src/inputRouter.js +7 -1
- package/script/tui/src/keyboard.js +14 -0
- package/script/tui/src/surface.d.ts +1 -1
|
@@ -10,8 +10,7 @@ const toSchedule = (raw) => {
|
|
|
10
10
|
};
|
|
11
11
|
export const createScheduleTaskTool = (deps) => ({
|
|
12
12
|
name: 'schedule_task',
|
|
13
|
-
description: 'Persist a
|
|
14
|
-
prompt: 'Use `schedule_task` to register work that should run later or on a schedule: a `cron` expression, an `everyMs` heartbeat, or a one-shot `at` timestamp. It invokes a skill on a prompt, like `run_skill`.',
|
|
13
|
+
description: 'Persist work to run later or on a schedule: a `cron` expression, an `everyMs` heartbeat, or a one-shot `at` timestamp. It invokes a skill on a prompt (like `run_skill`) via a sub-agent.',
|
|
15
14
|
parameters: {
|
|
16
15
|
type: 'object',
|
|
17
16
|
properties: {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { ContentPart, LoopEvent, Message } from 'mu-core';
|
|
1
|
+
import type { ContentPart, LoopEvent, Message, Tool } from 'mu-core';
|
|
2
2
|
export type AgentSessionEvent = {
|
|
3
3
|
type: 'turn_start';
|
|
4
4
|
input: Message;
|
|
@@ -11,6 +11,7 @@ export type AgentSessionEvent = {
|
|
|
11
11
|
export interface AgentSession {
|
|
12
12
|
readonly id: string;
|
|
13
13
|
readonly messages: readonly Message[];
|
|
14
|
+
readonly tools: readonly Tool[];
|
|
14
15
|
send(input: string | ContentPart[]): Promise<void>;
|
|
15
16
|
abort(): void;
|
|
16
17
|
subscribe(listener: (event: AgentSessionEvent) => void): () => void;
|
|
@@ -18,8 +18,7 @@ export const runSkill = async (deps, args) => {
|
|
|
18
18
|
};
|
|
19
19
|
export const createRunSkillTool = (deps) => ({
|
|
20
20
|
name: 'run_skill',
|
|
21
|
-
description: 'Invoke a sub-agent equipped with a named skill to carry out a
|
|
22
|
-
prompt: 'To carry out a self-contained task under a skill, call `run_skill` with the skill name, the task, and the agent persona to run it as.',
|
|
21
|
+
description: 'Invoke a sub-agent equipped with a named skill to carry out a self-contained task — pass the skill name, the task, and the agent persona to run it as. Returns its final answer.',
|
|
23
22
|
parameters: {
|
|
24
23
|
type: 'object',
|
|
25
24
|
properties: {
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
export const createSkillTool = (registry) => ({
|
|
2
2
|
name: 'skill',
|
|
3
|
-
description
|
|
4
|
-
|
|
3
|
+
get description() {
|
|
4
|
+
const base = "Load a named skill's instructions into the conversation, then follow them.";
|
|
5
5
|
const list = registry.list();
|
|
6
6
|
if (!list.length)
|
|
7
|
-
return
|
|
7
|
+
return base;
|
|
8
8
|
const catalog = list.map((skill) => `- ${skill.name}${skill.description ? `: ${skill.description}` : ''}`).join('\n');
|
|
9
|
-
return
|
|
9
|
+
return `${base} When a request matches one of these skills, call \`skill\` with its name BEFORE acting:\n${catalog}`;
|
|
10
10
|
},
|
|
11
11
|
parameters: {
|
|
12
12
|
type: 'object',
|
|
@@ -4,8 +4,7 @@ import { parseSkill } from './parser.js';
|
|
|
4
4
|
const slug = (name) => name.trim().toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/^-+|-+$/g, '');
|
|
5
5
|
export const createSkillWriterTool = (deps) => ({
|
|
6
6
|
name: 'create_skill',
|
|
7
|
-
description: 'Create a reusable skill
|
|
8
|
-
prompt: 'When you discover a reusable workflow worth keeping, capture it with `create_skill` (name, description, instructions). Use `scope: "local"` for a skill specific to this project, or `scope: "config"` to make it available across all projects. It can then be loaded via `skill`.',
|
|
7
|
+
description: 'Create a reusable skill (name, description, instructions) the agent can load on demand later — capture a reusable workflow worth keeping. `scope: "local"` saves it to this project, `scope: "config"` makes it available across all projects; load it later via `skill`.',
|
|
9
8
|
parameters: {
|
|
10
9
|
type: 'object',
|
|
11
10
|
properties: {
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { runSubAgent } from './runner.js';
|
|
2
|
-
const BASE_PROMPT = 'Delegate
|
|
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')
|
|
@@ -7,31 +7,58 @@ export const createSubAgentTool = (deps) => {
|
|
|
7
7
|
.join('\n');
|
|
8
8
|
return {
|
|
9
9
|
name: 'subagent',
|
|
10
|
-
description:
|
|
11
|
-
prompt: roster ? `${BASE_PROMPT}\n\nAvailable sub-agents:\n${roster}` : BASE_PROMPT,
|
|
10
|
+
description: roster ? `${BASE_PROMPT}\n\nAvailable sub-agents:\n${roster}` : BASE_PROMPT,
|
|
12
11
|
parameters: {
|
|
13
12
|
type: 'object',
|
|
14
13
|
properties: {
|
|
15
|
-
|
|
16
|
-
|
|
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
|
+
},
|
|
17
28
|
},
|
|
18
|
-
required: ['
|
|
29
|
+
required: ['tasks'],
|
|
19
30
|
additionalProperties: false,
|
|
20
31
|
},
|
|
21
32
|
run: async (input, ctx) => {
|
|
22
|
-
const {
|
|
23
|
-
if (!
|
|
24
|
-
return [{ type: 'text', text: 'Error: subagent requires `
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
signal: ctx.signal,
|
|
33
|
-
});
|
|
34
|
-
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
|
+
}));
|
|
35
43
|
},
|
|
36
44
|
};
|
|
37
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
|
-
|
|
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
|
-
|
|
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(-
|
|
53
|
+
this.scrollBy(-this.wheelStep);
|
|
29
54
|
else if (event.button === 'wheelDown')
|
|
30
|
-
this.scrollBy(
|
|
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'
|
|
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;
|
package/esm/tui/src/index.d.ts
CHANGED
|
@@ -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({
|
|
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
|
}
|
package/esm/tui/src/keyboard.js
CHANGED
|
@@ -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
|
}
|
package/esm/tui/src/surface.d.ts
CHANGED
|
@@ -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.
|
|
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.
|
|
20
|
-
"mu-tui": "^0.16.
|
|
19
|
+
"mu-core": "^0.16.11",
|
|
20
|
+
"mu-tui": "^0.16.11"
|
|
21
21
|
},
|
|
22
22
|
"_generatedBy": "dnt@dev"
|
|
23
23
|
}
|
|
@@ -13,8 +13,7 @@ const toSchedule = (raw) => {
|
|
|
13
13
|
};
|
|
14
14
|
const createScheduleTaskTool = (deps) => ({
|
|
15
15
|
name: 'schedule_task',
|
|
16
|
-
description: 'Persist a
|
|
17
|
-
prompt: 'Use `schedule_task` to register work that should run later or on a schedule: a `cron` expression, an `everyMs` heartbeat, or a one-shot `at` timestamp. It invokes a skill on a prompt, like `run_skill`.',
|
|
16
|
+
description: 'Persist work to run later or on a schedule: a `cron` expression, an `everyMs` heartbeat, or a one-shot `at` timestamp. It invokes a skill on a prompt (like `run_skill`) via a sub-agent.',
|
|
18
17
|
parameters: {
|
|
19
18
|
type: 'object',
|
|
20
19
|
properties: {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { ContentPart, LoopEvent, Message } from 'mu-core';
|
|
1
|
+
import type { ContentPart, LoopEvent, Message, Tool } from 'mu-core';
|
|
2
2
|
export type AgentSessionEvent = {
|
|
3
3
|
type: 'turn_start';
|
|
4
4
|
input: Message;
|
|
@@ -11,6 +11,7 @@ export type AgentSessionEvent = {
|
|
|
11
11
|
export interface AgentSession {
|
|
12
12
|
readonly id: string;
|
|
13
13
|
readonly messages: readonly Message[];
|
|
14
|
+
readonly tools: readonly Tool[];
|
|
14
15
|
send(input: string | ContentPart[]): Promise<void>;
|
|
15
16
|
abort(): void;
|
|
16
17
|
subscribe(listener: (event: AgentSessionEvent) => void): () => void;
|
|
@@ -22,8 +22,7 @@ const runSkill = async (deps, args) => {
|
|
|
22
22
|
exports.runSkill = runSkill;
|
|
23
23
|
const createRunSkillTool = (deps) => ({
|
|
24
24
|
name: 'run_skill',
|
|
25
|
-
description: 'Invoke a sub-agent equipped with a named skill to carry out a
|
|
26
|
-
prompt: 'To carry out a self-contained task under a skill, call `run_skill` with the skill name, the task, and the agent persona to run it as.',
|
|
25
|
+
description: 'Invoke a sub-agent equipped with a named skill to carry out a self-contained task — pass the skill name, the task, and the agent persona to run it as. Returns its final answer.',
|
|
27
26
|
parameters: {
|
|
28
27
|
type: 'object',
|
|
29
28
|
properties: {
|
|
@@ -3,13 +3,13 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.createSkillTool = void 0;
|
|
4
4
|
const createSkillTool = (registry) => ({
|
|
5
5
|
name: 'skill',
|
|
6
|
-
description
|
|
7
|
-
|
|
6
|
+
get description() {
|
|
7
|
+
const base = "Load a named skill's instructions into the conversation, then follow them.";
|
|
8
8
|
const list = registry.list();
|
|
9
9
|
if (!list.length)
|
|
10
|
-
return
|
|
10
|
+
return base;
|
|
11
11
|
const catalog = list.map((skill) => `- ${skill.name}${skill.description ? `: ${skill.description}` : ''}`).join('\n');
|
|
12
|
-
return
|
|
12
|
+
return `${base} When a request matches one of these skills, call \`skill\` with its name BEFORE acting:\n${catalog}`;
|
|
13
13
|
},
|
|
14
14
|
parameters: {
|
|
15
15
|
type: 'object',
|
|
@@ -7,8 +7,7 @@ const parser_js_1 = require("./parser.js");
|
|
|
7
7
|
const slug = (name) => name.trim().toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/^-+|-+$/g, '');
|
|
8
8
|
const createSkillWriterTool = (deps) => ({
|
|
9
9
|
name: 'create_skill',
|
|
10
|
-
description: 'Create a reusable skill
|
|
11
|
-
prompt: 'When you discover a reusable workflow worth keeping, capture it with `create_skill` (name, description, instructions). Use `scope: "local"` for a skill specific to this project, or `scope: "config"` to make it available across all projects. It can then be loaded via `skill`.',
|
|
10
|
+
description: 'Create a reusable skill (name, description, instructions) the agent can load on demand later — capture a reusable workflow worth keeping. `scope: "local"` saves it to this project, `scope: "config"` makes it available across all projects; load it later via `skill`.',
|
|
12
11
|
parameters: {
|
|
13
12
|
type: 'object',
|
|
14
13
|
properties: {
|
|
@@ -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
|
|
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')
|
|
@@ -10,32 +10,59 @@ const createSubAgentTool = (deps) => {
|
|
|
10
10
|
.join('\n');
|
|
11
11
|
return {
|
|
12
12
|
name: 'subagent',
|
|
13
|
-
description:
|
|
14
|
-
prompt: roster ? `${BASE_PROMPT}\n\nAvailable sub-agents:\n${roster}` : BASE_PROMPT,
|
|
13
|
+
description: roster ? `${BASE_PROMPT}\n\nAvailable sub-agents:\n${roster}` : BASE_PROMPT,
|
|
15
14
|
parameters: {
|
|
16
15
|
type: 'object',
|
|
17
16
|
properties: {
|
|
18
|
-
|
|
19
|
-
|
|
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
|
+
},
|
|
20
31
|
},
|
|
21
|
-
required: ['
|
|
32
|
+
required: ['tasks'],
|
|
22
33
|
additionalProperties: false,
|
|
23
34
|
},
|
|
24
35
|
run: async (input, ctx) => {
|
|
25
|
-
const {
|
|
26
|
-
if (!
|
|
27
|
-
return [{ type: 'text', text: 'Error: subagent requires `
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
signal: ctx.signal,
|
|
36
|
-
});
|
|
37
|
-
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
|
+
}));
|
|
38
46
|
},
|
|
39
47
|
};
|
|
40
48
|
};
|
|
41
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
|
-
|
|
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
|
-
|
|
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(-
|
|
56
|
+
this.scrollBy(-this.wheelStep);
|
|
32
57
|
else if (event.button === 'wheelDown')
|
|
33
|
-
this.scrollBy(
|
|
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'
|
|
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({
|
|
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 {
|