@yeaft/webchat-agent 0.1.511 → 0.1.513
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 +3 -2
- package/unify/engine.js +55 -4
- package/unify/memory/layout.js +1 -1
- package/unify/prompts.js +40 -5
- package/unify/templates/base.md +95 -0
- package/unify/templates/mode-dream.md +97 -0
- package/unify/templates/mode-unified.md +57 -0
- package/unify/templates/personas/explorer.md +26 -0
- package/unify/templates/personas/implementer.md +31 -0
- package/unify/templates/personas/researcher.md +25 -0
- package/unify/templates/personas/reviewer.md +26 -0
- package/unify/templates/tool-guidance.md +77 -0
- package/unify/tools/index.js +5 -5
- package/unify/tools/js-repl.js +42 -15
- package/unify/tools/memory-query.js +3 -1
- package/unify/tools/memory-search.js +102 -63
- package/unify/tools/task-tools.js +22 -5
- package/unify/tools/thread-tools.js +16 -11
- package/unify/tools/view-image.js +249 -50
- package/unify/tools/tool-search.js +0 -88
- package/unify/tools/write-stdin.js +0 -53
package/unify/tools/js-repl.js
CHANGED
|
@@ -3,6 +3,11 @@
|
|
|
3
3
|
*
|
|
4
4
|
* Runs JavaScript code in a persistent VM context, allowing
|
|
5
5
|
* state to be maintained across calls.
|
|
6
|
+
*
|
|
7
|
+
* task-333b: merged the former `JsReplReset` tool into this one as a
|
|
8
|
+
* `reset: true` parameter. A single tool with a reset flag eliminates a
|
|
9
|
+
* duplicate schema entry in the function-call catalogue. The old
|
|
10
|
+
* JsReplReset name is kept below as a deprecated alias for one release.
|
|
6
11
|
*/
|
|
7
12
|
|
|
8
13
|
import { defineTool } from './types.js';
|
|
@@ -55,7 +60,9 @@ defined in one call are available in subsequent calls.
|
|
|
55
60
|
|
|
56
61
|
Guidelines:
|
|
57
62
|
- Use for calculations, data transformations, and quick experiments
|
|
58
|
-
- State is preserved between calls
|
|
63
|
+
- State is preserved between calls
|
|
64
|
+
- Pass \`reset: true\` to wipe all state before evaluating (clean slate).
|
|
65
|
+
\`code\` becomes optional in this mode — pass reset alone to just clear.
|
|
59
66
|
- console.log output is captured and returned
|
|
60
67
|
- Returns the last expression's value plus any console output
|
|
61
68
|
- No filesystem or network access from within the REPL`,
|
|
@@ -64,16 +71,27 @@ Guidelines:
|
|
|
64
71
|
properties: {
|
|
65
72
|
code: {
|
|
66
73
|
type: 'string',
|
|
67
|
-
description: 'JavaScript code to evaluate',
|
|
74
|
+
description: 'JavaScript code to evaluate. Optional when reset=true and you only want to clear state.',
|
|
75
|
+
},
|
|
76
|
+
reset: {
|
|
77
|
+
type: 'boolean',
|
|
78
|
+
description: 'When true, reset the REPL context BEFORE evaluating `code`. If `code` is omitted, just resets.',
|
|
68
79
|
},
|
|
69
80
|
},
|
|
70
|
-
required: ['code'],
|
|
71
81
|
},
|
|
72
82
|
isConcurrencySafe: () => false,
|
|
73
83
|
isReadOnly: () => true,
|
|
74
84
|
async execute(input, ctx) {
|
|
75
|
-
const { code } = input;
|
|
76
|
-
|
|
85
|
+
const { code, reset } = input || {};
|
|
86
|
+
|
|
87
|
+
if (reset) {
|
|
88
|
+
vmContext = null;
|
|
89
|
+
if (!code) {
|
|
90
|
+
return JSON.stringify({ success: true, message: 'REPL context reset' });
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
if (!code) return JSON.stringify({ error: 'code is required (or pass reset=true to clear state)' });
|
|
77
95
|
|
|
78
96
|
const vmCtx = getContext();
|
|
79
97
|
|
|
@@ -87,6 +105,7 @@ Guidelines:
|
|
|
87
105
|
const resultStr = result === undefined ? '' : String(result);
|
|
88
106
|
|
|
89
107
|
const parts = [];
|
|
108
|
+
if (reset) parts.push('(REPL context reset)');
|
|
90
109
|
if (output.length > 0) parts.push(output.join('\n'));
|
|
91
110
|
if (resultStr) parts.push(`→ ${resultStr}`);
|
|
92
111
|
|
|
@@ -101,20 +120,28 @@ Guidelines:
|
|
|
101
120
|
},
|
|
102
121
|
});
|
|
103
122
|
|
|
123
|
+
/**
|
|
124
|
+
* Deprecated alias — `JsReplReset`. task-333b merged reset behaviour into
|
|
125
|
+
* `JsRepl` via `reset: true`. Kept registered for one release so older
|
|
126
|
+
* prompts / saved tool calls still resolve. Emits a one-time deprecation
|
|
127
|
+
* warning on first invocation, then delegates to jsRepl.execute with reset.
|
|
128
|
+
*/
|
|
129
|
+
const _jsReplResetWarned = { v: false };
|
|
130
|
+
function warnJsReplResetDeprecated() {
|
|
131
|
+
if (_jsReplResetWarned.v) return;
|
|
132
|
+
_jsReplResetWarned.v = true;
|
|
133
|
+
// eslint-disable-next-line no-console
|
|
134
|
+
console.warn('[deprecated] JsReplReset → JsRepl. Call JsRepl with { reset: true } to clear REPL state.');
|
|
135
|
+
}
|
|
136
|
+
|
|
104
137
|
export const jsReplReset = defineTool({
|
|
105
138
|
name: 'JsReplReset',
|
|
106
|
-
description: `
|
|
107
|
-
|
|
108
|
-
Clears all variables and state from previous evaluations.
|
|
109
|
-
Use when you want a clean slate.`,
|
|
110
|
-
parameters: {
|
|
111
|
-
type: 'object',
|
|
112
|
-
properties: {},
|
|
113
|
-
},
|
|
139
|
+
description: 'DEPRECATED — use JsRepl with `reset: true` instead. Resets the persistent REPL context. Removal target: v0.2.0.',
|
|
140
|
+
parameters: { type: 'object', properties: {} },
|
|
114
141
|
isConcurrencySafe: () => false,
|
|
115
142
|
isReadOnly: () => false,
|
|
116
143
|
async execute(input, ctx) {
|
|
117
|
-
|
|
118
|
-
return
|
|
144
|
+
warnJsReplResetDeprecated();
|
|
145
|
+
return jsRepl.execute({ reset: true }, ctx);
|
|
119
146
|
},
|
|
120
147
|
});
|
|
@@ -10,7 +10,9 @@
|
|
|
10
10
|
* 3. Returns a compact list suitable for LLM consumption
|
|
11
11
|
*
|
|
12
12
|
* Use this for fuzzy discovery over atomic entries. For loading a known
|
|
13
|
-
* classification file in full, use `
|
|
13
|
+
* classification file in full, use `memory_load` instead (renamed from
|
|
14
|
+
* the old `memory_search` in task-333b; the old name still works as a
|
|
15
|
+
* deprecated alias for one release).
|
|
14
16
|
*/
|
|
15
17
|
|
|
16
18
|
import { defineTool } from './types.js';
|
|
@@ -1,7 +1,12 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* memory-search.js
|
|
2
|
+
* memory-search.js → memory_load (task-333b rename).
|
|
3
3
|
*
|
|
4
|
-
*
|
|
4
|
+
* This tool loads memory classification files by path (it is NOT a search).
|
|
5
|
+
* task-333b renamed it from `memory_search` → `memory_load` so the name
|
|
6
|
+
* actually reflects behaviour; `memory_query` remains the fuzzy search tool.
|
|
7
|
+
*
|
|
8
|
+
* Backwards compatibility: a thin alias tool named `memory_search` is also
|
|
9
|
+
* exported (see bottom of file) so older transcripts/prompts still resolve.
|
|
5
10
|
*
|
|
6
11
|
* The system prompt injects `index.md` every turn, which lists all available
|
|
7
12
|
* classification files under `~/.yeaft/memory/` (single files + by-project /
|
|
@@ -9,9 +14,6 @@
|
|
|
9
14
|
* it calls this tool with `paths: [...]` to load one or more of those files
|
|
10
15
|
* in full.
|
|
11
16
|
*
|
|
12
|
-
* This is NOT a fuzzy search. It is a precise file loader. Use `memory_query`
|
|
13
|
-
* for fuzzy search over atomic entries.
|
|
14
|
-
*
|
|
15
17
|
* Only paths under `memory/` are accepted. `..` segments are rejected.
|
|
16
18
|
*/
|
|
17
19
|
|
|
@@ -21,9 +23,7 @@ import { readMemoryFile, listClassificationFiles } from '../memory/layout.js';
|
|
|
21
23
|
const MAX_FILES_PER_CALL = 5;
|
|
22
24
|
const MAX_BYTES_PER_FILE = 32000;
|
|
23
25
|
|
|
24
|
-
|
|
25
|
-
name: 'memory_search',
|
|
26
|
-
description: `Load one or more memory classification files in full.
|
|
26
|
+
const DESCRIPTION = `Load one or more memory classification files in full.
|
|
27
27
|
|
|
28
28
|
Paths are relative to ~/.yeaft/memory/. Allowed targets:
|
|
29
29
|
- user-preferences.md — merged user preferences
|
|
@@ -36,66 +36,105 @@ of available files. Use this tool when the index suggests a file is relevant
|
|
|
36
36
|
to the user's current request. For fuzzy search over atomic memory entries
|
|
37
37
|
(facts, lessons, preferences), use the memory_query tool instead.
|
|
38
38
|
|
|
39
|
-
Up to ${MAX_FILES_PER_CALL} files per call. Each file is capped at ${MAX_BYTES_PER_FILE} bytes
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
39
|
+
Up to ${MAX_FILES_PER_CALL} files per call. Each file is capped at ${MAX_BYTES_PER_FILE} bytes.`;
|
|
40
|
+
|
|
41
|
+
const PARAMETERS = {
|
|
42
|
+
type: 'object',
|
|
43
|
+
properties: {
|
|
44
|
+
paths: {
|
|
45
|
+
type: 'array',
|
|
46
|
+
items: { type: 'string' },
|
|
47
|
+
description: 'Relative paths under memory/. Example: ["by-project/claude-web-chat.md", "user-preferences.md"]',
|
|
48
48
|
},
|
|
49
|
-
required: ['paths'],
|
|
50
49
|
},
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
async execute(input, ctx) {
|
|
54
|
-
const yeaftDir = ctx?.yeaftDir;
|
|
55
|
-
if (!yeaftDir) {
|
|
56
|
-
return JSON.stringify({ error: 'Memory system not initialized (no yeaftDir in context)' });
|
|
57
|
-
}
|
|
50
|
+
required: ['paths'],
|
|
51
|
+
};
|
|
58
52
|
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
53
|
+
async function executeLoad(input, ctx) {
|
|
54
|
+
const yeaftDir = ctx?.yeaftDir;
|
|
55
|
+
if (!yeaftDir) {
|
|
56
|
+
return JSON.stringify({ error: 'Memory system not initialized (no yeaftDir in context)' });
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
const paths = Array.isArray(input?.paths) ? input.paths : [];
|
|
60
|
+
if (paths.length === 0) {
|
|
61
|
+
return JSON.stringify({
|
|
62
|
+
error: 'paths is required and must be a non-empty string array',
|
|
63
|
+
availablePaths: listClassificationFiles(yeaftDir).map(f => f.path),
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
const results = [];
|
|
68
|
+
const errors = [];
|
|
69
|
+
|
|
70
|
+
for (const rel of paths.slice(0, MAX_FILES_PER_CALL)) {
|
|
71
|
+
if (typeof rel !== 'string' || !rel.trim()) {
|
|
72
|
+
errors.push({ path: rel, error: 'not a non-empty string' });
|
|
73
|
+
continue;
|
|
74
|
+
}
|
|
75
|
+
if (rel.includes('..') || rel.startsWith('/')) {
|
|
76
|
+
errors.push({ path: rel, error: 'path must be relative and must not contain ..' });
|
|
77
|
+
continue;
|
|
78
|
+
}
|
|
79
|
+
if (!rel.endsWith('.md')) {
|
|
80
|
+
errors.push({ path: rel, error: 'only .md files are supported' });
|
|
81
|
+
continue;
|
|
65
82
|
}
|
|
66
83
|
|
|
67
|
-
const
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
if (typeof rel !== 'string' || !rel.trim()) {
|
|
72
|
-
errors.push({ path: rel, error: 'not a non-empty string' });
|
|
73
|
-
continue;
|
|
74
|
-
}
|
|
75
|
-
if (rel.includes('..') || rel.startsWith('/')) {
|
|
76
|
-
errors.push({ path: rel, error: 'path must be relative and must not contain ..' });
|
|
77
|
-
continue;
|
|
78
|
-
}
|
|
79
|
-
if (!rel.endsWith('.md')) {
|
|
80
|
-
errors.push({ path: rel, error: 'only .md files are supported' });
|
|
81
|
-
continue;
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
const text = readMemoryFile(yeaftDir, rel);
|
|
85
|
-
if (!text) {
|
|
86
|
-
errors.push({ path: rel, error: 'file not found or empty' });
|
|
87
|
-
continue;
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
const truncated = text.length > MAX_BYTES_PER_FILE;
|
|
91
|
-
results.push({
|
|
92
|
-
path: rel,
|
|
93
|
-
content: truncated ? text.slice(0, MAX_BYTES_PER_FILE) : text,
|
|
94
|
-
truncated,
|
|
95
|
-
size: text.length,
|
|
96
|
-
});
|
|
84
|
+
const text = readMemoryFile(yeaftDir, rel);
|
|
85
|
+
if (!text) {
|
|
86
|
+
errors.push({ path: rel, error: 'file not found or empty' });
|
|
87
|
+
continue;
|
|
97
88
|
}
|
|
98
89
|
|
|
99
|
-
|
|
100
|
-
|
|
90
|
+
const truncated = text.length > MAX_BYTES_PER_FILE;
|
|
91
|
+
results.push({
|
|
92
|
+
path: rel,
|
|
93
|
+
content: truncated ? text.slice(0, MAX_BYTES_PER_FILE) : text,
|
|
94
|
+
truncated,
|
|
95
|
+
size: text.length,
|
|
96
|
+
});
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
return JSON.stringify({ results, errors }, null, 2);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Canonical tool — `memory_load`. This is the default export so existing
|
|
104
|
+
* import sites (`import memorySearch from './memory-search.js'`) keep
|
|
105
|
+
* working; the renamed identity is expressed via the tool's `name`.
|
|
106
|
+
*/
|
|
107
|
+
const memoryLoad = defineTool({
|
|
108
|
+
name: 'memory_load',
|
|
109
|
+
description: DESCRIPTION,
|
|
110
|
+
parameters: PARAMETERS,
|
|
111
|
+
isConcurrencySafe: () => true,
|
|
112
|
+
isReadOnly: () => true,
|
|
113
|
+
execute: executeLoad,
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* Deprecated alias — `memory_search`. Kept so older prompts / saved tool
|
|
118
|
+
* calls still resolve. Delegates to the same executor. Do not use for new
|
|
119
|
+
* call sites. Emits a one-time console.warn on first invocation.
|
|
120
|
+
*/
|
|
121
|
+
const _memSearchWarned = { v: false };
|
|
122
|
+
async function executeLoadWithWarn(input, ctx) {
|
|
123
|
+
if (!_memSearchWarned.v) {
|
|
124
|
+
_memSearchWarned.v = true;
|
|
125
|
+
// eslint-disable-next-line no-console
|
|
126
|
+
console.warn('[deprecated] memory_search → memory_load. Use memory_load for path-based file loading; use memory_query for fuzzy keyword search.');
|
|
127
|
+
}
|
|
128
|
+
return executeLoad(input, ctx);
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
export const memorySearchAlias = defineTool({
|
|
132
|
+
name: 'memory_search',
|
|
133
|
+
description: 'DEPRECATED — use memory_load. Same params. Removal target: v0.2.0.',
|
|
134
|
+
parameters: PARAMETERS,
|
|
135
|
+
isConcurrencySafe: () => true,
|
|
136
|
+
isReadOnly: () => true,
|
|
137
|
+
execute: executeLoadWithWarn,
|
|
101
138
|
});
|
|
139
|
+
|
|
140
|
+
export default memoryLoad;
|
|
@@ -48,7 +48,9 @@ export const taskCreate = defineTool({
|
|
|
48
48
|
|
|
49
49
|
Tasks have a title, description, priority, and status.
|
|
50
50
|
Each task gets its own folder with task.md, progress.md, and memory.md.
|
|
51
|
-
Use this to break down complex work into trackable items
|
|
51
|
+
Use this to break down complex work into trackable items.
|
|
52
|
+
|
|
53
|
+
Pass \`parent_id\` to create a subtask under an existing task.`,
|
|
52
54
|
parameters: {
|
|
53
55
|
type: 'object',
|
|
54
56
|
properties: {
|
|
@@ -69,6 +71,10 @@ Use this to break down complex work into trackable items.`,
|
|
|
69
71
|
type: 'string',
|
|
70
72
|
description: 'Parent task ID for subtasks',
|
|
71
73
|
},
|
|
74
|
+
// Note (task-333b): `parent_task_id` is accepted by execute() as a
|
|
75
|
+
// soft-compat alias for `parent_id` (absorbed from the former
|
|
76
|
+
// SpawnTask tool) but intentionally NOT advertised in the schema to
|
|
77
|
+
// avoid giving the LLM two live params for one field.
|
|
72
78
|
},
|
|
73
79
|
required: ['title'],
|
|
74
80
|
},
|
|
@@ -78,9 +84,17 @@ Use this to break down complex work into trackable items.`,
|
|
|
78
84
|
const err = requireStore();
|
|
79
85
|
if (err) return err;
|
|
80
86
|
|
|
81
|
-
const { title, description, priority = 'medium', parent_id } = input;
|
|
87
|
+
const { title, description, priority = 'medium', parent_id, parent_task_id } = input;
|
|
82
88
|
if (!title) return JSON.stringify({ error: 'title is required' });
|
|
83
89
|
|
|
90
|
+
// task-333b: accept either `parent_id` (original TaskCreate field) or
|
|
91
|
+
// `parent_task_id` (the former SpawnTask field). When both are present,
|
|
92
|
+
// parent_id wins.
|
|
93
|
+
const parentId = parent_id || parent_task_id || null;
|
|
94
|
+
if (parentId && !taskStore.get(parentId)) {
|
|
95
|
+
return JSON.stringify({ error: `Parent task not found: ${parentId}` });
|
|
96
|
+
}
|
|
97
|
+
|
|
84
98
|
const id = `task-${randomUUID().slice(0, 8)}`;
|
|
85
99
|
const task = {
|
|
86
100
|
id,
|
|
@@ -88,7 +102,8 @@ Use this to break down complex work into trackable items.`,
|
|
|
88
102
|
description: description || '',
|
|
89
103
|
priority,
|
|
90
104
|
status: 'pending',
|
|
91
|
-
parentId
|
|
105
|
+
parentId,
|
|
106
|
+
parentTaskId: parentId, // design §5 canonical field
|
|
92
107
|
createdAt: Date.now(),
|
|
93
108
|
updatedAt: Date.now(),
|
|
94
109
|
};
|
|
@@ -97,8 +112,10 @@ Use this to break down complex work into trackable items.`,
|
|
|
97
112
|
|
|
98
113
|
return JSON.stringify({
|
|
99
114
|
success: true,
|
|
100
|
-
task: { id, title, priority, status: 'pending' },
|
|
101
|
-
message:
|
|
115
|
+
task: { id, title, priority, status: 'pending', parentTaskId: parentId },
|
|
116
|
+
message: parentId
|
|
117
|
+
? `Subtask created: ${title} (${id}) under ${parentId}`
|
|
118
|
+
: `Task created: ${title} (${id})`,
|
|
102
119
|
});
|
|
103
120
|
},
|
|
104
121
|
});
|
|
@@ -193,20 +193,24 @@ the same thread.`,
|
|
|
193
193
|
},
|
|
194
194
|
});
|
|
195
195
|
|
|
196
|
-
// ─── SpawnTask (
|
|
196
|
+
// ─── SpawnTask (deprecated alias — task-333b) ──────────────
|
|
197
|
+
//
|
|
198
|
+
// task-333b folded SpawnTask into TaskCreate (see task-tools.js). Kept
|
|
199
|
+
// registered for one release as a deprecated alias per PM constraint:
|
|
200
|
+
// LLM calls still resolve, but a one-time console.warn nudges migration.
|
|
201
|
+
// Prefer TaskCreate with `parent_task_id` for all new call sites.
|
|
202
|
+
|
|
203
|
+
const _spawnTaskWarned = { v: false };
|
|
204
|
+
function warnSpawnTaskDeprecated() {
|
|
205
|
+
if (_spawnTaskWarned.v) return;
|
|
206
|
+
_spawnTaskWarned.v = true;
|
|
207
|
+
// eslint-disable-next-line no-console
|
|
208
|
+
console.warn('[deprecated] SpawnTask → TaskCreate. Pass parent_task_id to TaskCreate for subtasks.');
|
|
209
|
+
}
|
|
197
210
|
|
|
198
|
-
/**
|
|
199
|
-
* SpawnTask replaces both the old SpawnTask and the separate SpawnSubtask:
|
|
200
|
-
* pass `parent_task_id` when you want a subtask, omit it for top-level.
|
|
201
|
-
* Per prev-1 rework note: eliminating two tools with the same effect.
|
|
202
|
-
*/
|
|
203
211
|
export const spawnTask = defineTool({
|
|
204
212
|
name: 'SpawnTask',
|
|
205
|
-
description: `
|
|
206
|
-
|
|
207
|
-
When parent_task_id is omitted → top-level task.
|
|
208
|
-
When parent_task_id is provided → subtask under that parent (parent must exist).
|
|
209
|
-
This replaces the deprecated SpawnSubtask tool.`,
|
|
213
|
+
description: `DEPRECATED — use TaskCreate with parent_id instead. Retained as a thin alias for backwards compatibility; delegates to the same task store. When parent_task_id is omitted this behaves like TaskCreate; when provided it creates a subtask under that parent (parent must exist). Removal target: v0.2.0.`,
|
|
210
214
|
parameters: {
|
|
211
215
|
type: 'object',
|
|
212
216
|
properties: {
|
|
@@ -226,6 +230,7 @@ This replaces the deprecated SpawnSubtask tool.`,
|
|
|
226
230
|
isConcurrencySafe: () => false,
|
|
227
231
|
isReadOnly: () => false,
|
|
228
232
|
async execute(input) {
|
|
233
|
+
warnSpawnTaskDeprecated();
|
|
229
234
|
const store = getTaskStore();
|
|
230
235
|
if (!store) {
|
|
231
236
|
return JSON.stringify({ error: 'Task store not initialized. Session may still be loading.' });
|