@yeaft/webchat-agent 0.1.442 → 0.1.443
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/package.json +1 -1
- package/unify/tools/agent.js +89 -0
- package/unify/tools/apply-patch.js +176 -0
- package/unify/tools/ask-user.js +62 -0
- package/unify/tools/bash.js +189 -0
- package/unify/tools/close-agent.js +58 -0
- package/unify/tools/file-edit.js +120 -0
- package/unify/tools/file-read.js +125 -0
- package/unify/tools/file-write.js +73 -0
- package/unify/tools/glob.js +143 -0
- package/unify/tools/grep.js +268 -0
- package/unify/tools/history-search.js +69 -0
- package/unify/tools/image-generation.js +97 -0
- package/unify/tools/index.js +92 -0
- package/unify/tools/js-repl.js +122 -0
- package/unify/tools/list-agents.js +56 -0
- package/unify/tools/list-dir.js +106 -0
- package/unify/tools/memory-read.js +91 -0
- package/unify/tools/memory-search.js +101 -0
- package/unify/tools/memory-write.js +114 -0
- package/unify/tools/notebook-edit.js +132 -0
- package/unify/tools/request-permissions.js +60 -0
- package/unify/tools/send-message.js +62 -0
- package/unify/tools/task-tools.js +358 -0
- package/unify/tools/tool-search.js +97 -0
- package/unify/tools/view-image.js +117 -0
- package/unify/tools/wait-agent.js +84 -0
- package/unify/tools/web-fetch.js +131 -0
- package/unify/tools/web-search.js +80 -0
- package/unify/tools/write-stdin.js +54 -0
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* image-generation.js — Generate images via external API.
|
|
3
|
+
*
|
|
4
|
+
* Delegates to a configured image generation service (DALL-E, etc.).
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { defineTool } from './types.js';
|
|
8
|
+
|
|
9
|
+
export default defineTool({
|
|
10
|
+
name: 'ImageGeneration',
|
|
11
|
+
description: `Generate an image from a text description.
|
|
12
|
+
|
|
13
|
+
Uses a configured image generation API to create images.
|
|
14
|
+
Requires an image generation API endpoint in config.
|
|
15
|
+
|
|
16
|
+
Guidelines:
|
|
17
|
+
- Provide detailed, specific descriptions for best results
|
|
18
|
+
- Specify style, composition, and mood
|
|
19
|
+
- Images are saved to the working directory`,
|
|
20
|
+
parameters: {
|
|
21
|
+
type: 'object',
|
|
22
|
+
properties: {
|
|
23
|
+
prompt: {
|
|
24
|
+
type: 'string',
|
|
25
|
+
description: 'Text description of the image to generate',
|
|
26
|
+
},
|
|
27
|
+
output_path: {
|
|
28
|
+
type: 'string',
|
|
29
|
+
description: 'File path to save the generated image',
|
|
30
|
+
},
|
|
31
|
+
size: {
|
|
32
|
+
type: 'string',
|
|
33
|
+
enum: ['256x256', '512x512', '1024x1024'],
|
|
34
|
+
description: 'Image size (default: "1024x1024")',
|
|
35
|
+
},
|
|
36
|
+
},
|
|
37
|
+
required: ['prompt'],
|
|
38
|
+
},
|
|
39
|
+
modes: ['chat', 'work'],
|
|
40
|
+
isConcurrencySafe: () => true,
|
|
41
|
+
isReadOnly: () => false,
|
|
42
|
+
async execute(input, ctx) {
|
|
43
|
+
const { prompt, output_path, size = '1024x1024' } = input;
|
|
44
|
+
if (!prompt) return JSON.stringify({ error: 'prompt is required' });
|
|
45
|
+
|
|
46
|
+
const imageApiUrl = ctx?.config?.imageApiUrl;
|
|
47
|
+
if (!imageApiUrl) {
|
|
48
|
+
return JSON.stringify({
|
|
49
|
+
error: 'No image generation API configured.',
|
|
50
|
+
hint: 'Configure imageApiUrl in ~/.yeaft/config.json',
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
try {
|
|
55
|
+
const response = await fetch(imageApiUrl, {
|
|
56
|
+
method: 'POST',
|
|
57
|
+
headers: { 'Content-Type': 'application/json' },
|
|
58
|
+
body: JSON.stringify({ prompt, size }),
|
|
59
|
+
signal: ctx?.signal,
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
if (!response.ok) {
|
|
63
|
+
return JSON.stringify({ error: `Image API returned ${response.status}: ${response.statusText}` });
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
const data = await response.json();
|
|
67
|
+
|
|
68
|
+
// If output_path specified, save the image
|
|
69
|
+
if (output_path && data.url) {
|
|
70
|
+
const { resolve: resolvePath } = await import('path');
|
|
71
|
+
const { writeFile } = await import('fs/promises');
|
|
72
|
+
|
|
73
|
+
const imgResponse = await fetch(data.url);
|
|
74
|
+
const buffer = Buffer.from(await imgResponse.arrayBuffer());
|
|
75
|
+
const absPath = resolvePath(ctx?.cwd || process.cwd(), output_path);
|
|
76
|
+
await writeFile(absPath, buffer);
|
|
77
|
+
|
|
78
|
+
return JSON.stringify({
|
|
79
|
+
success: true,
|
|
80
|
+
path: absPath,
|
|
81
|
+
size,
|
|
82
|
+
prompt: prompt.slice(0, 100),
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
return JSON.stringify({
|
|
87
|
+
success: true,
|
|
88
|
+
url: data.url,
|
|
89
|
+
size,
|
|
90
|
+
prompt: prompt.slice(0, 100),
|
|
91
|
+
});
|
|
92
|
+
} catch (err) {
|
|
93
|
+
if (err.name === 'AbortError') return JSON.stringify({ error: 'Generation cancelled' });
|
|
94
|
+
return JSON.stringify({ error: `Image generation failed: ${err.message}` });
|
|
95
|
+
}
|
|
96
|
+
},
|
|
97
|
+
});
|
package/unify/tools/index.js
CHANGED
|
@@ -9,21 +9,113 @@
|
|
|
9
9
|
*/
|
|
10
10
|
|
|
11
11
|
import { ToolRegistry } from './registry.js';
|
|
12
|
+
|
|
13
|
+
// --- Existing tools ---
|
|
12
14
|
import mcpTools from './mcp-tools.js';
|
|
13
15
|
import skillTool from './skill.js';
|
|
14
16
|
import enterWorktree from './enter-worktree.js';
|
|
15
17
|
import exitWorktree from './exit-worktree.js';
|
|
16
18
|
|
|
19
|
+
// --- P0 Core tools ---
|
|
20
|
+
import askUser from './ask-user.js';
|
|
21
|
+
import memoryRead from './memory-read.js';
|
|
22
|
+
import memoryWrite from './memory-write.js';
|
|
23
|
+
import memorySearch from './memory-search.js';
|
|
24
|
+
import webSearch from './web-search.js';
|
|
25
|
+
import webFetch from './web-fetch.js';
|
|
26
|
+
import historySearch from './history-search.js';
|
|
27
|
+
|
|
28
|
+
// --- P0 File tools ---
|
|
29
|
+
import bash from './bash.js';
|
|
30
|
+
import fileRead from './file-read.js';
|
|
31
|
+
import fileWrite from './file-write.js';
|
|
32
|
+
import fileEdit from './file-edit.js';
|
|
33
|
+
import globTool from './glob.js';
|
|
34
|
+
import grepTool from './grep.js';
|
|
35
|
+
import listDir from './list-dir.js';
|
|
36
|
+
import applyPatch from './apply-patch.js';
|
|
37
|
+
|
|
38
|
+
// --- P1 Agent tools ---
|
|
39
|
+
import agentTool from './agent.js';
|
|
40
|
+
import sendMessage from './send-message.js';
|
|
41
|
+
import waitAgent from './wait-agent.js';
|
|
42
|
+
import closeAgent from './close-agent.js';
|
|
43
|
+
import listAgents from './list-agents.js';
|
|
44
|
+
|
|
45
|
+
// --- P1 Task tools ---
|
|
46
|
+
import {
|
|
47
|
+
taskCreate,
|
|
48
|
+
taskUpdate,
|
|
49
|
+
taskList,
|
|
50
|
+
taskGet,
|
|
51
|
+
followupTask,
|
|
52
|
+
updatePlan,
|
|
53
|
+
} from './task-tools.js';
|
|
54
|
+
|
|
55
|
+
// --- P2 Auxiliary tools ---
|
|
56
|
+
import { jsRepl, jsReplReset } from './js-repl.js';
|
|
57
|
+
import notebookEdit from './notebook-edit.js';
|
|
58
|
+
import imageGeneration from './image-generation.js';
|
|
59
|
+
import viewImage from './view-image.js';
|
|
60
|
+
import toolSearch from './tool-search.js';
|
|
61
|
+
import requestPermissions from './request-permissions.js';
|
|
62
|
+
import writeStdin from './write-stdin.js';
|
|
63
|
+
|
|
17
64
|
/**
|
|
18
65
|
* All built-in tools, flattened into a single array.
|
|
19
66
|
* mcpTools is already an array; the rest are single ToolDef objects.
|
|
20
67
|
* @type {import('./types.js').ToolDef[]}
|
|
21
68
|
*/
|
|
22
69
|
export const allTools = [
|
|
70
|
+
// Existing tools
|
|
23
71
|
...mcpTools,
|
|
24
72
|
skillTool,
|
|
25
73
|
enterWorktree,
|
|
26
74
|
exitWorktree,
|
|
75
|
+
|
|
76
|
+
// P0 Core
|
|
77
|
+
askUser,
|
|
78
|
+
memoryRead,
|
|
79
|
+
memoryWrite,
|
|
80
|
+
memorySearch,
|
|
81
|
+
webSearch,
|
|
82
|
+
webFetch,
|
|
83
|
+
historySearch,
|
|
84
|
+
|
|
85
|
+
// P0 File
|
|
86
|
+
bash,
|
|
87
|
+
fileRead,
|
|
88
|
+
fileWrite,
|
|
89
|
+
fileEdit,
|
|
90
|
+
globTool,
|
|
91
|
+
grepTool,
|
|
92
|
+
listDir,
|
|
93
|
+
applyPatch,
|
|
94
|
+
|
|
95
|
+
// P1 Agent
|
|
96
|
+
agentTool,
|
|
97
|
+
sendMessage,
|
|
98
|
+
waitAgent,
|
|
99
|
+
closeAgent,
|
|
100
|
+
listAgents,
|
|
101
|
+
|
|
102
|
+
// P1 Task
|
|
103
|
+
taskCreate,
|
|
104
|
+
taskUpdate,
|
|
105
|
+
taskList,
|
|
106
|
+
taskGet,
|
|
107
|
+
followupTask,
|
|
108
|
+
updatePlan,
|
|
109
|
+
|
|
110
|
+
// P2 Auxiliary
|
|
111
|
+
jsRepl,
|
|
112
|
+
jsReplReset,
|
|
113
|
+
notebookEdit,
|
|
114
|
+
imageGeneration,
|
|
115
|
+
viewImage,
|
|
116
|
+
toolSearch,
|
|
117
|
+
requestPermissions,
|
|
118
|
+
writeStdin,
|
|
27
119
|
];
|
|
28
120
|
|
|
29
121
|
/**
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* js-repl.js — JavaScript REPL for evaluating expressions.
|
|
3
|
+
*
|
|
4
|
+
* Runs JavaScript code in a persistent VM context, allowing
|
|
5
|
+
* state to be maintained across calls.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { defineTool } from './types.js';
|
|
9
|
+
import { createContext, runInContext } from 'vm';
|
|
10
|
+
|
|
11
|
+
/** Persistent VM context per session. */
|
|
12
|
+
let vmContext = null;
|
|
13
|
+
|
|
14
|
+
function getContext() {
|
|
15
|
+
if (!vmContext) {
|
|
16
|
+
vmContext = createContext({
|
|
17
|
+
console: {
|
|
18
|
+
log: (...args) => { vmContext.__output.push(args.map(String).join(' ')); },
|
|
19
|
+
error: (...args) => { vmContext.__output.push('[error] ' + args.map(String).join(' ')); },
|
|
20
|
+
warn: (...args) => { vmContext.__output.push('[warn] ' + args.map(String).join(' ')); },
|
|
21
|
+
},
|
|
22
|
+
setTimeout,
|
|
23
|
+
setInterval,
|
|
24
|
+
clearTimeout,
|
|
25
|
+
clearInterval,
|
|
26
|
+
JSON,
|
|
27
|
+
Math,
|
|
28
|
+
Date,
|
|
29
|
+
RegExp,
|
|
30
|
+
Array,
|
|
31
|
+
Object,
|
|
32
|
+
String,
|
|
33
|
+
Number,
|
|
34
|
+
Boolean,
|
|
35
|
+
Map,
|
|
36
|
+
Set,
|
|
37
|
+
WeakMap,
|
|
38
|
+
WeakSet,
|
|
39
|
+
Promise,
|
|
40
|
+
Error,
|
|
41
|
+
Buffer,
|
|
42
|
+
__output: [],
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
vmContext.__output = [];
|
|
46
|
+
return vmContext;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export const jsRepl = defineTool({
|
|
50
|
+
name: 'JsRepl',
|
|
51
|
+
description: `Evaluate JavaScript code in a persistent REPL environment.
|
|
52
|
+
|
|
53
|
+
The REPL context persists across calls — variables and functions
|
|
54
|
+
defined in one call are available in subsequent calls.
|
|
55
|
+
|
|
56
|
+
Guidelines:
|
|
57
|
+
- Use for calculations, data transformations, and quick experiments
|
|
58
|
+
- State is preserved between calls (use JsReplReset to clear)
|
|
59
|
+
- console.log output is captured and returned
|
|
60
|
+
- Returns the last expression's value plus any console output
|
|
61
|
+
- No filesystem or network access from within the REPL`,
|
|
62
|
+
parameters: {
|
|
63
|
+
type: 'object',
|
|
64
|
+
properties: {
|
|
65
|
+
code: {
|
|
66
|
+
type: 'string',
|
|
67
|
+
description: 'JavaScript code to evaluate',
|
|
68
|
+
},
|
|
69
|
+
},
|
|
70
|
+
required: ['code'],
|
|
71
|
+
},
|
|
72
|
+
modes: ['chat', 'work'],
|
|
73
|
+
isConcurrencySafe: () => false,
|
|
74
|
+
isReadOnly: () => true,
|
|
75
|
+
async execute(input, ctx) {
|
|
76
|
+
const { code } = input;
|
|
77
|
+
if (!code) return JSON.stringify({ error: 'code is required' });
|
|
78
|
+
|
|
79
|
+
const vmCtx = getContext();
|
|
80
|
+
|
|
81
|
+
try {
|
|
82
|
+
const result = runInContext(code, vmCtx, {
|
|
83
|
+
timeout: 10000, // 10 second timeout
|
|
84
|
+
displayErrors: true,
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
const output = vmCtx.__output.slice();
|
|
88
|
+
const resultStr = result === undefined ? '' : String(result);
|
|
89
|
+
|
|
90
|
+
const parts = [];
|
|
91
|
+
if (output.length > 0) parts.push(output.join('\n'));
|
|
92
|
+
if (resultStr) parts.push(`→ ${resultStr}`);
|
|
93
|
+
|
|
94
|
+
return parts.join('\n') || '(no output)';
|
|
95
|
+
} catch (err) {
|
|
96
|
+
const output = vmCtx.__output.slice();
|
|
97
|
+
const parts = [];
|
|
98
|
+
if (output.length > 0) parts.push(output.join('\n'));
|
|
99
|
+
parts.push(`Error: ${err.message}`);
|
|
100
|
+
return parts.join('\n');
|
|
101
|
+
}
|
|
102
|
+
},
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
export const jsReplReset = defineTool({
|
|
106
|
+
name: 'JsReplReset',
|
|
107
|
+
description: `Reset the JavaScript REPL environment.
|
|
108
|
+
|
|
109
|
+
Clears all variables and state from previous evaluations.
|
|
110
|
+
Use when you want a clean slate.`,
|
|
111
|
+
parameters: {
|
|
112
|
+
type: 'object',
|
|
113
|
+
properties: {},
|
|
114
|
+
},
|
|
115
|
+
modes: ['chat', 'work'],
|
|
116
|
+
isConcurrencySafe: () => false,
|
|
117
|
+
isReadOnly: () => false,
|
|
118
|
+
async execute(input, ctx) {
|
|
119
|
+
vmContext = null;
|
|
120
|
+
return JSON.stringify({ success: true, message: 'REPL context reset' });
|
|
121
|
+
},
|
|
122
|
+
});
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* list-agents.js — List all active sub-agents.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { defineTool } from './types.js';
|
|
6
|
+
import { getAgentRegistry } from './agent.js';
|
|
7
|
+
|
|
8
|
+
export default defineTool({
|
|
9
|
+
name: 'ListAgents',
|
|
10
|
+
description: `List all sub-agents and their current status.
|
|
11
|
+
|
|
12
|
+
Shows agent IDs, names, tasks, status (created/active/completed/closed),
|
|
13
|
+
and message counts. Use to monitor parallel task progress.`,
|
|
14
|
+
parameters: {
|
|
15
|
+
type: 'object',
|
|
16
|
+
properties: {
|
|
17
|
+
include_closed: {
|
|
18
|
+
type: 'boolean',
|
|
19
|
+
description: 'Include closed agents in the list (default: false)',
|
|
20
|
+
},
|
|
21
|
+
},
|
|
22
|
+
},
|
|
23
|
+
modes: ['work'],
|
|
24
|
+
isConcurrencySafe: () => true,
|
|
25
|
+
isReadOnly: () => true,
|
|
26
|
+
async execute(input, ctx) {
|
|
27
|
+
const { include_closed = false } = input;
|
|
28
|
+
const agents = getAgentRegistry();
|
|
29
|
+
|
|
30
|
+
const agentList = [];
|
|
31
|
+
for (const [id, agent] of agents) {
|
|
32
|
+
if (!include_closed && agent.status === 'closed') continue;
|
|
33
|
+
agentList.push({
|
|
34
|
+
id,
|
|
35
|
+
name: agent.name,
|
|
36
|
+
status: agent.status,
|
|
37
|
+
task: agent.task?.slice(0, 200),
|
|
38
|
+
messages: agent.messages.length,
|
|
39
|
+
hasResult: !!agent.result,
|
|
40
|
+
createdAt: agent.createdAt,
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
if (agentList.length === 0) {
|
|
45
|
+
return JSON.stringify({
|
|
46
|
+
agents: [],
|
|
47
|
+
message: 'No active sub-agents',
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
return JSON.stringify({
|
|
52
|
+
agents: agentList,
|
|
53
|
+
totalCount: agentList.length,
|
|
54
|
+
}, null, 2);
|
|
55
|
+
},
|
|
56
|
+
});
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* list-dir.js — List directory contents.
|
|
3
|
+
*
|
|
4
|
+
* Lists files and directories with type, size, and modification time.
|
|
5
|
+
* Skips common large directories (node_modules, .git, etc.).
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { defineTool } from './types.js';
|
|
9
|
+
import { readdir, stat } from 'fs/promises';
|
|
10
|
+
import { existsSync } from 'fs';
|
|
11
|
+
import { resolve, join } from 'path';
|
|
12
|
+
|
|
13
|
+
/** Directories to skip in listings. */
|
|
14
|
+
const SKIP_DIRS = new Set([
|
|
15
|
+
'node_modules', '.git', '__pycache__', '.next', '.nuxt', '.cache',
|
|
16
|
+
]);
|
|
17
|
+
|
|
18
|
+
export default defineTool({
|
|
19
|
+
name: 'ListDir',
|
|
20
|
+
description: `List the contents of a directory.
|
|
21
|
+
|
|
22
|
+
Shows files and subdirectories with their types and sizes.
|
|
23
|
+
Directories are listed first, then files, both sorted alphabetically.
|
|
24
|
+
Common large directories (node_modules, .git) are skipped.
|
|
25
|
+
|
|
26
|
+
This is better than using Bash with 'ls' because it provides structured output.`,
|
|
27
|
+
parameters: {
|
|
28
|
+
type: 'object',
|
|
29
|
+
properties: {
|
|
30
|
+
path: {
|
|
31
|
+
type: 'string',
|
|
32
|
+
description: 'Directory path to list (default: cwd)',
|
|
33
|
+
},
|
|
34
|
+
show_hidden: {
|
|
35
|
+
type: 'boolean',
|
|
36
|
+
description: 'Include hidden files (starting with dot, default: true)',
|
|
37
|
+
},
|
|
38
|
+
},
|
|
39
|
+
},
|
|
40
|
+
modes: ['work'],
|
|
41
|
+
isConcurrencySafe: () => true,
|
|
42
|
+
isReadOnly: () => true,
|
|
43
|
+
async execute(input, ctx) {
|
|
44
|
+
const { path: dirPath, show_hidden = true } = input;
|
|
45
|
+
|
|
46
|
+
const cwd = ctx?.cwd || process.cwd();
|
|
47
|
+
const absPath = dirPath ? resolve(cwd, dirPath) : cwd;
|
|
48
|
+
|
|
49
|
+
if (!existsSync(absPath)) {
|
|
50
|
+
return JSON.stringify({ error: `Directory not found: ${absPath}` });
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
try {
|
|
54
|
+
const entries = await readdir(absPath, { withFileTypes: true });
|
|
55
|
+
const results = [];
|
|
56
|
+
|
|
57
|
+
for (const entry of entries) {
|
|
58
|
+
// Skip hidden files if not requested
|
|
59
|
+
if (!show_hidden && entry.name.startsWith('.')) continue;
|
|
60
|
+
|
|
61
|
+
// Skip large directories
|
|
62
|
+
if (entry.isDirectory() && SKIP_DIRS.has(entry.name)) continue;
|
|
63
|
+
|
|
64
|
+
try {
|
|
65
|
+
const fullPath = join(absPath, entry.name);
|
|
66
|
+
const fileStat = await stat(fullPath);
|
|
67
|
+
results.push({
|
|
68
|
+
name: entry.name,
|
|
69
|
+
type: entry.isDirectory() ? 'dir' : 'file',
|
|
70
|
+
size: fileStat.size,
|
|
71
|
+
modified: fileStat.mtime.toISOString(),
|
|
72
|
+
});
|
|
73
|
+
} catch {
|
|
74
|
+
results.push({
|
|
75
|
+
name: entry.name,
|
|
76
|
+
type: entry.isDirectory() ? 'dir' : 'file',
|
|
77
|
+
size: 0,
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// Sort: directories first, then files, alphabetically
|
|
83
|
+
results.sort((a, b) => {
|
|
84
|
+
if (a.type !== b.type) return a.type === 'dir' ? -1 : 1;
|
|
85
|
+
return a.name.localeCompare(b.name);
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
// Format as text
|
|
89
|
+
const lines = results.map(r => {
|
|
90
|
+
const typeChar = r.type === 'dir' ? '📁' : '📄';
|
|
91
|
+
const sizeStr = r.type === 'dir' ? '' : ` (${formatSize(r.size)})`;
|
|
92
|
+
return `${typeChar} ${r.name}${sizeStr}`;
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
return `${absPath}/\n\n${lines.join('\n')}` || `${absPath}/ (empty directory)`;
|
|
96
|
+
} catch (err) {
|
|
97
|
+
return JSON.stringify({ error: `Failed to list directory: ${err.message}` });
|
|
98
|
+
}
|
|
99
|
+
},
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
function formatSize(bytes) {
|
|
103
|
+
if (bytes < 1024) return `${bytes}B`;
|
|
104
|
+
if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)}KB`;
|
|
105
|
+
return `${(bytes / 1024 / 1024).toFixed(1)}MB`;
|
|
106
|
+
}
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* memory-read.js — Read memory entries from the Yeaft memory store.
|
|
3
|
+
*
|
|
4
|
+
* Reads the user profile (MEMORY.md), specific sections, or individual
|
|
5
|
+
* memory entries by name.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { defineTool } from './types.js';
|
|
9
|
+
|
|
10
|
+
export default defineTool({
|
|
11
|
+
name: 'MemoryRead',
|
|
12
|
+
description: `Read from Yeaft's persistent memory system.
|
|
13
|
+
|
|
14
|
+
Actions:
|
|
15
|
+
- "profile" — read the full MEMORY.md user profile
|
|
16
|
+
- "section" — read a specific section from MEMORY.md (e.g. "Facts", "Preferences")
|
|
17
|
+
- "entry" — read a specific memory entry by name
|
|
18
|
+
- "list" — list all memory entries (frontmatter only, no body)
|
|
19
|
+
- "scopes" — list all memory scopes and their entry counts`,
|
|
20
|
+
parameters: {
|
|
21
|
+
type: 'object',
|
|
22
|
+
properties: {
|
|
23
|
+
action: {
|
|
24
|
+
type: 'string',
|
|
25
|
+
enum: ['profile', 'section', 'entry', 'list', 'scopes'],
|
|
26
|
+
description: 'What to read from memory',
|
|
27
|
+
},
|
|
28
|
+
name: {
|
|
29
|
+
type: 'string',
|
|
30
|
+
description: 'Entry name slug (for "entry" action) or section name (for "section" action)',
|
|
31
|
+
},
|
|
32
|
+
},
|
|
33
|
+
required: ['action'],
|
|
34
|
+
},
|
|
35
|
+
modes: ['chat', 'work'],
|
|
36
|
+
isConcurrencySafe: () => true,
|
|
37
|
+
isReadOnly: () => true,
|
|
38
|
+
async execute(input, ctx) {
|
|
39
|
+
const memoryStore = ctx?.memoryStore;
|
|
40
|
+
if (!memoryStore) {
|
|
41
|
+
return JSON.stringify({ error: 'Memory system not initialized' });
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
try {
|
|
45
|
+
switch (input.action) {
|
|
46
|
+
case 'profile': {
|
|
47
|
+
const profile = memoryStore.readProfile();
|
|
48
|
+
return profile || '(No profile found — MEMORY.md is empty)';
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
case 'section': {
|
|
52
|
+
if (!input.name) return JSON.stringify({ error: 'name is required for "section" action' });
|
|
53
|
+
const section = memoryStore.readSection(input.name);
|
|
54
|
+
return section || `(Section "${input.name}" not found in MEMORY.md)`;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
case 'entry': {
|
|
58
|
+
if (!input.name) return JSON.stringify({ error: 'name is required for "entry" action' });
|
|
59
|
+
const entry = memoryStore.readEntry(input.name);
|
|
60
|
+
if (!entry) return JSON.stringify({ error: `Entry "${input.name}" not found` });
|
|
61
|
+
return JSON.stringify(entry, null, 2);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
case 'list': {
|
|
65
|
+
const entries = memoryStore.listEntries();
|
|
66
|
+
return JSON.stringify({
|
|
67
|
+
entries: entries.map(e => ({
|
|
68
|
+
name: e.name,
|
|
69
|
+
kind: e.kind,
|
|
70
|
+
scope: e.scope,
|
|
71
|
+
tags: e.tags,
|
|
72
|
+
importance: e.importance,
|
|
73
|
+
updated_at: e.updated_at,
|
|
74
|
+
})),
|
|
75
|
+
totalCount: entries.length,
|
|
76
|
+
}, null, 2);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
case 'scopes': {
|
|
80
|
+
const scopes = memoryStore.readScopes();
|
|
81
|
+
return JSON.stringify({ scopes }, null, 2);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
default:
|
|
85
|
+
return JSON.stringify({ error: `Unknown action: ${input.action}` });
|
|
86
|
+
}
|
|
87
|
+
} catch (err) {
|
|
88
|
+
return JSON.stringify({ error: `Memory read failed: ${err.message}` });
|
|
89
|
+
}
|
|
90
|
+
},
|
|
91
|
+
});
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* memory-search.js — Search memory entries by scope, tags, and keywords.
|
|
3
|
+
*
|
|
4
|
+
* Uses the MemoryStore's findByFilter for structured search.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { defineTool } from './types.js';
|
|
8
|
+
|
|
9
|
+
export default defineTool({
|
|
10
|
+
name: 'MemorySearch',
|
|
11
|
+
description: `Search Yeaft's persistent memory for relevant entries.
|
|
12
|
+
|
|
13
|
+
Searches by scope, tags, kind, or keyword. Results are scored by relevance:
|
|
14
|
+
- Exact scope match: highest score
|
|
15
|
+
- Ancestor/descendant scope: medium score
|
|
16
|
+
- Tag overlap: additional score per matching tag
|
|
17
|
+
- Keyword in content: found via full-text scan
|
|
18
|
+
|
|
19
|
+
Use this to find previously learned information before asking the user again.`,
|
|
20
|
+
parameters: {
|
|
21
|
+
type: 'object',
|
|
22
|
+
properties: {
|
|
23
|
+
scope: {
|
|
24
|
+
type: 'string',
|
|
25
|
+
description: 'Memory scope to search in (e.g. "global", "work/my-project")',
|
|
26
|
+
},
|
|
27
|
+
tags: {
|
|
28
|
+
type: 'array',
|
|
29
|
+
items: { type: 'string' },
|
|
30
|
+
description: 'Tags to filter by',
|
|
31
|
+
},
|
|
32
|
+
kind: {
|
|
33
|
+
type: 'string',
|
|
34
|
+
enum: ['fact', 'preference', 'skill', 'lesson', 'context', 'relation'],
|
|
35
|
+
description: 'Filter by memory kind',
|
|
36
|
+
},
|
|
37
|
+
keyword: {
|
|
38
|
+
type: 'string',
|
|
39
|
+
description: 'Keyword to search in entry content',
|
|
40
|
+
},
|
|
41
|
+
limit: {
|
|
42
|
+
type: 'number',
|
|
43
|
+
description: 'Maximum number of results (default: 15)',
|
|
44
|
+
},
|
|
45
|
+
},
|
|
46
|
+
},
|
|
47
|
+
modes: ['chat', 'work'],
|
|
48
|
+
isConcurrencySafe: () => true,
|
|
49
|
+
isReadOnly: () => true,
|
|
50
|
+
async execute(input, ctx) {
|
|
51
|
+
const memoryStore = ctx?.memoryStore;
|
|
52
|
+
if (!memoryStore) {
|
|
53
|
+
return JSON.stringify({ error: 'Memory system not initialized' });
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
try {
|
|
57
|
+
const limit = input.limit || 15;
|
|
58
|
+
|
|
59
|
+
// Use findByFilter for scope + tag search
|
|
60
|
+
let results = memoryStore.findByFilter({
|
|
61
|
+
scope: input.scope,
|
|
62
|
+
tags: input.tags || [],
|
|
63
|
+
limit: limit * 2, // over-fetch for post-filtering
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
// Filter by kind if specified
|
|
67
|
+
if (input.kind) {
|
|
68
|
+
results = results.filter(e => e.kind === input.kind);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// Filter by keyword if specified
|
|
72
|
+
if (input.keyword) {
|
|
73
|
+
const kw = input.keyword.toLowerCase();
|
|
74
|
+
results = results.filter(e =>
|
|
75
|
+
(e.content && e.content.toLowerCase().includes(kw)) ||
|
|
76
|
+
(e.name && e.name.toLowerCase().includes(kw)) ||
|
|
77
|
+
(e.tags && e.tags.some(t => t.toLowerCase().includes(kw)))
|
|
78
|
+
);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// Trim to limit
|
|
82
|
+
results = results.slice(0, limit);
|
|
83
|
+
|
|
84
|
+
return JSON.stringify({
|
|
85
|
+
results: results.map(e => ({
|
|
86
|
+
name: e.name,
|
|
87
|
+
kind: e.kind,
|
|
88
|
+
scope: e.scope,
|
|
89
|
+
tags: e.tags,
|
|
90
|
+
importance: e.importance,
|
|
91
|
+
content: e.content?.slice(0, 500) + (e.content?.length > 500 ? '...' : ''),
|
|
92
|
+
updated_at: e.updated_at,
|
|
93
|
+
score: e._score,
|
|
94
|
+
})),
|
|
95
|
+
totalResults: results.length,
|
|
96
|
+
}, null, 2);
|
|
97
|
+
} catch (err) {
|
|
98
|
+
return JSON.stringify({ error: `Memory search failed: ${err.message}` });
|
|
99
|
+
}
|
|
100
|
+
},
|
|
101
|
+
});
|