mu-harness 0.17.0 → 0.17.2

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,3 +1,4 @@
1
+ import os from 'node:os';
1
2
  import { join } from 'node:path';
2
3
  import process from 'node:process';
3
4
  import { createAgentRegistry, grantArg, loadAgents, toolDecision, toolNames } from '../agents/index.js';
@@ -10,6 +11,7 @@ import { createScheduler, createScheduleTaskTool, createTasksCommand, createTask
10
11
  import { createAgentSession, createSessionCatalog, createSessionManager, createSessionStore, persistTo, runTitler, } from '../session/index.js';
11
12
  import { createRunSkillTool, createSkillRegistry, createSkillTool, createSkillWriterTool, loadSkills, runSkill, } from '../skills/index.js';
12
13
  import { createSubAgentRegistry, createSubAgentTool, runSubAgent } from '../subAgents/index.js';
14
+ import { environmentBlock } from './environment.js';
13
15
  import { createModelRegistry } from './models.js';
14
16
  const TITLE_AGENT = {
15
17
  name: 'title',
@@ -22,13 +24,27 @@ export const createHarness = async (options) => {
22
24
  const cwd = options.cwd ?? process.cwd();
23
25
  const config = createHarnessConfig({ hostName, xdg });
24
26
  const models = createModelRegistry({ providers, default: model });
25
- const plugins = createPluginStore({ dir: join(config.configDir, 'plugins') });
27
+ const pluginsDir = join(config.configDir, 'plugins');
28
+ const agentsDir = join(config.configDir, 'agents');
29
+ const plugins = createPluginStore({ dir: pluginsDir });
26
30
  const newId = () => crypto.randomUUID();
27
31
  const pluginAgents = (sessionDefaults.plugins ?? []).flatMap((plugin) => plugin.agents ?? []);
28
- const diskAgents = await loadAgents(join(config.configDir, 'agents'));
32
+ const diskAgents = await loadAgents(agentsDir);
29
33
  const agents = createAgentRegistry([...hostAgents, ...pluginAgents, ...diskAgents]);
30
34
  const skillsDir = join(config.configDir, 'skills');
31
35
  const cwdSkillsDir = join(cwd, 'skills');
36
+ const envBlock = environmentBlock({
37
+ os: `${os.platform()} ${os.release()} (${os.arch()})`,
38
+ configDir: config.configDir,
39
+ pluginsDir,
40
+ skillsDir,
41
+ agentsDir,
42
+ hostName,
43
+ hostSourceUrl: options.sourceUrl,
44
+ });
45
+ const envHook = {
46
+ prepareRequest: ({ system }) => ({ system: system ? `${system}\n\n${envBlock}` : envBlock }),
47
+ };
32
48
  const pluginSkills = (sessionDefaults.plugins ?? []).flatMap((plugin) => plugin.skills ?? []);
33
49
  const cwdSkills = await loadSkills(cwdSkillsDir);
34
50
  const diskSkills = await loadSkills(skillsDir);
@@ -77,7 +93,7 @@ export const createHarness = async (options) => {
77
93
  });
78
94
  const spawn = (agent) => persistTo(store, persona(agent, {
79
95
  tools: sessionTools(agent),
80
- hooks: mergeHooks([sessionDefaults.hooks, allowList(toolNames(agent)), approvalHook(() => agent)]),
96
+ hooks: mergeHooks([sessionDefaults.hooks, allowList(toolNames(agent)), approvalHook(() => agent), envHook]),
81
97
  }));
82
98
  const scheduler = enableScheduler && tasks
83
99
  ? createScheduler({
@@ -125,7 +141,7 @@ export const createHarness = async (options) => {
125
141
  },
126
142
  revive: ({ id, model: ref, messages }) => createAgentSession({
127
143
  ...sessionDefaults,
128
- hooks: mergeHooks([sessionDefaults.hooks, approvalHook(() => approvals?.activeAgent())]),
144
+ hooks: mergeHooks([sessionDefaults.hooks, approvalHook(() => approvals?.activeAgent()), envHook]),
129
145
  tools: sessionTools(undefined, [createSubAgentTool({ registry: agents, spawn, runs, parentId: id })]),
130
146
  ...models.resolve(ref),
131
147
  id,
@@ -0,0 +1,10 @@
1
+ export interface EnvironmentInfo {
2
+ os: string;
3
+ configDir: string;
4
+ pluginsDir: string;
5
+ skillsDir: string;
6
+ agentsDir: string;
7
+ hostName: string;
8
+ hostSourceUrl?: string;
9
+ }
10
+ export declare function environmentBlock(info: EnvironmentInfo): string;
@@ -0,0 +1,14 @@
1
+ const HARNESS_SOURCE_URL = 'https://github.com/gaetan-puleo/mu-ai';
2
+ export function environmentBlock(info) {
3
+ const lines = [
4
+ `Operating system: ${info.os}`,
5
+ `Config directory: ${info.configDir}`,
6
+ `Plugins directory: ${info.pluginsDir}`,
7
+ `Skills directory: ${info.skillsDir}`,
8
+ `Sub-agents directory: ${info.agentsDir}`,
9
+ `Harness (mu) source code: ${HARNESS_SOURCE_URL}`,
10
+ ];
11
+ if (info.hostSourceUrl)
12
+ lines.push(`${info.hostName} source code: ${info.hostSourceUrl}`);
13
+ return `<env>\n${lines.join('\n')}\n</env>`;
14
+ }
@@ -23,6 +23,7 @@ export type HarnessOptions = HarnessConfigOptions & Omit<AgentSessionConfig, 'pr
23
23
  title?: boolean;
24
24
  titleModel?: string;
25
25
  cwd?: string;
26
+ sourceUrl?: string;
26
27
  scheduler?: boolean;
27
28
  approvals?: {
28
29
  manager: ApprovalManager;
@@ -54,12 +54,12 @@ export interface ChatHost {
54
54
  */
55
55
  banner?: string;
56
56
  /**
57
- * Lean input presentation: drop the surface-background input frame, the
58
- * model/provider/agent footer inside the input, and the context readout on
59
- * the status bar — leaving a bare prompt + editor. The {@link ChatHost.banner}
57
+ * Lean input presentation: keep the surface-background input frame but drop
58
+ * the model/provider/agent footer inside the input and the context readout on
59
+ * the status bar — leaving a framed prompt + editor. The {@link ChatHost.banner}
60
60
  * splash is independent and still shown if set. Hosts that want the full
61
- * information-rich input (surface frame, model · provider · @agent footer,
62
- * token/context usage in the status line) leave this unset (the default).
61
+ * information-rich input (model · provider · @agent footer, token/context usage
62
+ * in the status line) leave this unset (the default).
63
63
  */
64
64
  minimal?: boolean;
65
65
  onExit(code: number): void;
@@ -1116,8 +1116,6 @@ export class ChatApp {
1116
1116
  }
1117
1117
  inputPanel() {
1118
1118
  const inner = this.approvalView() ?? this.editorInner();
1119
- if (this.minimal)
1120
- return box(inner, { padding: 0 });
1121
1119
  return box(inner, { background: this.theme().colors.surface, padding: 1 });
1122
1120
  }
1123
1121
  editorInner() {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mu-harness",
3
- "version": "0.17.0",
3
+ "version": "0.17.2",
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",
@@ -23,8 +23,8 @@
23
23
  "@swc/wasm-typescript": "^1.15.0",
24
24
  "cli-highlight": "^2.1.11",
25
25
  "croner": "^9.0.0",
26
- "mu-core": "^0.17.0",
27
- "mu-tui": "^0.17.0"
26
+ "mu-core": "^0.17.2",
27
+ "mu-tui": "^0.17.2"
28
28
  },
29
29
  "_generatedBy": "dnt@dev",
30
30
  "types": "./esm/index.d.ts"
@@ -4,6 +4,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
4
4
  };
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
6
  exports.createHarness = void 0;
7
+ const node_os_1 = __importDefault(require("node:os"));
7
8
  const node_path_1 = require("node:path");
8
9
  const node_process_1 = __importDefault(require("node:process"));
9
10
  const index_js_1 = require("../agents/index.js");
@@ -16,6 +17,7 @@ const index_js_7 = require("../scheduler/index.js");
16
17
  const index_js_8 = require("../session/index.js");
17
18
  const index_js_9 = require("../skills/index.js");
18
19
  const index_js_10 = require("../subAgents/index.js");
20
+ const environment_js_1 = require("./environment.js");
19
21
  const models_js_1 = require("./models.js");
20
22
  const TITLE_AGENT = {
21
23
  name: 'title',
@@ -28,13 +30,27 @@ const createHarness = async (options) => {
28
30
  const cwd = options.cwd ?? node_process_1.default.cwd();
29
31
  const config = (0, index_js_3.createHarnessConfig)({ hostName, xdg });
30
32
  const models = (0, models_js_1.createModelRegistry)({ providers, default: model });
31
- const plugins = (0, index_js_6.createPluginStore)({ dir: (0, node_path_1.join)(config.configDir, 'plugins') });
33
+ const pluginsDir = (0, node_path_1.join)(config.configDir, 'plugins');
34
+ const agentsDir = (0, node_path_1.join)(config.configDir, 'agents');
35
+ const plugins = (0, index_js_6.createPluginStore)({ dir: pluginsDir });
32
36
  const newId = () => crypto.randomUUID();
33
37
  const pluginAgents = (sessionDefaults.plugins ?? []).flatMap((plugin) => plugin.agents ?? []);
34
- const diskAgents = await (0, index_js_1.loadAgents)((0, node_path_1.join)(config.configDir, 'agents'));
38
+ const diskAgents = await (0, index_js_1.loadAgents)(agentsDir);
35
39
  const agents = (0, index_js_1.createAgentRegistry)([...hostAgents, ...pluginAgents, ...diskAgents]);
36
40
  const skillsDir = (0, node_path_1.join)(config.configDir, 'skills');
37
41
  const cwdSkillsDir = (0, node_path_1.join)(cwd, 'skills');
42
+ const envBlock = (0, environment_js_1.environmentBlock)({
43
+ os: `${node_os_1.default.platform()} ${node_os_1.default.release()} (${node_os_1.default.arch()})`,
44
+ configDir: config.configDir,
45
+ pluginsDir,
46
+ skillsDir,
47
+ agentsDir,
48
+ hostName,
49
+ hostSourceUrl: options.sourceUrl,
50
+ });
51
+ const envHook = {
52
+ prepareRequest: ({ system }) => ({ system: system ? `${system}\n\n${envBlock}` : envBlock }),
53
+ };
38
54
  const pluginSkills = (sessionDefaults.plugins ?? []).flatMap((plugin) => plugin.skills ?? []);
39
55
  const cwdSkills = await (0, index_js_9.loadSkills)(cwdSkillsDir);
40
56
  const diskSkills = await (0, index_js_9.loadSkills)(skillsDir);
@@ -83,7 +99,7 @@ const createHarness = async (options) => {
83
99
  });
84
100
  const spawn = (agent) => (0, index_js_8.persistTo)(store, persona(agent, {
85
101
  tools: sessionTools(agent),
86
- hooks: (0, index_js_4.mergeHooks)([sessionDefaults.hooks, (0, index_js_5.allowList)((0, index_js_1.toolNames)(agent)), approvalHook(() => agent)]),
102
+ hooks: (0, index_js_4.mergeHooks)([sessionDefaults.hooks, (0, index_js_5.allowList)((0, index_js_1.toolNames)(agent)), approvalHook(() => agent), envHook]),
87
103
  }));
88
104
  const scheduler = enableScheduler && tasks
89
105
  ? (0, index_js_7.createScheduler)({
@@ -131,7 +147,7 @@ const createHarness = async (options) => {
131
147
  },
132
148
  revive: ({ id, model: ref, messages }) => (0, index_js_8.createAgentSession)({
133
149
  ...sessionDefaults,
134
- hooks: (0, index_js_4.mergeHooks)([sessionDefaults.hooks, approvalHook(() => approvals?.activeAgent())]),
150
+ hooks: (0, index_js_4.mergeHooks)([sessionDefaults.hooks, approvalHook(() => approvals?.activeAgent()), envHook]),
135
151
  tools: sessionTools(undefined, [(0, index_js_10.createSubAgentTool)({ registry: agents, spawn, runs, parentId: id })]),
136
152
  ...models.resolve(ref),
137
153
  id,
@@ -0,0 +1,10 @@
1
+ export interface EnvironmentInfo {
2
+ os: string;
3
+ configDir: string;
4
+ pluginsDir: string;
5
+ skillsDir: string;
6
+ agentsDir: string;
7
+ hostName: string;
8
+ hostSourceUrl?: string;
9
+ }
10
+ export declare function environmentBlock(info: EnvironmentInfo): string;
@@ -0,0 +1,17 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.environmentBlock = environmentBlock;
4
+ const HARNESS_SOURCE_URL = 'https://github.com/gaetan-puleo/mu-ai';
5
+ function environmentBlock(info) {
6
+ const lines = [
7
+ `Operating system: ${info.os}`,
8
+ `Config directory: ${info.configDir}`,
9
+ `Plugins directory: ${info.pluginsDir}`,
10
+ `Skills directory: ${info.skillsDir}`,
11
+ `Sub-agents directory: ${info.agentsDir}`,
12
+ `Harness (mu) source code: ${HARNESS_SOURCE_URL}`,
13
+ ];
14
+ if (info.hostSourceUrl)
15
+ lines.push(`${info.hostName} source code: ${info.hostSourceUrl}`);
16
+ return `<env>\n${lines.join('\n')}\n</env>`;
17
+ }
@@ -23,6 +23,7 @@ export type HarnessOptions = HarnessConfigOptions & Omit<AgentSessionConfig, 'pr
23
23
  title?: boolean;
24
24
  titleModel?: string;
25
25
  cwd?: string;
26
+ sourceUrl?: string;
26
27
  scheduler?: boolean;
27
28
  approvals?: {
28
29
  manager: ApprovalManager;
@@ -54,12 +54,12 @@ export interface ChatHost {
54
54
  */
55
55
  banner?: string;
56
56
  /**
57
- * Lean input presentation: drop the surface-background input frame, the
58
- * model/provider/agent footer inside the input, and the context readout on
59
- * the status bar — leaving a bare prompt + editor. The {@link ChatHost.banner}
57
+ * Lean input presentation: keep the surface-background input frame but drop
58
+ * the model/provider/agent footer inside the input and the context readout on
59
+ * the status bar — leaving a framed prompt + editor. The {@link ChatHost.banner}
60
60
  * splash is independent and still shown if set. Hosts that want the full
61
- * information-rich input (surface frame, model · provider · @agent footer,
62
- * token/context usage in the status line) leave this unset (the default).
61
+ * information-rich input (model · provider · @agent footer, token/context usage
62
+ * in the status line) leave this unset (the default).
63
63
  */
64
64
  minimal?: boolean;
65
65
  onExit(code: number): void;
@@ -1119,8 +1119,6 @@ class ChatApp {
1119
1119
  }
1120
1120
  inputPanel() {
1121
1121
  const inner = this.approvalView() ?? this.editorInner();
1122
- if (this.minimal)
1123
- return (0, mu_tui_1.box)(inner, { padding: 0 });
1124
1122
  return (0, mu_tui_1.box)(inner, { background: this.theme().colors.surface, padding: 1 });
1125
1123
  }
1126
1124
  editorInner() {