groove-dev 0.27.143 → 0.27.145
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/CLAUDE.md +0 -7
- package/node_modules/@groove-dev/cli/package.json +1 -1
- package/node_modules/@groove-dev/daemon/package.json +1 -1
- package/node_modules/@groove-dev/daemon/src/api.js +1086 -6532
- package/node_modules/@groove-dev/daemon/src/conversations.js +18 -48
- package/node_modules/@groove-dev/daemon/src/gateways/manager.js +35 -1
- package/node_modules/@groove-dev/daemon/src/index.js +3 -0
- package/node_modules/@groove-dev/daemon/src/journalist.js +23 -13
- package/node_modules/@groove-dev/daemon/src/mlx-server.js +365 -0
- package/node_modules/@groove-dev/daemon/src/model-lab.js +308 -12
- package/node_modules/@groove-dev/daemon/src/pm.js +1 -1
- package/node_modules/@groove-dev/daemon/src/process.js +2 -2
- package/node_modules/@groove-dev/daemon/src/providers/local.js +36 -8
- package/node_modules/@groove-dev/daemon/src/registry.js +21 -5
- package/node_modules/@groove-dev/daemon/src/routes/agents.js +812 -0
- package/node_modules/@groove-dev/daemon/src/routes/coordination.js +318 -0
- package/node_modules/@groove-dev/daemon/src/routes/files.js +751 -0
- package/node_modules/@groove-dev/daemon/src/routes/integrations.js +485 -0
- package/node_modules/@groove-dev/daemon/src/routes/network.js +1784 -0
- package/node_modules/@groove-dev/daemon/src/routes/providers.js +755 -0
- package/node_modules/@groove-dev/daemon/src/routes/schedules.js +110 -0
- package/node_modules/@groove-dev/daemon/src/routes/teams.js +650 -0
- package/node_modules/@groove-dev/daemon/src/scheduler.js +456 -24
- package/node_modules/@groove-dev/daemon/src/teams.js +1 -1
- package/node_modules/@groove-dev/daemon/src/validate.js +38 -1
- package/node_modules/@groove-dev/daemon/templates/mlx-setup.json +12 -0
- package/node_modules/@groove-dev/daemon/templates/tgi-setup.json +1 -1
- package/node_modules/@groove-dev/daemon/templates/vllm-setup.json +1 -1
- package/node_modules/@groove-dev/daemon/test/introducer.test.js +3 -3
- package/node_modules/@groove-dev/daemon/test/journalist.test.js +7 -10
- package/node_modules/@groove-dev/daemon/test/registry.test.js +38 -0
- package/node_modules/@groove-dev/gui/dist/assets/index-Bxc0gU06.js +1006 -0
- package/node_modules/@groove-dev/gui/dist/assets/index-C0pztKBn.css +1 -0
- package/node_modules/@groove-dev/gui/dist/index.html +2 -2
- package/node_modules/@groove-dev/gui/package.json +1 -1
- package/node_modules/@groove-dev/gui/src/{app.jsx → App.jsx} +0 -2
- package/node_modules/@groove-dev/gui/src/app.css +35 -0
- package/node_modules/@groove-dev/gui/src/components/agents/agent-config.jsx +1 -128
- package/node_modules/@groove-dev/gui/src/components/agents/agent-feed.jsx +210 -112
- package/node_modules/@groove-dev/gui/src/components/agents/agent-node.jsx +8 -13
- package/node_modules/@groove-dev/gui/src/components/agents/agent-panel.jsx +2 -70
- package/node_modules/@groove-dev/gui/src/components/agents/code-review.jsx +159 -122
- package/node_modules/@groove-dev/gui/src/components/agents/diff-viewer.jsx +23 -23
- package/node_modules/@groove-dev/gui/src/components/agents/journalist-panel.jsx +1 -1
- package/node_modules/@groove-dev/gui/src/components/agents/spawn-wizard.jsx +2 -135
- package/node_modules/@groove-dev/gui/src/components/automations/automation-card.jsx +274 -0
- package/node_modules/@groove-dev/gui/src/components/automations/automation-wizard.jsx +1136 -0
- package/node_modules/@groove-dev/gui/src/components/chat/chat-header.jsx +2 -0
- package/node_modules/@groove-dev/gui/src/components/chat/chat-input.jsx +68 -66
- package/node_modules/@groove-dev/gui/src/components/chat/chat-view.jsx +4 -8
- package/node_modules/@groove-dev/gui/src/components/dashboard/activity-feed.jsx +3 -3
- package/node_modules/@groove-dev/gui/src/components/dashboard/cache-ring.jsx +5 -5
- package/node_modules/@groove-dev/gui/src/components/dashboard/context-gauges.jsx +6 -8
- package/node_modules/@groove-dev/gui/src/components/dashboard/fleet-panel.jsx +8 -14
- package/node_modules/@groove-dev/gui/src/components/dashboard/intel-panel.jsx +238 -656
- package/node_modules/@groove-dev/gui/src/components/dashboard/kpi-card.jsx +3 -3
- package/node_modules/@groove-dev/gui/src/components/dashboard/routing-chart.jsx +3 -3
- package/node_modules/@groove-dev/gui/src/components/dashboard/team-burn-panel.jsx +1 -1
- package/node_modules/@groove-dev/gui/src/components/dashboard/token-chart.jsx +4 -4
- package/node_modules/@groove-dev/gui/src/components/lab/chat-playground.jsx +39 -31
- package/node_modules/@groove-dev/gui/src/components/lab/lab-assistant.jsx +316 -82
- package/node_modules/@groove-dev/gui/src/components/lab/metrics-panel.jsx +187 -32
- package/node_modules/@groove-dev/gui/src/components/lab/parameter-panel.jsx +200 -18
- package/node_modules/@groove-dev/gui/src/components/lab/preset-manager.jsx +17 -14
- package/node_modules/@groove-dev/gui/src/components/lab/runtime-config.jsx +335 -152
- package/node_modules/@groove-dev/gui/src/components/lab/system-prompt-editor.jsx +10 -8
- package/node_modules/@groove-dev/gui/src/components/layout/activity-bar.jsx +2 -4
- package/node_modules/@groove-dev/gui/src/components/layout/terminal-panel.jsx +4 -2
- package/node_modules/@groove-dev/gui/src/components/layout/welcome-splash.jsx +137 -108
- package/node_modules/@groove-dev/gui/src/components/network/network-health.jsx +2 -2
- package/node_modules/@groove-dev/gui/src/components/network/performance-dashboard.jsx +4 -4
- package/node_modules/@groove-dev/gui/src/components/settings/ssh-wizard.jsx +81 -99
- package/node_modules/@groove-dev/gui/src/components/ui/sheet.jsx +5 -2
- package/node_modules/@groove-dev/gui/src/components/ui/slider.jsx +8 -8
- package/node_modules/@groove-dev/gui/src/lib/cron.js +64 -0
- package/node_modules/@groove-dev/gui/src/lib/status.js +25 -24
- package/node_modules/@groove-dev/gui/src/lib/theme-hex.js +1 -0
- package/node_modules/@groove-dev/gui/src/stores/groove.js +51 -3144
- package/node_modules/@groove-dev/gui/src/stores/helpers.js +10 -0
- package/node_modules/@groove-dev/gui/src/stores/slices/agents-slice.js +459 -0
- package/node_modules/@groove-dev/gui/src/stores/slices/automations-slice.js +96 -0
- package/node_modules/@groove-dev/gui/src/stores/slices/chat-slice.js +226 -0
- package/node_modules/@groove-dev/gui/src/stores/slices/editor-slice.js +285 -0
- package/node_modules/@groove-dev/gui/src/stores/slices/marketplace-slice.js +461 -0
- package/node_modules/@groove-dev/gui/src/stores/slices/network-slice.js +361 -0
- package/node_modules/@groove-dev/gui/src/stores/slices/preview-slice.js +109 -0
- package/node_modules/@groove-dev/gui/src/stores/slices/providers-slice.js +897 -0
- package/node_modules/@groove-dev/gui/src/stores/slices/teams-slice.js +413 -0
- package/node_modules/@groove-dev/gui/src/stores/slices/ui-slice.js +98 -0
- package/node_modules/@groove-dev/gui/src/views/agents.jsx +5 -5
- package/node_modules/@groove-dev/gui/src/views/dashboard.jsx +12 -13
- package/node_modules/@groove-dev/gui/src/views/marketplace.jsx +191 -3
- package/node_modules/@groove-dev/gui/src/views/model-lab.jsx +54 -12
- package/node_modules/@groove-dev/gui/src/views/models.jsx +419 -496
- package/node_modules/@groove-dev/gui/src/views/network.jsx +3 -3
- package/node_modules/@groove-dev/gui/src/views/settings.jsx +81 -94
- package/node_modules/@groove-dev/gui/src/views/teams.jsx +40 -483
- package/node_modules/axios/CHANGELOG.md +260 -0
- package/node_modules/axios/README.md +595 -223
- package/node_modules/axios/dist/axios.js +1460 -1090
- package/node_modules/axios/dist/axios.js.map +1 -1
- package/node_modules/axios/dist/axios.min.js +3 -3
- package/node_modules/axios/dist/axios.min.js.map +1 -1
- package/node_modules/axios/dist/browser/axios.cjs +1560 -1132
- package/node_modules/axios/dist/browser/axios.cjs.map +1 -1
- package/node_modules/axios/dist/esm/axios.js +1557 -1128
- package/node_modules/axios/dist/esm/axios.js.map +1 -1
- package/node_modules/axios/dist/esm/axios.min.js +2 -2
- package/node_modules/axios/dist/esm/axios.min.js.map +1 -1
- package/node_modules/axios/dist/node/axios.cjs +1594 -1057
- package/node_modules/axios/dist/node/axios.cjs.map +1 -1
- package/node_modules/axios/index.d.cts +40 -41
- package/node_modules/axios/index.d.ts +151 -227
- package/node_modules/axios/index.js +2 -0
- package/node_modules/axios/lib/adapters/adapters.js +4 -2
- package/node_modules/axios/lib/adapters/fetch.js +147 -16
- package/node_modules/axios/lib/adapters/http.js +306 -58
- package/node_modules/axios/lib/adapters/xhr.js +6 -2
- package/node_modules/axios/lib/core/Axios.js +7 -3
- package/node_modules/axios/lib/core/AxiosError.js +120 -34
- package/node_modules/axios/lib/core/AxiosHeaders.js +27 -25
- package/node_modules/axios/lib/core/buildFullPath.js +1 -1
- package/node_modules/axios/lib/core/dispatchRequest.js +19 -7
- package/node_modules/axios/lib/core/mergeConfig.js +21 -4
- package/node_modules/axios/lib/core/settle.js +7 -11
- package/node_modules/axios/lib/defaults/index.js +14 -9
- package/node_modules/axios/lib/env/data.js +1 -1
- package/node_modules/axios/lib/helpers/AxiosURLSearchParams.js +1 -2
- package/node_modules/axios/lib/helpers/buildURL.js +1 -1
- package/node_modules/axios/lib/helpers/cookies.js +14 -2
- package/node_modules/axios/lib/helpers/estimateDataURLDecodedBytes.js +28 -1
- package/node_modules/axios/lib/helpers/formDataToJSON.js +3 -1
- package/node_modules/axios/lib/helpers/formDataToStream.js +3 -2
- package/node_modules/axios/lib/helpers/parseProtocol.js +1 -1
- package/node_modules/axios/lib/helpers/progressEventReducer.js +5 -5
- package/node_modules/axios/lib/helpers/resolveConfig.js +54 -18
- package/node_modules/axios/lib/helpers/shouldBypassProxy.js +74 -2
- package/node_modules/axios/lib/helpers/toFormData.js +10 -2
- package/node_modules/axios/lib/helpers/validator.js +3 -1
- package/node_modules/axios/lib/utils.js +33 -21
- package/node_modules/axios/package.json +17 -24
- package/node_modules/follow-redirects/README.md +7 -5
- package/node_modules/follow-redirects/index.js +24 -1
- package/node_modules/follow-redirects/package.json +1 -1
- package/package.json +1 -1
- package/packages/cli/package.json +1 -1
- package/packages/daemon/package.json +1 -1
- package/packages/daemon/src/api.js +1086 -6532
- package/packages/daemon/src/conversations.js +18 -48
- package/packages/daemon/src/gateways/manager.js +35 -1
- package/packages/daemon/src/index.js +3 -0
- package/packages/daemon/src/journalist.js +23 -13
- package/packages/daemon/src/mlx-server.js +365 -0
- package/packages/daemon/src/model-lab.js +308 -12
- package/packages/daemon/src/pm.js +1 -1
- package/packages/daemon/src/process.js +2 -2
- package/packages/daemon/src/providers/local.js +36 -8
- package/packages/daemon/src/registry.js +21 -5
- package/packages/daemon/src/routes/agents.js +812 -0
- package/packages/daemon/src/routes/coordination.js +318 -0
- package/packages/daemon/src/routes/files.js +751 -0
- package/packages/daemon/src/routes/integrations.js +485 -0
- package/packages/daemon/src/routes/network.js +1784 -0
- package/packages/daemon/src/routes/providers.js +755 -0
- package/packages/daemon/src/routes/schedules.js +110 -0
- package/packages/daemon/src/routes/teams.js +650 -0
- package/packages/daemon/src/scheduler.js +456 -24
- package/packages/daemon/src/teams.js +1 -1
- package/packages/daemon/src/validate.js +38 -1
- package/packages/daemon/templates/mlx-setup.json +12 -0
- package/packages/daemon/templates/tgi-setup.json +1 -1
- package/packages/daemon/templates/vllm-setup.json +1 -1
- package/packages/gui/dist/assets/index-Bxc0gU06.js +1006 -0
- package/packages/gui/dist/assets/index-C0pztKBn.css +1 -0
- package/packages/gui/dist/index.html +2 -2
- package/packages/gui/package.json +1 -1
- package/packages/gui/src/{app.jsx → App.jsx} +0 -2
- package/packages/gui/src/app.css +35 -0
- package/packages/gui/src/components/agents/agent-config.jsx +1 -128
- package/packages/gui/src/components/agents/agent-feed.jsx +210 -112
- package/packages/gui/src/components/agents/agent-node.jsx +8 -13
- package/packages/gui/src/components/agents/agent-panel.jsx +2 -70
- package/packages/gui/src/components/agents/code-review.jsx +159 -122
- package/packages/gui/src/components/agents/diff-viewer.jsx +23 -23
- package/packages/gui/src/components/agents/journalist-panel.jsx +1 -1
- package/packages/gui/src/components/agents/spawn-wizard.jsx +2 -135
- package/packages/gui/src/components/automations/automation-card.jsx +274 -0
- package/packages/gui/src/components/automations/automation-wizard.jsx +1136 -0
- package/packages/gui/src/components/chat/chat-header.jsx +2 -0
- package/packages/gui/src/components/chat/chat-input.jsx +68 -66
- package/packages/gui/src/components/chat/chat-view.jsx +4 -8
- package/packages/gui/src/components/dashboard/activity-feed.jsx +3 -3
- package/packages/gui/src/components/dashboard/cache-ring.jsx +5 -5
- package/packages/gui/src/components/dashboard/context-gauges.jsx +6 -8
- package/packages/gui/src/components/dashboard/fleet-panel.jsx +8 -14
- package/packages/gui/src/components/dashboard/intel-panel.jsx +238 -656
- package/packages/gui/src/components/dashboard/kpi-card.jsx +3 -3
- package/packages/gui/src/components/dashboard/routing-chart.jsx +3 -3
- package/packages/gui/src/components/dashboard/team-burn-panel.jsx +1 -1
- package/packages/gui/src/components/dashboard/token-chart.jsx +4 -4
- package/packages/gui/src/components/lab/chat-playground.jsx +39 -31
- package/packages/gui/src/components/lab/lab-assistant.jsx +316 -82
- package/packages/gui/src/components/lab/metrics-panel.jsx +187 -32
- package/packages/gui/src/components/lab/parameter-panel.jsx +200 -18
- package/packages/gui/src/components/lab/preset-manager.jsx +17 -14
- package/packages/gui/src/components/lab/runtime-config.jsx +335 -152
- package/packages/gui/src/components/lab/system-prompt-editor.jsx +10 -8
- package/packages/gui/src/components/layout/activity-bar.jsx +2 -4
- package/packages/gui/src/components/layout/terminal-panel.jsx +4 -2
- package/packages/gui/src/components/layout/welcome-splash.jsx +137 -108
- package/packages/gui/src/components/network/network-health.jsx +2 -2
- package/packages/gui/src/components/network/performance-dashboard.jsx +4 -4
- package/packages/gui/src/components/settings/ssh-wizard.jsx +81 -99
- package/packages/gui/src/components/ui/sheet.jsx +5 -2
- package/packages/gui/src/components/ui/slider.jsx +8 -8
- package/packages/gui/src/lib/cron.js +64 -0
- package/packages/gui/src/lib/status.js +25 -24
- package/packages/gui/src/lib/theme-hex.js +1 -0
- package/packages/gui/src/stores/groove.js +51 -3144
- package/packages/gui/src/stores/helpers.js +10 -0
- package/packages/gui/src/stores/slices/agents-slice.js +459 -0
- package/packages/gui/src/stores/slices/automations-slice.js +96 -0
- package/packages/gui/src/stores/slices/chat-slice.js +226 -0
- package/packages/gui/src/stores/slices/editor-slice.js +285 -0
- package/packages/gui/src/stores/slices/marketplace-slice.js +461 -0
- package/packages/gui/src/stores/slices/network-slice.js +361 -0
- package/packages/gui/src/stores/slices/preview-slice.js +109 -0
- package/packages/gui/src/stores/slices/providers-slice.js +897 -0
- package/packages/gui/src/stores/slices/teams-slice.js +413 -0
- package/packages/gui/src/stores/slices/ui-slice.js +98 -0
- package/packages/gui/src/views/agents.jsx +5 -5
- package/packages/gui/src/views/dashboard.jsx +12 -13
- package/packages/gui/src/views/marketplace.jsx +191 -3
- package/packages/gui/src/views/model-lab.jsx +54 -12
- package/packages/gui/src/views/models.jsx +419 -496
- package/packages/gui/src/views/network.jsx +3 -3
- package/packages/gui/src/views/settings.jsx +81 -94
- package/packages/gui/src/views/teams.jsx +40 -483
- package/SECURITY_SWEEP.md +0 -228
- package/TRAINING_DATA_v4.md +0 -6
- package/node_modules/@groove-dev/gui/dist/assets/index-CCVvAoQn.css +0 -1
- package/node_modules/@groove-dev/gui/dist/assets/index-DGIv_TRm.js +0 -984
- package/node_modules/@groove-dev/gui/src/components/agents/agent-chat.jsx +0 -379
- package/node_modules/@groove-dev/gui/src/views/preview.jsx +0 -6
- package/node_modules/@groove-dev/gui/src/views/subscription-panel.jsx +0 -327
- package/packages/gui/dist/assets/index-CCVvAoQn.css +0 -1
- package/packages/gui/dist/assets/index-DGIv_TRm.js +0 -984
- package/packages/gui/src/components/agents/agent-chat.jsx +0 -379
- package/packages/gui/src/views/preview.jsx +0 -6
- package/packages/gui/src/views/subscription-panel.jsx +0 -327
- package/test.py +0 -571
|
@@ -4,23 +4,34 @@
|
|
|
4
4
|
import { resolve } from 'path';
|
|
5
5
|
import { existsSync, readFileSync, writeFileSync, mkdirSync, readdirSync, unlinkSync } from 'fs';
|
|
6
6
|
import { randomUUID } from 'crypto';
|
|
7
|
-
|
|
7
|
+
import { homedir } from 'os';
|
|
8
|
+
import { spawn } from 'child_process';
|
|
9
|
+
import { LlamaServerManager } from './llama-server.js';
|
|
10
|
+
import { MLXServerManager } from './mlx-server.js';
|
|
11
|
+
const RUNTIME_TYPES = ['ollama', 'vllm', 'llama-cpp', 'mlx', 'tgi', 'openai-compatible'];
|
|
8
12
|
const DEFAULT_OLLAMA_ENDPOINT = 'http://localhost:11434';
|
|
13
|
+
const GLOBAL_GROOVE_DIR = resolve(homedir(), '.groove');
|
|
14
|
+
|
|
15
|
+
function localURL(endpoint) { return endpoint.replace('localhost', '127.0.0.1'); }
|
|
9
16
|
|
|
10
17
|
export class ModelLab {
|
|
11
18
|
constructor(daemon) {
|
|
12
19
|
this.daemon = daemon;
|
|
13
|
-
this.runtimesPath = resolve(
|
|
20
|
+
this.runtimesPath = resolve(GLOBAL_GROOVE_DIR, 'lab-runtimes.json');
|
|
14
21
|
this.presetsPath = resolve(daemon.grooveDir, 'lab-presets.json');
|
|
15
22
|
this.sessionsDir = resolve(daemon.grooveDir, 'lab-sessions');
|
|
16
23
|
this.runtimes = new Map();
|
|
17
24
|
this.presets = new Map();
|
|
18
25
|
this.sessions = new Map();
|
|
26
|
+
this._processes = new Map();
|
|
27
|
+
this._installedTools = null;
|
|
19
28
|
this._ensureDirs();
|
|
20
29
|
this._load();
|
|
30
|
+
this._detectInstalledTools();
|
|
21
31
|
}
|
|
22
32
|
|
|
23
33
|
_ensureDirs() {
|
|
34
|
+
try { mkdirSync(GLOBAL_GROOVE_DIR, { recursive: true }); } catch { /* best-effort */ }
|
|
24
35
|
try { mkdirSync(this.sessionsDir, { recursive: true }); } catch { /* best-effort */ }
|
|
25
36
|
}
|
|
26
37
|
|
|
@@ -57,6 +68,36 @@ export class ModelLab {
|
|
|
57
68
|
} catch { /* dir may not exist yet */ }
|
|
58
69
|
}
|
|
59
70
|
|
|
71
|
+
_detectInstalledTools() {
|
|
72
|
+
// Detect installed inference tools (not running servers) on startup.
|
|
73
|
+
// Results are cached and broadcast so the GUI can show "Start" buttons.
|
|
74
|
+
try {
|
|
75
|
+
const llamaInstalled = LlamaServerManager.isInstalled();
|
|
76
|
+
const mlxInstalled = MLXServerManager.isInstalled();
|
|
77
|
+
const mlxModels = MLXServerManager.scanModels();
|
|
78
|
+
const mlxVersion = mlxInstalled ? MLXServerManager.getVersion() : null;
|
|
79
|
+
|
|
80
|
+
this._installedTools = {
|
|
81
|
+
llama: { installed: llamaInstalled },
|
|
82
|
+
mlx: { installed: mlxInstalled, version: mlxVersion, models: mlxModels },
|
|
83
|
+
};
|
|
84
|
+
|
|
85
|
+
this.daemon?.broadcast({ type: 'lab:tools:detected', data: this._installedTools });
|
|
86
|
+
} catch {
|
|
87
|
+
this._installedTools = { llama: { installed: false }, mlx: { installed: false, version: null, models: [] } };
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
getInstalledTools() {
|
|
92
|
+
if (!this._installedTools) this._detectInstalledTools();
|
|
93
|
+
return this._installedTools;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
refreshInstalledTools() {
|
|
97
|
+
this._detectInstalledTools();
|
|
98
|
+
return this._installedTools;
|
|
99
|
+
}
|
|
100
|
+
|
|
60
101
|
_saveRuntimes() {
|
|
61
102
|
writeFileSync(this.runtimesPath, JSON.stringify([...this.runtimes.values()], null, 2));
|
|
62
103
|
}
|
|
@@ -74,7 +115,7 @@ export class ModelLab {
|
|
|
74
115
|
|
|
75
116
|
// ─── Runtimes ───────────────────────────────────────────────
|
|
76
117
|
|
|
77
|
-
async addRuntime({ name, type, endpoint, apiKey, models }) {
|
|
118
|
+
async addRuntime({ name, type, endpoint, apiKey, models, launchConfig }) {
|
|
78
119
|
const id = randomUUID().slice(0, 8);
|
|
79
120
|
const runtime = {
|
|
80
121
|
id,
|
|
@@ -83,6 +124,7 @@ export class ModelLab {
|
|
|
83
124
|
endpoint: endpoint.replace(/\/+$/, ''),
|
|
84
125
|
apiKey: apiKey || null,
|
|
85
126
|
models: models || [],
|
|
127
|
+
launchConfig: launchConfig || null,
|
|
86
128
|
createdAt: new Date().toISOString(),
|
|
87
129
|
};
|
|
88
130
|
this.runtimes.set(id, runtime);
|
|
@@ -96,7 +138,7 @@ export class ModelLab {
|
|
|
96
138
|
const rt = this.runtimes.get(id);
|
|
97
139
|
if (!rt) return null;
|
|
98
140
|
|
|
99
|
-
// Stop the llama-server process if this is a local
|
|
141
|
+
// Stop the llama-server process if this is a local GGUF runtime
|
|
100
142
|
if (rt._localModelId) {
|
|
101
143
|
const mm = this.daemon.modelManager;
|
|
102
144
|
const ls = this.daemon.llamaServer;
|
|
@@ -106,6 +148,22 @@ export class ModelLab {
|
|
|
106
148
|
}
|
|
107
149
|
}
|
|
108
150
|
|
|
151
|
+
// Stop the MLX server if this is an MLX runtime
|
|
152
|
+
if (rt._mlxModelId) {
|
|
153
|
+
const ms = this.daemon.mlxServer;
|
|
154
|
+
if (ms) {
|
|
155
|
+
const hfId = rt._mlxModelId.startsWith('mlx:') ? rt._mlxModelId.slice(4) : rt._mlxModelId;
|
|
156
|
+
ms.stopServer(hfId).catch(() => {});
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
// Stop any managed process
|
|
161
|
+
const proc = this._processes.get(id);
|
|
162
|
+
if (proc) {
|
|
163
|
+
try { proc.kill('SIGTERM'); } catch {}
|
|
164
|
+
this._processes.delete(id);
|
|
165
|
+
}
|
|
166
|
+
|
|
109
167
|
this.runtimes.delete(id);
|
|
110
168
|
this._saveRuntimes();
|
|
111
169
|
this.daemon.broadcast({ type: 'lab:runtime:removed', data: { id } });
|
|
@@ -113,6 +171,135 @@ export class ModelLab {
|
|
|
113
171
|
return rt;
|
|
114
172
|
}
|
|
115
173
|
|
|
174
|
+
async startRuntime(id) {
|
|
175
|
+
const rt = this.runtimes.get(id);
|
|
176
|
+
if (!rt) throw new Error('Runtime not found');
|
|
177
|
+
|
|
178
|
+
// MLX runtimes — use built-in MLXServerManager
|
|
179
|
+
if (rt.type === 'mlx' && !rt.launchConfig) {
|
|
180
|
+
const modelId = rt._mlxModelId || this._deriveModelId(rt, 'MLX - ');
|
|
181
|
+
if (modelId) {
|
|
182
|
+
const hfId = modelId.startsWith('mlx:') ? modelId.slice(4) : modelId;
|
|
183
|
+
const ms = this.daemon.mlxServer;
|
|
184
|
+
if (!ms) throw new Error('MLX server manager not available');
|
|
185
|
+
const endpoint = await ms.ensureServer(hfId);
|
|
186
|
+
rt.endpoint = endpoint;
|
|
187
|
+
if (!rt._mlxModelId) { rt._mlxModelId = `mlx:${hfId}`; }
|
|
188
|
+
this._saveRuntimes();
|
|
189
|
+
this.daemon.broadcast({ type: 'lab:runtime:started', data: { id } });
|
|
190
|
+
this.daemon.audit.log('lab.runtime.start', { id, name: rt.name });
|
|
191
|
+
return rt;
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
// llama-cpp runtimes — use built-in LlamaServerManager
|
|
196
|
+
if (rt.type === 'llama-cpp' && rt._localModelId && !rt.launchConfig) {
|
|
197
|
+
const mm = this.daemon.modelManager;
|
|
198
|
+
const ls = this.daemon.llamaServer;
|
|
199
|
+
if (!mm || !ls) throw new Error('llama-server not available');
|
|
200
|
+
const modelPath = mm.getModelPath(rt._localModelId);
|
|
201
|
+
if (!modelPath) throw new Error('Model file not found');
|
|
202
|
+
const endpoint = await ls.ensureServer(modelPath);
|
|
203
|
+
rt.endpoint = endpoint;
|
|
204
|
+
this._saveRuntimes();
|
|
205
|
+
this.daemon.broadcast({ type: 'lab:runtime:started', data: { id } });
|
|
206
|
+
this.daemon.audit.log('lab.runtime.start', { id, name: rt.name });
|
|
207
|
+
return rt;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
// Generic launchConfig runtimes (vLLM, TGI, etc.)
|
|
211
|
+
if (!rt.launchConfig) throw new Error('No launch config — use the assistant to set up this runtime first');
|
|
212
|
+
if (this._processes.has(id)) throw new Error('Server already running');
|
|
213
|
+
|
|
214
|
+
const lc = rt.launchConfig;
|
|
215
|
+
const proc = spawn(lc.command, lc.args, {
|
|
216
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
217
|
+
env: { ...process.env, ...lc.env },
|
|
218
|
+
detached: false,
|
|
219
|
+
});
|
|
220
|
+
|
|
221
|
+
if (!proc.pid) throw new Error('Failed to start server process');
|
|
222
|
+
|
|
223
|
+
this._processes.set(id, proc);
|
|
224
|
+
|
|
225
|
+
proc.on('exit', (code, signal) => {
|
|
226
|
+
this._processes.delete(id);
|
|
227
|
+
this.daemon?.broadcast({ type: 'lab:runtime:stopped', data: { id, code, signal } });
|
|
228
|
+
});
|
|
229
|
+
|
|
230
|
+
const endpoint = rt.endpoint.replace('localhost', '127.0.0.1');
|
|
231
|
+
const healthUrl = rt.type === 'ollama' ? `${endpoint}/api/tags` : `${endpoint}/v1/models`;
|
|
232
|
+
|
|
233
|
+
try {
|
|
234
|
+
await this._waitForServer(healthUrl, 60000);
|
|
235
|
+
} catch (err) {
|
|
236
|
+
await this.stopRuntime(id);
|
|
237
|
+
throw new Error(`Server failed to become healthy: ${err.message}`);
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
this.daemon.broadcast({ type: 'lab:runtime:started', data: { id } });
|
|
241
|
+
this.daemon.audit.log('lab.runtime.start', { id, name: rt.name });
|
|
242
|
+
return rt;
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
_deriveModelId(rt, prefix) {
|
|
246
|
+
if (rt.name && rt.name.startsWith(prefix)) {
|
|
247
|
+
return rt.name.slice(prefix.length).trim();
|
|
248
|
+
}
|
|
249
|
+
if (rt.models?.length > 0) {
|
|
250
|
+
return rt.models[0].id || rt.models[0].name;
|
|
251
|
+
}
|
|
252
|
+
return null;
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
async _waitForServer(url, timeout) {
|
|
256
|
+
const start = Date.now();
|
|
257
|
+
while (Date.now() - start < timeout) {
|
|
258
|
+
try {
|
|
259
|
+
const res = await fetch(url, { signal: AbortSignal.timeout(2000) });
|
|
260
|
+
if (res.ok) return;
|
|
261
|
+
} catch { /* server still starting */ }
|
|
262
|
+
await new Promise((r) => setTimeout(r, 1000));
|
|
263
|
+
}
|
|
264
|
+
throw new Error(`Health check timed out after ${timeout / 1000}s`);
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
async stopRuntime(id) {
|
|
268
|
+
const rt = this.runtimes.get(id);
|
|
269
|
+
if (!rt) throw new Error('Runtime not found');
|
|
270
|
+
|
|
271
|
+
if (rt._localModelId) {
|
|
272
|
+
const mm = this.daemon.modelManager;
|
|
273
|
+
const ls = this.daemon.llamaServer;
|
|
274
|
+
if (mm && ls) {
|
|
275
|
+
const modelPath = mm.getModelPath(rt._localModelId);
|
|
276
|
+
if (modelPath) await ls.stopServer(modelPath);
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
if (rt._mlxModelId) {
|
|
281
|
+
const ms = this.daemon.mlxServer;
|
|
282
|
+
if (ms) {
|
|
283
|
+
const hfId = rt._mlxModelId.startsWith('mlx:') ? rt._mlxModelId.slice(4) : rt._mlxModelId;
|
|
284
|
+
await ms.stopServer(hfId);
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
const proc = this._processes.get(id);
|
|
289
|
+
if (proc) {
|
|
290
|
+
await new Promise((resolve) => {
|
|
291
|
+
const timeout = setTimeout(() => { try { proc.kill('SIGKILL'); } catch {} }, 5000);
|
|
292
|
+
proc.on('exit', () => { clearTimeout(timeout); resolve(); });
|
|
293
|
+
try { proc.kill('SIGTERM'); } catch { clearTimeout(timeout); resolve(); }
|
|
294
|
+
});
|
|
295
|
+
this._processes.delete(id);
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
this.daemon.broadcast({ type: 'lab:runtime:stopped', data: { id } });
|
|
299
|
+
this.daemon.audit.log('lab.runtime.stop', { id, name: rt.name });
|
|
300
|
+
return rt;
|
|
301
|
+
}
|
|
302
|
+
|
|
116
303
|
getRuntime(id) {
|
|
117
304
|
return this.runtimes.get(id) || null;
|
|
118
305
|
}
|
|
@@ -147,9 +334,9 @@ export class ModelLab {
|
|
|
147
334
|
|
|
148
335
|
async _discoverModels(rt) {
|
|
149
336
|
if (rt.type === 'ollama') {
|
|
150
|
-
return this._discoverOllamaModels(rt.endpoint);
|
|
337
|
+
return this._discoverOllamaModels(localURL(rt.endpoint));
|
|
151
338
|
}
|
|
152
|
-
return this._discoverOpenAIModels(rt.endpoint, rt.apiKey);
|
|
339
|
+
return this._discoverOpenAIModels(localURL(rt.endpoint), rt.apiKey);
|
|
153
340
|
}
|
|
154
341
|
|
|
155
342
|
async _discoverOllamaModels(endpoint) {
|
|
@@ -185,15 +372,16 @@ export class ModelLab {
|
|
|
185
372
|
async getRuntimeStatus(rt) {
|
|
186
373
|
try {
|
|
187
374
|
const start = Date.now();
|
|
375
|
+
const ep = localURL(rt.endpoint);
|
|
188
376
|
if (rt.type === 'ollama') {
|
|
189
|
-
const resp = await fetch(`${
|
|
377
|
+
const resp = await fetch(`${ep}/api/tags`, {
|
|
190
378
|
signal: AbortSignal.timeout(5000),
|
|
191
379
|
});
|
|
192
380
|
return { online: resp.ok, latency: Date.now() - start };
|
|
193
381
|
}
|
|
194
382
|
const headers = {};
|
|
195
383
|
if (rt.apiKey) headers['Authorization'] = `Bearer ${rt.apiKey}`;
|
|
196
|
-
const resp = await fetch(`${
|
|
384
|
+
const resp = await fetch(`${ep}/v1/models`, {
|
|
197
385
|
headers,
|
|
198
386
|
signal: AbortSignal.timeout(5000),
|
|
199
387
|
});
|
|
@@ -320,7 +508,7 @@ export class ModelLab {
|
|
|
320
508
|
|
|
321
509
|
if (rt.type === 'ollama') {
|
|
322
510
|
try {
|
|
323
|
-
const mem = await this.getOllamaMemoryUsage(rt.endpoint);
|
|
511
|
+
const mem = await this.getOllamaMemoryUsage(localURL(rt.endpoint));
|
|
324
512
|
if (mem) onEvent({ type: 'memory', usage: mem });
|
|
325
513
|
} catch { /* ignore */ }
|
|
326
514
|
}
|
|
@@ -439,9 +627,13 @@ export class ModelLab {
|
|
|
439
627
|
this._saveSession(session);
|
|
440
628
|
}
|
|
441
629
|
|
|
442
|
-
// ─── Launch Local
|
|
630
|
+
// ─── Launch Local Model ──────────────────────────────────────
|
|
443
631
|
|
|
444
632
|
async launchLocalModel(modelId) {
|
|
633
|
+
if (modelId.startsWith('mlx:')) {
|
|
634
|
+
return this.launchMLXModel(modelId);
|
|
635
|
+
}
|
|
636
|
+
|
|
445
637
|
const mm = this.daemon.modelManager;
|
|
446
638
|
const ls = this.daemon.llamaServer;
|
|
447
639
|
if (!mm || !ls) throw new Error('Local model serving not available');
|
|
@@ -480,9 +672,113 @@ export class ModelLab {
|
|
|
480
672
|
}
|
|
481
673
|
|
|
482
674
|
listLocalModels() {
|
|
675
|
+
const models = [];
|
|
676
|
+
|
|
677
|
+
// GGUF models from ModelManager
|
|
483
678
|
const mm = this.daemon.modelManager;
|
|
484
|
-
if (
|
|
485
|
-
|
|
679
|
+
if (mm) {
|
|
680
|
+
for (const m of mm.getInstalled().filter((m) => m.exists)) {
|
|
681
|
+
models.push({ ...m, type: 'gguf', compatibleBackends: ['llama-cpp'] });
|
|
682
|
+
}
|
|
683
|
+
}
|
|
684
|
+
|
|
685
|
+
// HuggingFace cache models (MLX + standard HF)
|
|
686
|
+
try {
|
|
687
|
+
const hfModels = MLXServerManager.scanModels();
|
|
688
|
+
for (const m of hfModels) {
|
|
689
|
+
models.push(m);
|
|
690
|
+
}
|
|
691
|
+
} catch { /* scan may fail */ }
|
|
692
|
+
|
|
693
|
+
return models;
|
|
694
|
+
}
|
|
695
|
+
|
|
696
|
+
// ─── Model Suggestions ───────────────────────────────────────
|
|
697
|
+
|
|
698
|
+
async suggestAlternativeModel(modelId, targetBackend) {
|
|
699
|
+
const baseName = this._extractBaseName(modelId);
|
|
700
|
+
if (!baseName) return null;
|
|
701
|
+
|
|
702
|
+
const searchQueries = [];
|
|
703
|
+
if (targetBackend === 'mlx') {
|
|
704
|
+
searchQueries.push(`mlx-community/${baseName}`);
|
|
705
|
+
} else if (targetBackend === 'llama-cpp') {
|
|
706
|
+
searchQueries.push(baseName, `${baseName}-GGUF`);
|
|
707
|
+
} else if (targetBackend === 'vllm' || targetBackend === 'tgi') {
|
|
708
|
+
searchQueries.push(baseName);
|
|
709
|
+
}
|
|
710
|
+
|
|
711
|
+
for (const query of searchQueries) {
|
|
712
|
+
try {
|
|
713
|
+
const resp = await fetch(
|
|
714
|
+
`https://huggingface.co/api/models?search=${encodeURIComponent(query)}&limit=5&sort=downloads`,
|
|
715
|
+
{ signal: AbortSignal.timeout(8000) },
|
|
716
|
+
);
|
|
717
|
+
if (!resp.ok) continue;
|
|
718
|
+
const results = await resp.json();
|
|
719
|
+
|
|
720
|
+
for (const r of results) {
|
|
721
|
+
if (targetBackend === 'mlx' && !r.modelId?.includes('mlx')) continue;
|
|
722
|
+
if (targetBackend === 'llama-cpp' && !r.modelId?.toLowerCase().includes('gguf')) continue;
|
|
723
|
+
if (targetBackend === 'vllm' || targetBackend === 'tgi') {
|
|
724
|
+
if (r.modelId?.includes('gguf') || r.modelId?.includes('mlx')) continue;
|
|
725
|
+
}
|
|
726
|
+
return {
|
|
727
|
+
repoId: r.modelId,
|
|
728
|
+
name: r.modelId?.split('/').pop() || r.modelId,
|
|
729
|
+
downloads: r.downloads || 0,
|
|
730
|
+
};
|
|
731
|
+
}
|
|
732
|
+
} catch { /* network error, skip */ }
|
|
733
|
+
}
|
|
734
|
+
return null;
|
|
735
|
+
}
|
|
736
|
+
|
|
737
|
+
_extractBaseName(modelId) {
|
|
738
|
+
let name = modelId;
|
|
739
|
+
if (name.startsWith('mlx:') || name.startsWith('hf:')) name = name.slice(name.indexOf(':') + 1);
|
|
740
|
+
name = name.split('/').pop() || name;
|
|
741
|
+
name = name
|
|
742
|
+
.replace(/\.gguf$/i, '')
|
|
743
|
+
.replace(/[-_](4bit|8bit|3bit|bf16|fp16|MLX|GGUF|Q\d_\w+)/gi, '')
|
|
744
|
+
.replace(/-+$/, '');
|
|
745
|
+
return name || null;
|
|
746
|
+
}
|
|
747
|
+
|
|
748
|
+
// ─── Launch MLX Model ────────────────────────────────────────
|
|
749
|
+
|
|
750
|
+
async launchMLXModel(modelId) {
|
|
751
|
+
const ms = this.daemon.mlxServer;
|
|
752
|
+
if (!ms) throw new Error('MLX server manager not available');
|
|
753
|
+
|
|
754
|
+
// modelId is "mlx:mlx-community/ModelName"
|
|
755
|
+
const hfModelId = modelId.startsWith('mlx:') ? modelId.slice(4) : modelId;
|
|
756
|
+
|
|
757
|
+
const endpoint = await ms.ensureServer(hfModelId);
|
|
758
|
+
if (!endpoint) throw new Error('Failed to start MLX server');
|
|
759
|
+
|
|
760
|
+
// Check if we already have a runtime for this model
|
|
761
|
+
const existing = [...this.runtimes.values()].find(
|
|
762
|
+
(r) => r._mlxModelId === modelId,
|
|
763
|
+
);
|
|
764
|
+
if (existing) {
|
|
765
|
+
existing.endpoint = endpoint;
|
|
766
|
+
this._saveRuntimes();
|
|
767
|
+
this.daemon.broadcast({ type: 'lab:runtime:updated', data: existing });
|
|
768
|
+
return { runtime: existing, model: hfModelId };
|
|
769
|
+
}
|
|
770
|
+
|
|
771
|
+
const shortName = hfModelId.split('/').pop() || hfModelId;
|
|
772
|
+
const runtime = await this.addRuntime({
|
|
773
|
+
name: `MLX - ${shortName}`,
|
|
774
|
+
type: 'mlx',
|
|
775
|
+
endpoint,
|
|
776
|
+
models: [{ id: 'default', name: shortName }],
|
|
777
|
+
});
|
|
778
|
+
runtime._mlxModelId = modelId;
|
|
779
|
+
this._saveRuntimes();
|
|
780
|
+
|
|
781
|
+
return { runtime, model: hfModelId };
|
|
486
782
|
}
|
|
487
783
|
|
|
488
784
|
// ─── Auto-detect Ollama ─────────────────────────────────────
|
|
@@ -28,7 +28,7 @@ export class ProjectManager {
|
|
|
28
28
|
|
|
29
29
|
// Get agent registry for scope awareness
|
|
30
30
|
const agents = this.daemon.registry.getAll();
|
|
31
|
-
const agentRecord = agents.find((a) => a.name === agent);
|
|
31
|
+
const agentRecord = agents.find((a) => a.id === agent) || agents.find((a) => a.name === agent);
|
|
32
32
|
const scope = agentRecord?.scope?.join(', ') || 'unrestricted';
|
|
33
33
|
const role = agentRecord?.role || 'unknown';
|
|
34
34
|
|
|
@@ -1058,7 +1058,7 @@ For normal file edits within your scope, proceed without review.
|
|
|
1058
1058
|
// ─── Agent Loop path (local models with built-in agentic runtime) ───
|
|
1059
1059
|
if (provider.constructor.useAgentLoop) {
|
|
1060
1060
|
provider.normalizeConfig(spawnConfig);
|
|
1061
|
-
const loopConfig = provider.getLoopConfig(spawnConfig);
|
|
1061
|
+
const loopConfig = provider.getLoopConfig(spawnConfig, this.daemon);
|
|
1062
1062
|
logStream.write(`[${new Date().toISOString()}] GROOVE agent-loop: model=${loopConfig.model} api=${loopConfig.apiBase}\n`);
|
|
1063
1063
|
|
|
1064
1064
|
const loop = new AgentLoop({ daemon: this.daemon, agent, loopConfig, logStream });
|
|
@@ -2245,7 +2245,7 @@ For normal file edits within your scope, proceed without review.
|
|
|
2245
2245
|
if (storedKey) spawnConfig.apiKey = storedKey;
|
|
2246
2246
|
}
|
|
2247
2247
|
|
|
2248
|
-
const loopConfig = provider.getLoopConfig(spawnConfig);
|
|
2248
|
+
const loopConfig = provider.getLoopConfig(spawnConfig, this.daemon);
|
|
2249
2249
|
const loop = new AgentLoop({ daemon: this.daemon, agent: newAgent, loopConfig, logStream });
|
|
2250
2250
|
|
|
2251
2251
|
this.handles.set(newAgent.id, { loop, logStream });
|
|
@@ -133,18 +133,46 @@ export class LocalProvider extends Provider {
|
|
|
133
133
|
return config;
|
|
134
134
|
}
|
|
135
135
|
|
|
136
|
-
getLoopConfig(agent) {
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
// Determine API endpoint
|
|
141
|
-
let apiBase = 'http://localhost:11434/v1'; // Ollama's OpenAI-compatible endpoint (default)
|
|
136
|
+
getLoopConfig(agent, daemon) {
|
|
137
|
+
let model = agent.model || 'qwen2.5-coder:7b';
|
|
138
|
+
let apiBase = 'http://localhost:11434/v1';
|
|
139
|
+
let apiKey = agent.apiKey || null;
|
|
142
140
|
|
|
143
|
-
// Custom endpoint override from agent config or daemon config
|
|
144
141
|
if (agent.apiBase) {
|
|
145
142
|
apiBase = agent.apiBase;
|
|
146
143
|
}
|
|
147
144
|
|
|
145
|
+
// Resolve GGUF models (gguf:<id>) — find the lab runtime serving this model
|
|
146
|
+
if (model.startsWith('gguf:') && daemon?.modelLab) {
|
|
147
|
+
const ggufId = model.slice(5);
|
|
148
|
+
const runtimes = daemon.modelLab.listRuntimes();
|
|
149
|
+
const rt = runtimes.find(r =>
|
|
150
|
+
r._localModelId === ggufId ||
|
|
151
|
+
r.models?.some(rm => rm.id === ggufId || rm.name === ggufId)
|
|
152
|
+
);
|
|
153
|
+
if (rt) {
|
|
154
|
+
apiBase = rt.endpoint.includes('/v1') ? rt.endpoint : `${rt.endpoint}/v1`;
|
|
155
|
+
if (rt.apiKey) apiKey = rt.apiKey;
|
|
156
|
+
const rtModel = rt.models?.[0];
|
|
157
|
+
model = rtModel?.id || rtModel?.name || ggufId;
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
// Resolve runtime models (runtime:<runtimeId>:<modelId>)
|
|
162
|
+
if (model.startsWith('runtime:') && daemon?.modelLab) {
|
|
163
|
+
const parts = model.split(':');
|
|
164
|
+
const runtimeId = parts[1];
|
|
165
|
+
const modelId = parts.slice(2).join(':');
|
|
166
|
+
const rt = daemon.modelLab.getRuntime(runtimeId);
|
|
167
|
+
if (rt) {
|
|
168
|
+
apiBase = rt.endpoint.includes('/v1') ? rt.endpoint : `${rt.endpoint}/v1`;
|
|
169
|
+
if (rt.apiKey) apiKey = rt.apiKey;
|
|
170
|
+
model = modelId;
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
const contextWindow = this.getContextWindow(model);
|
|
175
|
+
|
|
148
176
|
return {
|
|
149
177
|
apiBase,
|
|
150
178
|
model,
|
|
@@ -153,7 +181,7 @@ export class LocalProvider extends Provider {
|
|
|
153
181
|
maxResponseTokens: 4096,
|
|
154
182
|
stream: true,
|
|
155
183
|
headers: {},
|
|
156
|
-
apiKey
|
|
184
|
+
apiKey,
|
|
157
185
|
introContext: agent.introContext || '',
|
|
158
186
|
};
|
|
159
187
|
}
|
|
@@ -11,16 +11,31 @@ export class Registry extends EventEmitter {
|
|
|
11
11
|
super();
|
|
12
12
|
this.state = state;
|
|
13
13
|
this.agents = new Map();
|
|
14
|
+
this._counters = new Map();
|
|
15
|
+
this._initCounters();
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
_initCounters() {
|
|
19
|
+
for (const agent of this.agents.values()) {
|
|
20
|
+
const match = agent.name.match(/^(.+)-(\d+)$/);
|
|
21
|
+
if (!match) continue;
|
|
22
|
+
const role = match[1];
|
|
23
|
+
const num = parseInt(match[2], 10);
|
|
24
|
+
const current = this._counters.get(role) || 0;
|
|
25
|
+
if (num > current) this._counters.set(role, num);
|
|
26
|
+
}
|
|
14
27
|
}
|
|
15
28
|
|
|
16
29
|
add(config) {
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
30
|
+
const role = config.role;
|
|
31
|
+
const count = (this._counters.get(role) || 0) + 1;
|
|
32
|
+
this._counters.set(role, count);
|
|
33
|
+
let name = config.name || `${role}-${count}`;
|
|
34
|
+
// Dedup: ensure name is globally unique (no two agents ever share a name)
|
|
20
35
|
const existing = this.getAll();
|
|
21
|
-
if (existing.some((a) => a.name === name
|
|
36
|
+
if (existing.some((a) => a.name === name)) {
|
|
22
37
|
let suffix = 2;
|
|
23
|
-
while (existing.some((a) => a.name === `${name}-${suffix}`
|
|
38
|
+
while (existing.some((a) => a.name === `${name}-${suffix}`)) suffix++;
|
|
24
39
|
name = `${name}-${suffix}`;
|
|
25
40
|
}
|
|
26
41
|
const agent = {
|
|
@@ -130,6 +145,7 @@ export class Registry extends EventEmitter {
|
|
|
130
145
|
agent.pid = null;
|
|
131
146
|
this.agents.set(agent.id, agent);
|
|
132
147
|
}
|
|
148
|
+
this._initCounters();
|
|
133
149
|
if (agents.length > 0) {
|
|
134
150
|
this.emit('change', { changed: agents.map((a) => a.id) });
|
|
135
151
|
}
|