@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.
@@ -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 tasks:
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 `@task-<nnn>` → switch to the thread attached to that task,
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} PendingTask
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<PendingTask>} [pendingTasks]
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 `@task-NNN` marker. Captures the task id WITHOUT the `@`.
82
- * Example matches: "@task-309 ...", "@task-abc ..."
81
+ * Match a leading `@feat-NNN` marker. Captures the feature id WITHOUT the `@`.
82
+ * Example matches: "@feat-309 ...", "@feat-abc ..."
83
83
  */
84
- const TASK_PREFIX_RE = /^@(task-[A-Za-z0-9_-]+)\b\s*/;
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
- pendingTasks = [],
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, pendingTasks,
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, pendingTasks,
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>, pendingTasks: Array<PendingTask> }} ctx
208
+ * @param {{ currentThreadId: string, allThreads: Array<ThreadSummary>, pendingFeatures: Array<PendingFeature> }} ctx
209
209
  * @returns {RouterDecision|null}
210
210
  */
211
- #parseExplicit(msg, { currentThreadId, allThreads, pendingTasks }) {
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
- // @task-NNN
237
- const mt = trimmed.match(TASK_PREFIX_RE);
236
+ // @feat-NNN
237
+ const mt = trimmed.match(FEATURE_PREFIX_RE);
238
238
  if (mt) {
239
- const taskId = mt[1];
240
- const task = pendingTasks.find(t => t && t.id === taskId);
241
- if (task && task.threadId) {
242
- const action = task.threadId === currentThreadId ? 'continue' : 'switch';
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: task.threadId,
246
- reason: `explicit @${taskId} → ${task.threadId}`,
245
+ targetThreadId: feature.threadId,
246
+ reason: `explicit @${featureId} → ${feature.threadId}`,
247
247
  source: 'explicit',
248
248
  };
249
249
  }
250
- // Task unknown or not attached — fall through to LLM.
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, pendingTasks }) {
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
- pendingTasks: (pendingTasks || []).map(t => ({
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, pendingTasks }) {
308
+ async #classifyWithLLM({ userMessage, currentThreadId, allThreads, pendingFeatures }) {
309
309
  const { system, messages } = this.#buildMessages({
310
- userMessage, currentThreadId, allThreads, pendingTasks,
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 { initTaskStore } from './tools/task-tools.js';
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 task store ─────────────────────────
171
- initTaskStore(yeaftDir, { readOnly: config._readOnly || false });
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
@@ -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 | Task |');
214
- lines.push('|--------|------|');
215
- for (const [threadId, taskId] of attachments.entries()) {
216
- lines.push(`| ${threadId} | ${taskId} |`);
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, taskId]) => ({ threadId, taskId })),
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.taskId === 'string',
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 → taskId */
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, taskId } of parseAttachments(raw)) {
398
+ for (const { threadId, featureId } of parseAttachments(raw)) {
399
399
  if (this.#threads.has(threadId)) {
400
- this.#attachments.set(threadId, taskId);
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 task attachment on source (it now belongs to target).
711
+ // Drop any feature attachment on source (it now belongs to target).
712
712
  if (this.#attachments.has(sourceId)) {
713
- const taskId = this.#attachments.get(sourceId);
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, taskId);
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
- attachTask(threadId, taskId) {
817
+ attachFeature(threadId, featureId) {
818
818
  if (!this.#threads.has(threadId)) {
819
819
  throw new Error(`thread not found: ${threadId}`);
820
820
  }
821
- if (!taskId || typeof taskId !== 'string') {
822
- throw new Error('taskId is required');
821
+ if (!featureId || typeof featureId !== 'string') {
822
+ throw new Error('featureId is required');
823
823
  }
824
- this.#attachments.set(threadId, taskId);
824
+ this.#attachments.set(threadId, featureId);
825
825
  this.#markAttachmentsDirty();
826
826
  }
827
827
 
828
- attachedTask(threadId) {
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, taskId]) => ({ threadId, taskId }));
833
+ return [...this.#attachments.entries()].map(([threadId, featureId]) => ({ threadId, featureId }));
834
834
  }
835
835
  }
836
836