@yeaft/webchat-agent 1.0.412 → 1.0.414
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/browser-runtime/browser-install.js +497 -0
- package/browser-runtime/cli.js +88 -0
- package/browser-runtime/config.js +116 -0
- package/browser-runtime/errors.js +8 -0
- package/browser-runtime/extension/manifest.json +18 -0
- package/browser-runtime/extension/offscreen.html +5 -0
- package/browser-runtime/extension/offscreen.js +101 -0
- package/browser-runtime/extension/popup.html +5 -0
- package/browser-runtime/extension/popup.js +1 -0
- package/browser-runtime/extension/service-worker.js +48 -0
- package/browser-runtime/extension.js +45 -0
- package/browser-runtime/index.js +5 -0
- package/browser-runtime/probe.js +427 -0
- package/browser-runtime/protocol.js +71 -0
- package/browser-runtime/service.js +132 -0
- package/browser-runtime/windows-version-job.ps1 +233 -0
- package/browser-runtime/windows-version-worker.js +75 -0
- package/browser-runtime/windows-version.js +85 -0
- package/cli.js +24 -7
- package/context.js +1 -0
- package/index.js +18 -1
- package/llm-config-cli.js +24 -21
- package/local-runtime/version.json +1 -1
- package/local-runtime/web/app.bundle.js +22 -5
- package/local-runtime/web/app.bundle.js.gz +0 -0
- package/local-runtime/web/index.html +2 -2
- package/local-runtime/web/style.bundle.css +1 -1
- package/local-runtime/web/style.bundle.css.gz +0 -0
- package/package.json +5 -1
- package/service/config.js +23 -2
- package/service/index.js +1 -0
- package/service/linux.js +3 -2
- package/yeaft/config-api.js +138 -192
- package/yeaft/config-store.js +192 -0
- package/yeaft/config.js +3 -0
- package/yeaft/init.js +20 -7
- package/yeaft/sessions/feature-flag.js +15 -33
- package/yeaft/storage/atomic.js +43 -17
- package/yeaft/tools/create-work-item.js +1 -1
- package/yeaft/tools/process-runner.js +86 -13
- package/yeaft/work-center/bridge.js +3 -2
- package/yeaft/work-center/completion-contract.js +6 -0
- package/yeaft/work-center/controller.js +2 -1
- package/yeaft/work-center/coordinator.js +45 -14
- package/yeaft/work-center/durable-model.js +45 -1
- package/yeaft/work-center/dynamic-coordination.js +34 -0
- package/yeaft/work-center/evidence.js +235 -0
- package/yeaft/work-center/mainline-projection.js +4 -1
- package/yeaft/work-center/projection.js +45 -4
- package/yeaft/work-center/runner.js +82 -7
- package/yeaft/work-center/service.js +6 -0
- package/yeaft/work-center/store.js +162 -19
- package/yeaft/work-center/workflow.js +6 -0
|
Binary file
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@yeaft/webchat-agent",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.414",
|
|
4
4
|
"description": "Remote worker agent for Yeaft Web Code Agent — connects the native Yeaft engine, CLI providers, and workbench tools",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"type": "module",
|
|
@@ -41,6 +41,8 @@
|
|
|
41
41
|
"docker-entrypoint.sh",
|
|
42
42
|
"yeaft/templates/**/*.md",
|
|
43
43
|
"yeaft/dream/prompts/**/*.md",
|
|
44
|
+
"browser-runtime/extension/**/*",
|
|
45
|
+
"browser-runtime/*.ps1",
|
|
44
46
|
"local-runtime/**/*"
|
|
45
47
|
],
|
|
46
48
|
"license": "MIT",
|
|
@@ -62,6 +64,7 @@
|
|
|
62
64
|
},
|
|
63
65
|
"dependencies": {
|
|
64
66
|
"@homebridge/node-pty-prebuilt-multiarch": "^0.13.1",
|
|
67
|
+
"@puppeteer/browsers": "2.13.0",
|
|
65
68
|
"bcrypt": "^6.0.0",
|
|
66
69
|
"compression": "^1.8.1",
|
|
67
70
|
"dotenv": "^16.3.1",
|
|
@@ -69,6 +72,7 @@
|
|
|
69
72
|
"jsonwebtoken": "^9.0.2",
|
|
70
73
|
"multer": "^2.2.0",
|
|
71
74
|
"nodemailer": "^9.0.3",
|
|
75
|
+
"puppeteer-core": "24.41.0",
|
|
72
76
|
"qrcode": "^1.5.4",
|
|
73
77
|
"speakeasy": "^2.0.0",
|
|
74
78
|
"tweetnacl": "^1.0.3",
|
package/service/config.js
CHANGED
|
@@ -113,7 +113,7 @@ export function resolveServiceInstanceId(args = [], env = process.env, options =
|
|
|
113
113
|
}
|
|
114
114
|
|
|
115
115
|
/** Resolve the CLI/env/default Yeaft data root for one Agent instance. */
|
|
116
|
-
|
|
116
|
+
function explicitYeaftDirFromArgs(args = []) {
|
|
117
117
|
let explicitYeaftDir = null;
|
|
118
118
|
for (let index = 0; index < args.length; index += 1) {
|
|
119
119
|
if (args[index] !== '--yeaft-dir') continue;
|
|
@@ -122,7 +122,28 @@ export function resolveYeaftDir(args = [], env = process.env, instanceId = DEFAU
|
|
|
122
122
|
explicitYeaftDir = value;
|
|
123
123
|
index += 1;
|
|
124
124
|
}
|
|
125
|
-
return explicitYeaftDir
|
|
125
|
+
return explicitYeaftDir;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
export function resolveYeaftDir(args = [], env = process.env, instanceId = DEFAULT_INSTANCE_ID) {
|
|
129
|
+
return explicitYeaftDirFromArgs(args) || env.YEAFT_DIR || getDefaultYeaftDir(instanceId);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/** Resolve the persisted data root for an explicitly selected management target. */
|
|
133
|
+
export function resolveManagedYeaftDir(
|
|
134
|
+
args = [],
|
|
135
|
+
env = process.env,
|
|
136
|
+
instanceId = DEFAULT_INSTANCE_ID,
|
|
137
|
+
dependencies = {},
|
|
138
|
+
) {
|
|
139
|
+
const explicitYeaftDir = explicitYeaftDirFromArgs(args);
|
|
140
|
+
if (explicitYeaftDir) return explicitYeaftDir;
|
|
141
|
+
const hasExplicitIdentity = args.includes('--name') || args.includes('--instance');
|
|
142
|
+
if (!hasExplicitIdentity && env.YEAFT_DIR) return env.YEAFT_DIR;
|
|
143
|
+
const readServiceConfig = dependencies.loadServiceConfig || loadServiceConfig;
|
|
144
|
+
const defaultYeaftDir = dependencies.getDefaultYeaftDir || getDefaultYeaftDir;
|
|
145
|
+
const persisted = readServiceConfig(instanceId)?.yeaftDir;
|
|
146
|
+
return persisted || defaultYeaftDir(instanceId);
|
|
126
147
|
}
|
|
127
148
|
|
|
128
149
|
/** Legacy alias for resolveServiceInstanceId(). */
|
package/service/index.js
CHANGED
package/service/linux.js
CHANGED
|
@@ -12,7 +12,7 @@ export function getSystemdServicePath(instanceId = DEFAULT_INSTANCE_ID) {
|
|
|
12
12
|
return join(homedir(), '.config', 'systemd', 'user', `${getServiceName(instanceId)}.service`);
|
|
13
13
|
}
|
|
14
14
|
|
|
15
|
-
function generateSystemdUnit(config) {
|
|
15
|
+
export function generateSystemdUnit(config) {
|
|
16
16
|
const nodePath = getNodePath();
|
|
17
17
|
const cliPath = getCliPath();
|
|
18
18
|
const logDir = getLogDir(config.instanceId);
|
|
@@ -38,7 +38,8 @@ ExecStart=${nodePath} ${cliPath}
|
|
|
38
38
|
WorkingDirectory=${config.workDir || homedir()}
|
|
39
39
|
Restart=on-failure
|
|
40
40
|
RestartSec=10
|
|
41
|
-
KillMode=
|
|
41
|
+
KillMode=mixed
|
|
42
|
+
TimeoutStopSec=15
|
|
42
43
|
${envLines.join('\n')}
|
|
43
44
|
Environment=PATH=${nodeBinDir}:${homedir()}/.local/bin:${homedir()}/.npm-global/bin:/usr/local/bin:/usr/bin:/bin
|
|
44
45
|
|
package/yeaft/config-api.js
CHANGED
|
@@ -8,12 +8,14 @@
|
|
|
8
8
|
* like maxContinueTurns or debug that don't belong in the UI.
|
|
9
9
|
*/
|
|
10
10
|
|
|
11
|
-
import { existsSync, readFileSync
|
|
11
|
+
import { existsSync, readFileSync } from 'fs';
|
|
12
12
|
import { join } from 'path';
|
|
13
13
|
import { DEFAULT_YEAFT_DIR } from './init.js';
|
|
14
14
|
import { normalizeProviderModels, parseModelRef, serializeModelForPersistence } from './models.js';
|
|
15
15
|
import { normaliseTelemetrySection, normaliseYeaftSection } from './config.js';
|
|
16
|
+
import { normaliseBrowserRuntimeSection, validateBrowserRuntimeUpdate } from '../browser-runtime/config.js';
|
|
16
17
|
import { normalizePluginConfig } from './plugins.js';
|
|
18
|
+
import { mutateAgentConfig, readAgentConfigForWrite } from './config-store.js';
|
|
17
19
|
import { isGitHubCopilotProvider, serializeKnownProviderForPersistence } from './llm/known-providers.js';
|
|
18
20
|
|
|
19
21
|
/**
|
|
@@ -28,16 +30,7 @@ import { isGitHubCopilotProvider, serializeKnownProviderForPersistence } from '.
|
|
|
28
30
|
* @throws {Error} when an existing config cannot be safely preserved
|
|
29
31
|
*/
|
|
30
32
|
function readConfigForWrite(configPath) {
|
|
31
|
-
|
|
32
|
-
const json = JSON.parse(readFileSync(configPath, 'utf8'));
|
|
33
|
-
if (!json || typeof json !== 'object' || Array.isArray(json)
|
|
34
|
-
|| Object.getPrototypeOf(json) !== Object.prototype) {
|
|
35
|
-
throw new Error('config.json must contain an object');
|
|
36
|
-
}
|
|
37
|
-
if (Object.prototype.hasOwnProperty.call(json, 'plugins')) {
|
|
38
|
-
normalizePluginConfig(json.plugins);
|
|
39
|
-
}
|
|
40
|
-
return json;
|
|
33
|
+
return readAgentConfigForWrite(configPath);
|
|
41
34
|
}
|
|
42
35
|
|
|
43
36
|
/**
|
|
@@ -142,85 +135,57 @@ function normalizeManagedModelDefaults(config) {
|
|
|
142
135
|
*/
|
|
143
136
|
export function updateLlmConfig(update, dir) {
|
|
144
137
|
const root = dir || process.env.YEAFT_DIR || DEFAULT_YEAFT_DIR;
|
|
145
|
-
const configPath = join(root, 'config.json');
|
|
146
|
-
|
|
147
|
-
// Preserve all existing fields only when the on-disk document and its
|
|
148
|
-
// Plugins policy are valid. Never turn a failed read into a fresh config.
|
|
149
|
-
let existing;
|
|
150
|
-
try {
|
|
151
|
-
existing = readConfigForWrite(configPath);
|
|
152
|
-
} catch (err) {
|
|
153
|
-
return { error: `Failed to read config.json: ${err?.message || err}` };
|
|
154
|
-
}
|
|
155
138
|
|
|
156
|
-
// Validate providers structure
|
|
157
139
|
if (update.providers !== undefined) {
|
|
158
|
-
if (!Array.isArray(update.providers)) {
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
for (const p of update.providers) {
|
|
162
|
-
if (!p.name || typeof p.name !== 'string') {
|
|
140
|
+
if (!Array.isArray(update.providers)) return { error: 'providers must be an array' };
|
|
141
|
+
for (const provider of update.providers) {
|
|
142
|
+
if (!provider.name || typeof provider.name !== 'string') {
|
|
163
143
|
return { error: 'Each provider must have a name' };
|
|
164
144
|
}
|
|
165
|
-
if (isGitHubCopilotProvider(
|
|
166
|
-
if (!
|
|
167
|
-
return { error: `Provider "${
|
|
145
|
+
if (isGitHubCopilotProvider(provider)) continue;
|
|
146
|
+
if (!provider.baseUrl || typeof provider.baseUrl !== 'string') {
|
|
147
|
+
return { error: `Provider "${provider.name}" must have a baseUrl` };
|
|
168
148
|
}
|
|
169
|
-
if (!Array.isArray(
|
|
170
|
-
return { error: `Provider "${
|
|
149
|
+
if (!Array.isArray(provider.models) || provider.models.length === 0) {
|
|
150
|
+
return { error: `Provider "${provider.name}" must have at least one model` };
|
|
171
151
|
}
|
|
172
152
|
}
|
|
173
|
-
// Normalize + re-serialize each provider's models so that:
|
|
174
|
-
// - id-only entries are persisted as plain strings (back-compat)
|
|
175
|
-
// - entries with ctx / maxOutput are persisted as objects
|
|
176
|
-
// - empty / 0 / NaN values get stripped
|
|
177
|
-
existing.providers = update.providers.map(p => {
|
|
178
|
-
const managed = serializeKnownProviderForPersistence(p);
|
|
179
|
-
if (managed) return managed;
|
|
180
|
-
const normalized = normalizeProviderModels(p);
|
|
181
|
-
return {
|
|
182
|
-
...p,
|
|
183
|
-
models: normalized.map(serializeModelForPersistence),
|
|
184
|
-
};
|
|
185
|
-
});
|
|
186
153
|
}
|
|
187
154
|
|
|
188
|
-
// Update model selections. A managed provider catalog is authoritative:
|
|
189
|
-
// when it drops the old Agent default, keep no hidden reference to that
|
|
190
|
-
// model in primaryModel or fastModel.
|
|
191
|
-
if (update.primaryModel !== undefined) {
|
|
192
|
-
existing.primaryModel = update.primaryModel || null;
|
|
193
|
-
}
|
|
194
|
-
if (update.fastModel !== undefined) {
|
|
195
|
-
existing.fastModel = update.fastModel || null;
|
|
196
|
-
}
|
|
197
|
-
if (update.providers !== undefined) normalizeManagedModelDefaults(existing);
|
|
198
|
-
if (update.language !== undefined) {
|
|
199
|
-
existing.language = update.language;
|
|
200
|
-
}
|
|
201
|
-
if (update.debug !== undefined) {
|
|
202
|
-
existing.debug = update.debug === true;
|
|
203
|
-
}
|
|
204
|
-
|
|
205
|
-
// Write back
|
|
206
155
|
try {
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
156
|
+
return mutateAgentConfig(root, existing => {
|
|
157
|
+
if (update.providers !== undefined) {
|
|
158
|
+
existing.providers = update.providers.map(provider => {
|
|
159
|
+
const managed = serializeKnownProviderForPersistence(provider);
|
|
160
|
+
if (managed) return managed;
|
|
161
|
+
return {
|
|
162
|
+
...provider,
|
|
163
|
+
models: normalizeProviderModels(provider).map(serializeModelForPersistence),
|
|
164
|
+
};
|
|
165
|
+
});
|
|
166
|
+
}
|
|
167
|
+
if (update.primaryModel !== undefined) existing.primaryModel = update.primaryModel || null;
|
|
168
|
+
if (update.fastModel !== undefined) existing.fastModel = update.fastModel || null;
|
|
169
|
+
if (update.providers !== undefined) normalizeManagedModelDefaults(existing);
|
|
170
|
+
if (update.language !== undefined) existing.language = update.language;
|
|
171
|
+
if (update.debug !== undefined) existing.debug = update.debug === true;
|
|
172
|
+
|
|
173
|
+
const agentConfig = {
|
|
174
|
+
providers: Array.isArray(existing.providers) ? existing.providers : [],
|
|
175
|
+
primaryModel: existing.primaryModel || null,
|
|
176
|
+
fastModel: existing.fastModel || null,
|
|
177
|
+
language: existing.language || 'en',
|
|
178
|
+
debug: existing.debug === true,
|
|
179
|
+
};
|
|
180
|
+
return {
|
|
181
|
+
...agentConfig,
|
|
182
|
+
agentConfig,
|
|
183
|
+
effectiveConfig: agentConfig,
|
|
184
|
+
};
|
|
185
|
+
});
|
|
186
|
+
} catch (error) {
|
|
187
|
+
return { error: `Failed to read config.json or persist update: ${error?.message || error}` };
|
|
210
188
|
}
|
|
211
|
-
|
|
212
|
-
const agentConfig = {
|
|
213
|
-
providers: Array.isArray(existing.providers) ? existing.providers : [],
|
|
214
|
-
primaryModel: existing.primaryModel || null,
|
|
215
|
-
fastModel: existing.fastModel || null,
|
|
216
|
-
language: existing.language || 'en',
|
|
217
|
-
debug: existing.debug === true,
|
|
218
|
-
};
|
|
219
|
-
return {
|
|
220
|
-
...agentConfig,
|
|
221
|
-
agentConfig,
|
|
222
|
-
effectiveConfig: agentConfig,
|
|
223
|
-
};
|
|
224
189
|
}
|
|
225
190
|
|
|
226
191
|
// ─── Yeaft runtime settings (task-318) ────────────────────────────
|
|
@@ -261,7 +226,6 @@ export function getYeaftSettings(dir) {
|
|
|
261
226
|
*/
|
|
262
227
|
export function updateYeaftSettings(update, dir) {
|
|
263
228
|
const root = dir || process.env.YEAFT_DIR || DEFAULT_YEAFT_DIR;
|
|
264
|
-
const configPath = join(root, 'config.json');
|
|
265
229
|
|
|
266
230
|
if (!update || typeof update !== 'object') {
|
|
267
231
|
return { error: 'update payload required' };
|
|
@@ -288,37 +252,26 @@ export function updateYeaftSettings(update, dir) {
|
|
|
288
252
|
}
|
|
289
253
|
}
|
|
290
254
|
|
|
291
|
-
// Preserve all existing fields only when the on-disk document and its
|
|
292
|
-
// Plugins policy are valid. A Settings update must not repair bad JSON into
|
|
293
|
-
// a config whose missing Plugins fields inherit all capabilities.
|
|
294
|
-
let existing;
|
|
295
|
-
try {
|
|
296
|
-
existing = readConfigForWrite(configPath);
|
|
297
|
-
} catch (err) {
|
|
298
|
-
return { error: `Failed to read config.json: ${err?.message || err}` };
|
|
299
|
-
}
|
|
300
|
-
|
|
301
|
-
const prev = normaliseYeaftSection(existing.yeaft);
|
|
302
|
-
const merged = {
|
|
303
|
-
maxConcurrentThreads: update.maxConcurrentThreads !== undefined
|
|
304
|
-
? Math.floor(Number(update.maxConcurrentThreads))
|
|
305
|
-
: prev.maxConcurrentThreads,
|
|
306
|
-
autoArchiveIdleDays: update.autoArchiveIdleDays !== undefined
|
|
307
|
-
? Math.floor(Number(update.autoArchiveIdleDays))
|
|
308
|
-
: prev.autoArchiveIdleDays,
|
|
309
|
-
recentTurnsLimit: update.recentTurnsLimit !== undefined
|
|
310
|
-
? Math.floor(Number(update.recentTurnsLimit))
|
|
311
|
-
: prev.recentTurnsLimit,
|
|
312
|
-
};
|
|
313
|
-
existing.yeaft = merged;
|
|
314
|
-
|
|
315
255
|
try {
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
256
|
+
return mutateAgentConfig(root, existing => {
|
|
257
|
+
const prev = normaliseYeaftSection(existing.yeaft);
|
|
258
|
+
const merged = {
|
|
259
|
+
maxConcurrentThreads: update.maxConcurrentThreads !== undefined
|
|
260
|
+
? Math.floor(Number(update.maxConcurrentThreads))
|
|
261
|
+
: prev.maxConcurrentThreads,
|
|
262
|
+
autoArchiveIdleDays: update.autoArchiveIdleDays !== undefined
|
|
263
|
+
? Math.floor(Number(update.autoArchiveIdleDays))
|
|
264
|
+
: prev.autoArchiveIdleDays,
|
|
265
|
+
recentTurnsLimit: update.recentTurnsLimit !== undefined
|
|
266
|
+
? Math.floor(Number(update.recentTurnsLimit))
|
|
267
|
+
: prev.recentTurnsLimit,
|
|
268
|
+
};
|
|
269
|
+
existing.yeaft = merged;
|
|
270
|
+
return merged;
|
|
271
|
+
});
|
|
272
|
+
} catch (error) {
|
|
273
|
+
return { error: `Failed to read config.json or persist update: ${error?.message || error}` };
|
|
319
274
|
}
|
|
320
|
-
|
|
321
|
-
return merged;
|
|
322
275
|
}
|
|
323
276
|
|
|
324
277
|
/**
|
|
@@ -357,25 +310,50 @@ export function updateTelemetrySettings(update, dir) {
|
|
|
357
310
|
if (Object.keys(update).some(key => !allowed.has(key))) {
|
|
358
311
|
return { error: 'unknown telemetry setting' };
|
|
359
312
|
}
|
|
313
|
+
const root = dir || process.env.YEAFT_DIR || DEFAULT_YEAFT_DIR;
|
|
314
|
+
try {
|
|
315
|
+
return mutateAgentConfig(root, existing => {
|
|
316
|
+
const merged = normaliseTelemetrySection({
|
|
317
|
+
...(existing.telemetry && typeof existing.telemetry === 'object' ? existing.telemetry : {}),
|
|
318
|
+
...update,
|
|
319
|
+
});
|
|
320
|
+
existing.telemetry = merged;
|
|
321
|
+
return merged;
|
|
322
|
+
});
|
|
323
|
+
} catch (error) {
|
|
324
|
+
return { error: `Failed to read config.json or persist update: ${error?.message || error}` };
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
// ─── Browser Runtime settings ───────────────────────────────────────
|
|
329
|
+
|
|
330
|
+
export function getBrowserRuntimeSettings(dir) {
|
|
360
331
|
const root = dir || process.env.YEAFT_DIR || DEFAULT_YEAFT_DIR;
|
|
361
332
|
const configPath = join(root, 'config.json');
|
|
362
|
-
|
|
333
|
+
if (!existsSync(configPath)) return normaliseBrowserRuntimeSection(null);
|
|
363
334
|
try {
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
335
|
+
const json = JSON.parse(readFileSync(configPath, 'utf8'));
|
|
336
|
+
return normaliseBrowserRuntimeSection(json.browserRuntime);
|
|
337
|
+
} catch (error) {
|
|
338
|
+
return { error: `Failed to read config.json: ${error.message}` };
|
|
367
339
|
}
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
export function updateBrowserRuntimeSettings(update, dir) {
|
|
343
|
+
const validationError = validateBrowserRuntimeUpdate(update);
|
|
344
|
+
if (validationError) return { error: validationError };
|
|
345
|
+
const root = dir || process.env.YEAFT_DIR || DEFAULT_YEAFT_DIR;
|
|
373
346
|
try {
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
347
|
+
return mutateAgentConfig(root, existing => {
|
|
348
|
+
const previous = existing.browserRuntime && typeof existing.browserRuntime === 'object'
|
|
349
|
+
? existing.browserRuntime
|
|
350
|
+
: {};
|
|
351
|
+
existing.browserRuntime = normaliseBrowserRuntimeSection({ ...previous, ...update });
|
|
352
|
+
return existing.browserRuntime;
|
|
353
|
+
});
|
|
354
|
+
} catch (error) {
|
|
355
|
+
return { error: `Failed to read config.json or persist update: ${error?.message || error}` };
|
|
377
356
|
}
|
|
378
|
-
return merged;
|
|
379
357
|
}
|
|
380
358
|
|
|
381
359
|
// ─── Search settings (web-search backend selection + Tavily key) ────
|
|
@@ -443,7 +421,6 @@ export function getSearchSettings(dir) {
|
|
|
443
421
|
*/
|
|
444
422
|
export function updateSearchSettings(update, dir) {
|
|
445
423
|
const root = dir || process.env.YEAFT_DIR || DEFAULT_YEAFT_DIR;
|
|
446
|
-
const configPath = join(root, 'config.json');
|
|
447
424
|
|
|
448
425
|
if (!update || typeof update !== 'object') {
|
|
449
426
|
return { error: 'update payload required' };
|
|
@@ -455,23 +432,17 @@ export function updateSearchSettings(update, dir) {
|
|
|
455
432
|
return { error: 'tavilyApiKey must be a string' };
|
|
456
433
|
}
|
|
457
434
|
|
|
458
|
-
let existing;
|
|
459
435
|
try {
|
|
460
|
-
existing
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
try {
|
|
472
|
-
writeFileSync(configPath, JSON.stringify(existing, null, 2) + '\n', 'utf8');
|
|
473
|
-
} catch (e) {
|
|
474
|
-
return { error: `Failed to write config.json: ${e.message}` };
|
|
436
|
+
mutateAgentConfig(root, existing => {
|
|
437
|
+
const prev = (existing && typeof existing.search === 'object' && existing.search) || {};
|
|
438
|
+
const merged = { ...prev };
|
|
439
|
+
if (update.backend !== undefined) merged.backend = update.backend;
|
|
440
|
+
if (update.tavilyApiKey !== undefined) merged.tavilyApiKey = update.tavilyApiKey;
|
|
441
|
+
if (update.disableHtmlFallback !== undefined) merged.disableHtmlFallback = !!update.disableHtmlFallback;
|
|
442
|
+
existing.search = merged;
|
|
443
|
+
});
|
|
444
|
+
} catch (error) {
|
|
445
|
+
return { error: `Failed to read config.json or persist update: ${error?.message || error}` };
|
|
475
446
|
}
|
|
476
447
|
return getSearchSettings(root);
|
|
477
448
|
}
|
|
@@ -549,24 +520,17 @@ export function getPluginConfig(dir) {
|
|
|
549
520
|
*/
|
|
550
521
|
export function updatePluginConfig(plugins, dir) {
|
|
551
522
|
const root = dir || process.env.YEAFT_DIR || DEFAULT_YEAFT_DIR;
|
|
552
|
-
const configPath = join(root, 'config.json');
|
|
553
523
|
let normalized;
|
|
554
|
-
let existing;
|
|
555
524
|
try {
|
|
556
|
-
existing = readConfigForWrite(configPath);
|
|
557
525
|
normalized = normalizePluginConfig(plugins);
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
writeFileSync(configPath, JSON.stringify(existing, null, 2) + '\n', 'utf8');
|
|
566
|
-
} catch (err) {
|
|
567
|
-
return { error: `Failed to write plugin config: ${err?.message || err}` };
|
|
526
|
+
return mutateAgentConfig(root, existing => {
|
|
527
|
+
if (Object.keys(normalized).length === 0) delete existing.plugins;
|
|
528
|
+
else existing.plugins = normalized;
|
|
529
|
+
return { plugins: normalized };
|
|
530
|
+
});
|
|
531
|
+
} catch (error) {
|
|
532
|
+
return { error: `Failed to read plugin config or persist update: ${error?.message || error}` };
|
|
568
533
|
}
|
|
569
|
-
return { plugins: normalized };
|
|
570
534
|
}
|
|
571
535
|
|
|
572
536
|
// ─── MCP server config (mcpServers array in config.json) ──
|
|
@@ -684,33 +648,21 @@ export function upsertMcpServer(server, dir) {
|
|
|
684
648
|
if (err) return { error: err };
|
|
685
649
|
|
|
686
650
|
const root = dir || process.env.YEAFT_DIR || DEFAULT_YEAFT_DIR;
|
|
687
|
-
const configPath = join(root, 'config.json');
|
|
688
|
-
let existing;
|
|
689
|
-
try {
|
|
690
|
-
existing = readConfigForWrite(configPath);
|
|
691
|
-
} catch (err) {
|
|
692
|
-
return { error: `Failed to read config.json: ${err?.message || err}` };
|
|
693
|
-
}
|
|
694
|
-
const list = Array.isArray(existing.mcpServers) ? existing.mcpServers.slice() : [];
|
|
695
|
-
|
|
696
651
|
const normalised = normaliseMcpServer(server);
|
|
697
652
|
if (!normalised) return { error: 'invalid server payload' };
|
|
698
653
|
|
|
699
|
-
const idx = list.findIndex(s => s && typeof s === 'object' && s.name === normalised.name);
|
|
700
|
-
if (idx >= 0) {
|
|
701
|
-
list[idx] = normalised;
|
|
702
|
-
} else {
|
|
703
|
-
list.push(normalised);
|
|
704
|
-
}
|
|
705
|
-
existing.mcpServers = list;
|
|
706
|
-
|
|
707
654
|
try {
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
655
|
+
return mutateAgentConfig(root, existing => {
|
|
656
|
+
const list = Array.isArray(existing.mcpServers) ? existing.mcpServers.slice() : [];
|
|
657
|
+
const index = list.findIndex(entry => entry && typeof entry === 'object' && entry.name === normalised.name);
|
|
658
|
+
if (index >= 0) list[index] = normalised;
|
|
659
|
+
else list.push(normalised);
|
|
660
|
+
existing.mcpServers = list;
|
|
661
|
+
return { servers: list.map(normaliseMcpServer).filter(Boolean), server: normalised };
|
|
662
|
+
});
|
|
663
|
+
} catch (error) {
|
|
664
|
+
return { error: `Failed to read config.json or persist update: ${error?.message || error}` };
|
|
711
665
|
}
|
|
712
|
-
|
|
713
|
-
return { servers: list.map(normaliseMcpServer).filter(Boolean), server: normalised };
|
|
714
666
|
}
|
|
715
667
|
|
|
716
668
|
/**
|
|
@@ -732,24 +684,18 @@ export function removeMcpServer(name, dir) {
|
|
|
732
684
|
// matching against the padded string.
|
|
733
685
|
const target = name.trim();
|
|
734
686
|
const root = dir || process.env.YEAFT_DIR || DEFAULT_YEAFT_DIR;
|
|
735
|
-
const configPath = join(root, 'config.json');
|
|
736
|
-
let existing;
|
|
737
|
-
try {
|
|
738
|
-
existing = readConfigForWrite(configPath);
|
|
739
|
-
} catch (err) {
|
|
740
|
-
return { error: `Failed to read config.json: ${err?.message || err}` };
|
|
741
|
-
}
|
|
742
|
-
const list = Array.isArray(existing.mcpServers) ? existing.mcpServers.slice() : [];
|
|
743
|
-
const next = list.filter(s => !(s && typeof s === 'object' && s.name === target));
|
|
744
|
-
const removed = next.length !== list.length;
|
|
745
|
-
existing.mcpServers = next;
|
|
746
|
-
|
|
747
687
|
try {
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
688
|
+
return mutateAgentConfig(root, existing => {
|
|
689
|
+
const list = Array.isArray(existing.mcpServers) ? existing.mcpServers.slice() : [];
|
|
690
|
+
const next = list.filter(entry => !(entry && typeof entry === 'object' && entry.name === target));
|
|
691
|
+
existing.mcpServers = next;
|
|
692
|
+
return {
|
|
693
|
+
servers: next.map(normaliseMcpServer).filter(Boolean),
|
|
694
|
+
removed: next.length !== list.length,
|
|
695
|
+
};
|
|
696
|
+
});
|
|
697
|
+
} catch (error) {
|
|
698
|
+
return { error: `Failed to read config.json or persist update: ${error?.message || error}` };
|
|
751
699
|
}
|
|
752
|
-
|
|
753
|
-
return { servers: next.map(normaliseMcpServer).filter(Boolean), removed };
|
|
754
700
|
}
|
|
755
701
|
|