groove-dev 0.27.140 → 0.27.142
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/node_modules/@groove-dev/cli/package.json +1 -1
- package/node_modules/@groove-dev/daemon/integrations-registry.json +12 -44
- package/node_modules/@groove-dev/daemon/package.json +1 -1
- package/node_modules/@groove-dev/daemon/src/api.js +100 -23
- package/node_modules/@groove-dev/daemon/src/integrations.js +10 -0
- package/node_modules/@groove-dev/daemon/src/introducer.js +1 -1
- package/node_modules/@groove-dev/daemon/src/journalist.js +171 -1
- package/node_modules/@groove-dev/daemon/src/keeper.js +2 -2
- package/node_modules/@groove-dev/daemon/src/memory.js +8 -5
- package/node_modules/@groove-dev/daemon/src/model-lab.js +11 -0
- package/node_modules/@groove-dev/daemon/src/process.js +65 -0
- package/node_modules/@groove-dev/daemon/src/rotator.js +25 -8
- package/node_modules/@groove-dev/daemon/src/validate.js +8 -0
- package/node_modules/@groove-dev/gui/dist/assets/{codemirror-BQqYnZfL.js → codemirror-BYKpdS2W.js} +10 -10
- package/node_modules/@groove-dev/gui/dist/assets/index-Bjd91ufV.js +984 -0
- package/node_modules/@groove-dev/gui/dist/assets/index-BqdwIFn4.css +1 -0
- package/node_modules/@groove-dev/gui/dist/index.html +3 -3
- package/node_modules/@groove-dev/gui/package.json +1 -1
- package/node_modules/@groove-dev/gui/src/app.jsx +0 -2
- package/node_modules/@groove-dev/gui/src/components/agents/agent-chat.jsx +3 -4
- package/node_modules/@groove-dev/gui/src/components/agents/agent-feed.jsx +8 -2
- package/node_modules/@groove-dev/gui/src/components/agents/agent-file-tree.jsx +12 -8
- package/node_modules/@groove-dev/gui/src/components/agents/agent-panel.jsx +79 -5
- package/node_modules/@groove-dev/gui/src/components/agents/code-review.jsx +5 -4
- package/node_modules/@groove-dev/gui/src/components/agents/workspace-mode.jsx +109 -12
- package/node_modules/@groove-dev/gui/src/components/dashboard/context-gauges.jsx +111 -0
- package/node_modules/@groove-dev/gui/src/components/dashboard/routing-chart.jsx +70 -33
- package/node_modules/@groove-dev/gui/src/components/editor/ai-panel.jsx +77 -6
- package/node_modules/@groove-dev/gui/src/components/editor/code-editor.jsx +2 -68
- package/node_modules/@groove-dev/gui/src/components/editor/file-tree.jsx +2 -49
- package/node_modules/@groove-dev/gui/src/components/editor/terminal.jsx +15 -4
- package/node_modules/@groove-dev/gui/src/components/keeper/global-modals.jsx +10 -10
- package/node_modules/@groove-dev/gui/src/components/layout/activity-bar.jsx +1 -2
- package/node_modules/@groove-dev/gui/src/components/layout/terminal-panel.jsx +151 -3
- package/node_modules/@groove-dev/gui/src/components/marketplace/integration-wizard.jsx +223 -18
- package/node_modules/@groove-dev/gui/src/stores/groove.js +107 -29
- package/node_modules/@groove-dev/gui/src/views/agents.jsx +114 -56
- package/node_modules/@groove-dev/gui/src/views/dashboard.jsx +2 -0
- package/node_modules/@groove-dev/gui/src/views/marketplace.jsx +3 -71
- package/node_modules/@groove-dev/gui/src/views/memory.jsx +9 -9
- package/node_modules/@groove-dev/gui/src/views/model-lab.jsx +1 -6
- package/node_modules/@groove-dev/gui/src/views/models.jsx +658 -565
- package/package.json +1 -1
- package/packages/cli/package.json +1 -1
- package/packages/daemon/integrations-registry.json +12 -44
- package/packages/daemon/package.json +1 -1
- package/packages/daemon/src/api.js +100 -23
- package/packages/daemon/src/integrations.js +10 -0
- package/packages/daemon/src/introducer.js +1 -1
- package/packages/daemon/src/journalist.js +171 -1
- package/packages/daemon/src/keeper.js +2 -2
- package/packages/daemon/src/memory.js +8 -5
- package/packages/daemon/src/model-lab.js +11 -0
- package/packages/daemon/src/process.js +65 -0
- package/packages/daemon/src/rotator.js +25 -8
- package/packages/daemon/src/validate.js +8 -0
- package/packages/gui/dist/assets/{codemirror-BQqYnZfL.js → codemirror-BYKpdS2W.js} +10 -10
- package/packages/gui/dist/assets/index-Bjd91ufV.js +984 -0
- package/packages/gui/dist/assets/index-BqdwIFn4.css +1 -0
- package/packages/gui/dist/index.html +3 -3
- package/packages/gui/package.json +1 -1
- package/packages/gui/src/app.jsx +0 -2
- package/packages/gui/src/components/agents/agent-chat.jsx +3 -4
- package/packages/gui/src/components/agents/agent-feed.jsx +8 -2
- package/packages/gui/src/components/agents/agent-file-tree.jsx +12 -8
- package/packages/gui/src/components/agents/agent-panel.jsx +79 -5
- package/packages/gui/src/components/agents/code-review.jsx +5 -4
- package/packages/gui/src/components/agents/workspace-mode.jsx +109 -12
- package/packages/gui/src/components/dashboard/context-gauges.jsx +111 -0
- package/packages/gui/src/components/dashboard/routing-chart.jsx +70 -33
- package/packages/gui/src/components/editor/ai-panel.jsx +77 -6
- package/packages/gui/src/components/editor/code-editor.jsx +2 -68
- package/packages/gui/src/components/editor/file-tree.jsx +2 -49
- package/packages/gui/src/components/editor/terminal.jsx +15 -4
- package/packages/gui/src/components/keeper/global-modals.jsx +10 -10
- package/packages/gui/src/components/layout/activity-bar.jsx +1 -2
- package/packages/gui/src/components/layout/terminal-panel.jsx +151 -3
- package/packages/gui/src/components/marketplace/integration-wizard.jsx +223 -18
- package/packages/gui/src/stores/groove.js +107 -29
- package/packages/gui/src/views/agents.jsx +114 -56
- package/packages/gui/src/views/dashboard.jsx +2 -0
- package/packages/gui/src/views/marketplace.jsx +3 -71
- package/packages/gui/src/views/memory.jsx +9 -9
- package/packages/gui/src/views/model-lab.jsx +1 -6
- package/packages/gui/src/views/models.jsx +658 -565
- package/plan_files/keeper-manual.md +53 -42
- package/node_modules/@groove-dev/gui/dist/assets/index-BV9CAiw1.css +0 -1
- package/node_modules/@groove-dev/gui/dist/assets/index-DK6UIz0n.js +0 -8698
- package/node_modules/@groove-dev/gui/src/components/toys/toy-card.jsx +0 -78
- package/node_modules/@groove-dev/gui/src/components/toys/toy-creator.jsx +0 -144
- package/node_modules/@groove-dev/gui/src/components/toys/toy-launcher.jsx +0 -187
- package/node_modules/@groove-dev/gui/src/views/toys.jsx +0 -162
- package/packages/gui/dist/assets/index-BV9CAiw1.css +0 -1
- package/packages/gui/dist/assets/index-DK6UIz0n.js +0 -8698
- package/packages/gui/src/components/toys/toy-card.jsx +0 -78
- package/packages/gui/src/components/toys/toy-creator.jsx +0 -144
- package/packages/gui/src/components/toys/toy-launcher.jsx +0 -187
- package/packages/gui/src/views/toys.jsx +0 -162
|
@@ -432,10 +432,10 @@
|
|
|
432
432
|
"icon": "elevenlabs",
|
|
433
433
|
"tags": ["voice", "tts", "audio", "speech"],
|
|
434
434
|
"roles": ["creative", "cmo", "support"],
|
|
435
|
-
"npmPackage": "elevenlabs-mcp",
|
|
435
|
+
"npmPackage": "@angelogiacco/elevenlabs-mcp-server",
|
|
436
436
|
"transport": "stdio",
|
|
437
437
|
"command": "npx",
|
|
438
|
-
"args": ["-y", "elevenlabs-mcp"],
|
|
438
|
+
"args": ["-y", "@angelogiacco/elevenlabs-mcp-server"],
|
|
439
439
|
"authType": "api-key",
|
|
440
440
|
"envKeys": [
|
|
441
441
|
{ "key": "ELEVENLABS_API_KEY", "label": "API Key", "required": true }
|
|
@@ -491,10 +491,10 @@
|
|
|
491
491
|
"icon": "hubspot",
|
|
492
492
|
"tags": ["crm", "sales", "contacts", "marketing"],
|
|
493
493
|
"roles": ["cmo", "analyst", "support"],
|
|
494
|
-
"npmPackage": "hubspot-
|
|
494
|
+
"npmPackage": "@hubspot/mcp-server",
|
|
495
495
|
"transport": "stdio",
|
|
496
496
|
"command": "npx",
|
|
497
|
-
"args": ["-y", "hubspot-
|
|
497
|
+
"args": ["-y", "@hubspot/mcp-server"],
|
|
498
498
|
"authType": "api-key",
|
|
499
499
|
"envKeys": [
|
|
500
500
|
{ "key": "HUBSPOT_ACCESS_TOKEN", "label": "Private App Token", "required": true }
|
|
@@ -701,10 +701,10 @@
|
|
|
701
701
|
"icon": "mixpanel",
|
|
702
702
|
"tags": ["analytics", "events", "funnels", "users"],
|
|
703
703
|
"roles": ["analyst", "cmo", "cfo"],
|
|
704
|
-
"npmPackage": "mixpanel
|
|
704
|
+
"npmPackage": "@mercuryml/mcp-mixpanel",
|
|
705
705
|
"transport": "stdio",
|
|
706
706
|
"command": "npx",
|
|
707
|
-
"args": ["-y", "mixpanel
|
|
707
|
+
"args": ["-y", "@mercuryml/mcp-mixpanel"],
|
|
708
708
|
"authType": "api-key",
|
|
709
709
|
"envKeys": [
|
|
710
710
|
{ "key": "MIXPANEL_API_SECRET", "label": "API Secret", "required": true }
|
|
@@ -761,10 +761,10 @@
|
|
|
761
761
|
"icon": "airtable",
|
|
762
762
|
"tags": ["database", "spreadsheet", "content", "crm"],
|
|
763
763
|
"roles": ["ea", "cmo", "analyst"],
|
|
764
|
-
"npmPackage": "airtable-mcp",
|
|
764
|
+
"npmPackage": "airtable-mcp-server",
|
|
765
765
|
"transport": "stdio",
|
|
766
766
|
"command": "npx",
|
|
767
|
-
"args": ["-y", "airtable-mcp"],
|
|
767
|
+
"args": ["-y", "airtable-mcp-server"],
|
|
768
768
|
"authType": "api-key",
|
|
769
769
|
"envKeys": [
|
|
770
770
|
{ "key": "AIRTABLE_API_KEY", "label": "Personal Access Token", "required": true }
|
|
@@ -823,10 +823,10 @@
|
|
|
823
823
|
"icon": "intercom",
|
|
824
824
|
"tags": ["chat", "support", "messaging", "customers"],
|
|
825
825
|
"roles": ["support", "cmo"],
|
|
826
|
-
"npmPackage": "intercom
|
|
826
|
+
"npmPackage": "@pipeworx/mcp-intercom",
|
|
827
827
|
"transport": "stdio",
|
|
828
828
|
"command": "npx",
|
|
829
|
-
"args": ["-y", "intercom
|
|
829
|
+
"args": ["-y", "@pipeworx/mcp-intercom"],
|
|
830
830
|
"authType": "api-key",
|
|
831
831
|
"envKeys": [
|
|
832
832
|
{ "key": "INTERCOM_ACCESS_TOKEN", "label": "Access Token", "required": true }
|
|
@@ -853,10 +853,10 @@
|
|
|
853
853
|
"icon": "twilio",
|
|
854
854
|
"tags": ["sms", "voice", "phone", "whatsapp"],
|
|
855
855
|
"roles": ["ea", "support", "cmo"],
|
|
856
|
-
"npmPackage": "twilio-mcp",
|
|
856
|
+
"npmPackage": "@twilio-alpha/mcp",
|
|
857
857
|
"transport": "stdio",
|
|
858
858
|
"command": "npx",
|
|
859
|
-
"args": ["-y", "twilio-mcp"],
|
|
859
|
+
"args": ["-y", "@twilio-alpha/mcp"],
|
|
860
860
|
"authType": "api-key",
|
|
861
861
|
"envKeys": [
|
|
862
862
|
{ "key": "TWILIO_ACCOUNT_SID", "label": "Account SID", "placeholder": "AC...", "required": true },
|
|
@@ -939,37 +939,5 @@
|
|
|
939
939
|
"rating": 0,
|
|
940
940
|
"ratingCount": 0,
|
|
941
941
|
"verified": "community"
|
|
942
|
-
},
|
|
943
|
-
{
|
|
944
|
-
"id": "plaid",
|
|
945
|
-
"name": "Plaid",
|
|
946
|
-
"description": "Bank connections and financial data",
|
|
947
|
-
"category": "finance",
|
|
948
|
-
"icon": "plaid",
|
|
949
|
-
"tags": ["banking", "transactions", "finance", "accounts"],
|
|
950
|
-
"roles": ["cfo", "analyst"],
|
|
951
|
-
"npmPackage": "plaid-mcp",
|
|
952
|
-
"transport": "stdio",
|
|
953
|
-
"command": "npx",
|
|
954
|
-
"args": ["-y", "plaid-mcp"],
|
|
955
|
-
"authType": "api-key",
|
|
956
|
-
"envKeys": [
|
|
957
|
-
{ "key": "PLAID_CLIENT_ID", "label": "Client ID", "required": true },
|
|
958
|
-
{ "key": "PLAID_SECRET", "label": "Secret", "required": true },
|
|
959
|
-
{ "key": "PLAID_ENV", "label": "Environment", "placeholder": "sandbox", "required": false }
|
|
960
|
-
],
|
|
961
|
-
"setupUrl": "https://dashboard.plaid.com/team/keys",
|
|
962
|
-
"setupSteps": [
|
|
963
|
-
"Click the link below to open Plaid team keys",
|
|
964
|
-
"Copy your Client ID and Secret",
|
|
965
|
-
"Use 'sandbox' environment for testing"
|
|
966
|
-
],
|
|
967
|
-
"requiresApproval": [],
|
|
968
|
-
"agentInstructions": "## Plaid Integration\n\nYou have Plaid connected via GROOVE.\n\n### API\n`POST http://localhost:31415/api/integrations/plaid/exec`\nBody: `{\"tool\": \"<tool>\", \"params\": {...}}`\n\n### Common Tools\n- `get_accounts` — params: {access_token}\n- `get_transactions` — params: {access_token, start_date, end_date}\n- `get_balance` — params: {access_token}\n\n### Rules\n- Use sandbox environment for testing\n- Never expose access_tokens in output",
|
|
969
|
-
"featured": false,
|
|
970
|
-
"downloads": 0,
|
|
971
|
-
"rating": 0,
|
|
972
|
-
"ratingCount": 0,
|
|
973
|
-
"verified": "community"
|
|
974
942
|
}
|
|
975
943
|
]
|
|
@@ -460,13 +460,14 @@ export function createApi(app, daemon) {
|
|
|
460
460
|
// Discoveries (error → fix pairs)
|
|
461
461
|
app.get('/api/memory/discoveries', (req, res) => {
|
|
462
462
|
const role = req.query.role;
|
|
463
|
+
const teamId = req.query.teamId;
|
|
463
464
|
const limit = Math.min(parseInt(req.query.limit) || 100, 500);
|
|
464
|
-
res.json({ discoveries: daemon.memory.listDiscoveries({ role, limit }) });
|
|
465
|
+
res.json({ discoveries: daemon.memory.listDiscoveries({ role, teamId, limit }) });
|
|
465
466
|
});
|
|
466
467
|
|
|
467
468
|
app.post('/api/memory/discoveries', (req, res) => {
|
|
468
|
-
const { agentId, role, trigger, fix, outcome } = req.body || {};
|
|
469
|
-
const result = daemon.memory.addDiscovery({ agentId, role, trigger, fix, outcome });
|
|
469
|
+
const { agentId, role, trigger, fix, outcome, teamId } = req.body || {};
|
|
470
|
+
const result = daemon.memory.addDiscovery({ agentId, role, trigger, fix, outcome, teamId });
|
|
470
471
|
if (!result.added && result.error) {
|
|
471
472
|
return res.status(400).json(result);
|
|
472
473
|
}
|
|
@@ -3171,11 +3172,21 @@ Keep responses concise. Help them think, don't lecture them about the system the
|
|
|
3171
3172
|
|
|
3172
3173
|
function validateFilePath(relPath, projectDir) {
|
|
3173
3174
|
if (!relPath || typeof relPath !== 'string') return { error: 'path is required' };
|
|
3174
|
-
if (relPath.
|
|
3175
|
-
|
|
3175
|
+
if (relPath.includes('\0')) return { error: 'Invalid path' };
|
|
3176
|
+
|
|
3177
|
+
let fullPath;
|
|
3178
|
+
if (relPath.startsWith('/')) {
|
|
3179
|
+
if (relPath.includes('..')) return { error: 'Invalid path' };
|
|
3180
|
+
if (!relPath.startsWith(projectDir + '/') && relPath !== projectDir) {
|
|
3181
|
+
return { error: 'Path outside project' };
|
|
3182
|
+
}
|
|
3183
|
+
fullPath = relPath;
|
|
3184
|
+
} else {
|
|
3185
|
+
if (relPath.includes('..')) return { error: 'Invalid path' };
|
|
3186
|
+
fullPath = resolve(projectDir, relPath);
|
|
3187
|
+
if (!fullPath.startsWith(projectDir)) return { error: 'Path outside project' };
|
|
3176
3188
|
}
|
|
3177
|
-
|
|
3178
|
-
if (!fullPath.startsWith(projectDir)) return { error: 'Path outside project' };
|
|
3189
|
+
|
|
3179
3190
|
// Symlink resolution — ensure real path is also within project
|
|
3180
3191
|
try {
|
|
3181
3192
|
const realPath = realpathSync(fullPath);
|
|
@@ -3231,8 +3242,11 @@ Keep responses concise. Help them think, don't lecture them about the system the
|
|
|
3231
3242
|
const raw = readdirSync(fullPath, { withFileTypes: true });
|
|
3232
3243
|
const entries = [];
|
|
3233
3244
|
|
|
3245
|
+
const HIDDEN_DIRS = new Set(['.git', 'node_modules', '.groove', '.next', '.nuxt', '__pycache__', '.venv', 'dist', '.cache']);
|
|
3246
|
+
const HIDDEN_FILES = new Set(['.DS_Store']);
|
|
3247
|
+
|
|
3234
3248
|
const dirs = raw.filter((e) => {
|
|
3235
|
-
if (e.name
|
|
3249
|
+
if (HIDDEN_FILES.has(e.name) || HIDDEN_DIRS.has(e.name)) return false;
|
|
3236
3250
|
if (e.isDirectory()) return true;
|
|
3237
3251
|
if (e.isSymbolicLink()) {
|
|
3238
3252
|
try { return statSync(resolve(fullPath, e.name)).isDirectory(); }
|
|
@@ -3241,7 +3255,7 @@ Keep responses concise. Help them think, don't lecture them about the system the
|
|
|
3241
3255
|
return false;
|
|
3242
3256
|
}).sort((a, b) => a.name.localeCompare(b.name));
|
|
3243
3257
|
const files = raw.filter((e) => {
|
|
3244
|
-
if (e.name
|
|
3258
|
+
if (HIDDEN_FILES.has(e.name)) return false;
|
|
3245
3259
|
if (e.isFile()) return true;
|
|
3246
3260
|
if (e.isSymbolicLink()) {
|
|
3247
3261
|
try { return statSync(resolve(fullPath, e.name)).isFile(); }
|
|
@@ -6986,30 +7000,93 @@ Keep responses concise. Help them think, don't lecture them about the system the
|
|
|
6986
7000
|
app.post('/api/lab/inference', async (req, res) => {
|
|
6987
7001
|
try {
|
|
6988
7002
|
const params = validateLabInferenceParams(req.body);
|
|
7003
|
+
const rt = daemon.modelLab.getRuntime(params.runtimeId);
|
|
7004
|
+
if (!rt) throw new Error('Runtime not found');
|
|
7005
|
+
|
|
7006
|
+
const url = new URL(`${rt.endpoint}/v1/chat/completions`);
|
|
7007
|
+
const reqHeaders = { 'Content-Type': 'application/json' };
|
|
7008
|
+
if (rt.apiKey) reqHeaders['Authorization'] = `Bearer ${rt.apiKey}`;
|
|
7009
|
+
|
|
7010
|
+
const body = {
|
|
7011
|
+
model: params.model,
|
|
7012
|
+
messages: params.messages,
|
|
7013
|
+
stream: true,
|
|
7014
|
+
};
|
|
7015
|
+
const pb = params.parameters || {};
|
|
7016
|
+
if (pb.temperature !== undefined) body.temperature = pb.temperature;
|
|
7017
|
+
if (pb.top_p !== undefined) body.top_p = pb.top_p;
|
|
7018
|
+
if (pb.top_k !== undefined) body.top_k = pb.top_k;
|
|
7019
|
+
if (pb.repeat_penalty !== undefined) body.repeat_penalty = pb.repeat_penalty;
|
|
7020
|
+
if (pb.max_tokens !== undefined) body.max_tokens = pb.max_tokens;
|
|
7021
|
+
if (pb.stop !== undefined) body.stop = pb.stop;
|
|
7022
|
+
if (pb.frequency_penalty !== undefined) body.frequency_penalty = pb.frequency_penalty;
|
|
7023
|
+
if (pb.presence_penalty !== undefined) body.presence_penalty = pb.presence_penalty;
|
|
7024
|
+
|
|
7025
|
+
const payload = JSON.stringify(body);
|
|
7026
|
+
|
|
7027
|
+
// Use Node http module directly — Electron's fetch has stream issues
|
|
7028
|
+
const { request: httpRequest } = await import('http');
|
|
7029
|
+
const upstream = await new Promise((resolve, reject) => {
|
|
7030
|
+
const r = httpRequest({
|
|
7031
|
+
hostname: url.hostname,
|
|
7032
|
+
port: url.port,
|
|
7033
|
+
path: url.pathname,
|
|
7034
|
+
method: 'POST',
|
|
7035
|
+
headers: { ...reqHeaders, 'Content-Length': Buffer.byteLength(payload) },
|
|
7036
|
+
timeout: 300000,
|
|
7037
|
+
}, resolve);
|
|
7038
|
+
r.on('error', reject);
|
|
7039
|
+
r.on('timeout', () => { r.destroy(); reject(new Error('Upstream timeout')); });
|
|
7040
|
+
r.write(payload);
|
|
7041
|
+
r.end();
|
|
7042
|
+
});
|
|
7043
|
+
|
|
7044
|
+
if (upstream.statusCode !== 200) {
|
|
7045
|
+
let errMsg = `HTTP ${upstream.statusCode}`;
|
|
7046
|
+
try {
|
|
7047
|
+
const chunks = [];
|
|
7048
|
+
for await (const c of upstream) chunks.push(c);
|
|
7049
|
+
const data = JSON.parse(Buffer.concat(chunks).toString());
|
|
7050
|
+
errMsg = data.error?.message || errMsg;
|
|
7051
|
+
} catch { /* ignore */ }
|
|
7052
|
+
throw new Error(errMsg);
|
|
7053
|
+
}
|
|
6989
7054
|
|
|
7055
|
+
// Pipe raw OpenAI-compatible SSE straight to client
|
|
6990
7056
|
res.setHeader('Content-Type', 'text/event-stream');
|
|
6991
7057
|
res.setHeader('Cache-Control', 'no-cache');
|
|
6992
7058
|
res.setHeader('Connection', 'keep-alive');
|
|
6993
7059
|
res.setHeader('X-Accel-Buffering', 'no');
|
|
6994
|
-
|
|
6995
|
-
|
|
6996
|
-
|
|
6997
|
-
|
|
6998
|
-
|
|
6999
|
-
|
|
7000
|
-
|
|
7001
|
-
|
|
7002
|
-
|
|
7003
|
-
|
|
7004
|
-
|
|
7005
|
-
|
|
7060
|
+
upstream.pipe(res);
|
|
7061
|
+
|
|
7062
|
+
// Collect content for session persistence
|
|
7063
|
+
if (params.sessionId) {
|
|
7064
|
+
let full = '';
|
|
7065
|
+
upstream.on('data', (chunk) => {
|
|
7066
|
+
const text = chunk.toString('utf8');
|
|
7067
|
+
for (const line of text.split('\n')) {
|
|
7068
|
+
const trimmed = line.trim();
|
|
7069
|
+
if (!trimmed.startsWith('data: ')) continue;
|
|
7070
|
+
const d = trimmed.slice(6);
|
|
7071
|
+
if (d === '[DONE]') continue;
|
|
7072
|
+
try {
|
|
7073
|
+
const p = JSON.parse(d);
|
|
7074
|
+
const c = p.choices?.[0]?.delta?.content;
|
|
7075
|
+
if (c) full += c;
|
|
7076
|
+
} catch { /* skip */ }
|
|
7077
|
+
}
|
|
7078
|
+
});
|
|
7079
|
+
upstream.on('end', () => {
|
|
7080
|
+
if (full) daemon.modelLab._appendToSession(params.sessionId, params.messages, { role: 'assistant', content: full });
|
|
7081
|
+
});
|
|
7006
7082
|
}
|
|
7083
|
+
|
|
7084
|
+
req.on('close', () => { upstream.destroy(); });
|
|
7007
7085
|
} catch (err) {
|
|
7008
7086
|
if (!res.headersSent) {
|
|
7009
7087
|
res.status(400).json({ error: err.message });
|
|
7010
7088
|
} else {
|
|
7011
|
-
res.
|
|
7012
|
-
res.end();
|
|
7089
|
+
try { res.end(); } catch { /* ignore */ }
|
|
7013
7090
|
}
|
|
7014
7091
|
}
|
|
7015
7092
|
});
|
|
@@ -127,6 +127,16 @@ export class IntegrationStore {
|
|
|
127
127
|
if (this._isInstalled(integrationId)) throw new Error(`Integration already installed: ${integrationId}`);
|
|
128
128
|
|
|
129
129
|
if (entry.npmPackage) {
|
|
130
|
+
// Pre-validate: check the package exists on npm before attempting install
|
|
131
|
+
try {
|
|
132
|
+
execFileSync('npm', ['view', entry.npmPackage, 'version'], {
|
|
133
|
+
stdio: 'pipe',
|
|
134
|
+
timeout: 10_000,
|
|
135
|
+
});
|
|
136
|
+
} catch {
|
|
137
|
+
throw new Error(`Package ${entry.npmPackage} is not available on npm. Use the agent-assisted install flow instead.`);
|
|
138
|
+
}
|
|
139
|
+
|
|
130
140
|
try {
|
|
131
141
|
execFileSync('npm', ['install', '--legacy-peer-deps', entry.npmPackage], {
|
|
132
142
|
cwd: this.integrationsDir,
|
|
@@ -437,7 +437,7 @@ export class Introducer {
|
|
|
437
437
|
}
|
|
438
438
|
|
|
439
439
|
if (!isLightPlanner && (hasTask || isRotation)) {
|
|
440
|
-
const discoveries = this.daemon.memory.getDiscoveriesMarkdown(newAgent.role, 8, 600, newAgent.scope);
|
|
440
|
+
const discoveries = this.daemon.memory.getDiscoveriesMarkdown(newAgent.role, 8, 600, newAgent.scope, newAgent.teamId);
|
|
441
441
|
if (discoveries) {
|
|
442
442
|
parts.push(`### Known Fixes for ${newAgent.role} Role\n${discoveries}`);
|
|
443
443
|
}
|
|
@@ -877,7 +877,7 @@ export class Journalist {
|
|
|
877
877
|
const entries = agentLog?.entries || [];
|
|
878
878
|
|
|
879
879
|
// Layer 7 memory: discoveries (inline, not pointer — agents lose context with pointers), constraints, specializations
|
|
880
|
-
const discoveries = this.daemon.memory?.getDiscoveriesMarkdown(agent.role, 10, 1500) || '';
|
|
880
|
+
const discoveries = this.daemon.memory?.getDiscoveriesMarkdown(agent.role, 10, 1500, agent.scope, agent.teamId) || '';
|
|
881
881
|
const constraints = this.daemon.memory?.getConstraintsMarkdown(2000) || '';
|
|
882
882
|
const specialization = this.daemon.memory?.getSpecialization(agent.id);
|
|
883
883
|
const specLine = specialization?.avgQualityScore != null
|
|
@@ -994,6 +994,175 @@ export class Journalist {
|
|
|
994
994
|
return brief;
|
|
995
995
|
}
|
|
996
996
|
|
|
997
|
+
// --- Conversation Thread Extraction (for idle resume) ---
|
|
998
|
+
|
|
999
|
+
/**
|
|
1000
|
+
* Extract the actual user↔assistant conversation from stream-json logs.
|
|
1001
|
+
* Returns the dialogue in chronological order — user messages interleaved
|
|
1002
|
+
* with Claude's text responses. This preserves the "why" context that
|
|
1003
|
+
* handoff briefs lose through summarization.
|
|
1004
|
+
*
|
|
1005
|
+
* Budget: keeps recent turns verbatim, summarizes oldest if over maxChars.
|
|
1006
|
+
*/
|
|
1007
|
+
extractConversationThread(agent, { maxChars = 60000 } = {}) {
|
|
1008
|
+
const logPath = resolve(this.daemon.grooveDir, 'logs', `${agent.name}.log`);
|
|
1009
|
+
if (!existsSync(logPath)) return null;
|
|
1010
|
+
|
|
1011
|
+
let content;
|
|
1012
|
+
try {
|
|
1013
|
+
content = readFileSync(logPath, 'utf8');
|
|
1014
|
+
} catch { return null; }
|
|
1015
|
+
|
|
1016
|
+
const lines = content.split('\n');
|
|
1017
|
+
const turns = []; // { role: 'user'|'assistant', text, timestamp }
|
|
1018
|
+
|
|
1019
|
+
for (const line of lines) {
|
|
1020
|
+
if (!line.trim() || line.startsWith('[')) continue;
|
|
1021
|
+
try {
|
|
1022
|
+
const data = JSON.parse(line);
|
|
1023
|
+
|
|
1024
|
+
// User messages
|
|
1025
|
+
if (data.type === 'user' && data.message?.content) {
|
|
1026
|
+
const msgContent = data.message.content;
|
|
1027
|
+
let text = '';
|
|
1028
|
+
if (typeof msgContent === 'string') {
|
|
1029
|
+
text = msgContent;
|
|
1030
|
+
} else if (Array.isArray(msgContent)) {
|
|
1031
|
+
// Extract text blocks, skip tool_result blocks (noise)
|
|
1032
|
+
text = msgContent
|
|
1033
|
+
.filter((b) => b.type === 'text' && b.text)
|
|
1034
|
+
.map((b) => b.text)
|
|
1035
|
+
.join('\n');
|
|
1036
|
+
}
|
|
1037
|
+
if (text.trim().length > 5) {
|
|
1038
|
+
turns.push({ role: 'user', text: text.trim(), timestamp: data.timestamp });
|
|
1039
|
+
}
|
|
1040
|
+
}
|
|
1041
|
+
|
|
1042
|
+
// Assistant text responses (what Claude said — the reasoning/explanations)
|
|
1043
|
+
if (data.type === 'assistant' && data.message?.content) {
|
|
1044
|
+
const blocks = Array.isArray(data.message.content) ? data.message.content : [];
|
|
1045
|
+
const textParts = blocks
|
|
1046
|
+
.filter((b) => b.type === 'text' && b.text && b.text.trim().length > 20)
|
|
1047
|
+
.map((b) => b.text.trim());
|
|
1048
|
+
if (textParts.length > 0) {
|
|
1049
|
+
turns.push({ role: 'assistant', text: textParts.join('\n'), timestamp: data.timestamp });
|
|
1050
|
+
}
|
|
1051
|
+
}
|
|
1052
|
+
} catch { /* skip non-JSON */ }
|
|
1053
|
+
}
|
|
1054
|
+
|
|
1055
|
+
if (turns.length === 0) return null;
|
|
1056
|
+
|
|
1057
|
+
// Deduplicate consecutive same-role turns (merge them)
|
|
1058
|
+
const merged = [];
|
|
1059
|
+
for (const turn of turns) {
|
|
1060
|
+
const last = merged[merged.length - 1];
|
|
1061
|
+
if (last && last.role === turn.role) {
|
|
1062
|
+
last.text += '\n' + turn.text;
|
|
1063
|
+
} else {
|
|
1064
|
+
merged.push({ ...turn });
|
|
1065
|
+
}
|
|
1066
|
+
}
|
|
1067
|
+
|
|
1068
|
+
// Build the thread — keep recent turns verbatim, truncate old ones if over budget
|
|
1069
|
+
let thread = '';
|
|
1070
|
+
const formatted = merged.map((t) => {
|
|
1071
|
+
const label = t.role === 'user' ? 'USER' : 'CLAUDE';
|
|
1072
|
+
return `[${label}]:\n${t.text}`;
|
|
1073
|
+
});
|
|
1074
|
+
|
|
1075
|
+
// Start from the end (most recent) and work backwards to fill budget
|
|
1076
|
+
const recentFirst = [...formatted].reverse();
|
|
1077
|
+
const kept = [];
|
|
1078
|
+
let totalLen = 0;
|
|
1079
|
+
|
|
1080
|
+
for (const entry of recentFirst) {
|
|
1081
|
+
if (totalLen + entry.length > maxChars) {
|
|
1082
|
+
// Truncate this entry to fit remaining budget
|
|
1083
|
+
const remaining = maxChars - totalLen;
|
|
1084
|
+
if (remaining > 200) {
|
|
1085
|
+
kept.push(entry.slice(0, remaining) + '\n[...truncated]');
|
|
1086
|
+
}
|
|
1087
|
+
break;
|
|
1088
|
+
}
|
|
1089
|
+
kept.push(entry);
|
|
1090
|
+
totalLen += entry.length;
|
|
1091
|
+
}
|
|
1092
|
+
|
|
1093
|
+
// Reverse back to chronological order
|
|
1094
|
+
kept.reverse();
|
|
1095
|
+
thread = kept.join('\n\n---\n\n');
|
|
1096
|
+
|
|
1097
|
+
return thread;
|
|
1098
|
+
}
|
|
1099
|
+
|
|
1100
|
+
/**
|
|
1101
|
+
* Build a full context-resume prompt that preserves the conversation
|
|
1102
|
+
* thread so a fresh agent picks up where the previous session left off.
|
|
1103
|
+
*/
|
|
1104
|
+
buildConversationResumePrompt(agent, userMessage) {
|
|
1105
|
+
const thread = this.extractConversationThread(agent);
|
|
1106
|
+
if (!thread) return null;
|
|
1107
|
+
|
|
1108
|
+
const constraints = this.daemon.memory?.getConstraintsMarkdown(2000) || '';
|
|
1109
|
+
const discoveries = this.daemon.memory?.getDiscoveriesMarkdown(agent.role, 5, 1000, agent.scope, agent.teamId) || '';
|
|
1110
|
+
|
|
1111
|
+
let prompt = [
|
|
1112
|
+
`# Session Context Resume`,
|
|
1113
|
+
``,
|
|
1114
|
+
`You are continuing a session that went idle. Below is the full conversation`,
|
|
1115
|
+
`from your previous session — your actual exchanges with the user. Pick up`,
|
|
1116
|
+
`exactly where you left off. The user's new message follows at the end.`,
|
|
1117
|
+
``,
|
|
1118
|
+
`Role: ${agent.role} | Provider: ${agent.provider} | Scope: ${agent.scope?.join(', ') || 'unrestricted'}`,
|
|
1119
|
+
agent.workingDir ? `Working directory: ${agent.workingDir}` : '',
|
|
1120
|
+
``,
|
|
1121
|
+
constraints ? `## Project Constraints\n\n${constraints}\n` : '',
|
|
1122
|
+
discoveries ? `## Known Issues & Fixes\n\n${discoveries}\n` : '',
|
|
1123
|
+
`## Previous Conversation\n\n${thread}`,
|
|
1124
|
+
``,
|
|
1125
|
+
`---`,
|
|
1126
|
+
``,
|
|
1127
|
+
`## New Message From User`,
|
|
1128
|
+
``,
|
|
1129
|
+
userMessage,
|
|
1130
|
+
``,
|
|
1131
|
+
`Continue seamlessly from the conversation above. You have the full context of what was discussed, what was tried, what worked and what didn't. Do not ask the user to repeat anything.`,
|
|
1132
|
+
].filter(Boolean).join('\n');
|
|
1133
|
+
|
|
1134
|
+
// Hard cap at 80K chars (~20K tokens) to leave plenty of room in context window
|
|
1135
|
+
if (prompt.length > 80000) {
|
|
1136
|
+
// Re-extract with smaller budget and rebuild
|
|
1137
|
+
const smallerThread = this.extractConversationThread(agent, { maxChars: 40000 });
|
|
1138
|
+
if (smallerThread) {
|
|
1139
|
+
prompt = [
|
|
1140
|
+
`# Session Context Resume`,
|
|
1141
|
+
``,
|
|
1142
|
+
`You are continuing a session that went idle. Below is the conversation`,
|
|
1143
|
+
`from your previous session (older turns summarized to fit). Pick up`,
|
|
1144
|
+
`exactly where you left off.`,
|
|
1145
|
+
``,
|
|
1146
|
+
`Role: ${agent.role} | Scope: ${agent.scope?.join(', ') || 'unrestricted'}`,
|
|
1147
|
+
agent.workingDir ? `Working directory: ${agent.workingDir}` : '',
|
|
1148
|
+
``,
|
|
1149
|
+
constraints ? `## Project Constraints\n\n${constraints}\n` : '',
|
|
1150
|
+
`## Previous Conversation\n\n${smallerThread}`,
|
|
1151
|
+
``,
|
|
1152
|
+
`---`,
|
|
1153
|
+
``,
|
|
1154
|
+
`## New Message From User`,
|
|
1155
|
+
``,
|
|
1156
|
+
userMessage,
|
|
1157
|
+
``,
|
|
1158
|
+
`Continue seamlessly. Do not ask the user to repeat anything.`,
|
|
1159
|
+
].filter(Boolean).join('\n');
|
|
1160
|
+
}
|
|
1161
|
+
}
|
|
1162
|
+
|
|
1163
|
+
return prompt;
|
|
1164
|
+
}
|
|
1165
|
+
|
|
997
1166
|
// --- Workspace Grouping ---
|
|
998
1167
|
|
|
999
1168
|
/**
|
|
@@ -1183,6 +1352,7 @@ export class Journalist {
|
|
|
1183
1352
|
this.daemon.memory.addDiscovery({
|
|
1184
1353
|
agentId: agent.id,
|
|
1185
1354
|
role: agent.role,
|
|
1355
|
+
teamId: agent.teamId || null,
|
|
1186
1356
|
trigger: trigger.slice(0, 300),
|
|
1187
1357
|
fix: fix.slice(0, 500),
|
|
1188
1358
|
outcome: 'success',
|
|
@@ -254,10 +254,10 @@ export class Keeper {
|
|
|
254
254
|
// ── Command parser ────────────────────────────────────────
|
|
255
255
|
|
|
256
256
|
static parseCommand(text) {
|
|
257
|
-
const cmdMatch = text.match(
|
|
257
|
+
const cmdMatch = text.match(/\[(save|append|update|delete|view|doc|link|read|instruct)\]/i);
|
|
258
258
|
if (!cmdMatch) return null;
|
|
259
259
|
const command = cmdMatch[1].toLowerCase();
|
|
260
|
-
const rest = text.slice(cmdMatch[0].length).trim();
|
|
260
|
+
const rest = text.slice(cmdMatch.index + cmdMatch[0].length).trim();
|
|
261
261
|
|
|
262
262
|
if (command === 'instruct') {
|
|
263
263
|
return { command, tags: [], extra: null };
|
|
@@ -261,7 +261,7 @@ export class MemoryStore {
|
|
|
261
261
|
|
|
262
262
|
// --- Discoveries (error → fix pairs) ---
|
|
263
263
|
|
|
264
|
-
addDiscovery({ agentId, role, trigger, fix, outcome = 'success' }) {
|
|
264
|
+
addDiscovery({ agentId, role, trigger, fix, outcome = 'success', teamId }) {
|
|
265
265
|
if (!trigger || !fix) return { added: false, error: 'trigger and fix required' };
|
|
266
266
|
if (outcome !== 'success') return { added: false, reason: 'only successes stored' };
|
|
267
267
|
|
|
@@ -277,6 +277,7 @@ export class MemoryStore {
|
|
|
277
277
|
ts: new Date().toISOString(),
|
|
278
278
|
agentId: agentId || null,
|
|
279
279
|
role: role || 'unknown',
|
|
280
|
+
teamId: teamId || null,
|
|
280
281
|
trigger: truncate(String(trigger).trim(), 300),
|
|
281
282
|
fix: truncate(String(fix).trim(), 500),
|
|
282
283
|
outcome,
|
|
@@ -298,7 +299,7 @@ export class MemoryStore {
|
|
|
298
299
|
}
|
|
299
300
|
}
|
|
300
301
|
|
|
301
|
-
listDiscoveries({ role, limit = 100 } = {}) {
|
|
302
|
+
listDiscoveries({ role, teamId, limit = 100 } = {}) {
|
|
302
303
|
if (!existsSync(this.discoveriesPath)) return [];
|
|
303
304
|
try {
|
|
304
305
|
const lines = readFileSync(this.discoveriesPath, 'utf8').split('\n').filter(Boolean);
|
|
@@ -306,7 +307,9 @@ export class MemoryStore {
|
|
|
306
307
|
for (const line of lines) {
|
|
307
308
|
try {
|
|
308
309
|
const e = JSON.parse(line);
|
|
309
|
-
if (
|
|
310
|
+
if (role && e.role !== role) continue;
|
|
311
|
+
if (teamId && e.teamId && e.teamId !== teamId) continue;
|
|
312
|
+
entries.push(e);
|
|
310
313
|
} catch { /* skip malformed */ }
|
|
311
314
|
}
|
|
312
315
|
return entries.slice(-limit).reverse(); // newest first
|
|
@@ -328,8 +331,8 @@ export class MemoryStore {
|
|
|
328
331
|
} catch { /* best-effort */ }
|
|
329
332
|
}
|
|
330
333
|
|
|
331
|
-
getDiscoveriesMarkdown(role, limit = 20, maxChars = 4000, scope) {
|
|
332
|
-
let entries = this.listDiscoveries({ role, limit: limit * 3 });
|
|
334
|
+
getDiscoveriesMarkdown(role, limit = 20, maxChars = 4000, scope, teamId) {
|
|
335
|
+
let entries = this.listDiscoveries({ role, teamId, limit: limit * 3 });
|
|
333
336
|
if (entries.length === 0) return '';
|
|
334
337
|
|
|
335
338
|
if (scope && Array.isArray(scope) && scope.length > 0) {
|
|
@@ -95,6 +95,17 @@ export class ModelLab {
|
|
|
95
95
|
removeRuntime(id) {
|
|
96
96
|
const rt = this.runtimes.get(id);
|
|
97
97
|
if (!rt) return null;
|
|
98
|
+
|
|
99
|
+
// Stop the llama-server process if this is a local model runtime
|
|
100
|
+
if (rt._localModelId) {
|
|
101
|
+
const mm = this.daemon.modelManager;
|
|
102
|
+
const ls = this.daemon.llamaServer;
|
|
103
|
+
if (mm && ls) {
|
|
104
|
+
const modelPath = mm.getModelPath(rt._localModelId);
|
|
105
|
+
if (modelPath) ls.stopServer(modelPath).catch(() => {});
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
98
109
|
this.runtimes.delete(id);
|
|
99
110
|
this._saveRuntimes();
|
|
100
111
|
this.daemon.broadcast({ type: 'lab:runtime:removed', data: { id } });
|