@yeaft/webchat-agent 0.1.615 → 0.1.617
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/connection/upgrade.js +102 -0
- package/package.json +1 -1
- package/unify/compact/orchestrator.js +1 -1
- package/unify/{task-message.js → feature-message.js} +38 -38
- package/unify/features/store.js +583 -0
- package/unify/{tasks → features}/summary.js +54 -85
- package/unify/memory/dream-shard.js +15 -15
- package/unify/memory/recall-r6.js +2 -2
- package/unify/memory/schema.js +7 -7
- package/unify/memory/scope-tree.js +6 -6
- package/unify/pipeline/dispatcher.js +7 -7
- package/unify/router/intent-classifier.js +25 -25
- package/unify/session.js +3 -3
- package/unify/threads/store.js +18 -18
- package/unify/tools/{task-tools.js → feature-tools.js} +180 -175
- package/unify/tools/index.js +21 -21
- package/unify/tools/thread-tools.js +29 -108
- package/unify/web-bridge.js +61 -61
- package/unify/memory/migrate-r5-to-r6.js +0 -611
- package/unify/migration/detect.js +0 -176
- package/unify/migration/map-fields.js +0 -293
- package/unify/migration/v0-to-v1.js +0 -588
- package/unify/tasks/store.js +0 -652
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* intent-classifier.js — task-309 (Phase 2 Router).
|
|
3
3
|
*
|
|
4
4
|
* Routes an incoming user message to one of four intents relative to the
|
|
5
|
-
* current set of live threads + pending
|
|
5
|
+
* current set of live threads + pending features:
|
|
6
6
|
*
|
|
7
7
|
* - 'continue' — append to currentThreadId (default / most common)
|
|
8
8
|
* - 'interrupt' — steal focus on another LIVE thread (e.g. user replies
|
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
*
|
|
15
15
|
* 1. **Explicit signal parse** (no LLM):
|
|
16
16
|
* - Prefix `@thread-<id>` → switch/interrupt that thread (direct).
|
|
17
|
-
* - Prefix `@
|
|
17
|
+
* - Prefix `@feat-<nnn>` → switch to the thread attached to that task,
|
|
18
18
|
* if any; otherwise fall through to LLM.
|
|
19
19
|
* 2. **User override lookup**: if UI previously called `.override(msgId,…)`
|
|
20
20
|
* for this message, return that decision verbatim.
|
|
@@ -53,7 +53,7 @@
|
|
|
53
53
|
* @property {string} [goal]
|
|
54
54
|
* @property {string} [status]
|
|
55
55
|
*
|
|
56
|
-
* @typedef {Object}
|
|
56
|
+
* @typedef {Object} PendingFeature
|
|
57
57
|
* @property {string} id
|
|
58
58
|
* @property {string} [title]
|
|
59
59
|
* @property {string} [threadId] — attached thread, if any
|
|
@@ -63,7 +63,7 @@
|
|
|
63
63
|
* @property {string} userMessage
|
|
64
64
|
* @property {string} currentThreadId
|
|
65
65
|
* @property {Array<ThreadSummary>} [allThreads]
|
|
66
|
-
* @property {Array<
|
|
66
|
+
* @property {Array<PendingFeature>} [pendingFeatures]
|
|
67
67
|
* @property {string} [messageId] — if provided, any stored override for this
|
|
68
68
|
* id is consulted before the LLM path
|
|
69
69
|
*/
|
|
@@ -78,10 +78,10 @@ const VALID_ACTIONS = ['continue', 'interrupt', 'fork', 'switch'];
|
|
|
78
78
|
const THREAD_PREFIX_RE = /^@(thread-[A-Za-z0-9_-]+)\b\s*/;
|
|
79
79
|
|
|
80
80
|
/**
|
|
81
|
-
* Match a leading `@
|
|
82
|
-
* Example matches: "@
|
|
81
|
+
* Match a leading `@feat-NNN` marker. Captures the feature id WITHOUT the `@`.
|
|
82
|
+
* Example matches: "@feat-309 ...", "@feat-abc ..."
|
|
83
83
|
*/
|
|
84
|
-
const
|
|
84
|
+
const FEATURE_PREFIX_RE = /^@(feat-[A-Za-z0-9_-]+)\b\s*/;
|
|
85
85
|
|
|
86
86
|
export class IntentClassifier {
|
|
87
87
|
/** @type {object} */ #adapter;
|
|
@@ -161,7 +161,7 @@ export class IntentClassifier {
|
|
|
161
161
|
userMessage,
|
|
162
162
|
currentThreadId,
|
|
163
163
|
allThreads = [],
|
|
164
|
-
|
|
164
|
+
pendingFeatures = [],
|
|
165
165
|
messageId,
|
|
166
166
|
} = input || {};
|
|
167
167
|
|
|
@@ -181,14 +181,14 @@ export class IntentClassifier {
|
|
|
181
181
|
|
|
182
182
|
// 2. Explicit signals (no LLM call).
|
|
183
183
|
const explicit = this.#parseExplicit(userMessage, {
|
|
184
|
-
currentThreadId, allThreads,
|
|
184
|
+
currentThreadId, allThreads, pendingFeatures,
|
|
185
185
|
});
|
|
186
186
|
if (explicit) return explicit;
|
|
187
187
|
|
|
188
188
|
// 3. LLM classification (best-effort).
|
|
189
189
|
try {
|
|
190
190
|
const decision = await this.#classifyWithLLM({
|
|
191
|
-
userMessage, currentThreadId, allThreads,
|
|
191
|
+
userMessage, currentThreadId, allThreads, pendingFeatures,
|
|
192
192
|
});
|
|
193
193
|
return this.#validateOrFallback(decision, {
|
|
194
194
|
currentThreadId, allThreads, reason: 'llm',
|
|
@@ -205,10 +205,10 @@ export class IntentClassifier {
|
|
|
205
205
|
|
|
206
206
|
/**
|
|
207
207
|
* @param {string} msg
|
|
208
|
-
* @param {{ currentThreadId: string, allThreads: Array<ThreadSummary>,
|
|
208
|
+
* @param {{ currentThreadId: string, allThreads: Array<ThreadSummary>, pendingFeatures: Array<PendingFeature> }} ctx
|
|
209
209
|
* @returns {RouterDecision|null}
|
|
210
210
|
*/
|
|
211
|
-
#parseExplicit(msg, { currentThreadId, allThreads,
|
|
211
|
+
#parseExplicit(msg, { currentThreadId, allThreads, pendingFeatures }) {
|
|
212
212
|
const trimmed = msg.replace(/^\s+/, '');
|
|
213
213
|
|
|
214
214
|
// @thread-xxx
|
|
@@ -233,21 +233,21 @@ export class IntentClassifier {
|
|
|
233
233
|
};
|
|
234
234
|
}
|
|
235
235
|
|
|
236
|
-
// @
|
|
237
|
-
const mt = trimmed.match(
|
|
236
|
+
// @feat-NNN
|
|
237
|
+
const mt = trimmed.match(FEATURE_PREFIX_RE);
|
|
238
238
|
if (mt) {
|
|
239
|
-
const
|
|
240
|
-
const
|
|
241
|
-
if (
|
|
242
|
-
const action =
|
|
239
|
+
const featureId = mt[1];
|
|
240
|
+
const feature = pendingFeatures.find(t => t && t.id === featureId);
|
|
241
|
+
if (feature && feature.threadId) {
|
|
242
|
+
const action = feature.threadId === currentThreadId ? 'continue' : 'switch';
|
|
243
243
|
return {
|
|
244
244
|
action,
|
|
245
|
-
targetThreadId:
|
|
246
|
-
reason: `explicit @${
|
|
245
|
+
targetThreadId: feature.threadId,
|
|
246
|
+
reason: `explicit @${featureId} → ${feature.threadId}`,
|
|
247
247
|
source: 'explicit',
|
|
248
248
|
};
|
|
249
249
|
}
|
|
250
|
-
//
|
|
250
|
+
// Feature unknown or not attached — fall through to LLM.
|
|
251
251
|
return null;
|
|
252
252
|
}
|
|
253
253
|
|
|
@@ -259,7 +259,7 @@ export class IntentClassifier {
|
|
|
259
259
|
// ──────────────────────────────────────────────────────────────
|
|
260
260
|
|
|
261
261
|
/** Build the prompt/messages for the router LLM call. */
|
|
262
|
-
#buildMessages({ userMessage, currentThreadId, allThreads,
|
|
262
|
+
#buildMessages({ userMessage, currentThreadId, allThreads, pendingFeatures }) {
|
|
263
263
|
const system = [
|
|
264
264
|
'You are a thread-routing classifier for a multi-thread AI chat.',
|
|
265
265
|
'Given the user message and current thread context, pick exactly one action:',
|
|
@@ -286,7 +286,7 @@ export class IntentClassifier {
|
|
|
286
286
|
goal: t.goal || '',
|
|
287
287
|
status: t.status || 'active',
|
|
288
288
|
})),
|
|
289
|
-
|
|
289
|
+
pendingFeatures: (pendingFeatures || []).map(t => ({
|
|
290
290
|
id: t.id,
|
|
291
291
|
title: t.title || '',
|
|
292
292
|
threadId: t.threadId || null,
|
|
@@ -305,9 +305,9 @@ export class IntentClassifier {
|
|
|
305
305
|
}
|
|
306
306
|
|
|
307
307
|
/** @returns {Promise<RouterDecision>} */
|
|
308
|
-
async #classifyWithLLM({ userMessage, currentThreadId, allThreads,
|
|
308
|
+
async #classifyWithLLM({ userMessage, currentThreadId, allThreads, pendingFeatures }) {
|
|
309
309
|
const { system, messages } = this.#buildMessages({
|
|
310
|
-
userMessage, currentThreadId, allThreads,
|
|
310
|
+
userMessage, currentThreadId, allThreads, pendingFeatures,
|
|
311
311
|
});
|
|
312
312
|
// Q2: router uses primaryModel (no fast-model split yet).
|
|
313
313
|
const model = this.#config.primaryModel || this.#config.model;
|
package/unify/session.js
CHANGED
|
@@ -23,7 +23,7 @@ import { openMemoryShardStore } from './memory/shard-store.js';
|
|
|
23
23
|
import { SkillManager, createSkillManager } from './skills.js';
|
|
24
24
|
import { MCPManager } from './mcp.js';
|
|
25
25
|
import { createFullRegistry } from './tools/index.js';
|
|
26
|
-
import {
|
|
26
|
+
import { initFeatureStore } from './tools/feature-tools.js';
|
|
27
27
|
import { initThreadStore } from './threads/store.js';
|
|
28
28
|
import { Engine } from './engine.js';
|
|
29
29
|
import { createThreadEngineRegistry } from './threads/engine-registry.js';
|
|
@@ -167,8 +167,8 @@ export async function loadSession(options = {}) {
|
|
|
167
167
|
console.warn(`[Yeaft] Failed to open R6 memory shard store: ${err?.message || err}`);
|
|
168
168
|
}
|
|
169
169
|
|
|
170
|
-
// ─── 5a. Initialize
|
|
171
|
-
|
|
170
|
+
// ─── 5a. Initialize feature store ──────────────────────
|
|
171
|
+
initFeatureStore(yeaftDir, { readOnly: config._readOnly || false });
|
|
172
172
|
|
|
173
173
|
// ─── 5b. Initialize thread store (task-299 Phase 1) ────
|
|
174
174
|
// task-307a: now file-backed under ~/.yeaft/threads/. Passing the
|
package/unify/threads/store.js
CHANGED
|
@@ -210,10 +210,10 @@ function generateIndex(threads, currentId, attachments) {
|
|
|
210
210
|
lines.push('');
|
|
211
211
|
lines.push('## Attachments');
|
|
212
212
|
lines.push('');
|
|
213
|
-
lines.push('| Thread |
|
|
214
|
-
lines.push('
|
|
215
|
-
for (const [threadId,
|
|
216
|
-
lines.push(`| ${threadId} | ${
|
|
213
|
+
lines.push('| Thread | Feature |');
|
|
214
|
+
lines.push('|--------|---------|');
|
|
215
|
+
for (const [threadId, featureId] of attachments.entries()) {
|
|
216
|
+
lines.push(`| ${threadId} | ${featureId} |`);
|
|
217
217
|
}
|
|
218
218
|
}
|
|
219
219
|
return lines.join('\n') + '\n';
|
|
@@ -226,7 +226,7 @@ function generateIndex(threads, currentId, attachments) {
|
|
|
226
226
|
*/
|
|
227
227
|
function serializeAttachments(attachments) {
|
|
228
228
|
return JSON.stringify(
|
|
229
|
-
[...attachments.entries()].map(([threadId,
|
|
229
|
+
[...attachments.entries()].map(([threadId, featureId]) => ({ threadId, featureId })),
|
|
230
230
|
null,
|
|
231
231
|
2,
|
|
232
232
|
) + '\n';
|
|
@@ -237,7 +237,7 @@ function parseAttachments(raw) {
|
|
|
237
237
|
const arr = JSON.parse(raw);
|
|
238
238
|
if (!Array.isArray(arr)) return [];
|
|
239
239
|
return arr.filter(
|
|
240
|
-
(e) => e && typeof e.threadId === 'string' && typeof e.
|
|
240
|
+
(e) => e && typeof e.threadId === 'string' && typeof e.featureId === 'string',
|
|
241
241
|
);
|
|
242
242
|
} catch {
|
|
243
243
|
return [];
|
|
@@ -268,7 +268,7 @@ export class ThreadStore {
|
|
|
268
268
|
#threads;
|
|
269
269
|
/** @type {string} */
|
|
270
270
|
#currentId;
|
|
271
|
-
/** @type {Map<string, string>} threadId →
|
|
271
|
+
/** @type {Map<string, string>} threadId → featureId */
|
|
272
272
|
#attachments;
|
|
273
273
|
|
|
274
274
|
/** @type {string|null} */
|
|
@@ -395,9 +395,9 @@ export class ThreadStore {
|
|
|
395
395
|
try {
|
|
396
396
|
if (this.#attachmentsPath && existsSync(this.#attachmentsPath)) {
|
|
397
397
|
const raw = readFileSync(this.#attachmentsPath, 'utf8');
|
|
398
|
-
for (const { threadId,
|
|
398
|
+
for (const { threadId, featureId } of parseAttachments(raw)) {
|
|
399
399
|
if (this.#threads.has(threadId)) {
|
|
400
|
-
this.#attachments.set(threadId,
|
|
400
|
+
this.#attachments.set(threadId, featureId);
|
|
401
401
|
}
|
|
402
402
|
}
|
|
403
403
|
}
|
|
@@ -708,13 +708,13 @@ export class ThreadStore {
|
|
|
708
708
|
this.#currentId = targetId;
|
|
709
709
|
}
|
|
710
710
|
|
|
711
|
-
// Drop any
|
|
711
|
+
// Drop any feature attachment on source (it now belongs to target).
|
|
712
712
|
if (this.#attachments.has(sourceId)) {
|
|
713
|
-
const
|
|
713
|
+
const featureId = this.#attachments.get(sourceId);
|
|
714
714
|
this.#attachments.delete(sourceId);
|
|
715
715
|
// Preserve attachment on target if it had none; otherwise keep target's.
|
|
716
716
|
if (!this.#attachments.has(targetId)) {
|
|
717
|
-
this.#attachments.set(targetId,
|
|
717
|
+
this.#attachments.set(targetId, featureId);
|
|
718
718
|
}
|
|
719
719
|
this.#markAttachmentsDirty();
|
|
720
720
|
}
|
|
@@ -814,23 +814,23 @@ export class ThreadStore {
|
|
|
814
814
|
}
|
|
815
815
|
}
|
|
816
816
|
|
|
817
|
-
|
|
817
|
+
attachFeature(threadId, featureId) {
|
|
818
818
|
if (!this.#threads.has(threadId)) {
|
|
819
819
|
throw new Error(`thread not found: ${threadId}`);
|
|
820
820
|
}
|
|
821
|
-
if (!
|
|
822
|
-
throw new Error('
|
|
821
|
+
if (!featureId || typeof featureId !== 'string') {
|
|
822
|
+
throw new Error('featureId is required');
|
|
823
823
|
}
|
|
824
|
-
this.#attachments.set(threadId,
|
|
824
|
+
this.#attachments.set(threadId, featureId);
|
|
825
825
|
this.#markAttachmentsDirty();
|
|
826
826
|
}
|
|
827
827
|
|
|
828
|
-
|
|
828
|
+
attachedFeature(threadId) {
|
|
829
829
|
return this.#attachments.get(threadId) || null;
|
|
830
830
|
}
|
|
831
831
|
|
|
832
832
|
listAttachments() {
|
|
833
|
-
return [...this.#attachments.entries()].map(([threadId,
|
|
833
|
+
return [...this.#attachments.entries()].map(([threadId, featureId]) => ({ threadId, featureId }));
|
|
834
834
|
}
|
|
835
835
|
}
|
|
836
836
|
|