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.
Files changed (85) hide show
  1. package/moe-training/client/domain-tagger.js +1 -1
  2. package/moe-training/scripts/retag-delegate-yield.js +303 -0
  3. package/moe-training/test/shared/envelope-schema.test.js +3 -3
  4. package/node_modules/@groove-dev/cli/package.json +1 -1
  5. package/node_modules/@groove-dev/daemon/package.json +1 -1
  6. package/node_modules/@groove-dev/daemon/src/adaptive.js +77 -0
  7. package/node_modules/@groove-dev/daemon/src/api.js +35 -5
  8. package/node_modules/@groove-dev/daemon/src/journalist.js +28 -12
  9. package/node_modules/@groove-dev/daemon/src/model-lab.js +53 -76
  10. package/node_modules/@groove-dev/daemon/src/process.js +91 -2
  11. package/node_modules/@groove-dev/daemon/src/rotator.js +45 -3
  12. package/node_modules/@groove-dev/gui/dist/assets/{index-Dozp69tK.js → index-BrZHF7pK.js} +1770 -1766
  13. package/node_modules/@groove-dev/gui/dist/assets/index-DIfiwdKl.css +1 -0
  14. package/node_modules/@groove-dev/gui/dist/index.html +2 -2
  15. package/node_modules/@groove-dev/gui/package.json +1 -1
  16. package/node_modules/@groove-dev/gui/src/components/agents/agent-chat.jsx +60 -18
  17. package/node_modules/@groove-dev/gui/src/components/agents/agent-feed.jsx +42 -20
  18. package/node_modules/@groove-dev/gui/src/components/agents/agent-file-tree.jsx +1 -1
  19. package/node_modules/@groove-dev/gui/src/components/agents/workspace-mode.jsx +1 -1
  20. package/node_modules/@groove-dev/gui/src/components/chat/chat-messages.jsx +2 -22
  21. package/node_modules/@groove-dev/gui/src/components/editor/code-editor.jsx +9 -9
  22. package/node_modules/@groove-dev/gui/src/components/editor/file-tree.jsx +1 -1
  23. package/node_modules/@groove-dev/gui/src/components/editor/terminal.jsx +7 -0
  24. package/node_modules/@groove-dev/gui/src/components/lab/chat-playground.jsx +59 -51
  25. package/node_modules/@groove-dev/gui/src/components/lab/lab-assistant.jsx +48 -48
  26. package/node_modules/@groove-dev/gui/src/components/lab/metrics-panel.jsx +39 -38
  27. package/node_modules/@groove-dev/gui/src/components/lab/parameter-panel.jsx +4 -5
  28. package/node_modules/@groove-dev/gui/src/components/lab/preset-manager.jsx +11 -11
  29. package/node_modules/@groove-dev/gui/src/components/lab/runtime-config.jsx +66 -62
  30. package/node_modules/@groove-dev/gui/src/components/lab/system-prompt-editor.jsx +13 -13
  31. package/node_modules/@groove-dev/gui/src/components/layout/breadcrumb-bar.jsx +1 -1
  32. package/node_modules/@groove-dev/gui/src/components/preview/preview-workspace.jsx +62 -22
  33. package/node_modules/@groove-dev/gui/src/components/ui/slider.jsx +16 -17
  34. package/node_modules/@groove-dev/gui/src/components/ui/table-tree.jsx +38 -0
  35. package/node_modules/@groove-dev/gui/src/stores/groove.js +23 -9
  36. package/node_modules/@groove-dev/gui/src/views/editor.jsx +1 -1
  37. package/node_modules/@groove-dev/gui/src/views/model-lab.jsx +101 -87
  38. package/node_modules/moe-training/client/domain-tagger.js +1 -1
  39. package/node_modules/moe-training/scripts/retag-delegate-yield.js +303 -0
  40. package/node_modules/moe-training/test/shared/envelope-schema.test.js +3 -3
  41. package/package.json +1 -1
  42. package/packages/cli/package.json +1 -1
  43. package/packages/daemon/package.json +1 -1
  44. package/packages/daemon/src/adaptive.js +77 -0
  45. package/packages/daemon/src/api.js +35 -5
  46. package/packages/daemon/src/journalist.js +28 -12
  47. package/packages/daemon/src/model-lab.js +53 -76
  48. package/packages/daemon/src/process.js +91 -2
  49. package/packages/daemon/src/rotator.js +45 -3
  50. package/packages/gui/dist/assets/{index-Dozp69tK.js → index-BrZHF7pK.js} +1770 -1766
  51. package/packages/gui/dist/assets/index-DIfiwdKl.css +1 -0
  52. package/packages/gui/dist/index.html +2 -2
  53. package/packages/gui/package.json +1 -1
  54. package/packages/gui/src/components/agents/agent-chat.jsx +60 -18
  55. package/packages/gui/src/components/agents/agent-feed.jsx +42 -20
  56. package/packages/gui/src/components/agents/agent-file-tree.jsx +1 -1
  57. package/packages/gui/src/components/agents/workspace-mode.jsx +1 -1
  58. package/packages/gui/src/components/chat/chat-messages.jsx +2 -22
  59. package/packages/gui/src/components/editor/code-editor.jsx +9 -9
  60. package/packages/gui/src/components/editor/file-tree.jsx +1 -1
  61. package/packages/gui/src/components/editor/terminal.jsx +7 -0
  62. package/packages/gui/src/components/lab/chat-playground.jsx +59 -51
  63. package/packages/gui/src/components/lab/lab-assistant.jsx +48 -48
  64. package/packages/gui/src/components/lab/metrics-panel.jsx +39 -38
  65. package/packages/gui/src/components/lab/parameter-panel.jsx +4 -5
  66. package/packages/gui/src/components/lab/preset-manager.jsx +11 -11
  67. package/packages/gui/src/components/lab/runtime-config.jsx +66 -62
  68. package/packages/gui/src/components/lab/system-prompt-editor.jsx +13 -13
  69. package/packages/gui/src/components/layout/breadcrumb-bar.jsx +1 -1
  70. package/packages/gui/src/components/preview/preview-workspace.jsx +62 -22
  71. package/packages/gui/src/components/ui/slider.jsx +16 -17
  72. package/packages/gui/src/components/ui/table-tree.jsx +38 -0
  73. package/packages/gui/src/stores/groove.js +23 -9
  74. package/packages/gui/src/views/editor.jsx +1 -1
  75. package/packages/gui/src/views/model-lab.jsx +101 -87
  76. package/plan_files/DELEGATE_YIELD_TRAINING_TAGS.md +135 -0
  77. package/plan_files/session-quality-rotation-fixes.md +218 -0
  78. package/test.py +571 -0
  79. package/node_modules/@groove-dev/gui/dist/assets/index-BgQL4bNl.css +0 -1
  80. package/packages/gui/dist/assets/index-BgQL4bNl.css +0 -1
  81. /package/{AGENT_ORCHESTRATION.md → plan_files/AGENT_ORCHESTRATION.md} +0 -0
  82. /package/{DYNAMIC_LEAF_ARCH.md → plan_files/DYNAMIC_LEAF_ARCH.md} +0 -0
  83. /package/{EMBEDDING_DIAGNOSTIC.md → plan_files/EMBEDDING_DIAGNOSTIC.md} +0 -0
  84. /package/{EMBEDDING_SERVICE_BUILD_PLAN.md → plan_files/EMBEDDING_SERVICE_BUILD_PLAN.md} +0 -0
  85. /package/{MOE_TRAINING_PIPELINE.md → plan_files/MOE_TRAINING_PIPELINE.md} +0 -0
@@ -220,6 +220,7 @@ export const useGrooveStore = create((set, get) => ({
220
220
  }),
221
221
  labSystemPrompt: localStorage.getItem('groove:labSystemPrompt') || '',
222
222
  labStreaming: false,
223
+ labAbortController: null,
223
224
  labLocalModels: [],
224
225
  labLaunching: null,
225
226
  labLlamaInstalled: null,
@@ -3501,6 +3502,9 @@ export const useGrooveStore = create((set, get) => ({
3501
3502
  return { labSessions: sessions };
3502
3503
  });
3503
3504
 
3505
+ const abortController = new AbortController();
3506
+ set({ labAbortController: abortController });
3507
+
3504
3508
  const startTime = performance.now();
3505
3509
  let firstTokenTime = null;
3506
3510
  let tokenCount = 0;
@@ -3536,6 +3540,7 @@ export const useGrooveStore = create((set, get) => ({
3536
3540
  method: 'POST',
3537
3541
  headers: { 'Content-Type': 'application/json' },
3538
3542
  body: JSON.stringify(body),
3543
+ signal: abortController.signal,
3539
3544
  });
3540
3545
 
3541
3546
  if (!res.ok) {
@@ -3639,20 +3644,29 @@ export const useGrooveStore = create((set, get) => ({
3639
3644
  });
3640
3645
  }
3641
3646
  } catch (err) {
3642
- set((s) => {
3643
- const sessions = s.labSessions.map((sess) => {
3644
- if (sess.id !== sessionId) return sess;
3645
- const msgs = [...sess.messages];
3646
- msgs[msgs.length - 1] = { ...msgs[msgs.length - 1], content: `Error: ${err.message}`, error: true };
3647
- return { ...sess, messages: msgs };
3647
+ if (err.name === 'AbortError') {
3648
+ // User cancelled keep whatever content was already streamed
3649
+ } else {
3650
+ set((s) => {
3651
+ const sessions = s.labSessions.map((sess) => {
3652
+ if (sess.id !== sessionId) return sess;
3653
+ const msgs = [...sess.messages];
3654
+ msgs[msgs.length - 1] = { ...msgs[msgs.length - 1], content: `Error: ${err.message}`, error: true };
3655
+ return { ...sess, messages: msgs };
3656
+ });
3657
+ return { labSessions: sessions };
3648
3658
  });
3649
- return { labSessions: sessions };
3650
- });
3659
+ }
3651
3660
  } finally {
3652
- set({ labStreaming: false });
3661
+ set({ labStreaming: false, labAbortController: null });
3653
3662
  }
3654
3663
  },
3655
3664
 
3665
+ stopLabInference() {
3666
+ const ctrl = get().labAbortController;
3667
+ if (ctrl) ctrl.abort();
3668
+ },
3669
+
3656
3670
  saveLabPreset(name) {
3657
3671
  const st = get();
3658
3672
  const preset = {
@@ -121,7 +121,7 @@ export default function EditorView() {
121
121
  {sidebarCollapsed && (
122
122
  <button
123
123
  onClick={() => setSidebarCollapsed(false)}
124
- className="flex-shrink-0 w-6 flex items-center justify-center border-r border-border bg-surface-2 text-text-4 hover:text-text-0 hover:bg-surface-3 transition-colors cursor-pointer"
124
+ className="flex-shrink-0 w-6 flex items-start justify-center pt-2 border-r border-border bg-surface-2 text-text-4 hover:text-text-0 hover:bg-surface-3 transition-colors cursor-pointer"
125
125
  title="Show sidebar"
126
126
  >
127
127
  <PanelLeftOpen size={14} />
@@ -2,7 +2,6 @@
2
2
  import { useState, useCallback, useRef, useEffect } from 'react';
3
3
  import { useGrooveStore } from '../stores/groove';
4
4
  import { ScrollArea } from '../components/ui/scroll-area';
5
- import { Badge } from '../components/ui/badge';
6
5
  import { Tooltip } from '../components/ui/tooltip';
7
6
  import { Combobox } from '../components/ui/combobox';
8
7
  import { RuntimeConfig, LaunchModel } from '../components/lab/runtime-config';
@@ -13,7 +12,7 @@ import { LabAssistant } from '../components/lab/lab-assistant';
13
12
  import { MetricsPanel } from '../components/lab/metrics-panel';
14
13
  import { PresetManager } from '../components/lab/preset-manager';
15
14
  import { cn } from '../lib/cn';
16
- import { FlaskConical, PanelLeftOpen, PanelRightOpen, Box } from 'lucide-react';
15
+ import { FlaskConical, PanelLeftClose, PanelLeftOpen, PanelRightClose, PanelRightOpen, Box } from 'lucide-react';
17
16
 
18
17
  const LEFT_DEFAULT = 280;
19
18
  const LEFT_MIN = 220;
@@ -32,7 +31,7 @@ function ModelSelector() {
32
31
 
33
32
  return (
34
33
  <div className="space-y-1.5">
35
- <span className="text-xs font-semibold font-sans text-text-2 uppercase tracking-wider">Model</span>
34
+ <span className="text-2xs font-semibold font-sans text-text-3 uppercase tracking-wider">Model</span>
36
35
  <Combobox
37
36
  value={activeModel || ''}
38
37
  onChange={setActiveModel}
@@ -53,15 +52,38 @@ function ModelSelector() {
53
52
  );
54
53
  }
55
54
 
56
- function ResizeHandle({ onMouseDown, direction = 'vertical' }) {
55
+ function ResizeHandle({ onMouseDown }) {
57
56
  return (
58
57
  <div
59
58
  onMouseDown={onMouseDown}
60
- className={cn(
61
- 'flex-shrink-0 bg-border hover:bg-accent/40 transition-colors',
62
- direction === 'vertical' ? 'w-px cursor-col-resize hover:w-0.5' : 'h-px cursor-row-resize hover:h-0.5',
63
- )}
64
- />
59
+ className="flex-shrink-0 w-[3px] cursor-col-resize group relative"
60
+ >
61
+ <div className="absolute inset-y-0 -left-1 -right-1" />
62
+ <div className="absolute inset-y-0 left-[1px] w-px bg-border group-hover:bg-accent/50 transition-colors" />
63
+ </div>
64
+ );
65
+ }
66
+
67
+ function PanelToggle({ collapsed, onClick, side }) {
68
+ const Icon = side === 'left'
69
+ ? (collapsed ? PanelLeftOpen : PanelLeftClose)
70
+ : (collapsed ? PanelRightOpen : PanelRightClose);
71
+ const label = side === 'left'
72
+ ? (collapsed ? 'Show config' : 'Hide config')
73
+ : (collapsed ? 'Show metrics' : 'Hide metrics');
74
+
75
+ return (
76
+ <Tooltip content={label}>
77
+ <button
78
+ onClick={onClick}
79
+ className={cn(
80
+ 'p-1 transition-colors cursor-pointer',
81
+ collapsed ? 'text-text-4 hover:text-text-1' : 'text-text-3 hover:text-text-1',
82
+ )}
83
+ >
84
+ <Icon size={14} />
85
+ </button>
86
+ </Tooltip>
65
87
  );
66
88
  }
67
89
 
@@ -130,93 +152,79 @@ export default function ModelLabView() {
130
152
 
131
153
  return (
132
154
  <div className="h-full flex flex-col bg-surface-0">
133
- {/* Header */}
134
- <div className="flex-shrink-0 flex items-center justify-between px-5 py-2.5 border-b border-border">
135
- <div className="flex items-center gap-2.5">
136
- <FlaskConical size={16} className="text-accent" />
137
- <h1 className="text-sm font-bold font-sans text-text-0">Model Lab</h1>
138
- <Badge variant="accent">Beta</Badge>
139
- </div>
140
- <div className="flex items-center gap-1">
141
- <Tooltip content={leftCollapsed ? 'Show config panel' : 'Hide config panel'}>
142
- <button
143
- onClick={() => setLeftCollapsed(!leftCollapsed)}
144
- className={cn(
145
- 'p-1.5 rounded-md transition-colors cursor-pointer',
146
- leftCollapsed ? 'text-text-3 hover:text-accent hover:bg-accent/10' : 'text-accent bg-accent/10',
147
- )}
148
- >
149
- <PanelLeftOpen size={14} />
150
- </button>
151
- </Tooltip>
152
- <Tooltip content={rightCollapsed ? 'Show metrics panel' : 'Hide metrics panel'}>
153
- <button
154
- onClick={() => setRightCollapsed(!rightCollapsed)}
155
- className={cn(
156
- 'p-1.5 rounded-md transition-colors cursor-pointer',
157
- rightCollapsed ? 'text-text-3 hover:text-accent hover:bg-accent/10' : 'text-accent bg-accent/10',
158
- )}
159
- >
160
- <PanelRightOpen size={14} />
161
- </button>
162
- </Tooltip>
163
- </div>
164
- </div>
165
-
166
155
  {/* 3-panel layout */}
167
156
  <div className="flex-1 flex min-h-0">
168
157
  {/* Left panel — config */}
169
158
  <div
170
159
  className={cn(
171
- 'flex-shrink-0 border-r border-border transition-all duration-200 overflow-hidden',
172
- leftCollapsed && 'w-0 border-r-0',
160
+ 'flex-shrink-0 transition-all duration-200 overflow-hidden',
161
+ leftCollapsed && 'w-0',
173
162
  )}
174
163
  style={leftCollapsed ? undefined : { width: leftWidth }}
175
164
  >
176
- <ScrollArea className="h-full">
177
- <div className="px-4 py-4 space-y-5">
178
- <LaunchModel />
179
- <div className="border-t border-border-subtle" />
180
- <RuntimeConfig />
181
- <div className="border-t border-border-subtle" />
182
- <ModelSelector />
183
- <div className="border-t border-border-subtle" />
184
- <ParameterPanel />
185
- <div className="border-t border-border-subtle" />
186
- <PresetManager />
187
- <div className="border-t border-border-subtle" />
188
- <SystemPromptEditor />
165
+ <div className="h-full flex flex-col">
166
+ <div className="flex-shrink-0 flex items-center justify-between px-4 h-10">
167
+ <div className="flex items-center gap-2">
168
+ <FlaskConical size={13} className="text-accent" />
169
+ <span className="text-xs font-semibold font-sans text-text-1">Model Lab</span>
170
+ </div>
171
+ <PanelToggle collapsed={false} onClick={() => setLeftCollapsed(true)} side="left" />
189
172
  </div>
190
- </ScrollArea>
173
+ <ScrollArea className="flex-1 min-h-0">
174
+ <div className="px-4 pb-4 space-y-5">
175
+ <LaunchModel />
176
+ <div className="h-px bg-border-subtle" />
177
+ <RuntimeConfig />
178
+ <div className="h-px bg-border-subtle" />
179
+ <ModelSelector />
180
+ <div className="h-px bg-border-subtle" />
181
+ <ParameterPanel />
182
+ <div className="h-px bg-border-subtle" />
183
+ <PresetManager />
184
+ <div className="h-px bg-border-subtle" />
185
+ <SystemPromptEditor />
186
+ </div>
187
+ </ScrollArea>
188
+ </div>
191
189
  </div>
192
190
 
193
191
  {!leftCollapsed && <ResizeHandle onMouseDown={onLeftMouseDown} />}
194
192
 
195
193
  {/* Center panel — chat playground / assistant */}
196
194
  <div className="flex-1 min-w-0 flex flex-col">
197
- {labAssistantAgentId && (
198
- <div className="flex-shrink-0 flex items-center gap-1 px-3 pt-2">
199
- <button
200
- onClick={() => setLabAssistantMode(false)}
201
- className={cn(
202
- 'px-3 py-1.5 text-xs font-sans font-medium rounded-t-md transition-colors cursor-pointer',
203
- !labAssistantMode ? 'text-accent bg-accent/10' : 'text-text-3 hover:text-text-1',
204
- )}
205
- >
206
- Playground
207
- </button>
208
- <button
209
- onClick={() => setLabAssistantMode(true)}
210
- className={cn(
211
- 'px-3 py-1.5 text-xs font-sans font-medium rounded-t-md transition-colors cursor-pointer',
212
- labAssistantMode ? 'text-accent bg-accent/10' : 'text-text-3 hover:text-text-1',
213
- )}
214
- >
215
- Assistant
216
- </button>
217
- </div>
218
- )}
219
- <div className="flex-1 min-h-0 p-3">
195
+ {/* Center header bar */}
196
+ <div className="flex-shrink-0 flex items-center h-10 px-3 gap-2">
197
+ {leftCollapsed && (
198
+ <PanelToggle collapsed onClick={() => setLeftCollapsed(false)} side="left" />
199
+ )}
200
+ {labAssistantAgentId && (
201
+ <div className="flex items-center gap-px bg-surface-2 rounded p-px">
202
+ <button
203
+ onClick={() => setLabAssistantMode(false)}
204
+ className={cn(
205
+ 'px-3 py-1 text-2xs font-sans font-medium rounded-sm transition-colors cursor-pointer',
206
+ !labAssistantMode ? 'text-text-0 bg-surface-4' : 'text-text-3 hover:text-text-1',
207
+ )}
208
+ >
209
+ Playground
210
+ </button>
211
+ <button
212
+ onClick={() => setLabAssistantMode(true)}
213
+ className={cn(
214
+ 'px-3 py-1 text-2xs font-sans font-medium rounded-sm transition-colors cursor-pointer',
215
+ labAssistantMode ? 'text-text-0 bg-surface-4' : 'text-text-3 hover:text-text-1',
216
+ )}
217
+ >
218
+ Assistant
219
+ </button>
220
+ </div>
221
+ )}
222
+ <div className="flex-1" />
223
+ {rightCollapsed && (
224
+ <PanelToggle collapsed onClick={() => setRightCollapsed(false)} side="right" />
225
+ )}
226
+ </div>
227
+ <div className="flex-1 min-h-0">
220
228
  {labAssistantMode && labAssistantAgentId ? <LabAssistant /> : <ChatPlayground />}
221
229
  </div>
222
230
  </div>
@@ -226,16 +234,22 @@ export default function ModelLabView() {
226
234
  {/* Right panel — metrics */}
227
235
  <div
228
236
  className={cn(
229
- 'flex-shrink-0 border-l border-border transition-all duration-200 overflow-hidden',
230
- rightCollapsed && 'w-0 border-l-0',
237
+ 'flex-shrink-0 transition-all duration-200 overflow-hidden',
238
+ rightCollapsed && 'w-0',
231
239
  )}
232
240
  style={rightCollapsed ? undefined : { width: rightWidth }}
233
241
  >
234
- <ScrollArea className="h-full">
235
- <div className="px-4 py-4">
236
- <MetricsPanel />
242
+ <div className="h-full flex flex-col">
243
+ <div className="flex-shrink-0 flex items-center justify-between px-4 h-10">
244
+ <span className="text-2xs font-semibold font-sans text-text-3 uppercase tracking-wider">Metrics</span>
245
+ <PanelToggle collapsed={false} onClick={() => setRightCollapsed(true)} side="right" />
237
246
  </div>
238
- </ScrollArea>
247
+ <ScrollArea className="flex-1 min-h-0">
248
+ <div className="px-4 pb-4">
249
+ <MetricsPanel />
250
+ </div>
251
+ </ScrollArea>
252
+ </div>
239
253
  </div>
240
254
  </div>
241
255
  </div>
@@ -2,7 +2,7 @@
2
2
 
3
3
  import { EMBEDDING_SERVICE_URL } from '../shared/constants.js';
4
4
 
5
- const DEFAULT_MODEL = 'BAAI/bge-small-en-v1.5';
5
+ const DEFAULT_MODEL = 'BAAI/bge-base-en-v1.5';
6
6
  const DEFAULT_TOP_K = 3;
7
7
 
8
8
  // ~40 domains covering broad technical territory.
@@ -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-small-en-v1.5',
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-small-en-v1.5',
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-small-en-v1.5',
501
+ model: 'BAAI/bge-base-en-v1.5',
502
502
  vector: [0.1, 'bad', 0.3],
503
503
  source_text: 'test',
504
504
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "groove-dev",
3
- "version": "0.27.134",
3
+ "version": "0.27.136",
4
4
  "description": "Open-source agent orchestration layer — the AI company OS. Local model agent engine (GGUF/Ollama/llama-server), HuggingFace model browser, MCP integrations (Slack, Gmail, Stripe, 15+), agent scheduling (cron), business roles (CMO, CFO, EA). GUI dashboard, multi-agent coordination, zero cold-start, infinite sessions. Works with Claude Code, Codex, Gemini CLI, Ollama, any local model.",
5
5
  "license": "FSL-1.1-Apache-2.0",
6
6
  "author": "Groove Dev <hello@groovedev.ai> (https://groovedev.ai)",