@yeaft/webchat-agent 0.1.653 → 0.1.654
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/index.js +3 -17
- package/unify/tools/thread-tools.js +0 -289
package/package.json
CHANGED
package/unify/tools/index.js
CHANGED
|
@@ -61,15 +61,9 @@ import {
|
|
|
61
61
|
featureSummaryPost,
|
|
62
62
|
} from './feature-tools.js';
|
|
63
63
|
|
|
64
|
-
//
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
switchThread,
|
|
68
|
-
listThreads,
|
|
69
|
-
attachThreadToFeature,
|
|
70
|
-
readThreadSummary,
|
|
71
|
-
readThreadRecent,
|
|
72
|
-
} from './thread-tools.js';
|
|
64
|
+
// H2.f.4: thread tools (spawnThread/switchThread/listThreads/...) deleted.
|
|
65
|
+
// The agent now runs in a single conversation; multi-thread orchestration
|
|
66
|
+
// has been retired across the H2.f series.
|
|
73
67
|
|
|
74
68
|
// --- P2 Auxiliary tools ---
|
|
75
69
|
// task-333b L1 delete: ToolSearch and WriteStdin removed — the function-call
|
|
@@ -137,14 +131,6 @@ export const allTools = [
|
|
|
137
131
|
updatePlan,
|
|
138
132
|
featureSummaryPost,
|
|
139
133
|
|
|
140
|
-
// P1 Thread (task-299 Phase 1)
|
|
141
|
-
spawnThread,
|
|
142
|
-
switchThread,
|
|
143
|
-
listThreads,
|
|
144
|
-
attachThreadToFeature,
|
|
145
|
-
readThreadSummary,
|
|
146
|
-
readThreadRecent,
|
|
147
|
-
|
|
148
134
|
// P2 Auxiliary
|
|
149
135
|
jsRepl,
|
|
150
136
|
jsReplReset,
|
|
@@ -1,289 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* thread-tools.js — Thread-spawning tools for Unify Engine (Phase 1).
|
|
3
|
-
*
|
|
4
|
-
* Phase 1 scope (task-299 rework):
|
|
5
|
-
* - SpawnThread — create a new thread
|
|
6
|
-
* - SwitchThread — set the engine's currentThreadId marker
|
|
7
|
-
* - ListThreads — list threads + current marker + cached stats
|
|
8
|
-
* - AttachThreadToFeature — bind a thread to an existing feature
|
|
9
|
-
* - ReadThreadSummary — cross-reference: summary of a thread (id/name/
|
|
10
|
-
* status/messageCount/lastMessageAt/feature)
|
|
11
|
-
* - ReadThreadRecent — cross-reference: last N messages of a thread
|
|
12
|
-
*
|
|
13
|
-
* Phase 1 uses the in-memory ThreadStore (agent/unify/threads/store.js)
|
|
14
|
-
* and the existing ConversationStore for messages. When task-298 merges,
|
|
15
|
-
* ThreadStore becomes file-backed with the SAME API, so these tools
|
|
16
|
-
* continue to work unchanged.
|
|
17
|
-
*/
|
|
18
|
-
|
|
19
|
-
import { defineTool } from './types.js';
|
|
20
|
-
import { getThreadStore, MAIN_THREAD_ID } from '../threads/store.js';
|
|
21
|
-
import { getFeatureStore } from './feature-tools.js';
|
|
22
|
-
|
|
23
|
-
// ─── SpawnThread ─────────────────────────────────────────
|
|
24
|
-
|
|
25
|
-
export const spawnThread = defineTool({
|
|
26
|
-
name: 'SpawnThread',
|
|
27
|
-
description: `Create a new thread (conversation track) for parallel work.
|
|
28
|
-
|
|
29
|
-
A thread is a named conversation track that groups related messages and
|
|
30
|
-
tool calls under a single goal. Use when the work needs a fresh focus
|
|
31
|
-
track separate from the current conversation.
|
|
32
|
-
|
|
33
|
-
Returns the new threadId (format: "thr-xxxxxxxx"). Does NOT switch the
|
|
34
|
-
engine to the new thread — call SwitchThread to activate it.`,
|
|
35
|
-
parameters: {
|
|
36
|
-
type: 'object',
|
|
37
|
-
properties: {
|
|
38
|
-
name: { type: 'string', description: 'Short human-readable name' },
|
|
39
|
-
goal: { type: 'string', description: 'Optional one-sentence goal' },
|
|
40
|
-
parent_thread_id: {
|
|
41
|
-
type: 'string',
|
|
42
|
-
description: 'Optional parent threadId for hierarchy',
|
|
43
|
-
},
|
|
44
|
-
},
|
|
45
|
-
required: ['name'],
|
|
46
|
-
},
|
|
47
|
-
isConcurrencySafe: () => false,
|
|
48
|
-
isReadOnly: () => false,
|
|
49
|
-
async execute(input) {
|
|
50
|
-
const { name, goal, parent_thread_id } = input || {};
|
|
51
|
-
if (!name) return JSON.stringify({ error: 'name is required' });
|
|
52
|
-
try {
|
|
53
|
-
const store = getThreadStore();
|
|
54
|
-
const t = store.create({ name, goal, parentThreadId: parent_thread_id || null });
|
|
55
|
-
return JSON.stringify({
|
|
56
|
-
success: true,
|
|
57
|
-
thread: {
|
|
58
|
-
id: t.id,
|
|
59
|
-
name: t.name,
|
|
60
|
-
goal: t.goal,
|
|
61
|
-
parentThreadId: t.parentThreadId,
|
|
62
|
-
status: t.status,
|
|
63
|
-
},
|
|
64
|
-
message: `Thread created: ${t.name} (${t.id})`,
|
|
65
|
-
});
|
|
66
|
-
} catch (err) {
|
|
67
|
-
return JSON.stringify({ error: err.message || String(err) });
|
|
68
|
-
}
|
|
69
|
-
},
|
|
70
|
-
});
|
|
71
|
-
|
|
72
|
-
// ─── SwitchThread ────────────────────────────────────────
|
|
73
|
-
|
|
74
|
-
export const switchThread = defineTool({
|
|
75
|
-
name: 'SwitchThread',
|
|
76
|
-
description: `Switch the engine's active thread marker.
|
|
77
|
-
|
|
78
|
-
All messages and tool calls persisted after this call will carry the new
|
|
79
|
-
threadId (Phase 1: marker only — the Engine reads it when persisting and
|
|
80
|
-
the web-bridge forwards it to the UI). Use 'main' to return to the root
|
|
81
|
-
thread.`,
|
|
82
|
-
parameters: {
|
|
83
|
-
type: 'object',
|
|
84
|
-
properties: {
|
|
85
|
-
thread_id: { type: 'string', description: 'Thread id to switch to' },
|
|
86
|
-
},
|
|
87
|
-
required: ['thread_id'],
|
|
88
|
-
},
|
|
89
|
-
isConcurrencySafe: () => false,
|
|
90
|
-
isReadOnly: () => false,
|
|
91
|
-
async execute(input) {
|
|
92
|
-
const { thread_id } = input || {};
|
|
93
|
-
if (!thread_id) return JSON.stringify({ error: 'thread_id is required' });
|
|
94
|
-
try {
|
|
95
|
-
const store = getThreadStore();
|
|
96
|
-
store.switch(thread_id);
|
|
97
|
-
return JSON.stringify({
|
|
98
|
-
success: true,
|
|
99
|
-
currentThreadId: store.currentId,
|
|
100
|
-
message: `Switched to thread ${thread_id}`,
|
|
101
|
-
});
|
|
102
|
-
} catch (err) {
|
|
103
|
-
return JSON.stringify({ error: err.message || String(err) });
|
|
104
|
-
}
|
|
105
|
-
},
|
|
106
|
-
});
|
|
107
|
-
|
|
108
|
-
// ─── ListThreads ─────────────────────────────────────────
|
|
109
|
-
|
|
110
|
-
export const listThreads = defineTool({
|
|
111
|
-
name: 'ListThreads',
|
|
112
|
-
description: `List all threads with cached status / messageCount / lastMessageAt.
|
|
113
|
-
|
|
114
|
-
Returns the data needed by the Phase 2 sidebar (task-300): each entry
|
|
115
|
-
exposes id, name, goal, parentThreadId, status ('active'|'idle'|'archived'),
|
|
116
|
-
messageCount, lastMessageAt, archived, attachedTaskId. Reads only cached
|
|
117
|
-
fields — does not scan messages.`,
|
|
118
|
-
parameters: { type: 'object', properties: {} },
|
|
119
|
-
isConcurrencySafe: () => true,
|
|
120
|
-
isReadOnly: () => true,
|
|
121
|
-
async execute() {
|
|
122
|
-
const store = getThreadStore();
|
|
123
|
-
const threads = store.list().map(t => ({
|
|
124
|
-
id: t.id,
|
|
125
|
-
name: t.name,
|
|
126
|
-
goal: t.goal,
|
|
127
|
-
parentThreadId: t.parentThreadId,
|
|
128
|
-
status: t.status,
|
|
129
|
-
messageCount: t.messageCount,
|
|
130
|
-
lastMessageAt: t.lastMessageAt,
|
|
131
|
-
lastActivityAt: t.lastActivityAt ?? t.lastMessageAt,
|
|
132
|
-
archived: t.archived,
|
|
133
|
-
unread: t.unread || 0,
|
|
134
|
-
preview: t.preview || '',
|
|
135
|
-
attachedFeatureId: store.attachedFeature(t.id),
|
|
136
|
-
}));
|
|
137
|
-
return JSON.stringify(
|
|
138
|
-
{
|
|
139
|
-
currentThreadId: store.currentId,
|
|
140
|
-
threads,
|
|
141
|
-
totalCount: threads.length,
|
|
142
|
-
},
|
|
143
|
-
null,
|
|
144
|
-
2,
|
|
145
|
-
);
|
|
146
|
-
},
|
|
147
|
-
});
|
|
148
|
-
|
|
149
|
-
// ─── AttachThreadToFeature ──────────────────────────────────
|
|
150
|
-
|
|
151
|
-
export const attachThreadToFeature = defineTool({
|
|
152
|
-
name: 'AttachThreadToFeature',
|
|
153
|
-
description: `Link an existing thread to an existing feature.
|
|
154
|
-
|
|
155
|
-
Use to record which thread is responsible for which feature. The thread and
|
|
156
|
-
the feature must both already exist. Overwrites any previous attachment for
|
|
157
|
-
the same thread.`,
|
|
158
|
-
parameters: {
|
|
159
|
-
type: 'object',
|
|
160
|
-
properties: {
|
|
161
|
-
thread_id: { type: 'string' },
|
|
162
|
-
feature_id: { type: 'string' },
|
|
163
|
-
},
|
|
164
|
-
required: ['thread_id', 'feature_id'],
|
|
165
|
-
},
|
|
166
|
-
isConcurrencySafe: () => false,
|
|
167
|
-
isReadOnly: () => false,
|
|
168
|
-
async execute(input) {
|
|
169
|
-
const { thread_id, feature_id } = input || {};
|
|
170
|
-
if (!thread_id) return JSON.stringify({ error: 'thread_id is required' });
|
|
171
|
-
if (!feature_id) return JSON.stringify({ error: 'feature_id is required' });
|
|
172
|
-
|
|
173
|
-
const featureStore = getFeatureStore();
|
|
174
|
-
if (featureStore) {
|
|
175
|
-
const feature = featureStore.get(feature_id);
|
|
176
|
-
if (!feature) return JSON.stringify({ error: `Feature not found: ${feature_id}` });
|
|
177
|
-
}
|
|
178
|
-
|
|
179
|
-
try {
|
|
180
|
-
const store = getThreadStore();
|
|
181
|
-
store.attachFeature(thread_id, feature_id);
|
|
182
|
-
return JSON.stringify({
|
|
183
|
-
success: true,
|
|
184
|
-
threadId: thread_id,
|
|
185
|
-
featureId: feature_id,
|
|
186
|
-
message: `Thread ${thread_id} attached to feature ${feature_id}`,
|
|
187
|
-
});
|
|
188
|
-
} catch (err) {
|
|
189
|
-
return JSON.stringify({ error: err.message || String(err) });
|
|
190
|
-
}
|
|
191
|
-
},
|
|
192
|
-
});
|
|
193
|
-
|
|
194
|
-
// ─── SpawnTask removed (task-333b) — use FeatureCreate with parent_feature_id
|
|
195
|
-
|
|
196
|
-
// ─── ReadThreadSummary (cross-reference, design §6 Q5) ──
|
|
197
|
-
|
|
198
|
-
export const readThreadSummary = defineTool({
|
|
199
|
-
name: 'ReadThreadSummary',
|
|
200
|
-
description: `Return a one-shot summary of a thread: id, name, goal, status,
|
|
201
|
-
messageCount, lastMessageAt, parentThreadId, attachedFeatureId.
|
|
202
|
-
|
|
203
|
-
Use this to cross-reference work on another thread without switching.`,
|
|
204
|
-
parameters: {
|
|
205
|
-
type: 'object',
|
|
206
|
-
properties: {
|
|
207
|
-
thread_id: { type: 'string' },
|
|
208
|
-
},
|
|
209
|
-
required: ['thread_id'],
|
|
210
|
-
},
|
|
211
|
-
isConcurrencySafe: () => true,
|
|
212
|
-
isReadOnly: () => true,
|
|
213
|
-
async execute(input) {
|
|
214
|
-
const { thread_id } = input || {};
|
|
215
|
-
if (!thread_id) return JSON.stringify({ error: 'thread_id is required' });
|
|
216
|
-
const store = getThreadStore();
|
|
217
|
-
const t = store.get(thread_id);
|
|
218
|
-
if (!t) return JSON.stringify({ error: `Thread not found: ${thread_id}` });
|
|
219
|
-
return JSON.stringify(
|
|
220
|
-
{
|
|
221
|
-
id: t.id,
|
|
222
|
-
name: t.name,
|
|
223
|
-
goal: t.goal,
|
|
224
|
-
status: t.status,
|
|
225
|
-
archived: t.archived,
|
|
226
|
-
messageCount: t.messageCount,
|
|
227
|
-
lastMessageAt: t.lastMessageAt,
|
|
228
|
-
parentThreadId: t.parentThreadId,
|
|
229
|
-
attachedFeatureId: store.attachedFeature(t.id),
|
|
230
|
-
createdAt: t.createdAt,
|
|
231
|
-
updatedAt: t.updatedAt,
|
|
232
|
-
},
|
|
233
|
-
null,
|
|
234
|
-
2,
|
|
235
|
-
);
|
|
236
|
-
},
|
|
237
|
-
});
|
|
238
|
-
|
|
239
|
-
// ─── ReadThreadRecent (cross-reference, design §6 Q5) ───
|
|
240
|
-
|
|
241
|
-
export const readThreadRecent = defineTool({
|
|
242
|
-
name: 'ReadThreadRecent',
|
|
243
|
-
description: `Return the last N messages on a specific thread.
|
|
244
|
-
|
|
245
|
-
Requires an engine ConversationStore in context (ctx.conversationStore).
|
|
246
|
-
Reads conversation history and filters by threadId. Default N=20, max 200.
|
|
247
|
-
Use this to review another thread's recent activity without switching.`,
|
|
248
|
-
parameters: {
|
|
249
|
-
type: 'object',
|
|
250
|
-
properties: {
|
|
251
|
-
thread_id: { type: 'string' },
|
|
252
|
-
limit: { type: 'number', description: 'Max messages to return (default 20, max 200)' },
|
|
253
|
-
},
|
|
254
|
-
required: ['thread_id'],
|
|
255
|
-
},
|
|
256
|
-
isConcurrencySafe: () => true,
|
|
257
|
-
isReadOnly: () => true,
|
|
258
|
-
async execute(input, ctx) {
|
|
259
|
-
const { thread_id, limit } = input || {};
|
|
260
|
-
if (!thread_id) return JSON.stringify({ error: 'thread_id is required' });
|
|
261
|
-
const store = getThreadStore();
|
|
262
|
-
if (!store.has(thread_id)) {
|
|
263
|
-
return JSON.stringify({ error: `Thread not found: ${thread_id}` });
|
|
264
|
-
}
|
|
265
|
-
const conv = ctx?.conversationStore;
|
|
266
|
-
if (!conv || typeof conv.loadRecent !== 'function') {
|
|
267
|
-
return JSON.stringify({
|
|
268
|
-
error: 'conversation store unavailable in tool context',
|
|
269
|
-
});
|
|
270
|
-
}
|
|
271
|
-
const cap = Math.max(1, Math.min(Number(limit) || 20, 200));
|
|
272
|
-
// Over-fetch then filter by thread, so N still applies post-filter.
|
|
273
|
-
const raw = conv.loadRecent(cap * 4);
|
|
274
|
-
const filtered = raw
|
|
275
|
-
.filter(m => (m.threadId || MAIN_THREAD_ID) === thread_id)
|
|
276
|
-
.slice(-cap)
|
|
277
|
-
.map(m => ({
|
|
278
|
-
role: m.role,
|
|
279
|
-
content: typeof m.content === 'string' ? m.content : JSON.stringify(m.content),
|
|
280
|
-
createdAt: m.createdAt || null,
|
|
281
|
-
threadId: m.threadId || MAIN_THREAD_ID,
|
|
282
|
-
}));
|
|
283
|
-
return JSON.stringify(
|
|
284
|
-
{ threadId: thread_id, count: filtered.length, messages: filtered },
|
|
285
|
-
null,
|
|
286
|
-
2,
|
|
287
|
-
);
|
|
288
|
-
},
|
|
289
|
-
});
|