mu-harness 0.16.11 → 0.16.13
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/agents/index.d.ts +2 -2
- package/esm/harness/npm/src/agents/index.js +1 -1
- package/esm/harness/npm/src/agents/parser.js +16 -3
- package/esm/harness/npm/src/agents/registry.d.ts +2 -1
- package/esm/harness/npm/src/agents/registry.js +47 -12
- package/esm/harness/npm/src/agents/types.d.ts +2 -1
- package/esm/harness/npm/src/commands/defaults.d.ts +3 -1
- package/esm/harness/npm/src/commands/defaults.js +2 -2
- package/esm/harness/npm/src/harness/create.js +23 -10
- package/package.json +3 -3
- package/script/harness/npm/src/agents/index.d.ts +2 -2
- package/script/harness/npm/src/agents/index.js +2 -1
- package/script/harness/npm/src/agents/parser.js +16 -3
- package/script/harness/npm/src/agents/registry.d.ts +2 -1
- package/script/harness/npm/src/agents/registry.js +50 -14
- package/script/harness/npm/src/agents/types.d.ts +2 -1
- package/script/harness/npm/src/commands/defaults.d.ts +3 -1
- package/script/harness/npm/src/commands/defaults.js +2 -2
- package/script/harness/npm/src/harness/create.js +22 -9
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export type { Agent, ToolDecision, ToolGrants } from './types.js';
|
|
2
|
-
export { type AgentRegistry, createAgentRegistry, toolDecision, toolNames } from './registry.js';
|
|
1
|
+
export type { Agent, GrantValue, ToolDecision, ToolGrants } from './types.js';
|
|
2
|
+
export { type AgentRegistry, createAgentRegistry, grantArg, toolDecision, toolNames } from './registry.js';
|
|
3
3
|
export { parseAgent } from './parser.js';
|
|
4
4
|
export { loadAgents } from './loader.js';
|
|
@@ -1,9 +1,17 @@
|
|
|
1
1
|
import { parseFrontmatter, str } from '../common/index.js';
|
|
2
2
|
const DECISIONS = new Set(['allow', 'ask', 'deny']);
|
|
3
|
+
const isDecision = (value) => typeof value === 'string' && DECISIONS.has(value);
|
|
3
4
|
const parseStringList = (raw) => (Array.isArray(raw) ? raw : raw.split(','))
|
|
4
5
|
.filter((entry) => typeof entry === 'string')
|
|
5
6
|
.map((entry) => entry.trim())
|
|
6
7
|
.filter(Boolean);
|
|
8
|
+
const parseDecisionMap = (raw) => {
|
|
9
|
+
const out = {};
|
|
10
|
+
for (const [key, value] of Object.entries(raw))
|
|
11
|
+
if (isDecision(value))
|
|
12
|
+
out[key] = value;
|
|
13
|
+
return Object.keys(out).length > 0 ? out : undefined;
|
|
14
|
+
};
|
|
7
15
|
const parseTools = (raw) => {
|
|
8
16
|
if (Array.isArray(raw) || typeof raw === 'string') {
|
|
9
17
|
const list = parseStringList(raw);
|
|
@@ -11,9 +19,14 @@ const parseTools = (raw) => {
|
|
|
11
19
|
}
|
|
12
20
|
if (raw && typeof raw === 'object') {
|
|
13
21
|
const out = {};
|
|
14
|
-
for (const [tool,
|
|
15
|
-
if (
|
|
16
|
-
out[tool] =
|
|
22
|
+
for (const [tool, value] of Object.entries(raw)) {
|
|
23
|
+
if (isDecision(value))
|
|
24
|
+
out[tool] = value;
|
|
25
|
+
else if (value && typeof value === 'object' && !Array.isArray(value)) {
|
|
26
|
+
const nested = parseDecisionMap(value);
|
|
27
|
+
if (nested)
|
|
28
|
+
out[tool] = nested;
|
|
29
|
+
}
|
|
17
30
|
}
|
|
18
31
|
return Object.keys(out).length > 0 ? out : undefined;
|
|
19
32
|
}
|
|
@@ -3,6 +3,7 @@ export interface AgentRegistry {
|
|
|
3
3
|
list(): Agent[];
|
|
4
4
|
get(name: string): Agent | undefined;
|
|
5
5
|
}
|
|
6
|
-
export declare const
|
|
6
|
+
export declare const grantArg: (tool: string, input: unknown) => string | undefined;
|
|
7
|
+
export declare const toolDecision: (agent: Agent, tool: string, arg?: string) => ToolDecision;
|
|
7
8
|
export declare const toolNames: (agent: Agent) => string[] | undefined;
|
|
8
9
|
export declare const createAgentRegistry: (agents?: Agent[]) => AgentRegistry;
|
|
@@ -1,24 +1,59 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
1
|
+
import { matchesGlob } from 'node:path';
|
|
2
|
+
const asMap = (grants) => {
|
|
3
|
+
if (!grants)
|
|
3
4
|
return undefined;
|
|
4
|
-
if (Array.isArray(
|
|
5
|
-
return Object.fromEntries(
|
|
6
|
-
return
|
|
5
|
+
if (Array.isArray(grants))
|
|
6
|
+
return Object.fromEntries(grants.map((name) => [name, 'allow']));
|
|
7
|
+
return grants;
|
|
7
8
|
};
|
|
8
|
-
|
|
9
|
-
|
|
9
|
+
const matchKey = (keys, name) => {
|
|
10
|
+
if (keys.includes(name))
|
|
11
|
+
return name;
|
|
12
|
+
let glob;
|
|
13
|
+
for (const key of keys) {
|
|
14
|
+
if (key === '*' || key === name)
|
|
15
|
+
continue;
|
|
16
|
+
if (matchesGlob(name, key) && (glob === undefined || key.length > glob.length))
|
|
17
|
+
glob = key;
|
|
18
|
+
}
|
|
19
|
+
if (glob !== undefined)
|
|
20
|
+
return glob;
|
|
21
|
+
return keys.includes('*') ? '*' : undefined;
|
|
22
|
+
};
|
|
23
|
+
const resolveGrant = (grants, tool, arg) => {
|
|
24
|
+
const map = asMap(grants);
|
|
10
25
|
if (!map)
|
|
11
26
|
return 'allow';
|
|
12
|
-
|
|
27
|
+
const key = matchKey(Object.keys(map), tool);
|
|
28
|
+
if (key === undefined)
|
|
29
|
+
return 'deny';
|
|
30
|
+
const value = map[key];
|
|
31
|
+
if (typeof value === 'string')
|
|
32
|
+
return value;
|
|
33
|
+
if (arg === undefined)
|
|
34
|
+
return 'allow';
|
|
35
|
+
const inner = matchKey(Object.keys(value), arg);
|
|
36
|
+
return inner === undefined ? 'deny' : value[inner];
|
|
13
37
|
};
|
|
14
|
-
|
|
15
|
-
const map = asMap(
|
|
38
|
+
const grantNames = (grants) => {
|
|
39
|
+
const map = asMap(grants);
|
|
16
40
|
if (!map)
|
|
17
41
|
return undefined;
|
|
18
|
-
|
|
42
|
+
const wildcard = map['*'];
|
|
43
|
+
if (wildcard !== undefined && wildcard !== 'deny')
|
|
19
44
|
return ['*'];
|
|
20
|
-
return Object.entries(map).filter(([,
|
|
45
|
+
return Object.entries(map).filter(([, value]) => value !== 'deny').map(([name]) => name);
|
|
46
|
+
};
|
|
47
|
+
const GRANT_ARG = { skill: 'name', bash: 'command' };
|
|
48
|
+
export const grantArg = (tool, input) => {
|
|
49
|
+
const field = GRANT_ARG[tool];
|
|
50
|
+
if (!field || typeof input !== 'object' || input === null)
|
|
51
|
+
return undefined;
|
|
52
|
+
const value = input[field];
|
|
53
|
+
return typeof value === 'string' ? value : undefined;
|
|
21
54
|
};
|
|
55
|
+
export const toolDecision = (agent, tool, arg) => resolveGrant(agent.tools, tool, arg);
|
|
56
|
+
export const toolNames = (agent) => grantNames(agent.tools);
|
|
22
57
|
const merge = (base, child) => ({
|
|
23
58
|
name: child.name,
|
|
24
59
|
description: child.description || base.description,
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
export type ToolDecision = 'allow' | 'ask' | 'deny';
|
|
2
|
-
export type
|
|
2
|
+
export type GrantValue = ToolDecision | Record<string, ToolDecision>;
|
|
3
|
+
export type ToolGrants = string[] | Record<string, GrantValue>;
|
|
3
4
|
export interface Agent {
|
|
4
5
|
name: string;
|
|
5
6
|
description: string;
|
|
@@ -4,6 +4,8 @@ import type { SkillRegistry } from '../skills/index.js';
|
|
|
4
4
|
import type { Command } from './types.js';
|
|
5
5
|
export declare const createAgentsCommand: (agents: AgentRegistry) => Command;
|
|
6
6
|
export declare const createSkillsCommand: (skills: SkillRegistry) => Command;
|
|
7
|
-
export declare const createSessionsCommand: (sessions: SessionManager
|
|
7
|
+
export declare const createSessionsCommand: (sessions: SessionManager, options?: {
|
|
8
|
+
cwd?: string;
|
|
9
|
+
}) => Command;
|
|
8
10
|
export declare const createQuitCommand: (onQuit: () => void | Promise<void>) => Command;
|
|
9
11
|
export declare const createHelpCommand: (list: () => Command[]) => Command;
|
|
@@ -20,11 +20,11 @@ export const createSkillsCommand = (skills) => ({
|
|
|
20
20
|
return { ok: true, output: lines.join('\n') };
|
|
21
21
|
},
|
|
22
22
|
});
|
|
23
|
-
export const createSessionsCommand = (sessions) => ({
|
|
23
|
+
export const createSessionsCommand = (sessions, options) => ({
|
|
24
24
|
name: 'sessions',
|
|
25
25
|
description: 'List saved sessions',
|
|
26
26
|
run: async () => {
|
|
27
|
-
const list = await sessions.list();
|
|
27
|
+
const list = await sessions.list(options?.cwd ? { cwd: options.cwd } : undefined);
|
|
28
28
|
if (list.length === 0)
|
|
29
29
|
return { ok: true, output: 'No sessions yet.' };
|
|
30
30
|
const lines = list.map((s) => `- ${s.title || s.id}`);
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { join } from 'node:path';
|
|
2
2
|
import process from 'node:process';
|
|
3
|
-
import { createAgentRegistry, loadAgents, toolDecision, toolNames } from '../agents/index.js';
|
|
3
|
+
import { createAgentRegistry, grantArg, loadAgents, toolDecision, toolNames } from '../agents/index.js';
|
|
4
4
|
import { createAgentsCommand, createCommandRegistry, createHelpCommand, createSessionsCommand, createSkillsCommand, } from '../commands/index.js';
|
|
5
5
|
import { createHarnessConfig } from '../config/index.js';
|
|
6
6
|
import { mergeHooks } from '../hooks/index.js';
|
|
@@ -33,22 +33,35 @@ export const createHarness = async (options) => {
|
|
|
33
33
|
const cwdSkills = await loadSkills(cwdSkillsDir);
|
|
34
34
|
const diskSkills = await loadSkills(skillsDir);
|
|
35
35
|
const skills = createSkillRegistry([...hostSkills, ...pluginSkills, ...cwdSkills, ...diskSkills]);
|
|
36
|
-
const
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
36
|
+
const skillWriterTool = createSkillWriterTool({
|
|
37
|
+
dirs: { local: cwdSkillsDir, config: skillsDir },
|
|
38
|
+
registry: skills,
|
|
39
|
+
});
|
|
40
|
+
const scopeSkills = (agent) => {
|
|
41
|
+
if (!agent)
|
|
42
|
+
return skills;
|
|
43
|
+
return skills.select(skills.list().map((s) => s.name).filter((name) => toolDecision(agent, 'skill', name) !== 'deny'));
|
|
44
|
+
};
|
|
40
45
|
const tasks = enableScheduler ? createTaskStore({ dir: join(config.configDir, 'tasks') }) : undefined;
|
|
41
46
|
let schedulerTools = [];
|
|
42
47
|
const runs = createSubAgentRegistry();
|
|
43
48
|
const store = createSessionStore({ dir: join(config.dataDir, 'sessions') });
|
|
44
|
-
const sessionTools = (extra = []) => [
|
|
49
|
+
const sessionTools = (agent, extra = []) => [
|
|
50
|
+
...(sessionDefaults.tools ?? []),
|
|
51
|
+
...extra,
|
|
52
|
+
createSkillTool(scopeSkills(agent)),
|
|
53
|
+
skillWriterTool,
|
|
54
|
+
...schedulerTools,
|
|
55
|
+
];
|
|
45
56
|
const approvalHook = (getAgent) => approvals
|
|
46
57
|
? approvals.manager.hooksFor({
|
|
47
58
|
decide: (call) => {
|
|
48
59
|
const agent = getAgent();
|
|
49
60
|
if (!agent)
|
|
50
61
|
return 'allow';
|
|
51
|
-
|
|
62
|
+
if (approvals.decide)
|
|
63
|
+
return approvals.decide(agent, call);
|
|
64
|
+
return toolDecision(agent, call.name, grantArg(call.name, call.input));
|
|
52
65
|
},
|
|
53
66
|
agent: () => getAgent()?.name,
|
|
54
67
|
})
|
|
@@ -62,7 +75,7 @@ export const createHarness = async (options) => {
|
|
|
62
75
|
id: newId(),
|
|
63
76
|
});
|
|
64
77
|
const spawn = (agent) => persistTo(store, persona(agent, {
|
|
65
|
-
tools: sessionTools(),
|
|
78
|
+
tools: sessionTools(agent),
|
|
66
79
|
hooks: mergeHooks([sessionDefaults.hooks, allowList(toolNames(agent)), approvalHook(() => agent)]),
|
|
67
80
|
}));
|
|
68
81
|
const scheduler = enableScheduler && tasks
|
|
@@ -112,7 +125,7 @@ export const createHarness = async (options) => {
|
|
|
112
125
|
revive: ({ id, model: ref, messages }) => createAgentSession({
|
|
113
126
|
...sessionDefaults,
|
|
114
127
|
hooks: mergeHooks([sessionDefaults.hooks, approvalHook(() => approvals?.activeAgent())]),
|
|
115
|
-
tools: sessionTools([createSubAgentTool({ registry: agents, spawn, runs, parentId: id })]),
|
|
128
|
+
tools: sessionTools(undefined, [createSubAgentTool({ registry: agents, spawn, runs, parentId: id })]),
|
|
116
129
|
...models.resolve(ref),
|
|
117
130
|
id,
|
|
118
131
|
messages,
|
|
@@ -121,7 +134,7 @@ export const createHarness = async (options) => {
|
|
|
121
134
|
const commands = createCommandRegistry([
|
|
122
135
|
createAgentsCommand(agents),
|
|
123
136
|
createSkillsCommand(skills),
|
|
124
|
-
createSessionsCommand(sessions),
|
|
137
|
+
createSessionsCommand(sessions, { cwd }),
|
|
125
138
|
...(tasks ? [createTasksCommand(tasks)] : []),
|
|
126
139
|
]);
|
|
127
140
|
commands.register(createHelpCommand(() => commands.list()));
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "mu-harness",
|
|
3
|
-
"version": "0.16.
|
|
3
|
+
"version": "0.16.13",
|
|
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.13",
|
|
20
|
+
"mu-tui": "^0.16.13"
|
|
21
21
|
},
|
|
22
22
|
"_generatedBy": "dnt@dev"
|
|
23
23
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export type { Agent, ToolDecision, ToolGrants } from './types.js';
|
|
2
|
-
export { type AgentRegistry, createAgentRegistry, toolDecision, toolNames } from './registry.js';
|
|
1
|
+
export type { Agent, GrantValue, ToolDecision, ToolGrants } from './types.js';
|
|
2
|
+
export { type AgentRegistry, createAgentRegistry, grantArg, toolDecision, toolNames } from './registry.js';
|
|
3
3
|
export { parseAgent } from './parser.js';
|
|
4
4
|
export { loadAgents } from './loader.js';
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.loadAgents = exports.parseAgent = exports.toolNames = exports.toolDecision = exports.createAgentRegistry = void 0;
|
|
3
|
+
exports.loadAgents = exports.parseAgent = exports.toolNames = exports.toolDecision = exports.grantArg = exports.createAgentRegistry = void 0;
|
|
4
4
|
var registry_js_1 = require("./registry.js");
|
|
5
5
|
Object.defineProperty(exports, "createAgentRegistry", { enumerable: true, get: function () { return registry_js_1.createAgentRegistry; } });
|
|
6
|
+
Object.defineProperty(exports, "grantArg", { enumerable: true, get: function () { return registry_js_1.grantArg; } });
|
|
6
7
|
Object.defineProperty(exports, "toolDecision", { enumerable: true, get: function () { return registry_js_1.toolDecision; } });
|
|
7
8
|
Object.defineProperty(exports, "toolNames", { enumerable: true, get: function () { return registry_js_1.toolNames; } });
|
|
8
9
|
var parser_js_1 = require("./parser.js");
|
|
@@ -3,10 +3,18 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.parseAgent = void 0;
|
|
4
4
|
const index_js_1 = require("../common/index.js");
|
|
5
5
|
const DECISIONS = new Set(['allow', 'ask', 'deny']);
|
|
6
|
+
const isDecision = (value) => typeof value === 'string' && DECISIONS.has(value);
|
|
6
7
|
const parseStringList = (raw) => (Array.isArray(raw) ? raw : raw.split(','))
|
|
7
8
|
.filter((entry) => typeof entry === 'string')
|
|
8
9
|
.map((entry) => entry.trim())
|
|
9
10
|
.filter(Boolean);
|
|
11
|
+
const parseDecisionMap = (raw) => {
|
|
12
|
+
const out = {};
|
|
13
|
+
for (const [key, value] of Object.entries(raw))
|
|
14
|
+
if (isDecision(value))
|
|
15
|
+
out[key] = value;
|
|
16
|
+
return Object.keys(out).length > 0 ? out : undefined;
|
|
17
|
+
};
|
|
10
18
|
const parseTools = (raw) => {
|
|
11
19
|
if (Array.isArray(raw) || typeof raw === 'string') {
|
|
12
20
|
const list = parseStringList(raw);
|
|
@@ -14,9 +22,14 @@ const parseTools = (raw) => {
|
|
|
14
22
|
}
|
|
15
23
|
if (raw && typeof raw === 'object') {
|
|
16
24
|
const out = {};
|
|
17
|
-
for (const [tool,
|
|
18
|
-
if (
|
|
19
|
-
out[tool] =
|
|
25
|
+
for (const [tool, value] of Object.entries(raw)) {
|
|
26
|
+
if (isDecision(value))
|
|
27
|
+
out[tool] = value;
|
|
28
|
+
else if (value && typeof value === 'object' && !Array.isArray(value)) {
|
|
29
|
+
const nested = parseDecisionMap(value);
|
|
30
|
+
if (nested)
|
|
31
|
+
out[tool] = nested;
|
|
32
|
+
}
|
|
20
33
|
}
|
|
21
34
|
return Object.keys(out).length > 0 ? out : undefined;
|
|
22
35
|
}
|
|
@@ -3,6 +3,7 @@ export interface AgentRegistry {
|
|
|
3
3
|
list(): Agent[];
|
|
4
4
|
get(name: string): Agent | undefined;
|
|
5
5
|
}
|
|
6
|
-
export declare const
|
|
6
|
+
export declare const grantArg: (tool: string, input: unknown) => string | undefined;
|
|
7
|
+
export declare const toolDecision: (agent: Agent, tool: string, arg?: string) => ToolDecision;
|
|
7
8
|
export declare const toolNames: (agent: Agent) => string[] | undefined;
|
|
8
9
|
export declare const createAgentRegistry: (agents?: Agent[]) => AgentRegistry;
|
|
@@ -1,28 +1,64 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.createAgentRegistry = exports.toolNames = exports.toolDecision = void 0;
|
|
4
|
-
const
|
|
5
|
-
|
|
3
|
+
exports.createAgentRegistry = exports.toolNames = exports.toolDecision = exports.grantArg = void 0;
|
|
4
|
+
const node_path_1 = require("node:path");
|
|
5
|
+
const asMap = (grants) => {
|
|
6
|
+
if (!grants)
|
|
6
7
|
return undefined;
|
|
7
|
-
if (Array.isArray(
|
|
8
|
-
return Object.fromEntries(
|
|
9
|
-
return
|
|
8
|
+
if (Array.isArray(grants))
|
|
9
|
+
return Object.fromEntries(grants.map((name) => [name, 'allow']));
|
|
10
|
+
return grants;
|
|
10
11
|
};
|
|
11
|
-
const
|
|
12
|
-
|
|
12
|
+
const matchKey = (keys, name) => {
|
|
13
|
+
if (keys.includes(name))
|
|
14
|
+
return name;
|
|
15
|
+
let glob;
|
|
16
|
+
for (const key of keys) {
|
|
17
|
+
if (key === '*' || key === name)
|
|
18
|
+
continue;
|
|
19
|
+
if ((0, node_path_1.matchesGlob)(name, key) && (glob === undefined || key.length > glob.length))
|
|
20
|
+
glob = key;
|
|
21
|
+
}
|
|
22
|
+
if (glob !== undefined)
|
|
23
|
+
return glob;
|
|
24
|
+
return keys.includes('*') ? '*' : undefined;
|
|
25
|
+
};
|
|
26
|
+
const resolveGrant = (grants, tool, arg) => {
|
|
27
|
+
const map = asMap(grants);
|
|
13
28
|
if (!map)
|
|
14
29
|
return 'allow';
|
|
15
|
-
|
|
30
|
+
const key = matchKey(Object.keys(map), tool);
|
|
31
|
+
if (key === undefined)
|
|
32
|
+
return 'deny';
|
|
33
|
+
const value = map[key];
|
|
34
|
+
if (typeof value === 'string')
|
|
35
|
+
return value;
|
|
36
|
+
if (arg === undefined)
|
|
37
|
+
return 'allow';
|
|
38
|
+
const inner = matchKey(Object.keys(value), arg);
|
|
39
|
+
return inner === undefined ? 'deny' : value[inner];
|
|
16
40
|
};
|
|
17
|
-
|
|
18
|
-
const
|
|
19
|
-
const map = asMap(agent.tools);
|
|
41
|
+
const grantNames = (grants) => {
|
|
42
|
+
const map = asMap(grants);
|
|
20
43
|
if (!map)
|
|
21
44
|
return undefined;
|
|
22
|
-
|
|
45
|
+
const wildcard = map['*'];
|
|
46
|
+
if (wildcard !== undefined && wildcard !== 'deny')
|
|
23
47
|
return ['*'];
|
|
24
|
-
return Object.entries(map).filter(([,
|
|
48
|
+
return Object.entries(map).filter(([, value]) => value !== 'deny').map(([name]) => name);
|
|
25
49
|
};
|
|
50
|
+
const GRANT_ARG = { skill: 'name', bash: 'command' };
|
|
51
|
+
const grantArg = (tool, input) => {
|
|
52
|
+
const field = GRANT_ARG[tool];
|
|
53
|
+
if (!field || typeof input !== 'object' || input === null)
|
|
54
|
+
return undefined;
|
|
55
|
+
const value = input[field];
|
|
56
|
+
return typeof value === 'string' ? value : undefined;
|
|
57
|
+
};
|
|
58
|
+
exports.grantArg = grantArg;
|
|
59
|
+
const toolDecision = (agent, tool, arg) => resolveGrant(agent.tools, tool, arg);
|
|
60
|
+
exports.toolDecision = toolDecision;
|
|
61
|
+
const toolNames = (agent) => grantNames(agent.tools);
|
|
26
62
|
exports.toolNames = toolNames;
|
|
27
63
|
const merge = (base, child) => ({
|
|
28
64
|
name: child.name,
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
export type ToolDecision = 'allow' | 'ask' | 'deny';
|
|
2
|
-
export type
|
|
2
|
+
export type GrantValue = ToolDecision | Record<string, ToolDecision>;
|
|
3
|
+
export type ToolGrants = string[] | Record<string, GrantValue>;
|
|
3
4
|
export interface Agent {
|
|
4
5
|
name: string;
|
|
5
6
|
description: string;
|
|
@@ -4,6 +4,8 @@ import type { SkillRegistry } from '../skills/index.js';
|
|
|
4
4
|
import type { Command } from './types.js';
|
|
5
5
|
export declare const createAgentsCommand: (agents: AgentRegistry) => Command;
|
|
6
6
|
export declare const createSkillsCommand: (skills: SkillRegistry) => Command;
|
|
7
|
-
export declare const createSessionsCommand: (sessions: SessionManager
|
|
7
|
+
export declare const createSessionsCommand: (sessions: SessionManager, options?: {
|
|
8
|
+
cwd?: string;
|
|
9
|
+
}) => Command;
|
|
8
10
|
export declare const createQuitCommand: (onQuit: () => void | Promise<void>) => Command;
|
|
9
11
|
export declare const createHelpCommand: (list: () => Command[]) => Command;
|
|
@@ -25,11 +25,11 @@ const createSkillsCommand = (skills) => ({
|
|
|
25
25
|
},
|
|
26
26
|
});
|
|
27
27
|
exports.createSkillsCommand = createSkillsCommand;
|
|
28
|
-
const createSessionsCommand = (sessions) => ({
|
|
28
|
+
const createSessionsCommand = (sessions, options) => ({
|
|
29
29
|
name: 'sessions',
|
|
30
30
|
description: 'List saved sessions',
|
|
31
31
|
run: async () => {
|
|
32
|
-
const list = await sessions.list();
|
|
32
|
+
const list = await sessions.list(options?.cwd ? { cwd: options.cwd } : undefined);
|
|
33
33
|
if (list.length === 0)
|
|
34
34
|
return { ok: true, output: 'No sessions yet.' };
|
|
35
35
|
const lines = list.map((s) => `- ${s.title || s.id}`);
|
|
@@ -39,22 +39,35 @@ const createHarness = async (options) => {
|
|
|
39
39
|
const cwdSkills = await (0, index_js_9.loadSkills)(cwdSkillsDir);
|
|
40
40
|
const diskSkills = await (0, index_js_9.loadSkills)(skillsDir);
|
|
41
41
|
const skills = (0, index_js_9.createSkillRegistry)([...hostSkills, ...pluginSkills, ...cwdSkills, ...diskSkills]);
|
|
42
|
-
const
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
42
|
+
const skillWriterTool = (0, index_js_9.createSkillWriterTool)({
|
|
43
|
+
dirs: { local: cwdSkillsDir, config: skillsDir },
|
|
44
|
+
registry: skills,
|
|
45
|
+
});
|
|
46
|
+
const scopeSkills = (agent) => {
|
|
47
|
+
if (!agent)
|
|
48
|
+
return skills;
|
|
49
|
+
return skills.select(skills.list().map((s) => s.name).filter((name) => (0, index_js_1.toolDecision)(agent, 'skill', name) !== 'deny'));
|
|
50
|
+
};
|
|
46
51
|
const tasks = enableScheduler ? (0, index_js_7.createTaskStore)({ dir: (0, node_path_1.join)(config.configDir, 'tasks') }) : undefined;
|
|
47
52
|
let schedulerTools = [];
|
|
48
53
|
const runs = (0, index_js_10.createSubAgentRegistry)();
|
|
49
54
|
const store = (0, index_js_8.createSessionStore)({ dir: (0, node_path_1.join)(config.dataDir, 'sessions') });
|
|
50
|
-
const sessionTools = (extra = []) => [
|
|
55
|
+
const sessionTools = (agent, extra = []) => [
|
|
56
|
+
...(sessionDefaults.tools ?? []),
|
|
57
|
+
...extra,
|
|
58
|
+
(0, index_js_9.createSkillTool)(scopeSkills(agent)),
|
|
59
|
+
skillWriterTool,
|
|
60
|
+
...schedulerTools,
|
|
61
|
+
];
|
|
51
62
|
const approvalHook = (getAgent) => approvals
|
|
52
63
|
? approvals.manager.hooksFor({
|
|
53
64
|
decide: (call) => {
|
|
54
65
|
const agent = getAgent();
|
|
55
66
|
if (!agent)
|
|
56
67
|
return 'allow';
|
|
57
|
-
|
|
68
|
+
if (approvals.decide)
|
|
69
|
+
return approvals.decide(agent, call);
|
|
70
|
+
return (0, index_js_1.toolDecision)(agent, call.name, (0, index_js_1.grantArg)(call.name, call.input));
|
|
58
71
|
},
|
|
59
72
|
agent: () => getAgent()?.name,
|
|
60
73
|
})
|
|
@@ -68,7 +81,7 @@ const createHarness = async (options) => {
|
|
|
68
81
|
id: newId(),
|
|
69
82
|
});
|
|
70
83
|
const spawn = (agent) => (0, index_js_8.persistTo)(store, persona(agent, {
|
|
71
|
-
tools: sessionTools(),
|
|
84
|
+
tools: sessionTools(agent),
|
|
72
85
|
hooks: (0, index_js_4.mergeHooks)([sessionDefaults.hooks, (0, index_js_5.allowList)((0, index_js_1.toolNames)(agent)), approvalHook(() => agent)]),
|
|
73
86
|
}));
|
|
74
87
|
const scheduler = enableScheduler && tasks
|
|
@@ -118,7 +131,7 @@ const createHarness = async (options) => {
|
|
|
118
131
|
revive: ({ id, model: ref, messages }) => (0, index_js_8.createAgentSession)({
|
|
119
132
|
...sessionDefaults,
|
|
120
133
|
hooks: (0, index_js_4.mergeHooks)([sessionDefaults.hooks, approvalHook(() => approvals?.activeAgent())]),
|
|
121
|
-
tools: sessionTools([(0, index_js_10.createSubAgentTool)({ registry: agents, spawn, runs, parentId: id })]),
|
|
134
|
+
tools: sessionTools(undefined, [(0, index_js_10.createSubAgentTool)({ registry: agents, spawn, runs, parentId: id })]),
|
|
122
135
|
...models.resolve(ref),
|
|
123
136
|
id,
|
|
124
137
|
messages,
|
|
@@ -127,7 +140,7 @@ const createHarness = async (options) => {
|
|
|
127
140
|
const commands = (0, index_js_2.createCommandRegistry)([
|
|
128
141
|
(0, index_js_2.createAgentsCommand)(agents),
|
|
129
142
|
(0, index_js_2.createSkillsCommand)(skills),
|
|
130
|
-
(0, index_js_2.createSessionsCommand)(sessions),
|
|
143
|
+
(0, index_js_2.createSessionsCommand)(sessions, { cwd }),
|
|
131
144
|
...(tasks ? [(0, index_js_7.createTasksCommand)(tasks)] : []),
|
|
132
145
|
]);
|
|
133
146
|
commands.register((0, index_js_2.createHelpCommand)(() => commands.list()));
|