@yeaft/webchat-agent 1.0.78 → 1.0.79
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/buffer.js +6 -2
- package/connection/message-router.js +64 -1
- package/conversation.js +32 -4
- package/crew/builtin-actions.js +154 -0
- package/crew/context-loader.js +171 -0
- package/crew/control.js +444 -0
- package/crew/human-interaction.js +195 -0
- package/crew/persistence.js +295 -0
- package/crew/role-management.js +182 -0
- package/crew/role-output.js +461 -0
- package/crew/role-query.js +406 -0
- package/crew/role-states.js +180 -0
- package/crew/routing-fallback.js +64 -0
- package/crew/routing-metrics.js +215 -0
- package/crew/routing.js +951 -0
- package/crew/session.js +648 -0
- package/crew/shared-dir.js +266 -0
- package/crew/task-files.js +554 -0
- package/crew/ui-messages.js +274 -0
- package/crew/worktree.js +130 -0
- package/crew-i18n.js +579 -0
- package/crew.js +48 -0
- package/history.js +2 -2
- package/index.js +6 -0
- package/package.json +1 -1
- package/providers/claude-code.js +1 -1
- package/yeaft/attachments.js +2 -2
- package/yeaft/config.js +1 -1
- package/yeaft/conversation/persist.js +112 -458
- package/yeaft/conversation/search.js +15 -51
- package/yeaft/session.js +18 -27
- package/yeaft/web-bridge.js +15 -27
|
@@ -8,24 +8,14 @@ import { existsSync, readdirSync, readFileSync, statSync } from 'fs';
|
|
|
8
8
|
import { join } from 'path';
|
|
9
9
|
import { parseMessage, parseSeqFromId } from './persist.js';
|
|
10
10
|
|
|
11
|
-
function parseJsonLine(line) {
|
|
12
|
-
if (!line || !line.trim()) return null;
|
|
13
|
-
try {
|
|
14
|
-
const msg = JSON.parse(line);
|
|
15
|
-
return msg && typeof msg === 'object' ? msg : null;
|
|
16
|
-
} catch {
|
|
17
|
-
return null;
|
|
18
|
-
}
|
|
19
|
-
}
|
|
20
|
-
|
|
21
11
|
/**
|
|
22
|
-
* Search
|
|
12
|
+
* Search messages in a directory for a keyword.
|
|
23
13
|
*
|
|
24
14
|
* @param {string} dir — messages directory
|
|
25
15
|
* @param {string} keyword — search term (case-insensitive)
|
|
26
16
|
* @returns {object[]} — matching messages
|
|
27
17
|
*/
|
|
28
|
-
function
|
|
18
|
+
function searchDir(dir, keyword) {
|
|
29
19
|
if (!existsSync(dir)) return [];
|
|
30
20
|
|
|
31
21
|
const lowerKeyword = keyword.toLowerCase();
|
|
@@ -45,29 +35,6 @@ function searchMarkdownDir(dir, keyword) {
|
|
|
45
35
|
return results;
|
|
46
36
|
}
|
|
47
37
|
|
|
48
|
-
function searchSegmentDir(dir, keyword) {
|
|
49
|
-
if (!existsSync(dir)) return [];
|
|
50
|
-
const lowerKeyword = keyword.toLowerCase();
|
|
51
|
-
const files = readdirSync(dir)
|
|
52
|
-
.filter(f => f.endsWith('.jsonl'))
|
|
53
|
-
.sort()
|
|
54
|
-
.reverse();
|
|
55
|
-
|
|
56
|
-
const results = [];
|
|
57
|
-
for (const file of files) {
|
|
58
|
-
const raw = readFileSync(join(dir, file), 'utf8');
|
|
59
|
-
if (!raw.toLowerCase().includes(lowerKeyword)) continue;
|
|
60
|
-
const lines = raw.split('\n');
|
|
61
|
-
for (let i = lines.length - 1; i >= 0; i -= 1) {
|
|
62
|
-
const line = lines[i];
|
|
63
|
-
if (!line || !line.toLowerCase().includes(lowerKeyword)) continue;
|
|
64
|
-
const msg = parseJsonLine(line);
|
|
65
|
-
if (msg) results.push(msg);
|
|
66
|
-
}
|
|
67
|
-
}
|
|
68
|
-
return results;
|
|
69
|
-
}
|
|
70
|
-
|
|
71
38
|
function compareNewest(a, b) {
|
|
72
39
|
const sa = parseSeqFromId(a?.id);
|
|
73
40
|
const sb = parseSeqFromId(b?.id);
|
|
@@ -75,7 +42,7 @@ function compareNewest(a, b) {
|
|
|
75
42
|
return String(b?.time || '').localeCompare(String(a?.time || ''));
|
|
76
43
|
}
|
|
77
44
|
|
|
78
|
-
function
|
|
45
|
+
function sessionConversationMessageDirs(dir) {
|
|
79
46
|
const dirs = [];
|
|
80
47
|
const seen = new Set();
|
|
81
48
|
for (const rootName of ['sessions', 'groups']) {
|
|
@@ -90,9 +57,12 @@ function sessionConversationDirs(dir) {
|
|
|
90
57
|
}
|
|
91
58
|
|
|
92
59
|
const conversationDir = join(sessionDir, 'conversation');
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
60
|
+
for (const kind of ['messages', 'cold']) {
|
|
61
|
+
const messagesDir = join(conversationDir, kind);
|
|
62
|
+
if (seen.has(messagesDir)) continue;
|
|
63
|
+
seen.add(messagesDir);
|
|
64
|
+
dirs.push(messagesDir);
|
|
65
|
+
}
|
|
96
66
|
}
|
|
97
67
|
}
|
|
98
68
|
return dirs;
|
|
@@ -109,23 +79,17 @@ function sessionConversationDirs(dir) {
|
|
|
109
79
|
export function searchMessages(dir, keyword, limit = 20) {
|
|
110
80
|
if (!keyword || !keyword.trim()) return [];
|
|
111
81
|
|
|
112
|
-
const
|
|
113
|
-
join(dir, 'chat'),
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
const markdownDirs = [
|
|
118
|
-
...conversationDirs.flatMap(d => [join(d, 'messages'), join(d, 'cold')]),
|
|
82
|
+
const dirs = [
|
|
83
|
+
join(dir, 'chat', 'messages'),
|
|
84
|
+
join(dir, 'chat', 'cold'),
|
|
85
|
+
...sessionConversationMessageDirs(dir),
|
|
119
86
|
// Compatibility for profiles created before chat/session split.
|
|
120
87
|
join(dir, 'conversation', 'messages'),
|
|
121
88
|
join(dir, 'conversation', 'cold'),
|
|
122
89
|
];
|
|
123
|
-
const segmentDirs = conversationDirs.map(d => join(d, 'segments'));
|
|
124
90
|
|
|
125
|
-
return
|
|
126
|
-
|
|
127
|
-
...markdownDirs.flatMap(d => searchMarkdownDir(d, keyword)),
|
|
128
|
-
]
|
|
91
|
+
return dirs
|
|
92
|
+
.flatMap(d => searchDir(d, keyword))
|
|
129
93
|
.sort(compareNewest)
|
|
130
94
|
.slice(0, limit);
|
|
131
95
|
}
|
package/yeaft/session.js
CHANGED
|
@@ -45,7 +45,7 @@ import { TaskManager } from './tasks/manager.js';
|
|
|
45
45
|
// resumes with the same onDemand/recent membership it had on
|
|
46
46
|
// disconnect. Engine.#runQuery uses the registry to populate the
|
|
47
47
|
// AMS each turn and to run `memory/adjust.js` post-turn.
|
|
48
|
-
import { ensureDefaultSessionIfEmpty
|
|
48
|
+
import { ensureDefaultSessionIfEmpty } from './sessions/session-crud.js';
|
|
49
49
|
import { seedDefaultVps } from './vp/seed-defaults.js';
|
|
50
50
|
import { topUpDefaultVps } from './vp/seed-topup.js';
|
|
51
51
|
import { archiveLegacyScopes } from './memory/seed-backfill.js';
|
|
@@ -71,7 +71,6 @@ const DEFAULT_COMPACT_TRIGGER_RATIO = 0.7;
|
|
|
71
71
|
/**
|
|
72
72
|
* @typedef {Object} SessionOptions
|
|
73
73
|
* @property {string} [dir] — Yeaft data directory override (default: ~/.yeaft)
|
|
74
|
-
* @property {string} [workDir] — Session workDir; when provided, storage lives under <workDir>/.yeaft while config still comes from dir/defaults
|
|
75
74
|
* @property {string} [model] — Model override
|
|
76
75
|
* @property {string} [language] — Language override ('en' | 'zh')
|
|
77
76
|
* @property {boolean} [debug] — Debug mode override
|
|
@@ -137,7 +136,6 @@ function prepareToolStatsDir(yeaftDir) {
|
|
|
137
136
|
export async function loadSession(options = {}) {
|
|
138
137
|
const {
|
|
139
138
|
dir,
|
|
140
|
-
workDir,
|
|
141
139
|
model,
|
|
142
140
|
language,
|
|
143
141
|
debug,
|
|
@@ -148,29 +146,21 @@ export async function loadSession(options = {}) {
|
|
|
148
146
|
serverMode = false,
|
|
149
147
|
} = options;
|
|
150
148
|
|
|
151
|
-
// ─── 1. Determine
|
|
152
|
-
// Must happen BEFORE loadConfig so that first-run
|
|
153
|
-
// default config.json that loadConfig can read.
|
|
154
|
-
// Sessions store conversation/session data under <workDir>/.yeaft,
|
|
155
|
-
// but runtime config still comes from the agent-local configDir.
|
|
149
|
+
// ─── 1. Determine yeaftDir + ensure directory structure ──
|
|
150
|
+
// Must happen BEFORE loadConfig so that first-run
|
|
151
|
+
// generates a default config.json that loadConfig can read.
|
|
156
152
|
const overrides = { ...configOverrides };
|
|
157
153
|
if (dir) overrides.dir = dir;
|
|
158
154
|
if (model) overrides.model = model;
|
|
159
155
|
if (language) overrides.language = language;
|
|
160
156
|
if (debug !== undefined) overrides.debug = debug;
|
|
161
157
|
|
|
162
|
-
const
|
|
163
|
-
const
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
// Log any warnings from directory initialization.
|
|
170
|
-
const initWarnings = yeaftDir === configDir
|
|
171
|
-
? configInitResult.warnings
|
|
172
|
-
: [...configInitResult.warnings, ...storeInitResult.warnings];
|
|
173
|
-
for (const w of initWarnings) {
|
|
158
|
+
const yeaftDir = overrides.dir || process.env.YEAFT_DIR || DEFAULT_YEAFT_DIR;
|
|
159
|
+
const initResult = initYeaftDir(yeaftDir);
|
|
160
|
+
overrides.dir = yeaftDir;
|
|
161
|
+
|
|
162
|
+
// Log any warnings from directory initialization
|
|
163
|
+
for (const w of initResult.warnings) {
|
|
174
164
|
console.warn(`[Yeaft] ${w}`);
|
|
175
165
|
}
|
|
176
166
|
|
|
@@ -222,7 +212,7 @@ export async function loadSession(options = {}) {
|
|
|
222
212
|
// ─── 2a. Permission pre-check ─────────────────────────
|
|
223
213
|
// If the data dir is not writable, mark session as read-only.
|
|
224
214
|
// Persistence (conversation, memory, dream) is skipped in this mode.
|
|
225
|
-
if (!
|
|
215
|
+
if (!initResult.writable) {
|
|
226
216
|
config._readOnly = true;
|
|
227
217
|
console.warn(`[Yeaft] ${yeaftDir} is not writable — running in read-only mode`);
|
|
228
218
|
}
|
|
@@ -375,23 +365,24 @@ export async function loadSession(options = {}) {
|
|
|
375
365
|
}
|
|
376
366
|
|
|
377
367
|
// ─── 6. Load skills ────────────────────────────────────
|
|
378
|
-
//
|
|
379
|
-
//
|
|
380
|
-
|
|
368
|
+
// Project tier root for skills + MCP project assets. Per-session workDir
|
|
369
|
+
// overlays are loaded by web-bridge once it knows the selected Session meta;
|
|
370
|
+
// the base runtime still uses the agent process cwd for global/default status.
|
|
371
|
+
const projectTierRoot = process.cwd();
|
|
381
372
|
|
|
382
373
|
let skillManager;
|
|
383
374
|
if (skipSkills) {
|
|
384
375
|
// Pass the literal user-tier dir (matches the normal branch's tier 2)
|
|
385
376
|
// so any save/remove calls land in the same place users expect. New
|
|
386
377
|
// `SkillManager` API takes literal scan dirs — no auto-suffix of /skills.
|
|
387
|
-
skillManager = new SkillManager(join(
|
|
378
|
+
skillManager = new SkillManager(join(yeaftDir, 'skills'));
|
|
388
379
|
// Don't call .load() — empty skill manager
|
|
389
380
|
} else {
|
|
390
|
-
skillManager = createSkillManager(
|
|
381
|
+
skillManager = createSkillManager(yeaftDir, projectTierRoot);
|
|
391
382
|
}
|
|
392
383
|
|
|
393
384
|
// ─── 7. Connect MCP servers ────────────────────────────
|
|
394
|
-
const mcpConfig = loadMCPConfig(
|
|
385
|
+
const mcpConfig = loadMCPConfig(yeaftDir, undefined, projectTierRoot);
|
|
395
386
|
const mcpManager = new MCPManager();
|
|
396
387
|
let mcpStatus = { connected: [], failed: [] };
|
|
397
388
|
|
package/yeaft/web-bridge.js
CHANGED
|
@@ -12,7 +12,10 @@
|
|
|
12
12
|
*
|
|
13
13
|
* task-330c lint guard:
|
|
14
14
|
* ⚠️ DO NOT introduce greedy `text.replace(/---ROUTE---[\s\S]*$/g, '')`
|
|
15
|
-
* style strips on incoming/outgoing message bodies.
|
|
15
|
+
* style strips on incoming/outgoing message bodies. Crew ROUTE
|
|
16
|
+
* stripping is owned EXCLUSIVELY by `agent/crew/routing.js`
|
|
17
|
+
* `parseRoutes()` which returns `{routes, displayBody}` with exact
|
|
18
|
+
* ranges removed.
|
|
16
19
|
*/
|
|
17
20
|
|
|
18
21
|
import { delimiter, join } from 'node:path';
|
|
@@ -50,7 +53,6 @@ import {
|
|
|
50
53
|
scanWorkdirSessions,
|
|
51
54
|
restoreSessionToRegistry,
|
|
52
55
|
readWorkDirRegistry,
|
|
53
|
-
yeaftDirForWorkDir,
|
|
54
56
|
} from './sessions/session-crud.js';
|
|
55
57
|
import { openSession, loadSessionMeta } from './sessions/session-store.js';
|
|
56
58
|
import { loadSessionConfig, resolveSessionConfig, SessionConfigError } from './sessions/session-config.js';
|
|
@@ -412,13 +414,6 @@ function projectRuntimeKey(workDir) {
|
|
|
412
414
|
return normalizeSessionWorkDir(workDir) || '__agent_cwd__';
|
|
413
415
|
}
|
|
414
416
|
|
|
415
|
-
function resolveStoreYeaftDirForSession(defaultYeaftDir, { sessionId = null, sessionMeta = null, workDir = '' } = {}) {
|
|
416
|
-
const normalizedWorkDir = normalizeSessionWorkDir(workDir || sessionMeta?.workDir);
|
|
417
|
-
if (normalizedWorkDir) return yeaftDirForWorkDir(normalizedWorkDir);
|
|
418
|
-
if (sessionId) return resolveSessionYeaftDir(defaultYeaftDir, sessionId);
|
|
419
|
-
return defaultYeaftDir;
|
|
420
|
-
}
|
|
421
|
-
|
|
422
417
|
function createThreadId() {
|
|
423
418
|
return `thr_${Date.now().toString(36)}_${randomUUID().slice(0, 8)}`;
|
|
424
419
|
}
|
|
@@ -3588,7 +3583,7 @@ async function runYeaftSessionSend(msg) {
|
|
|
3588
3583
|
|
|
3589
3584
|
// ── Attachments (images + files) ───────────────────────────────
|
|
3590
3585
|
// Server has already resolved fileId → { name, mimeType, data:base64,
|
|
3591
|
-
// isImage } via the client-conversation.js relay
|
|
3586
|
+
// isImage } via the same path crew uses (client-conversation.js relay
|
|
3592
3587
|
// for `yeaft_*`). We persist files to disk under the agent's CWD so
|
|
3593
3588
|
// file-tools (file-read / bash) can pick them up with relative paths,
|
|
3594
3589
|
// and we build per-image content blocks for the LLM call. The
|
|
@@ -3885,11 +3880,6 @@ async function ensureSessionLoaded(opts = {}) {
|
|
|
3885
3880
|
sessionLoadPromise = (async () => {
|
|
3886
3881
|
const yeaftDir = ctx.CONFIG?.yeaftDir;
|
|
3887
3882
|
const normalizedWorkDir = normalizeSessionWorkDir(opts?.workDir || opts?.sessionMeta?.workDir);
|
|
3888
|
-
const sessionYeaftDir = resolveStoreYeaftDirForSession(yeaftDir, {
|
|
3889
|
-
sessionId: opts?.sessionId || opts?.sessionMeta?.id || null,
|
|
3890
|
-
sessionMeta: opts?.sessionMeta || null,
|
|
3891
|
-
workDir: normalizedWorkDir,
|
|
3892
|
-
});
|
|
3893
3883
|
session = await loadSession({
|
|
3894
3884
|
...(yeaftDir && { dir: yeaftDir }),
|
|
3895
3885
|
...(normalizedWorkDir && { workDir: normalizedWorkDir }),
|
|
@@ -5650,22 +5640,12 @@ export async function handleYeaftLoadHistory(msg) {
|
|
|
5650
5640
|
const limit = (typeof msg.limit === 'number') ? msg.limit : 10;
|
|
5651
5641
|
ensureYeaftConversationId();
|
|
5652
5642
|
|
|
5653
|
-
let sessionMetaForRuntime = null;
|
|
5654
|
-
let sessionYeaftDir = yeaftDir;
|
|
5655
|
-
if (sessionId) {
|
|
5656
|
-
try {
|
|
5657
|
-
sessionYeaftDir = resolveSessionYeaftDir(yeaftDir, sessionId);
|
|
5658
|
-
const metaDir = join(sessionsRoot(sessionYeaftDir), sessionId);
|
|
5659
|
-
sessionMetaForRuntime = loadSessionMeta(metaDir);
|
|
5660
|
-
} catch { /* best-effort metadata hint */ }
|
|
5661
|
-
}
|
|
5662
|
-
|
|
5663
5643
|
// First paint must not wait for full Yeaft runtime boot (MCP connects,
|
|
5664
|
-
// skill scans, memory index sync). The conversation
|
|
5644
|
+
// skill scans, memory index sync). The conversation markdown store is the
|
|
5665
5645
|
// source of truth and can be opened cheaply, so replay the visible message
|
|
5666
5646
|
// window immediately, then finish loadSession below for actual turns.
|
|
5667
5647
|
const coldStoreStart = perfNowMs();
|
|
5668
|
-
const coldStore = new ConversationStore(
|
|
5648
|
+
const coldStore = new ConversationStore(yeaftDir);
|
|
5669
5649
|
traceDuration('history.cold_store_open', coldStoreStart);
|
|
5670
5650
|
if (sessionId && (afterSeqRaw !== null || afterMessageId)) {
|
|
5671
5651
|
let afterSeq = afterSeqRaw;
|
|
@@ -5695,6 +5675,14 @@ export async function handleYeaftLoadHistory(msg) {
|
|
|
5695
5675
|
}
|
|
5696
5676
|
historyAlreadyReplayed = true;
|
|
5697
5677
|
|
|
5678
|
+
let sessionMetaForRuntime = null;
|
|
5679
|
+
if (sessionId) {
|
|
5680
|
+
try {
|
|
5681
|
+
const groupYeaftDir = resolveSessionYeaftDir(yeaftDir, sessionId);
|
|
5682
|
+
const metaDir = join(sessionsRoot(groupYeaftDir), sessionId);
|
|
5683
|
+
sessionMetaForRuntime = loadSessionMeta(metaDir);
|
|
5684
|
+
} catch { /* best-effort metadata hint */ }
|
|
5685
|
+
}
|
|
5698
5686
|
// Full runtime boot can be expensive (memory FTS sync, skills, MCP, dream
|
|
5699
5687
|
// boot checks). It is not needed to render persisted history, so keep this
|
|
5700
5688
|
// request short and let message-send await the same single-flight boot when
|