@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.
@@ -0,0 +1,114 @@
1
+ /**
2
+ * memory-write.js — Write memory entries to the Yeaft memory store.
3
+ *
4
+ * Creates, updates, or deletes memory entries. Also supports
5
+ * appending lines to MEMORY.md sections and overwriting the profile.
6
+ */
7
+
8
+ import { defineTool } from './types.js';
9
+
10
+ export default defineTool({
11
+ name: 'MemoryWrite',
12
+ description: `Write to Yeaft's persistent memory system.
13
+
14
+ Actions:
15
+ - "write_entry" — create or update a memory entry (entries/*.md)
16
+ - "delete_entry" — delete a memory entry by name
17
+ - "write_profile" — overwrite the full MEMORY.md profile
18
+ - "add_to_section" — append a line to a section in MEMORY.md
19
+
20
+ Memory kinds: fact, preference, skill, lesson, context, relation
21
+ Importance levels: low, normal, high, critical`,
22
+ parameters: {
23
+ type: 'object',
24
+ properties: {
25
+ action: {
26
+ type: 'string',
27
+ enum: ['write_entry', 'delete_entry', 'write_profile', 'add_to_section'],
28
+ description: 'What memory operation to perform',
29
+ },
30
+ entry: {
31
+ type: 'object',
32
+ description: 'Memory entry data (for "write_entry")',
33
+ properties: {
34
+ name: { type: 'string', description: 'Entry name (will be slugified for filename)' },
35
+ kind: { type: 'string', enum: ['fact', 'preference', 'skill', 'lesson', 'context', 'relation'] },
36
+ scope: { type: 'string', description: 'Scope path, e.g. "global", "work/my-project"' },
37
+ tags: { type: 'array', items: { type: 'string' } },
38
+ importance: { type: 'string', enum: ['low', 'normal', 'high', 'critical'] },
39
+ content: { type: 'string', description: 'The memory content (markdown body)' },
40
+ },
41
+ required: ['name', 'content'],
42
+ },
43
+ name: {
44
+ type: 'string',
45
+ description: 'Entry name slug (for "delete_entry") or section name (for "add_to_section")',
46
+ },
47
+ content: {
48
+ type: 'string',
49
+ description: 'Content for "write_profile" or line to add for "add_to_section"',
50
+ },
51
+ },
52
+ required: ['action'],
53
+ },
54
+ modes: ['chat', 'work'],
55
+ isConcurrencySafe: () => false,
56
+ isReadOnly: () => false,
57
+ async execute(input, ctx) {
58
+ const memoryStore = ctx?.memoryStore;
59
+ if (!memoryStore) {
60
+ return JSON.stringify({ error: 'Memory system not initialized' });
61
+ }
62
+
63
+ try {
64
+ switch (input.action) {
65
+ case 'write_entry': {
66
+ if (!input.entry) return JSON.stringify({ error: 'entry is required for "write_entry"' });
67
+ if (!input.entry.name) return JSON.stringify({ error: 'entry.name is required' });
68
+ if (!input.entry.content) return JSON.stringify({ error: 'entry.content is required' });
69
+
70
+ const slug = memoryStore.writeEntry(input.entry);
71
+ return JSON.stringify({
72
+ success: true,
73
+ slug,
74
+ message: `Memory entry "${input.entry.name}" saved as ${slug}.md`,
75
+ });
76
+ }
77
+
78
+ case 'delete_entry': {
79
+ if (!input.name) return JSON.stringify({ error: 'name is required for "delete_entry"' });
80
+ const deleted = memoryStore.deleteEntry(input.name);
81
+ return JSON.stringify({
82
+ success: deleted,
83
+ message: deleted
84
+ ? `Deleted memory entry "${input.name}"`
85
+ : `Entry "${input.name}" not found`,
86
+ });
87
+ }
88
+
89
+ case 'write_profile': {
90
+ if (!input.content && input.content !== '') {
91
+ return JSON.stringify({ error: 'content is required for "write_profile"' });
92
+ }
93
+ memoryStore.writeProfile(input.content);
94
+ return JSON.stringify({ success: true, message: 'MEMORY.md updated' });
95
+ }
96
+
97
+ case 'add_to_section': {
98
+ if (!input.name) return JSON.stringify({ error: 'name (section) is required for "add_to_section"' });
99
+ if (!input.content) return JSON.stringify({ error: 'content (line) is required for "add_to_section"' });
100
+ memoryStore.addToSection(input.name, input.content);
101
+ return JSON.stringify({
102
+ success: true,
103
+ message: `Added to section "${input.name}" in MEMORY.md`,
104
+ });
105
+ }
106
+
107
+ default:
108
+ return JSON.stringify({ error: `Unknown action: ${input.action}` });
109
+ }
110
+ } catch (err) {
111
+ return JSON.stringify({ error: `Memory write failed: ${err.message}` });
112
+ }
113
+ },
114
+ });
@@ -0,0 +1,132 @@
1
+ /**
2
+ * notebook-edit.js — Edit Jupyter notebook cells.
3
+ *
4
+ * Reads and modifies .ipynb notebook files by cell index.
5
+ */
6
+
7
+ import { defineTool } from './types.js';
8
+ import { readFile, writeFile } from 'fs/promises';
9
+ import { existsSync } from 'fs';
10
+ import { resolve } from 'path';
11
+
12
+ export default defineTool({
13
+ name: 'NotebookEdit',
14
+ description: `Edit a Jupyter notebook (.ipynb file) cell.
15
+
16
+ Actions:
17
+ - "replace" — replace the source of a cell at the given index
18
+ - "insert" — insert a new cell after the given index
19
+ - "delete" — delete the cell at the given index
20
+ - "read" — read the notebook content (all cells)
21
+
22
+ Cell types: "code" or "markdown"`,
23
+ parameters: {
24
+ type: 'object',
25
+ properties: {
26
+ notebook_path: {
27
+ type: 'string',
28
+ description: 'Path to the .ipynb file',
29
+ },
30
+ action: {
31
+ type: 'string',
32
+ enum: ['replace', 'insert', 'delete', 'read'],
33
+ description: 'Operation to perform (default: "replace")',
34
+ },
35
+ cell_index: {
36
+ type: 'number',
37
+ description: 'Cell index (0-based)',
38
+ },
39
+ cell_type: {
40
+ type: 'string',
41
+ enum: ['code', 'markdown'],
42
+ description: 'Cell type for insert/replace',
43
+ },
44
+ source: {
45
+ type: 'string',
46
+ description: 'New cell source content',
47
+ },
48
+ },
49
+ required: ['notebook_path'],
50
+ },
51
+ modes: ['work'],
52
+ isConcurrencySafe: () => false,
53
+ isReadOnly: (input) => input?.action === 'read',
54
+ async execute(input, ctx) {
55
+ const { notebook_path, action = 'replace', cell_index, cell_type, source } = input;
56
+ if (!notebook_path) return JSON.stringify({ error: 'notebook_path is required' });
57
+
58
+ const cwd = ctx?.cwd || process.cwd();
59
+ const absPath = resolve(cwd, notebook_path);
60
+
61
+ if (!existsSync(absPath)) {
62
+ if (action === 'read') return JSON.stringify({ error: `Notebook not found: ${absPath}` });
63
+ // For write actions on new file, create an empty notebook
64
+ }
65
+
66
+ try {
67
+ let notebook;
68
+ if (existsSync(absPath)) {
69
+ const raw = await readFile(absPath, 'utf-8');
70
+ notebook = JSON.parse(raw);
71
+ } else {
72
+ notebook = {
73
+ cells: [],
74
+ metadata: { kernelspec: { display_name: 'Python 3', language: 'python', name: 'python3' } },
75
+ nbformat: 4,
76
+ nbformat_minor: 5,
77
+ };
78
+ }
79
+
80
+ if (action === 'read') {
81
+ return JSON.stringify({
82
+ cells: notebook.cells.map((cell, i) => ({
83
+ index: i,
84
+ cell_type: cell.cell_type,
85
+ source: Array.isArray(cell.source) ? cell.source.join('') : cell.source,
86
+ outputs: cell.outputs ? cell.outputs.length : 0,
87
+ })),
88
+ totalCells: notebook.cells.length,
89
+ }, null, 2);
90
+ }
91
+
92
+ if (action === 'replace') {
93
+ if (cell_index === undefined) return JSON.stringify({ error: 'cell_index is required for replace' });
94
+ if (source === undefined) return JSON.stringify({ error: 'source is required for replace' });
95
+ if (cell_index < 0 || cell_index >= notebook.cells.length) {
96
+ return JSON.stringify({ error: `Cell index ${cell_index} out of range (0-${notebook.cells.length - 1})` });
97
+ }
98
+
99
+ notebook.cells[cell_index].source = source.split('\n').map((l, i, arr) => i < arr.length - 1 ? l + '\n' : l);
100
+ if (cell_type) notebook.cells[cell_index].cell_type = cell_type;
101
+ } else if (action === 'insert') {
102
+ if (source === undefined) return JSON.stringify({ error: 'source is required for insert' });
103
+ const type = cell_type || 'code';
104
+ const newCell = {
105
+ cell_type: type,
106
+ source: source.split('\n').map((l, i, arr) => i < arr.length - 1 ? l + '\n' : l),
107
+ metadata: {},
108
+ ...(type === 'code' ? { outputs: [], execution_count: null } : {}),
109
+ };
110
+ const insertIdx = cell_index !== undefined ? cell_index + 1 : notebook.cells.length;
111
+ notebook.cells.splice(insertIdx, 0, newCell);
112
+ } else if (action === 'delete') {
113
+ if (cell_index === undefined) return JSON.stringify({ error: 'cell_index is required for delete' });
114
+ if (cell_index < 0 || cell_index >= notebook.cells.length) {
115
+ return JSON.stringify({ error: `Cell index ${cell_index} out of range` });
116
+ }
117
+ notebook.cells.splice(cell_index, 1);
118
+ }
119
+
120
+ await writeFile(absPath, JSON.stringify(notebook, null, 1), 'utf-8');
121
+
122
+ return JSON.stringify({
123
+ success: true,
124
+ action,
125
+ totalCells: notebook.cells.length,
126
+ message: `Notebook ${action}d successfully`,
127
+ });
128
+ } catch (err) {
129
+ return JSON.stringify({ error: `Notebook edit failed: ${err.message}` });
130
+ }
131
+ },
132
+ });
@@ -0,0 +1,60 @@
1
+ /**
2
+ * request-permissions.js — Request permission for dangerous operations.
3
+ *
4
+ * When an operation is flagged as destructive, this tool requests
5
+ * explicit user permission before proceeding.
6
+ */
7
+
8
+ import { defineTool } from './types.js';
9
+
10
+ export default defineTool({
11
+ name: 'RequestPermissions',
12
+ description: `Request permission from the user for a potentially dangerous operation.
13
+
14
+ Use this before executing destructive operations like:
15
+ - Deleting files or directories
16
+ - Running commands that modify system state
17
+ - Force-pushing to git
18
+ - Resetting databases
19
+
20
+ The user must explicitly approve before you proceed.`,
21
+ parameters: {
22
+ type: 'object',
23
+ properties: {
24
+ operation: {
25
+ type: 'string',
26
+ description: 'Description of the operation that needs permission',
27
+ },
28
+ reason: {
29
+ type: 'string',
30
+ description: 'Why this operation is necessary',
31
+ },
32
+ risk_level: {
33
+ type: 'string',
34
+ enum: ['low', 'medium', 'high', 'critical'],
35
+ description: 'Risk level of the operation',
36
+ },
37
+ },
38
+ required: ['operation'],
39
+ },
40
+ modes: ['work'],
41
+ isConcurrencySafe: () => false,
42
+ isReadOnly: () => true,
43
+ async execute(input, ctx) {
44
+ const { operation, reason, risk_level = 'medium' } = input;
45
+ if (!operation) return JSON.stringify({ error: 'operation is required' });
46
+
47
+ // In a full integration, this would use the ask_user mechanism
48
+ // to get explicit permission. For now, return a structured request.
49
+ return JSON.stringify({
50
+ type: 'permission_request',
51
+ operation,
52
+ reason: reason || 'Operation requires explicit permission',
53
+ riskLevel: risk_level,
54
+ message: `⚠️ Permission required for: ${operation}` +
55
+ (reason ? `\nReason: ${reason}` : '') +
56
+ `\nRisk level: ${risk_level}`,
57
+ hint: 'User must explicitly approve this operation before proceeding.',
58
+ });
59
+ },
60
+ });
@@ -0,0 +1,62 @@
1
+ /**
2
+ * send-message.js — Send a message to a sub-agent.
3
+ */
4
+
5
+ import { defineTool } from './types.js';
6
+ import { getAgentRegistry } from './agent.js';
7
+
8
+ export default defineTool({
9
+ name: 'SendMessage',
10
+ description: `Send a message to a sub-agent.
11
+
12
+ Use this to give tasks, provide instructions, or relay information to a sub-agent.
13
+ The message is queued for the agent to process.`,
14
+ parameters: {
15
+ type: 'object',
16
+ properties: {
17
+ agent_id: {
18
+ type: 'string',
19
+ description: 'The sub-agent ID (returned by Agent tool)',
20
+ },
21
+ message: {
22
+ type: 'string',
23
+ description: 'The message to send to the agent',
24
+ },
25
+ },
26
+ required: ['agent_id', 'message'],
27
+ },
28
+ modes: ['work'],
29
+ isConcurrencySafe: () => false,
30
+ isReadOnly: () => false,
31
+ async execute(input, ctx) {
32
+ const { agent_id, message } = input;
33
+ if (!agent_id) return JSON.stringify({ error: 'agent_id is required' });
34
+ if (!message) return JSON.stringify({ error: 'message is required' });
35
+
36
+ const agents = getAgentRegistry();
37
+ const agent = agents.get(agent_id);
38
+
39
+ if (!agent) {
40
+ return JSON.stringify({ error: `Agent not found: ${agent_id}` });
41
+ }
42
+
43
+ if (agent.status === 'closed') {
44
+ return JSON.stringify({ error: `Agent "${agent.name}" is closed` });
45
+ }
46
+
47
+ agent.messages.push({
48
+ role: 'user',
49
+ content: message,
50
+ timestamp: Date.now(),
51
+ });
52
+ agent.status = 'active';
53
+
54
+ return JSON.stringify({
55
+ success: true,
56
+ agentId: agent_id,
57
+ name: agent.name,
58
+ messageCount: agent.messages.length,
59
+ message: `Message sent to agent "${agent.name}"`,
60
+ });
61
+ },
62
+ });