@yeaft/webchat-agent 1.0.27 → 1.0.29
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/cli.js +9 -9
- package/connection/message-router.js +3 -4
- package/llm-config-cli.js +48 -0
- package/package.json +1 -1
- package/service/index.js +19 -1
- package/yeaft/debug-trace.js +68 -1
- package/yeaft/web-bridge.js +16 -1
package/cli.js
CHANGED
|
@@ -41,7 +41,7 @@ const subArgs = args.slice(1);
|
|
|
41
41
|
const SERVICE_COMMANDS = ['install', 'uninstall', 'start', 'stop', 'restart', 'status', 'logs'];
|
|
42
42
|
|
|
43
43
|
if (command === 'doctor') {
|
|
44
|
-
handleDoctorCommand();
|
|
44
|
+
await handleDoctorCommand();
|
|
45
45
|
} else if (command === 'llm') {
|
|
46
46
|
await handleLlmCommand(subArgs);
|
|
47
47
|
} else if (command === 'upgrade') {
|
|
@@ -51,7 +51,7 @@ if (command === 'doctor') {
|
|
|
51
51
|
} else if (command === '--help' || command === '-h') {
|
|
52
52
|
printHelp();
|
|
53
53
|
} else if (SERVICE_COMMANDS.includes(command)) {
|
|
54
|
-
handleServiceCommand(command, subArgs);
|
|
54
|
+
await handleServiceCommand(command, subArgs);
|
|
55
55
|
} else {
|
|
56
56
|
// Normal agent startup — parse flags and set env vars
|
|
57
57
|
parseAndStart(args);
|
|
@@ -385,13 +385,13 @@ function parseLlmArgs(args) {
|
|
|
385
385
|
async function handleServiceCommand(command, args) {
|
|
386
386
|
const service = await import('./service.js');
|
|
387
387
|
switch (command) {
|
|
388
|
-
case 'install': service.install(args); break;
|
|
389
|
-
case 'uninstall': service.uninstall(args); break;
|
|
390
|
-
case 'start': service.start(args); break;
|
|
391
|
-
case 'stop': service.stop(args); break;
|
|
392
|
-
case 'restart': service.restart(args); break;
|
|
393
|
-
case 'status': service.status(args); break;
|
|
394
|
-
case 'logs': service.logs(args); break;
|
|
388
|
+
case 'install': await service.install(args); break;
|
|
389
|
+
case 'uninstall': await service.uninstall(args); break;
|
|
390
|
+
case 'start': await service.start(args); break;
|
|
391
|
+
case 'stop': await service.stop(args); break;
|
|
392
|
+
case 'restart': await service.restart(args); break;
|
|
393
|
+
case 'status': await service.status(args); break;
|
|
394
|
+
case 'logs': await service.logs(args); break;
|
|
395
395
|
}
|
|
396
396
|
}
|
|
397
397
|
|
|
@@ -676,10 +676,9 @@ export async function handleMessage(msg) {
|
|
|
676
676
|
await handleYeaftFetchToolStats(msg);
|
|
677
677
|
break;
|
|
678
678
|
|
|
679
|
-
//
|
|
680
|
-
//
|
|
681
|
-
//
|
|
682
|
-
// turn is invisible.
|
|
679
|
+
// Hydrate the Yeaft debug panel from the persistent file-backed trace.
|
|
680
|
+
// Without this, the panel only shows turns that happened after it was
|
|
681
|
+
// opened — every previous turn is invisible.
|
|
683
682
|
case 'yeaft_fetch_debug_history':
|
|
684
683
|
case 'unify_fetch_debug_history':
|
|
685
684
|
await handleYeaftFetchDebugHistory(msg);
|
package/llm-config-cli.js
CHANGED
|
@@ -8,6 +8,8 @@ import {
|
|
|
8
8
|
modelIdsFromProviderModels,
|
|
9
9
|
} from './llm-model-discovery.js';
|
|
10
10
|
|
|
11
|
+
export const DEFAULT_GITHUB_COPILOT_MODEL = 'gpt-5.5';
|
|
12
|
+
|
|
11
13
|
const VALID_PROTOCOLS = new Set(['anthropic', 'openai-responses']);
|
|
12
14
|
const VALID_CREDENTIAL_PROVIDERS = new Set(['github-copilot']);
|
|
13
15
|
|
|
@@ -230,6 +232,52 @@ export async function useGitHubCopilot(config, options = {}) {
|
|
|
230
232
|
return { config: next, provider, discovery };
|
|
231
233
|
}
|
|
232
234
|
|
|
235
|
+
export function hasLocalLlmConfig(config = {}) {
|
|
236
|
+
const providers = Array.isArray(config.providers) ? config.providers.filter(Boolean) : [];
|
|
237
|
+
return providers.length > 0 || Boolean(config.primaryModel) || Boolean(config.fastModel);
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
export function isDefaultSeedLlmConfig(config = {}) {
|
|
241
|
+
const providers = Array.isArray(config.providers) ? config.providers.filter(Boolean) : [];
|
|
242
|
+
if (providers.length !== 1) return false;
|
|
243
|
+
const provider = providers[0];
|
|
244
|
+
return provider?.name === 'my-proxy'
|
|
245
|
+
&& provider?.baseUrl === 'http://localhost:6628/v1'
|
|
246
|
+
&& provider?.apiKey === 'proxy'
|
|
247
|
+
&& typeof config.primaryModel === 'string'
|
|
248
|
+
&& config.primaryModel.startsWith('my-proxy/');
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
export async function tryAutoConfigureGitHubCopilot(configPath = getDefaultYeaftConfigPath(), options = {}) {
|
|
252
|
+
let current;
|
|
253
|
+
try {
|
|
254
|
+
current = readLocalLlmConfig(configPath);
|
|
255
|
+
} catch (error) {
|
|
256
|
+
return { configured: false, reason: 'invalid-config', error, config: null };
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
const allowConfigured = Boolean(options.allowConfigured) || isDefaultSeedLlmConfig(current);
|
|
260
|
+
if (!allowConfigured && hasLocalLlmConfig(current)) {
|
|
261
|
+
return { configured: false, reason: 'already-configured', config: current };
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
try {
|
|
265
|
+
const result = await useGitHubCopilot(current, {
|
|
266
|
+
...options,
|
|
267
|
+
model: options.model || DEFAULT_GITHUB_COPILOT_MODEL,
|
|
268
|
+
});
|
|
269
|
+
writeLocalLlmConfig(result.config, configPath);
|
|
270
|
+
return { configured: true, reason: 'configured', ...result };
|
|
271
|
+
} catch (error) {
|
|
272
|
+
return {
|
|
273
|
+
configured: false,
|
|
274
|
+
reason: error?.code === 'COPILOT_CREDENTIAL_MISSING' ? 'credential-missing' : 'unavailable',
|
|
275
|
+
error,
|
|
276
|
+
config: current,
|
|
277
|
+
};
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
|
|
233
281
|
|
|
234
282
|
export async function useOpenAICompatible(config, options = {}, env = process.env) {
|
|
235
283
|
const name = options.name ? String(options.name).trim() : 'openai';
|
package/package.json
CHANGED
package/service/index.js
CHANGED
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
* Routes install/uninstall/start/stop/restart/status/logs to the correct platform module.
|
|
4
4
|
*/
|
|
5
5
|
import { existsSync } from 'fs';
|
|
6
|
+
import { join } from 'path';
|
|
6
7
|
import { platform } from 'os';
|
|
7
8
|
import {
|
|
8
9
|
getConfigDir, getLogDir, getConfigPath,
|
|
@@ -10,6 +11,7 @@ import {
|
|
|
10
11
|
parseServiceArgs, validateConfig, getInstanceIdFromArgs, getDefaultYeaftDir
|
|
11
12
|
} from './config.js';
|
|
12
13
|
import { initYeaftDir } from '../yeaft/init.js';
|
|
14
|
+
import { DEFAULT_GITHUB_COPILOT_MODEL, tryAutoConfigureGitHubCopilot } from '../llm-config-cli.js';
|
|
13
15
|
import { getSystemdServicePath, linuxInstall, linuxUninstall, linuxStart, linuxStop, linuxRestart, linuxStatus, linuxLogs } from './linux.js';
|
|
14
16
|
import { getLaunchdPlistPath, macInstall, macUninstall, macStart, macStop, macRestart, macStatus, macLogs } from './macos.js';
|
|
15
17
|
import { winInstall, winUninstall, winStart, winStop, winRestart, winStatus, winLogs } from './windows.js';
|
|
@@ -35,6 +37,19 @@ export {
|
|
|
35
37
|
|
|
36
38
|
const os = platform();
|
|
37
39
|
|
|
40
|
+
export async function autoConfigureGitHubCopilotIfAvailable(yeaftDir, options = {}) {
|
|
41
|
+
const result = await tryAutoConfigureGitHubCopilot(join(yeaftDir, 'config.json'), options);
|
|
42
|
+
if (result.configured) {
|
|
43
|
+
console.log(`Configured GitHub Copilot provider automatically with ${DEFAULT_GITHUB_COPILOT_MODEL}.`);
|
|
44
|
+
if (result.discovery?.warning) console.log(`Warning: ${result.discovery.warning}`);
|
|
45
|
+
} else if (result.reason === 'already-configured') {
|
|
46
|
+
console.log('LLM config already exists; skipped automatic GitHub Copilot setup.');
|
|
47
|
+
} else if (result.reason === 'invalid-config') {
|
|
48
|
+
console.log('Existing LLM config is invalid; skipped automatic GitHub Copilot setup.');
|
|
49
|
+
}
|
|
50
|
+
return result;
|
|
51
|
+
}
|
|
52
|
+
|
|
38
53
|
function ensureInstalled(instanceId) {
|
|
39
54
|
if (os === 'linux') {
|
|
40
55
|
if (!existsSync(getSystemdServicePath(instanceId))) {
|
|
@@ -50,7 +65,7 @@ function ensureInstalled(instanceId) {
|
|
|
50
65
|
// Windows check is done inside individual functions
|
|
51
66
|
}
|
|
52
67
|
|
|
53
|
-
export function install(args) {
|
|
68
|
+
export async function install(args) {
|
|
54
69
|
const config = parseServiceArgs(args);
|
|
55
70
|
validateConfig(config);
|
|
56
71
|
saveServiceConfig(config);
|
|
@@ -59,6 +74,9 @@ export function install(args) {
|
|
|
59
74
|
// so `yeaft` CLI is ready to use immediately after install
|
|
60
75
|
const effectiveYeaftDir = config.yeaftDir || getDefaultYeaftDir(config.instanceId);
|
|
61
76
|
const { dir, created } = initYeaftDir(effectiveYeaftDir);
|
|
77
|
+
await autoConfigureGitHubCopilotIfAvailable(dir, {
|
|
78
|
+
allowConfigured: created.includes(join(dir, 'config.json')),
|
|
79
|
+
});
|
|
62
80
|
if (created.length > 0) {
|
|
63
81
|
console.log(`Initialized ${dir}`);
|
|
64
82
|
console.log(` Edit ${dir}/config.json to configure LLM providers.`);
|
package/yeaft/debug-trace.js
CHANGED
|
@@ -34,6 +34,7 @@ const MAX_INLINE_VALUE_BYTES = 1024 * 1024;
|
|
|
34
34
|
const MAX_RAW_REQUEST_BYTES = 2 * 1024 * 1024;
|
|
35
35
|
const TRACE_FLUSH_INTERVAL_MS = 5_000;
|
|
36
36
|
const TRACE_FLUSH_DIRTY_LOOPS = 10;
|
|
37
|
+
const MAX_SEARCH_PATTERN_CHARS = 300;
|
|
37
38
|
|
|
38
39
|
function isPlainObject(value) {
|
|
39
40
|
return value && typeof value === 'object' && !Array.isArray(value);
|
|
@@ -74,6 +75,70 @@ function readJson(filePath) {
|
|
|
74
75
|
}
|
|
75
76
|
}
|
|
76
77
|
|
|
78
|
+
function compileTraceSearchRegex(search) {
|
|
79
|
+
const raw = typeof search === 'string' ? search.trim() : '';
|
|
80
|
+
if (!raw) return null;
|
|
81
|
+
if (raw.length > MAX_SEARCH_PATTERN_CHARS) {
|
|
82
|
+
throw new Error(`Debug search regex is too long; max ${MAX_SEARCH_PATTERN_CHARS} characters`);
|
|
83
|
+
}
|
|
84
|
+
let pattern = raw;
|
|
85
|
+
let flags = 'i';
|
|
86
|
+
const slashForm = raw.match(/^\/(.*)\/([a-z]*)$/);
|
|
87
|
+
if (slashForm) {
|
|
88
|
+
pattern = slashForm[1];
|
|
89
|
+
flags = slashForm[2] || '';
|
|
90
|
+
}
|
|
91
|
+
if (regexHasUnsafeQuantifiedGroup(pattern)) {
|
|
92
|
+
throw new Error('Debug search regex contains an unsafe quantified group; refine the pattern');
|
|
93
|
+
}
|
|
94
|
+
const allowed = new Set(['d', 'g', 'i', 'm', 's', 'u', 'v', 'y']);
|
|
95
|
+
const uniqueFlags = [];
|
|
96
|
+
for (const ch of flags) {
|
|
97
|
+
if (!allowed.has(ch)) throw new Error(`Invalid debug search regex flag: ${ch}`);
|
|
98
|
+
if (!uniqueFlags.includes(ch)) uniqueFlags.push(ch);
|
|
99
|
+
}
|
|
100
|
+
if (!slashForm && !uniqueFlags.includes('i')) uniqueFlags.push('i');
|
|
101
|
+
const stableFlags = uniqueFlags.filter(ch => ch !== 'g' && ch !== 'y').join('');
|
|
102
|
+
return new RegExp(pattern, stableFlags);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
function regexHasUnsafeQuantifiedGroup(pattern) {
|
|
106
|
+
const groupBody = String.raw`(?:[^()\\]|\\.|\[[^\]]*\]|\([^()]*\))*`;
|
|
107
|
+
const nestedQuantifier = new RegExp(String.raw`\(${groupBody}[+*{]${groupBody}\)\s*[+*{]`);
|
|
108
|
+
const quantifiedAlternation = new RegExp(String.raw`\(${groupBody}\|${groupBody}\)\s*[+*{]`);
|
|
109
|
+
return nestedQuantifier.test(pattern) || quantifiedAlternation.test(pattern);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
function buildTraceSearchDocument(trace) {
|
|
113
|
+
const loops = Array.isArray(trace?.loops) ? trace.loops : [];
|
|
114
|
+
const tools = Array.isArray(trace?.tools) ? trace.tools : [];
|
|
115
|
+
const toolNames = tools.map(t => t?.toolName || t?.name || '').filter(Boolean).join(' ');
|
|
116
|
+
const loopModels = loops.map(l => l?.model || '').filter(Boolean).join(' ');
|
|
117
|
+
const stopReasons = loops.map(l => l?.stopReason || '').filter(Boolean).join(' ');
|
|
118
|
+
return [
|
|
119
|
+
trace?.requestId,
|
|
120
|
+
trace?.traceId,
|
|
121
|
+
trace?.messageId,
|
|
122
|
+
trace?.sessionId,
|
|
123
|
+
trace?.vpId,
|
|
124
|
+
trace?.threadId,
|
|
125
|
+
trace?.mode,
|
|
126
|
+
trace?.userPrompt,
|
|
127
|
+
loopModels,
|
|
128
|
+
stopReasons,
|
|
129
|
+
toolNames,
|
|
130
|
+
].filter(v => v != null && v !== '').map(String).join('\n').slice(0, 20_000);
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
function traceMatchesRegex(trace, regex) {
|
|
134
|
+
if (!regex) return true;
|
|
135
|
+
try {
|
|
136
|
+
return regex.test(buildTraceSearchDocument(trace));
|
|
137
|
+
} catch {
|
|
138
|
+
return false;
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
77
142
|
function truncateText(value, maxBytes = MAX_TEXT_BYTES) {
|
|
78
143
|
if (value == null) return value ?? null;
|
|
79
144
|
const str = String(value);
|
|
@@ -696,12 +761,14 @@ export class DebugTrace {
|
|
|
696
761
|
.flatMap(({ trace }) => traceToLegacyRows(trace));
|
|
697
762
|
}
|
|
698
763
|
|
|
699
|
-
fetchRecentDebugHistory({ limit = MAX_HISTORY_LIMIT, dreamLimit = 5, sessionId = null, threadId = null, indexOnly = false, detailTurnId = null } = {}) {
|
|
764
|
+
fetchRecentDebugHistory({ limit = MAX_HISTORY_LIMIT, dreamLimit = 5, sessionId = null, threadId = null, indexOnly = false, detailTurnId = null, search = '' } = {}) {
|
|
700
765
|
this.#flushPendingSync();
|
|
701
766
|
const lim = Math.max(1, Math.min(MAX_HISTORY_LIMIT, Number(limit) || MAX_HISTORY_LIMIT));
|
|
702
767
|
const requestedDetailTurnId = typeof detailTurnId === 'string' && detailTurnId ? detailTurnId : null;
|
|
768
|
+
const searchRegex = requestedDetailTurnId ? null : compileTraceSearchRegex(search);
|
|
703
769
|
const traces = this.#traceSummaries(sessionId)
|
|
704
770
|
.filter(({ trace }) => !threadId || trace.threadId === threadId)
|
|
771
|
+
.filter(({ trace }) => requestedDetailTurnId || traceMatchesRegex(trace, searchRegex))
|
|
705
772
|
.map(({ trace }) => trace);
|
|
706
773
|
const dreamEvents = this.#readDreamEvents({ sessionId, dreamLimit });
|
|
707
774
|
if (requestedDetailTurnId) {
|
package/yeaft/web-bridge.js
CHANGED
|
@@ -4491,6 +4491,7 @@ export async function handleYeaftFetchToolStats(_msg = {}) {
|
|
|
4491
4491
|
* - `detailTurnId` — fetch full loops/tools for one request
|
|
4492
4492
|
* - `sessionId` — narrow by Session
|
|
4493
4493
|
* - `threadId` — narrow by thread
|
|
4494
|
+
* - `search` — regex matched against bounded request summaries
|
|
4494
4495
|
*
|
|
4495
4496
|
* Sends:
|
|
4496
4497
|
* { type: 'yeaft_debug_history', loops: [...], turns: [...], indexOnly, detailTurnId }
|
|
@@ -4503,6 +4504,9 @@ export async function handleYeaftFetchDebugHistory(msg = {}) {
|
|
|
4503
4504
|
const dreamLimit = Number.isFinite(msg?.dreamLimit) ? Number(msg.dreamLimit) : 5;
|
|
4504
4505
|
const sessionId = typeof msg?.sessionId === 'string' && msg.sessionId ? msg.sessionId : null;
|
|
4505
4506
|
const threadId = typeof msg?.threadId === 'string' && msg.threadId ? msg.threadId : null;
|
|
4507
|
+
const search = typeof msg?.search === 'string' ? msg.search.trim() : '';
|
|
4508
|
+
const requestId = typeof msg?.requestId === 'string' && msg.requestId ? msg.requestId : null;
|
|
4509
|
+
const requestKind = typeof msg?.requestKind === 'string' && msg.requestKind ? msg.requestKind : null;
|
|
4506
4510
|
const indexOnly = !!msg?.indexOnly;
|
|
4507
4511
|
const detailTurnId = typeof msg?.detailTurnId === 'string' && msg.detailTurnId ? msg.detailTurnId : null;
|
|
4508
4512
|
let loops = [];
|
|
@@ -4511,7 +4515,7 @@ export async function handleYeaftFetchDebugHistory(msg = {}) {
|
|
|
4511
4515
|
let hasMore = false;
|
|
4512
4516
|
try {
|
|
4513
4517
|
if (session?.trace && typeof session.trace.fetchRecentDebugHistory === 'function') {
|
|
4514
|
-
const out = session.trace.fetchRecentDebugHistory({ limit, dreamLimit, sessionId, threadId, indexOnly, detailTurnId });
|
|
4518
|
+
const out = session.trace.fetchRecentDebugHistory({ limit, dreamLimit, sessionId, threadId, indexOnly, detailTurnId, search });
|
|
4515
4519
|
loops = Array.isArray(out?.loops) ? out.loops : [];
|
|
4516
4520
|
turns = Array.isArray(out?.turns) ? out.turns : [];
|
|
4517
4521
|
dreamEvents = Array.isArray(out?.dreamEvents) ? out.dreamEvents : [];
|
|
@@ -4523,6 +4527,14 @@ export async function handleYeaftFetchDebugHistory(msg = {}) {
|
|
|
4523
4527
|
loops: [],
|
|
4524
4528
|
turns: [],
|
|
4525
4529
|
dreamEvents: [],
|
|
4530
|
+
requestId,
|
|
4531
|
+
requestKind,
|
|
4532
|
+
sessionId,
|
|
4533
|
+
threadId,
|
|
4534
|
+
search,
|
|
4535
|
+
limit,
|
|
4536
|
+
indexOnly,
|
|
4537
|
+
detailTurnId,
|
|
4526
4538
|
error: err && err.message ? err.message : String(err),
|
|
4527
4539
|
});
|
|
4528
4540
|
return;
|
|
@@ -4532,8 +4544,11 @@ export async function handleYeaftFetchDebugHistory(msg = {}) {
|
|
|
4532
4544
|
loops,
|
|
4533
4545
|
turns,
|
|
4534
4546
|
dreamEvents,
|
|
4547
|
+
requestId,
|
|
4548
|
+
requestKind,
|
|
4535
4549
|
sessionId,
|
|
4536
4550
|
threadId,
|
|
4551
|
+
search,
|
|
4537
4552
|
hasMore,
|
|
4538
4553
|
limit,
|
|
4539
4554
|
indexOnly,
|