groove-dev 0.27.134 → 0.27.136
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/moe-training/client/domain-tagger.js +1 -1
- package/moe-training/scripts/retag-delegate-yield.js +303 -0
- package/moe-training/test/shared/envelope-schema.test.js +3 -3
- 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/adaptive.js +77 -0
- package/node_modules/@groove-dev/daemon/src/api.js +35 -5
- package/node_modules/@groove-dev/daemon/src/journalist.js +28 -12
- package/node_modules/@groove-dev/daemon/src/model-lab.js +53 -76
- package/node_modules/@groove-dev/daemon/src/process.js +91 -2
- package/node_modules/@groove-dev/daemon/src/rotator.js +45 -3
- package/node_modules/@groove-dev/gui/dist/assets/{index-Dozp69tK.js → index-BrZHF7pK.js} +1770 -1766
- package/node_modules/@groove-dev/gui/dist/assets/index-DIfiwdKl.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/components/agents/agent-chat.jsx +60 -18
- package/node_modules/@groove-dev/gui/src/components/agents/agent-feed.jsx +42 -20
- package/node_modules/@groove-dev/gui/src/components/agents/agent-file-tree.jsx +1 -1
- package/node_modules/@groove-dev/gui/src/components/agents/workspace-mode.jsx +1 -1
- package/node_modules/@groove-dev/gui/src/components/chat/chat-messages.jsx +2 -22
- package/node_modules/@groove-dev/gui/src/components/editor/code-editor.jsx +9 -9
- package/node_modules/@groove-dev/gui/src/components/editor/file-tree.jsx +1 -1
- package/node_modules/@groove-dev/gui/src/components/editor/terminal.jsx +7 -0
- package/node_modules/@groove-dev/gui/src/components/lab/chat-playground.jsx +59 -51
- package/node_modules/@groove-dev/gui/src/components/lab/lab-assistant.jsx +48 -48
- package/node_modules/@groove-dev/gui/src/components/lab/metrics-panel.jsx +39 -38
- package/node_modules/@groove-dev/gui/src/components/lab/parameter-panel.jsx +4 -5
- package/node_modules/@groove-dev/gui/src/components/lab/preset-manager.jsx +11 -11
- package/node_modules/@groove-dev/gui/src/components/lab/runtime-config.jsx +66 -62
- package/node_modules/@groove-dev/gui/src/components/lab/system-prompt-editor.jsx +13 -13
- package/node_modules/@groove-dev/gui/src/components/layout/breadcrumb-bar.jsx +1 -1
- package/node_modules/@groove-dev/gui/src/components/preview/preview-workspace.jsx +62 -22
- package/node_modules/@groove-dev/gui/src/components/ui/slider.jsx +16 -17
- package/node_modules/@groove-dev/gui/src/components/ui/table-tree.jsx +38 -0
- package/node_modules/@groove-dev/gui/src/stores/groove.js +23 -9
- package/node_modules/@groove-dev/gui/src/views/editor.jsx +1 -1
- package/node_modules/@groove-dev/gui/src/views/model-lab.jsx +101 -87
- package/node_modules/moe-training/client/domain-tagger.js +1 -1
- package/node_modules/moe-training/scripts/retag-delegate-yield.js +303 -0
- package/node_modules/moe-training/test/shared/envelope-schema.test.js +3 -3
- package/package.json +1 -1
- package/packages/cli/package.json +1 -1
- package/packages/daemon/package.json +1 -1
- package/packages/daemon/src/adaptive.js +77 -0
- package/packages/daemon/src/api.js +35 -5
- package/packages/daemon/src/journalist.js +28 -12
- package/packages/daemon/src/model-lab.js +53 -76
- package/packages/daemon/src/process.js +91 -2
- package/packages/daemon/src/rotator.js +45 -3
- package/packages/gui/dist/assets/{index-Dozp69tK.js → index-BrZHF7pK.js} +1770 -1766
- package/packages/gui/dist/assets/index-DIfiwdKl.css +1 -0
- package/packages/gui/dist/index.html +2 -2
- package/packages/gui/package.json +1 -1
- package/packages/gui/src/components/agents/agent-chat.jsx +60 -18
- package/packages/gui/src/components/agents/agent-feed.jsx +42 -20
- package/packages/gui/src/components/agents/agent-file-tree.jsx +1 -1
- package/packages/gui/src/components/agents/workspace-mode.jsx +1 -1
- package/packages/gui/src/components/chat/chat-messages.jsx +2 -22
- package/packages/gui/src/components/editor/code-editor.jsx +9 -9
- package/packages/gui/src/components/editor/file-tree.jsx +1 -1
- package/packages/gui/src/components/editor/terminal.jsx +7 -0
- package/packages/gui/src/components/lab/chat-playground.jsx +59 -51
- package/packages/gui/src/components/lab/lab-assistant.jsx +48 -48
- package/packages/gui/src/components/lab/metrics-panel.jsx +39 -38
- package/packages/gui/src/components/lab/parameter-panel.jsx +4 -5
- package/packages/gui/src/components/lab/preset-manager.jsx +11 -11
- package/packages/gui/src/components/lab/runtime-config.jsx +66 -62
- package/packages/gui/src/components/lab/system-prompt-editor.jsx +13 -13
- package/packages/gui/src/components/layout/breadcrumb-bar.jsx +1 -1
- package/packages/gui/src/components/preview/preview-workspace.jsx +62 -22
- package/packages/gui/src/components/ui/slider.jsx +16 -17
- package/packages/gui/src/components/ui/table-tree.jsx +38 -0
- package/packages/gui/src/stores/groove.js +23 -9
- package/packages/gui/src/views/editor.jsx +1 -1
- package/packages/gui/src/views/model-lab.jsx +101 -87
- package/plan_files/DELEGATE_YIELD_TRAINING_TAGS.md +135 -0
- package/plan_files/session-quality-rotation-fixes.md +218 -0
- package/test.py +571 -0
- package/node_modules/@groove-dev/gui/dist/assets/index-BgQL4bNl.css +0 -1
- package/packages/gui/dist/assets/index-BgQL4bNl.css +0 -1
- /package/{AGENT_ORCHESTRATION.md → plan_files/AGENT_ORCHESTRATION.md} +0 -0
- /package/{DYNAMIC_LEAF_ARCH.md → plan_files/DYNAMIC_LEAF_ARCH.md} +0 -0
- /package/{EMBEDDING_DIAGNOSTIC.md → plan_files/EMBEDDING_DIAGNOSTIC.md} +0 -0
- /package/{EMBEDDING_SERVICE_BUILD_PLAN.md → plan_files/EMBEDDING_SERVICE_BUILD_PLAN.md} +0 -0
- /package/{MOE_TRAINING_PIPELINE.md → plan_files/MOE_TRAINING_PIPELINE.md} +0 -0
|
@@ -0,0 +1,303 @@
|
|
|
1
|
+
// FSL-1.1-Apache-2.0 — see LICENSE
|
|
2
|
+
//
|
|
3
|
+
// Retroactive tagger: scans planner/fullstack session envelopes and re-tags
|
|
4
|
+
// delegation dispatches as "delegate" steps and artifact handoffs as "yield" steps.
|
|
5
|
+
//
|
|
6
|
+
// Usage:
|
|
7
|
+
// node retag-delegate-yield.js <input.jsonl> [output.jsonl]
|
|
8
|
+
//
|
|
9
|
+
// If output is omitted, writes to stdout. Input can also be piped via stdin.
|
|
10
|
+
// Only modifies planner/fullstack trajectory envelopes — SESSION_CLOSE and
|
|
11
|
+
// USER_FEEDBACK envelopes pass through unchanged.
|
|
12
|
+
|
|
13
|
+
import { createReadStream, createWriteStream, existsSync } from 'fs';
|
|
14
|
+
import { createInterface } from 'readline';
|
|
15
|
+
|
|
16
|
+
// --- Pattern A: Delegation detection ---
|
|
17
|
+
// A thought step reasoning about needing a specialist, followed by an action
|
|
18
|
+
// that dispatches to another agent. The dispatch action+observation get replaced
|
|
19
|
+
// with a single delegate step.
|
|
20
|
+
|
|
21
|
+
const DELEGATION_THOUGHT_RE = /\b(specialist|dispatch|delegate|hand off|route to|needs? a .*(backend|frontend|fullstack|database|devops|planner)|re-route|different agent|another agent|pass this to)\b/i;
|
|
22
|
+
|
|
23
|
+
const DELEGATION_ACTION_RE = /\b(dispatch|spawn|agent|delegate|hand off|route|assign)\b/i;
|
|
24
|
+
|
|
25
|
+
const AGENT_ID_RE = /\b(backend|frontend|fullstack|planner|devops|database|chat|advisor|qc)[-_]?\d+\b/gi;
|
|
26
|
+
|
|
27
|
+
function isDelegationThought(step) {
|
|
28
|
+
if (step.type !== 'thought') return false;
|
|
29
|
+
return DELEGATION_THOUGHT_RE.test(step.content || '');
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function isDelegationAction(step) {
|
|
33
|
+
if (step.type !== 'action') return false;
|
|
34
|
+
const tool = (step.tool || '').toLowerCase();
|
|
35
|
+
const content = (step.content || '').toLowerCase();
|
|
36
|
+
if (tool === 'agent' || tool === 'dispatch') return true;
|
|
37
|
+
if (DELEGATION_ACTION_RE.test(content)) return true;
|
|
38
|
+
return false;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function extractDelegateTask(actionStep, observationStep) {
|
|
42
|
+
let task = '';
|
|
43
|
+
const content = actionStep.content || '';
|
|
44
|
+
const args = actionStep.arguments || {};
|
|
45
|
+
|
|
46
|
+
// Try to extract the task from arguments (e.g. Agent tool input)
|
|
47
|
+
if (args.prompt) {
|
|
48
|
+
task = args.prompt;
|
|
49
|
+
} else if (args.message) {
|
|
50
|
+
task = args.message;
|
|
51
|
+
} else if (args.task) {
|
|
52
|
+
task = args.task;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// Fall back to parsing the content after "Dispatch to X:" or similar
|
|
56
|
+
if (!task) {
|
|
57
|
+
const match = content.match(/(?:dispatch(?:ed)? to \S+:\s*|:\s*)(.*)/i);
|
|
58
|
+
task = match ? match[1] : content;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// Strip agent ID references — the router picks the target, not the delegator
|
|
62
|
+
task = task.replace(AGENT_ID_RE, '').replace(/\s{2,}/g, ' ').trim();
|
|
63
|
+
|
|
64
|
+
return task;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// --- Pattern B: Yield detection ---
|
|
68
|
+
// An action(Write/Edit) producing a file, followed by observation(success),
|
|
69
|
+
// followed by a resolution whose content suggests artifact handoff to another agent.
|
|
70
|
+
|
|
71
|
+
const WRITE_TOOLS = new Set(['write', 'edit', 'create', 'save']);
|
|
72
|
+
|
|
73
|
+
const YIELD_RESOLUTION_RE = /\b(next agent|can now|build on this|picks? up|hand(?:ed|s|ing)? off|artifact|ready for|phase complete|my part is done|produced|output for)\b/i;
|
|
74
|
+
|
|
75
|
+
function isWriteAction(step) {
|
|
76
|
+
if (step.type !== 'action') return false;
|
|
77
|
+
const tool = (step.tool || '').toLowerCase();
|
|
78
|
+
return WRITE_TOOLS.has(tool);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
function isSuccessObservation(step) {
|
|
82
|
+
if (step.type !== 'observation') return false;
|
|
83
|
+
const c = (step.content || '').toLowerCase();
|
|
84
|
+
if (step.is_error) return false;
|
|
85
|
+
return !c.includes('error') || c.includes('0 error');
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
function isYieldResolution(step) {
|
|
89
|
+
if (step.type !== 'resolution') return false;
|
|
90
|
+
return YIELD_RESOLUTION_RE.test(step.content || '');
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
function extractFilePath(writeStep) {
|
|
94
|
+
const args = writeStep.arguments || {};
|
|
95
|
+
if (args.file_path) return args.file_path;
|
|
96
|
+
if (args.path) return args.path;
|
|
97
|
+
|
|
98
|
+
// Try to parse path from content
|
|
99
|
+
const content = writeStep.content || '';
|
|
100
|
+
const match = content.match(/(?:Writing|Wrote|Created?|Saving?|Edit(?:ing|ed)?)\s+(\S+\.\w+)/i);
|
|
101
|
+
return match ? match[1] : null;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
function buildYieldSummary(resolutionStep, maxTokens = 20) {
|
|
105
|
+
const content = (resolutionStep.content || '').trim();
|
|
106
|
+
// Take first sentence, cap at ~80 chars for ~20 tokens
|
|
107
|
+
const firstSentence = content.split(/[.!?\n]/)[0].trim();
|
|
108
|
+
return firstSentence.slice(0, 80);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// --- Main retagging logic ---
|
|
112
|
+
|
|
113
|
+
function retagTrajectory(steps) {
|
|
114
|
+
if (!Array.isArray(steps) || steps.length < 2) return { steps, delegateCount: 0, yieldCount: 0 };
|
|
115
|
+
|
|
116
|
+
const result = [];
|
|
117
|
+
let delegateCount = 0;
|
|
118
|
+
let yieldCount = 0;
|
|
119
|
+
let i = 0;
|
|
120
|
+
|
|
121
|
+
while (i < steps.length) {
|
|
122
|
+
// Pattern A: thought(delegation) → action(dispatch) → observation → ...
|
|
123
|
+
// Replace action+observation with a single delegate step
|
|
124
|
+
if (i + 2 < steps.length &&
|
|
125
|
+
isDelegationThought(steps[i]) &&
|
|
126
|
+
isDelegationAction(steps[i + 1])) {
|
|
127
|
+
|
|
128
|
+
// Keep the thought
|
|
129
|
+
result.push({ ...steps[i] });
|
|
130
|
+
|
|
131
|
+
const actionStep = steps[i + 1];
|
|
132
|
+
const nextStep = steps[i + 2];
|
|
133
|
+
const obsStep = nextStep.type === 'observation' ? nextStep : null;
|
|
134
|
+
|
|
135
|
+
const task = extractDelegateTask(actionStep, obsStep);
|
|
136
|
+
|
|
137
|
+
if (task) {
|
|
138
|
+
result.push({
|
|
139
|
+
step: actionStep.step,
|
|
140
|
+
type: 'delegate',
|
|
141
|
+
content: task,
|
|
142
|
+
timestamp: actionStep.timestamp,
|
|
143
|
+
token_count: Math.max(1, Math.ceil(task.length / 4)),
|
|
144
|
+
});
|
|
145
|
+
delegateCount++;
|
|
146
|
+
|
|
147
|
+
// Skip the action and observation
|
|
148
|
+
i += obsStep ? 3 : 2;
|
|
149
|
+
|
|
150
|
+
// If the next step is a resolution that just confirms dispatch, skip it too
|
|
151
|
+
if (i < steps.length && steps[i].type === 'resolution') {
|
|
152
|
+
const rc = (steps[i].content || '').toLowerCase();
|
|
153
|
+
if (rc.includes('dispatch') || rc.includes('delegat') || rc.includes('handed off') || rc.length < 50) {
|
|
154
|
+
i++;
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
continue;
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
// Pattern B: action(Write/Edit) → observation(success) → resolution(handoff)
|
|
162
|
+
// Replace resolution with yield
|
|
163
|
+
if (i + 2 < steps.length &&
|
|
164
|
+
isWriteAction(steps[i]) &&
|
|
165
|
+
isSuccessObservation(steps[i + 1]) &&
|
|
166
|
+
isYieldResolution(steps[i + 2])) {
|
|
167
|
+
|
|
168
|
+
// Keep the action and observation as-is
|
|
169
|
+
result.push({ ...steps[i] });
|
|
170
|
+
result.push({ ...steps[i + 1] });
|
|
171
|
+
|
|
172
|
+
const writeStep = steps[i];
|
|
173
|
+
const resStep = steps[i + 2];
|
|
174
|
+
const path = extractFilePath(writeStep);
|
|
175
|
+
const summary = buildYieldSummary(resStep);
|
|
176
|
+
|
|
177
|
+
const yieldStep = {
|
|
178
|
+
step: resStep.step,
|
|
179
|
+
type: 'yield',
|
|
180
|
+
content: summary,
|
|
181
|
+
timestamp: resStep.timestamp,
|
|
182
|
+
token_count: Math.max(1, Math.ceil(summary.length / 4)),
|
|
183
|
+
};
|
|
184
|
+
if (path) yieldStep.path = path;
|
|
185
|
+
|
|
186
|
+
result.push(yieldStep);
|
|
187
|
+
yieldCount++;
|
|
188
|
+
i += 3;
|
|
189
|
+
continue;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
// No pattern match — pass through unchanged
|
|
193
|
+
result.push({ ...steps[i] });
|
|
194
|
+
i++;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
return { steps: result, delegateCount, yieldCount };
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
// --- Process envelopes ---
|
|
201
|
+
|
|
202
|
+
async function processStream(input, output) {
|
|
203
|
+
const rl = createInterface({ input, crlfDelay: Infinity });
|
|
204
|
+
|
|
205
|
+
let totalEnvelopes = 0;
|
|
206
|
+
let modifiedEnvelopes = 0;
|
|
207
|
+
let totalDelegates = 0;
|
|
208
|
+
let totalYields = 0;
|
|
209
|
+
let skippedRoles = 0;
|
|
210
|
+
|
|
211
|
+
for await (const line of rl) {
|
|
212
|
+
const trimmed = line.trim();
|
|
213
|
+
if (!trimmed) continue;
|
|
214
|
+
|
|
215
|
+
let envelope;
|
|
216
|
+
try {
|
|
217
|
+
envelope = JSON.parse(trimmed);
|
|
218
|
+
} catch {
|
|
219
|
+
output.write(trimmed + '\n');
|
|
220
|
+
continue;
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
totalEnvelopes++;
|
|
224
|
+
|
|
225
|
+
// Pass through non-trajectory envelopes unchanged
|
|
226
|
+
if (envelope.type === 'SESSION_CLOSE' || envelope.type === 'USER_FEEDBACK') {
|
|
227
|
+
output.write(JSON.stringify(envelope) + '\n');
|
|
228
|
+
continue;
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
// Only retag planner and fullstack sessions (where delegation/yield patterns occur)
|
|
232
|
+
const role = envelope.metadata?.agent_role;
|
|
233
|
+
if (!role || !['planner', 'fullstack', 'advisor'].includes(role)) {
|
|
234
|
+
skippedRoles++;
|
|
235
|
+
output.write(JSON.stringify(envelope) + '\n');
|
|
236
|
+
continue;
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
const steps = envelope.trajectory_log;
|
|
240
|
+
if (!Array.isArray(steps) || steps.length < 2) {
|
|
241
|
+
output.write(JSON.stringify(envelope) + '\n');
|
|
242
|
+
continue;
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
const { steps: retagged, delegateCount, yieldCount } = retagTrajectory(steps);
|
|
246
|
+
|
|
247
|
+
if (delegateCount > 0 || yieldCount > 0) {
|
|
248
|
+
modifiedEnvelopes++;
|
|
249
|
+
totalDelegates += delegateCount;
|
|
250
|
+
totalYields += yieldCount;
|
|
251
|
+
envelope.trajectory_log = retagged;
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
output.write(JSON.stringify(envelope) + '\n');
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
return { totalEnvelopes, modifiedEnvelopes, totalDelegates, totalYields, skippedRoles };
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
// --- Entry point ---
|
|
261
|
+
|
|
262
|
+
async function main() {
|
|
263
|
+
const args = process.argv.slice(2);
|
|
264
|
+
const inputPath = args[0];
|
|
265
|
+
const outputPath = args[1];
|
|
266
|
+
|
|
267
|
+
let input;
|
|
268
|
+
if (inputPath && inputPath !== '-') {
|
|
269
|
+
if (!existsSync(inputPath)) {
|
|
270
|
+
console.error(`Error: input file not found: ${inputPath}`);
|
|
271
|
+
process.exit(1);
|
|
272
|
+
}
|
|
273
|
+
input = createReadStream(inputPath, 'utf8');
|
|
274
|
+
} else {
|
|
275
|
+
input = process.stdin;
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
let output;
|
|
279
|
+
if (outputPath) {
|
|
280
|
+
output = createWriteStream(outputPath, 'utf8');
|
|
281
|
+
} else {
|
|
282
|
+
output = process.stdout;
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
const stats = await processStream(input, output);
|
|
286
|
+
|
|
287
|
+
if (output !== process.stdout) {
|
|
288
|
+
output.end();
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
// Print stats to stderr so they don't mix with JSONL output
|
|
292
|
+
console.error(`\n--- Retag Summary ---`);
|
|
293
|
+
console.error(`Envelopes processed: ${stats.totalEnvelopes}`);
|
|
294
|
+
console.error(`Envelopes modified: ${stats.modifiedEnvelopes}`);
|
|
295
|
+
console.error(`Delegate steps added: ${stats.totalDelegates}`);
|
|
296
|
+
console.error(`Yield steps added: ${stats.totalYields}`);
|
|
297
|
+
console.error(`Skipped (wrong role): ${stats.skippedRoles}`);
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
main().catch((err) => {
|
|
301
|
+
console.error(`Fatal: ${err.message}`);
|
|
302
|
+
process.exit(1);
|
|
303
|
+
});
|
|
@@ -475,7 +475,7 @@ describe('envelope-schema', () => {
|
|
|
475
475
|
it('accepts valid session_embedding object', () => {
|
|
476
476
|
const env = validEnvelope();
|
|
477
477
|
env.metadata.session_embedding = {
|
|
478
|
-
model: 'BAAI/bge-
|
|
478
|
+
model: 'BAAI/bge-base-en-v1.5',
|
|
479
479
|
vector: [0.0234, -0.0891, 0.1247, 0.0562],
|
|
480
480
|
source_text: 'Write a Python decorator that caches function results',
|
|
481
481
|
};
|
|
@@ -486,7 +486,7 @@ describe('envelope-schema', () => {
|
|
|
486
486
|
it('rejects session_embedding with empty vector', () => {
|
|
487
487
|
const env = validEnvelope();
|
|
488
488
|
env.metadata.session_embedding = {
|
|
489
|
-
model: 'BAAI/bge-
|
|
489
|
+
model: 'BAAI/bge-base-en-v1.5',
|
|
490
490
|
vector: [],
|
|
491
491
|
source_text: 'test',
|
|
492
492
|
};
|
|
@@ -498,7 +498,7 @@ describe('envelope-schema', () => {
|
|
|
498
498
|
it('rejects session_embedding with non-numeric vector values', () => {
|
|
499
499
|
const env = validEnvelope();
|
|
500
500
|
env.metadata.session_embedding = {
|
|
501
|
-
model: 'BAAI/bge-
|
|
501
|
+
model: 'BAAI/bge-base-en-v1.5',
|
|
502
502
|
vector: [0.1, 'bad', 0.3],
|
|
503
503
|
source_text: 'test',
|
|
504
504
|
};
|
|
@@ -137,6 +137,20 @@ export class AdaptiveThresholds {
|
|
|
137
137
|
const filesWritten = signals.filesWritten || 0;
|
|
138
138
|
score += Math.min(filesWritten * 2, 10); // Cap at +10
|
|
139
139
|
|
|
140
|
+
// Output length decay: assistant responses shrinking dramatically
|
|
141
|
+
if (signals.outputLengthDecay) score -= 10;
|
|
142
|
+
|
|
143
|
+
// Tool output volume: bloated context from large tool results
|
|
144
|
+
const toolVol = signals.toolOutputVolume || 0;
|
|
145
|
+
if (toolVol === 2) score -= 10;
|
|
146
|
+
else if (toolVol === 1) score -= 5;
|
|
147
|
+
|
|
148
|
+
// Turn latency trend: agent slowing down significantly
|
|
149
|
+
if (signals.turnLatencyTrend) score -= 5;
|
|
150
|
+
|
|
151
|
+
// Bash repetition: agent stuck running identical commands
|
|
152
|
+
if (signals.bashRepetition) score -= 8;
|
|
153
|
+
|
|
140
154
|
// Clamp to 0-100
|
|
141
155
|
return Math.max(0, Math.min(100, score));
|
|
142
156
|
}
|
|
@@ -165,20 +179,43 @@ export class AdaptiveThresholds {
|
|
|
165
179
|
filesWritten: 0,
|
|
166
180
|
fileChurn: 0, // same file written 3+ times → possible circular refactoring
|
|
167
181
|
errorTrend: 0, // errors increasing in recent window → degradation signal
|
|
182
|
+
outputLengthDecay: 0, // last 5 assistant turns avg <50% of first 5 → declining output
|
|
183
|
+
toolOutputVolume: 0, // cumulative tool result chars (>300KB = bloated context)
|
|
184
|
+
turnLatencyTrend: 0, // avg gap in last 10 entries >2x first 10 → slowing down
|
|
185
|
+
bashRepetition: 0, // 3+ identical consecutive Bash commands → stuck in loop
|
|
168
186
|
};
|
|
169
187
|
|
|
170
188
|
const writtenFiles = new Set();
|
|
171
189
|
const fileWriteCounts = {};
|
|
172
190
|
const writeEditOps = [];
|
|
191
|
+
const assistantOutputLengths = [];
|
|
192
|
+
let toolOutputBytes = 0;
|
|
193
|
+
const entryTimestamps = [];
|
|
194
|
+
const bashCommands = [];
|
|
173
195
|
|
|
174
196
|
for (const entry of entries) {
|
|
197
|
+
if (entry.timestamp) entryTimestamps.push(new Date(entry.timestamp).getTime());
|
|
198
|
+
|
|
175
199
|
if (entry.type === 'error') {
|
|
176
200
|
signals.errorCount++;
|
|
177
201
|
}
|
|
178
202
|
|
|
203
|
+
// Track assistant output lengths for decay detection
|
|
204
|
+
if (entry.type === 'thinking' && entry.text) {
|
|
205
|
+
assistantOutputLengths.push(entry.text.length);
|
|
206
|
+
}
|
|
207
|
+
|
|
179
208
|
if (entry.type === 'tool') {
|
|
180
209
|
signals.toolCalls++;
|
|
181
210
|
|
|
211
|
+
// Track tool result output volume
|
|
212
|
+
if (entry.output) toolOutputBytes += entry.output.length;
|
|
213
|
+
|
|
214
|
+
// Track Bash commands for repetition detection
|
|
215
|
+
if (entry.tool === 'Bash' && entry.input) {
|
|
216
|
+
bashCommands.push(entry.input);
|
|
217
|
+
}
|
|
218
|
+
|
|
182
219
|
if (entry.tool === 'Write' || entry.tool === 'Edit') {
|
|
183
220
|
if (entry.input) {
|
|
184
221
|
writtenFiles.add(entry.input);
|
|
@@ -245,6 +282,46 @@ export class AdaptiveThresholds {
|
|
|
245
282
|
signals.errorTrend = secondHalfErrors - firstHalfErrors;
|
|
246
283
|
}
|
|
247
284
|
|
|
285
|
+
// Output length decay: if last 5 assistant outputs avg <50% of first 5
|
|
286
|
+
if (assistantOutputLengths.length >= 10) {
|
|
287
|
+
const first5 = assistantOutputLengths.slice(0, 5);
|
|
288
|
+
const last5 = assistantOutputLengths.slice(-5);
|
|
289
|
+
const firstAvg = first5.reduce((a, b) => a + b, 0) / 5;
|
|
290
|
+
const lastAvg = last5.reduce((a, b) => a + b, 0) / 5;
|
|
291
|
+
if (firstAvg > 0 && lastAvg < firstAvg * 0.5) signals.outputLengthDecay = 1;
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
// Tool output volume: cumulative tool result size
|
|
295
|
+
if (toolOutputBytes > 600_000) signals.toolOutputVolume = 2;
|
|
296
|
+
else if (toolOutputBytes > 300_000) signals.toolOutputVolume = 1;
|
|
297
|
+
|
|
298
|
+
// Turn latency trend: avg gap in last 10 entries >2x first 10
|
|
299
|
+
if (entryTimestamps.length >= 20) {
|
|
300
|
+
const gaps = (ts) => {
|
|
301
|
+
const g = [];
|
|
302
|
+
for (let i = 1; i < ts.length; i++) g.push(ts[i] - ts[i - 1]);
|
|
303
|
+
return g;
|
|
304
|
+
};
|
|
305
|
+
const firstGaps = gaps(entryTimestamps.slice(0, 11));
|
|
306
|
+
const lastGaps = gaps(entryTimestamps.slice(-11));
|
|
307
|
+
const avgFirst = firstGaps.reduce((a, b) => a + b, 0) / firstGaps.length;
|
|
308
|
+
const avgLast = lastGaps.reduce((a, b) => a + b, 0) / lastGaps.length;
|
|
309
|
+
if (avgFirst > 0 && avgLast > avgFirst * 2) signals.turnLatencyTrend = 1;
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
// Bash repetition: 3+ identical consecutive Bash commands
|
|
313
|
+
let maxConsecutive = 0;
|
|
314
|
+
let streak = 1;
|
|
315
|
+
for (let i = 1; i < bashCommands.length; i++) {
|
|
316
|
+
if (bashCommands[i] === bashCommands[i - 1]) {
|
|
317
|
+
streak++;
|
|
318
|
+
if (streak > maxConsecutive) maxConsecutive = streak;
|
|
319
|
+
} else {
|
|
320
|
+
streak = 1;
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
if (maxConsecutive >= 3) signals.bashRepetition = 1;
|
|
324
|
+
|
|
248
325
|
return signals;
|
|
249
326
|
}
|
|
250
327
|
|
|
@@ -124,6 +124,38 @@ export function createApi(app, daemon) {
|
|
|
124
124
|
res.json({ status: 'ok', uptime: process.uptime() });
|
|
125
125
|
});
|
|
126
126
|
|
|
127
|
+
// Debug: test fetch to llama-server from daemon runtime
|
|
128
|
+
app.get('/api/lab/debug-fetch', async (req, res) => {
|
|
129
|
+
const target = req.query.url || 'http://localhost:8081/v1/chat/completions';
|
|
130
|
+
const log = [];
|
|
131
|
+
try {
|
|
132
|
+
log.push(`fetch → ${target}`);
|
|
133
|
+
log.push(`node ${process.version}, electron ${process.versions.electron || 'N/A'}`);
|
|
134
|
+
const start = Date.now();
|
|
135
|
+
const r = await fetch(target, {
|
|
136
|
+
method: 'POST',
|
|
137
|
+
headers: { 'Content-Type': 'application/json' },
|
|
138
|
+
body: JSON.stringify({ model: 'Qwen3-0.6B-Q8_0.gguf', messages: [{ role: 'user', content: 'Say ok' }], stream: true, max_tokens: 10 }),
|
|
139
|
+
signal: AbortSignal.timeout(10000),
|
|
140
|
+
});
|
|
141
|
+
log.push(`status=${r.status} in ${Date.now() - start}ms`);
|
|
142
|
+
const reader = r.body.getReader();
|
|
143
|
+
let chunks = 0;
|
|
144
|
+
while (chunks < 5) {
|
|
145
|
+
const { done, value } = await reader.read();
|
|
146
|
+
if (done) break;
|
|
147
|
+
chunks++;
|
|
148
|
+
log.push(`chunk ${chunks}: ${new TextDecoder().decode(value).slice(0, 120)}`);
|
|
149
|
+
}
|
|
150
|
+
reader.cancel();
|
|
151
|
+
log.push(`total chunks read: ${chunks}`);
|
|
152
|
+
res.json({ ok: true, log });
|
|
153
|
+
} catch (err) {
|
|
154
|
+
log.push(`ERROR: ${err.message}`);
|
|
155
|
+
res.json({ ok: false, log, error: err.message });
|
|
156
|
+
}
|
|
157
|
+
});
|
|
158
|
+
|
|
127
159
|
// List all agents
|
|
128
160
|
app.get('/api/agents', (req, res) => {
|
|
129
161
|
res.json(daemon.registry.getAll());
|
|
@@ -6703,11 +6735,9 @@ Keep responses concise. Help them think, don't lecture them about the system the
|
|
|
6703
6735
|
let closed = false;
|
|
6704
6736
|
req.on('close', () => { closed = true; });
|
|
6705
6737
|
|
|
6706
|
-
|
|
6707
|
-
|
|
6708
|
-
|
|
6709
|
-
res.write(`data: ${JSON.stringify(event)}\n\n`);
|
|
6710
|
-
}
|
|
6738
|
+
await daemon.modelLab.streamInference(params, (event) => {
|
|
6739
|
+
if (!closed) res.write(`data: ${JSON.stringify(event)}\n\n`);
|
|
6740
|
+
});
|
|
6711
6741
|
|
|
6712
6742
|
if (!closed) {
|
|
6713
6743
|
res.write('data: [DONE]\n\n');
|
|
@@ -460,7 +460,7 @@ export class Journalist {
|
|
|
460
460
|
'(What was completed. Name files, functions, and line numbers.)',
|
|
461
461
|
'',
|
|
462
462
|
'Be specific. Name files, functions, and line numbers. Do not summarize vaguely.',
|
|
463
|
-
'Keep your response under
|
|
463
|
+
'Keep your response under 1500 characters.',
|
|
464
464
|
'',
|
|
465
465
|
'---',
|
|
466
466
|
'',
|
|
@@ -469,7 +469,7 @@ export class Journalist {
|
|
|
469
469
|
];
|
|
470
470
|
|
|
471
471
|
let totalChars = 0;
|
|
472
|
-
const cap =
|
|
472
|
+
const cap = 15_000;
|
|
473
473
|
for (const entry of entries.slice(-200)) {
|
|
474
474
|
const line = this.formatEntry(entry);
|
|
475
475
|
if (totalChars + line.length > cap) break;
|
|
@@ -853,15 +853,15 @@ export class Journalist {
|
|
|
853
853
|
const agentLog = filteredLogs[agent.id];
|
|
854
854
|
const entries = agentLog?.entries || [];
|
|
855
855
|
|
|
856
|
-
// Layer 7 memory: discoveries, constraints, specializations
|
|
857
|
-
const discoveries = this.daemon.memory?.getDiscoveriesMarkdown(agent.role, 10,
|
|
856
|
+
// Layer 7 memory: discoveries (inline, not pointer — agents lose context with pointers), constraints, specializations
|
|
857
|
+
const discoveries = this.daemon.memory?.getDiscoveriesMarkdown(agent.role, 10, 1500) || '';
|
|
858
858
|
const constraints = this.daemon.memory?.getConstraintsMarkdown(2000) || '';
|
|
859
859
|
const specialization = this.daemon.memory?.getSpecialization(agent.id);
|
|
860
860
|
const specLine = specialization?.avgQualityScore != null
|
|
861
861
|
? `- Quality profile: ${specialization.avgQualityScore}/100 across ${specialization.sessionCount} sessions`
|
|
862
862
|
: '';
|
|
863
863
|
|
|
864
|
-
const recentChain = this.daemon.memory?.getRecentHandoffMarkdown(agent.role,
|
|
864
|
+
const recentChain = this.daemon.memory?.getRecentHandoffMarkdown(agent.role, 1, 1500, agent.workingDir, agent.teamId) || '';
|
|
865
865
|
|
|
866
866
|
const agentFeedback = this.getUserFeedback(agent.id).slice(-5);
|
|
867
867
|
const conversationSummary = agentFeedback.length > 0
|
|
@@ -871,7 +871,7 @@ export class Journalist {
|
|
|
871
871
|
const recentTools = entries
|
|
872
872
|
.filter((e) => e.type === 'tool' || e.type === 'error')
|
|
873
873
|
.slice(-5)
|
|
874
|
-
.map((e) => `- ${e.type === 'error' ? 'ERROR ' : ''}${e.tool}: ${(e.input || e.text || '').slice(0,
|
|
874
|
+
.map((e) => `- ${e.type === 'error' ? 'ERROR ' : ''}${e.tool}: ${(e.input || e.text || '').slice(0, 200)}`)
|
|
875
875
|
.join('\n');
|
|
876
876
|
|
|
877
877
|
// Try AI-synthesized session summary
|
|
@@ -908,7 +908,7 @@ export class Journalist {
|
|
|
908
908
|
const fallbackRecentTools = entries
|
|
909
909
|
.filter((e) => e.type === 'tool' || e.type === 'error')
|
|
910
910
|
.slice(-5)
|
|
911
|
-
.map((e) => `- ${e.type === 'error' ? 'ERROR ' : ''}${e.tool}: ${(e.input || '').slice(0,
|
|
911
|
+
.map((e) => `- ${e.type === 'error' ? 'ERROR ' : ''}${e.tool}: ${(e.input || '').slice(0, 200)}`)
|
|
912
912
|
.join('\n');
|
|
913
913
|
|
|
914
914
|
const fallbackParts = [];
|
|
@@ -919,7 +919,13 @@ export class Journalist {
|
|
|
919
919
|
sessionSummary = fallbackParts.join('\n\n');
|
|
920
920
|
}
|
|
921
921
|
|
|
922
|
-
|
|
922
|
+
// For quality_degradation rotations, drop user messages (already in session summary)
|
|
923
|
+
const includeUserMessages = options.reason !== 'quality_degradation';
|
|
924
|
+
|
|
925
|
+
// Cap Original Task to 1000 chars — task descriptions for debugging can be long
|
|
926
|
+
const originalTask = agent.prompt ? agent.prompt.slice(0, 1000) + (agent.prompt.length > 1000 ? '…' : '') : '';
|
|
927
|
+
|
|
928
|
+
let brief = [
|
|
923
929
|
`# Handoff Brief — ${agent.name} (${agent.role})`,
|
|
924
930
|
``,
|
|
925
931
|
`Role: ${agent.role} | Scope: ${agent.scope?.join(', ') || 'unrestricted'} | Provider: ${agent.provider}`,
|
|
@@ -927,17 +933,27 @@ export class Journalist {
|
|
|
927
933
|
`Rotation: ${options.reason || 'manual'}${options.qualityScore ? ` (quality: ${options.qualityScore}/100)` : ''} | Tokens: ${agent.tokensUsed}`,
|
|
928
934
|
specLine,
|
|
929
935
|
``,
|
|
930
|
-
|
|
936
|
+
// Priority order: session summary (contains unresolved errors) first,
|
|
937
|
+
// then constraints, then discoveries, then tools — so the most critical
|
|
938
|
+
// debugging context survives even if the brief hits the hard cap.
|
|
939
|
+
sessionSummary ? `## Session Summary\n\n${sessionSummary}\n` : '',
|
|
931
940
|
constraints ? `## Project Constraints (must follow)\n\n${constraints}\n` : '',
|
|
941
|
+
discoveries ? `## Known Issues & Fixes\n\n${discoveries}\n` : '',
|
|
932
942
|
recentTools ? `## Last 5 Tool Calls\n\n${recentTools}\n` : '',
|
|
933
|
-
|
|
934
|
-
conversationSummary ? `## Recent User Messages\n\n${conversationSummary}\n` : '',
|
|
943
|
+
includeUserMessages && conversationSummary ? `## Recent User Messages\n\n${conversationSummary}\n` : '',
|
|
935
944
|
recentChain ? `## Rotation History\n\n${recentChain}\n` : '',
|
|
936
|
-
|
|
945
|
+
originalTask ? `## Original Task\n\n${originalTask}\n` : '',
|
|
937
946
|
``,
|
|
938
947
|
agent.role === 'planner' ? 'CRITICAL: You are a PLANNING ONLY agent. Do NOT implement code. Route all work to your team via .groove/recommended-team.json.\n' : '',
|
|
939
948
|
`Continue seamlessly — finish the work and deliver the output.`,
|
|
940
949
|
].filter(Boolean).join('\n');
|
|
950
|
+
|
|
951
|
+
// Hard cap: 8000 chars — enough for debugging context without overwhelming the new agent
|
|
952
|
+
if (brief.length > 8000) {
|
|
953
|
+
brief = brief.slice(0, 7950) + '\n\n[Brief truncated — see session logs for full context]';
|
|
954
|
+
}
|
|
955
|
+
|
|
956
|
+
return brief;
|
|
941
957
|
}
|
|
942
958
|
|
|
943
959
|
// --- Workspace Grouping ---
|