@yeaft/webchat-agent 0.1.510 → 0.1.512

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.
@@ -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 (use JsReplReset to clear)
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
- if (!code) return JSON.stringify({ error: 'code is required' });
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: `Reset the JavaScript REPL environment.
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
- vmContext = null;
118
- return JSON.stringify({ success: true, message: 'REPL context reset' });
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 `memory_search` instead.
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 Load memory classification files by path.
2
+ * memory-search.js memory_load (task-333b rename).
3
3
  *
4
- * New-layout tool (task-287 memory refactor).
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
- export default defineTool({
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
- parameters: {
41
- type: 'object',
42
- properties: {
43
- paths: {
44
- type: 'array',
45
- items: { type: 'string' },
46
- description: 'Relative paths under memory/. Example: ["by-project/claude-web-chat.md", "user-preferences.md"]',
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
- isConcurrencySafe: () => true,
52
- isReadOnly: () => true,
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
- 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
- });
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 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;
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
- return JSON.stringify({ results, errors }, null, 2);
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: parent_id || null,
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: `Task created: ${title} (${id})`,
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 (mergedsubtask via parent_task_id) ────
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: `Spawn a new task. Pass parent_task_id to create a subtask.
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.' });