groove-dev 0.27.28 → 0.27.30
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/package.json +1 -1
- package/node_modules/@groove-dev/daemon/src/journalist.js +103 -45
- package/node_modules/@groove-dev/daemon/test/journalist.test.js +1 -1
- package/node_modules/@groove-dev/gui/dist/assets/{index-CjjmUhoW.css → index-BwNjgBny.css} +1 -1
- package/node_modules/@groove-dev/gui/dist/assets/{index-Ch1N9G4Z.js → index-PxWmJjcJ.js} +290 -290
- package/node_modules/@groove-dev/gui/dist/index.html +2 -2
- package/node_modules/@groove-dev/gui/package.json +1 -1
- package/node_modules/@groove-dev/gui/src/app.css +10 -0
- package/node_modules/@groove-dev/gui/src/components/agents/agent-config.jsx +2 -2
- package/node_modules/@groove-dev/gui/src/components/agents/spawn-wizard.jsx +4 -4
- package/node_modules/@groove-dev/gui/src/components/editor/terminal.jsx +21 -13
- package/node_modules/@groove-dev/gui/src/components/layout/terminal-panel.jsx +1 -1
- package/node_modules/@groove-dev/gui/src/views/settings.jsx +4 -4
- package/package.json +1 -1
- package/packages/cli/package.json +1 -1
- package/packages/daemon/package.json +1 -1
- package/packages/daemon/src/journalist.js +103 -45
- package/packages/gui/dist/assets/{index-CjjmUhoW.css → index-BwNjgBny.css} +1 -1
- package/packages/gui/dist/assets/{index-Ch1N9G4Z.js → index-PxWmJjcJ.js} +290 -290
- package/packages/gui/dist/index.html +2 -2
- package/packages/gui/package.json +1 -1
- package/packages/gui/src/app.css +10 -0
- package/packages/gui/src/components/agents/agent-config.jsx +2 -2
- package/packages/gui/src/components/agents/spawn-wizard.jsx +4 -4
- package/packages/gui/src/components/editor/terminal.jsx +21 -13
- package/packages/gui/src/components/layout/terminal-panel.jsx +1 -1
- package/packages/gui/src/views/settings.jsx +4 -4
- package/.groove-staging/state.json +0 -3
- package/.groove-staging/timeline.json +0 -13
- package/DECENTRALIZED_NET_WP_V1.md +0 -871
- package/decentralized-net/ACTION_PLAN.md +0 -422
|
@@ -6,12 +6,12 @@
|
|
|
6
6
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
7
7
|
<link rel="icon" type="image/png" href="/favicon.png" />
|
|
8
8
|
<title>Groove GUI</title>
|
|
9
|
-
<script type="module" crossorigin src="/assets/index-
|
|
9
|
+
<script type="module" crossorigin src="/assets/index-PxWmJjcJ.js"></script>
|
|
10
10
|
<link rel="modulepreload" crossorigin href="/assets/vendor-C0HXlhrU.js">
|
|
11
11
|
<link rel="modulepreload" crossorigin href="/assets/reactflow-BQPfi37R.js">
|
|
12
12
|
<link rel="modulepreload" crossorigin href="/assets/codemirror-BBL3i_JW.js">
|
|
13
13
|
<link rel="modulepreload" crossorigin href="/assets/xterm--7_ns2zW.js">
|
|
14
|
-
<link rel="stylesheet" crossorigin href="/assets/index-
|
|
14
|
+
<link rel="stylesheet" crossorigin href="/assets/index-BwNjgBny.css">
|
|
15
15
|
</head>
|
|
16
16
|
<body>
|
|
17
17
|
<div id="root"></div>
|
|
@@ -80,6 +80,16 @@ html {
|
|
|
80
80
|
::-webkit-scrollbar-thumb:hover { background: var(--color-surface-6); }
|
|
81
81
|
::selection { background: rgba(51, 175, 188, 0.25); }
|
|
82
82
|
|
|
83
|
+
/* ── Terminal (xterm) ────────────────────────────────────── */
|
|
84
|
+
|
|
85
|
+
.xterm-viewport::-webkit-scrollbar { width: 6px; }
|
|
86
|
+
.xterm-viewport::-webkit-scrollbar-track { background: transparent; }
|
|
87
|
+
.xterm-viewport::-webkit-scrollbar-thumb { background: var(--color-surface-5); border-radius: 3px; }
|
|
88
|
+
.xterm-viewport::-webkit-scrollbar-thumb:hover { background: var(--color-surface-6); }
|
|
89
|
+
.xterm-viewport { scrollbar-width: thin; scrollbar-color: var(--color-surface-5) transparent; }
|
|
90
|
+
.xterm { padding: 0; }
|
|
91
|
+
.xterm-screen { width: 100% !important; }
|
|
92
|
+
|
|
83
93
|
/* ── Animations ───────────────────────────────────────────── */
|
|
84
94
|
|
|
85
95
|
@keyframes pulse {
|
|
@@ -366,7 +366,7 @@ export function AgentConfig({ agent }) {
|
|
|
366
366
|
<div className="space-y-1.5">
|
|
367
367
|
{providers.map((p) => {
|
|
368
368
|
const isActive = p.id === agent.provider;
|
|
369
|
-
const available = p.installed || p.hasKey;
|
|
369
|
+
const available = p.authType === 'subscription' ? (p.installed || p.authStatus?.authenticated) : p.authType === 'local' ? p.installed : (p.installed && p.hasKey);
|
|
370
370
|
const isExpanded = expandedProvider === p.id;
|
|
371
371
|
const models = p.models || [];
|
|
372
372
|
return (
|
|
@@ -381,7 +381,7 @@ export function AgentConfig({ agent }) {
|
|
|
381
381
|
{p.name || p.id}
|
|
382
382
|
</span>
|
|
383
383
|
{isActive && <Badge variant="accent" className="text-2xs">Active</Badge>}
|
|
384
|
-
{!available && <span className="text-2xs text-text-4 font-sans">{p.
|
|
384
|
+
{!available && <span className="text-2xs text-text-4 font-sans">{!p.installed ? 'Not installed' : 'No key'}</span>}
|
|
385
385
|
<ChevronDown size={12} className={cn('text-text-4 transition-transform', isExpanded && 'rotate-180')} />
|
|
386
386
|
</button>
|
|
387
387
|
|
|
@@ -90,14 +90,14 @@ export function SpawnWizard() {
|
|
|
90
90
|
const selectedRole = role || customRole;
|
|
91
91
|
const selectedProvider = providers.find((p) => p.id === provider);
|
|
92
92
|
const availableModels = selectedProvider?.models || [];
|
|
93
|
-
const installedProviders = providers.filter((p) => p.installed);
|
|
93
|
+
const installedProviders = providers.filter((p) => p.authType === 'api-key' ? (p.installed && p.hasKey) : p.installed);
|
|
94
94
|
|
|
95
95
|
useEffect(() => {
|
|
96
96
|
if (open) {
|
|
97
97
|
fetchProviders().then((data) => {
|
|
98
98
|
const list = Array.isArray(data) ? data : data.providers || [];
|
|
99
99
|
setProviders(list);
|
|
100
|
-
const installed = list.filter((p) => p.installed);
|
|
100
|
+
const installed = list.filter((p) => p.authType === 'api-key' ? (p.installed && p.hasKey) : p.installed);
|
|
101
101
|
if (installed.length > 0 && !provider) {
|
|
102
102
|
const priority = ['claude-code', 'gemini', 'codex', 'ollama'];
|
|
103
103
|
const best = priority.find((pid) => installed.some((p) => p.id === pid)) || installed[0].id;
|
|
@@ -408,8 +408,8 @@ export function SpawnWizard() {
|
|
|
408
408
|
>
|
|
409
409
|
<option value="">Auto</option>
|
|
410
410
|
{providers.map((p) => (
|
|
411
|
-
<option key={p.id} value={p.id} disabled={!p.installed}>
|
|
412
|
-
{p.name}{!p.installed ? ' (
|
|
411
|
+
<option key={p.id} value={p.id} disabled={p.authType === 'api-key' ? !(p.installed && p.hasKey) : !p.installed}>
|
|
412
|
+
{p.name}{!p.installed ? ' (Not installed)' : (p.authType === 'api-key' && !p.hasKey) ? ' (No API key)' : ''}
|
|
413
413
|
</option>
|
|
414
414
|
))}
|
|
415
415
|
</select>
|
|
@@ -8,14 +8,15 @@ import { TerminalPanel } from '../layout/terminal-panel';
|
|
|
8
8
|
|
|
9
9
|
const THEME = {
|
|
10
10
|
background: '#1a1e25',
|
|
11
|
-
foreground: '#
|
|
11
|
+
foreground: '#c8ccd4',
|
|
12
12
|
cursor: '#33afbc',
|
|
13
13
|
cursorAccent: '#1a1e25',
|
|
14
|
-
selectionBackground: 'rgba(51, 175, 188, 0.
|
|
14
|
+
selectionBackground: 'rgba(51, 175, 188, 0.3)',
|
|
15
|
+
selectionForeground: '#ffffff',
|
|
15
16
|
black: '#1a1e25', red: '#e06c75', green: '#4ae168', yellow: '#e5c07b',
|
|
16
|
-
blue: '#61afef', magenta: '#c678dd', cyan: '#33afbc', white: '#
|
|
17
|
-
brightBlack: '#
|
|
18
|
-
brightBlue: '#61afef', brightMagenta: '#c678dd', brightCyan: '#
|
|
17
|
+
blue: '#61afef', magenta: '#c678dd', cyan: '#33afbc', white: '#abb2bf',
|
|
18
|
+
brightBlack: '#5c6370', brightRed: '#f07178', brightGreen: '#4ae168', brightYellow: '#e5c07b',
|
|
19
|
+
brightBlue: '#61afef', brightMagenta: '#c678dd', brightCyan: '#56b6c2', brightWhite: '#ffffff',
|
|
19
20
|
};
|
|
20
21
|
|
|
21
22
|
let tabCounter = 0;
|
|
@@ -35,12 +36,19 @@ function TerminalInstance({ tabId, visible }) {
|
|
|
35
36
|
const term = new XTerm({
|
|
36
37
|
theme: THEME,
|
|
37
38
|
fontFamily: "'JetBrains Mono Variable', 'SF Mono', monospace",
|
|
38
|
-
fontSize:
|
|
39
|
-
lineHeight: 1.
|
|
39
|
+
fontSize: 12,
|
|
40
|
+
lineHeight: 1.1,
|
|
41
|
+
letterSpacing: 0,
|
|
40
42
|
cursorBlink: true,
|
|
41
43
|
cursorStyle: 'bar',
|
|
42
|
-
|
|
44
|
+
cursorWidth: 1,
|
|
45
|
+
scrollback: 10000,
|
|
43
46
|
allowProposedApi: true,
|
|
47
|
+
minimumContrastRatio: 1,
|
|
48
|
+
drawBoldTextInBrightColors: true,
|
|
49
|
+
fontWeight: '400',
|
|
50
|
+
fontWeightBold: '600',
|
|
51
|
+
overviewRulerWidth: 0,
|
|
44
52
|
});
|
|
45
53
|
|
|
46
54
|
const fitAddon = new FitAddon();
|
|
@@ -50,10 +58,6 @@ function TerminalInstance({ tabId, visible }) {
|
|
|
50
58
|
termRef.current = term;
|
|
51
59
|
fitRef.current = fitAddon;
|
|
52
60
|
|
|
53
|
-
requestAnimationFrame(() => {
|
|
54
|
-
try { fitAddon.fit(); } catch {}
|
|
55
|
-
});
|
|
56
|
-
|
|
57
61
|
let spawnAttempts = 0;
|
|
58
62
|
function trySpawn() {
|
|
59
63
|
spawnAttempts++;
|
|
@@ -97,7 +101,11 @@ function TerminalInstance({ tabId, visible }) {
|
|
|
97
101
|
});
|
|
98
102
|
}
|
|
99
103
|
|
|
100
|
-
|
|
104
|
+
// Fit first, then spawn — ensures PTY gets correct column count
|
|
105
|
+
requestAnimationFrame(() => {
|
|
106
|
+
try { fitAddon.fit(); } catch {}
|
|
107
|
+
trySpawn();
|
|
108
|
+
});
|
|
101
109
|
|
|
102
110
|
const observer = new ResizeObserver(() => {
|
|
103
111
|
requestAnimationFrame(() => { try { fitAddon.fit(); } catch {} });
|
|
@@ -52,7 +52,7 @@ function ProviderCard({ provider, onKeyChange }) {
|
|
|
52
52
|
const isSubscription = provider.authType === 'subscription';
|
|
53
53
|
const isReady = isLocal ? provider.installed
|
|
54
54
|
: isSubscription ? (provider.installed || provider.authStatus?.authenticated)
|
|
55
|
-
: provider.hasKey;
|
|
55
|
+
: (provider.installed && provider.hasKey);
|
|
56
56
|
|
|
57
57
|
async function handleSetKey() {
|
|
58
58
|
if (!keyInput.trim()) return;
|
|
@@ -149,7 +149,7 @@ function ProviderCard({ provider, onKeyChange }) {
|
|
|
149
149
|
{isReady ? (
|
|
150
150
|
<Badge variant="success" className="text-2xs gap-1"><Check size={8} /> Ready</Badge>
|
|
151
151
|
) : (
|
|
152
|
-
<Badge variant="default" className="text-2xs">{
|
|
152
|
+
<Badge variant="default" className="text-2xs">{!provider.installed ? 'Not installed' : isSubscription ? 'Not authenticated' : 'No key'}</Badge>
|
|
153
153
|
)}
|
|
154
154
|
</div>
|
|
155
155
|
|
|
@@ -999,7 +999,7 @@ export default function SettingsView() {
|
|
|
999
999
|
const connectedCount = providers.filter((p) => {
|
|
1000
1000
|
if (p.authType === 'local') return p.installed;
|
|
1001
1001
|
if (p.authType === 'subscription') return p.installed;
|
|
1002
|
-
return p.hasKey;
|
|
1002
|
+
return p.installed && p.hasKey;
|
|
1003
1003
|
}).length;
|
|
1004
1004
|
|
|
1005
1005
|
// Rotation threshold display: 0 = auto, otherwise show as percentage
|
|
@@ -1074,7 +1074,7 @@ export default function SettingsView() {
|
|
|
1074
1074
|
onChange={(e) => updateConfig('defaultProvider', e.target.value)}
|
|
1075
1075
|
className="w-full h-8 px-2.5 text-xs bg-surface-0 border border-border-subtle rounded-md text-text-0 font-mono focus:outline-none focus:ring-1 focus:ring-accent cursor-pointer"
|
|
1076
1076
|
>
|
|
1077
|
-
{providers.filter((p) => p.installed || p.hasKey).map((p) => (
|
|
1077
|
+
{providers.filter((p) => p.installed && (p.authType === 'local' || p.authType === 'subscription' || p.hasKey)).map((p) => (
|
|
1078
1078
|
<option key={p.id} value={p.id}>{p.name}</option>
|
|
1079
1079
|
))}
|
|
1080
1080
|
</select>
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "groove-dev",
|
|
3
|
-
"version": "0.27.
|
|
3
|
+
"version": "0.27.30",
|
|
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)",
|
|
@@ -166,7 +166,7 @@ export class Journalist {
|
|
|
166
166
|
return false;
|
|
167
167
|
}
|
|
168
168
|
|
|
169
|
-
collectFilteredLogs(agents) {
|
|
169
|
+
collectFilteredLogs(agents, { since } = {}) {
|
|
170
170
|
const result = {};
|
|
171
171
|
|
|
172
172
|
for (const agent of agents) {
|
|
@@ -181,7 +181,7 @@ export class Journalist {
|
|
|
181
181
|
const size = Buffer.byteLength(content);
|
|
182
182
|
this.lastLogSizes[agent.id] = size;
|
|
183
183
|
|
|
184
|
-
const { entries, explorationEntries } = this.filterLog(content, agent);
|
|
184
|
+
const { entries, explorationEntries } = this.filterLog(content, agent, { since });
|
|
185
185
|
result[agent.id] = { agent, entries, explorationEntries };
|
|
186
186
|
} catch {
|
|
187
187
|
result[agent.id] = { agent, entries: [], explorationEntries: [] };
|
|
@@ -191,7 +191,7 @@ export class Journalist {
|
|
|
191
191
|
return result;
|
|
192
192
|
}
|
|
193
193
|
|
|
194
|
-
filterLog(rawLog, agent) {
|
|
194
|
+
filterLog(rawLog, agent, { since } = {}) {
|
|
195
195
|
// Parse stream-json lines and extract meaningful events.
|
|
196
196
|
// Focus on PROGRESS (writes, edits, commands with results) not EXPLORATION (reads, greps).
|
|
197
197
|
// Exploration tools are tracked separately so handoff briefs can include what was examined.
|
|
@@ -200,12 +200,15 @@ export class Journalist {
|
|
|
200
200
|
const lines = rawLog.split('\n');
|
|
201
201
|
const toolResults = new Map(); // tool_use_id -> result text
|
|
202
202
|
|
|
203
|
+
const sinceDate = since ? new Date(since) : null;
|
|
204
|
+
|
|
203
205
|
// First pass: collect tool results (and error flags) so we can attach them to tool calls
|
|
204
206
|
const toolErrors = new Set(); // tool_use_ids that returned errors
|
|
205
207
|
for (const line of lines) {
|
|
206
208
|
if (!line.trim() || line.startsWith('[')) continue;
|
|
207
209
|
try {
|
|
208
210
|
const data = JSON.parse(line);
|
|
211
|
+
if (sinceDate && data.timestamp && new Date(data.timestamp) < sinceDate) continue;
|
|
209
212
|
if (data.type === 'user' && data.message?.content) {
|
|
210
213
|
const content = Array.isArray(data.message.content) ? data.message.content : [];
|
|
211
214
|
for (const block of content) {
|
|
@@ -227,6 +230,7 @@ export class Journalist {
|
|
|
227
230
|
|
|
228
231
|
try {
|
|
229
232
|
const data = JSON.parse(line);
|
|
233
|
+
if (sinceDate && data.timestamp && new Date(data.timestamp) < sinceDate) continue;
|
|
230
234
|
|
|
231
235
|
// Tool use — only keep WRITES, EDITS, and COMMANDS (progress, not exploration)
|
|
232
236
|
if (data.type === 'assistant' && data.message?.content) {
|
|
@@ -415,6 +419,56 @@ export class Journalist {
|
|
|
415
419
|
return parts.join('\n');
|
|
416
420
|
}
|
|
417
421
|
|
|
422
|
+
buildRotationSynthesisPrompt(agent, entries, options = {}) {
|
|
423
|
+
const dir = agent.workingDir ? `\nWorking directory: ${agent.workingDir}` : '';
|
|
424
|
+
const reason = options.reason || 'manual rotation';
|
|
425
|
+
|
|
426
|
+
const parts = [
|
|
427
|
+
'You are briefing the next AI agent taking over this exact role.',
|
|
428
|
+
`The previous agent (${agent.name}, role: ${agent.role}) is being rotated out.`,
|
|
429
|
+
`Scope: ${agent.scope?.join(', ') || 'unrestricted'}${dir}`,
|
|
430
|
+
`Rotation reason: ${reason}`,
|
|
431
|
+
'',
|
|
432
|
+
'Analyze the session log below and produce a structured handoff brief.',
|
|
433
|
+
'',
|
|
434
|
+
'Output EXACTLY these sections:',
|
|
435
|
+
'',
|
|
436
|
+
'## Accomplishments',
|
|
437
|
+
'(What was completed. Name files, functions, and line numbers.)',
|
|
438
|
+
'',
|
|
439
|
+
'## In Progress',
|
|
440
|
+
'(What was actively being worked on when rotation happened.)',
|
|
441
|
+
'',
|
|
442
|
+
'## Key Decisions',
|
|
443
|
+
'(Architectural or implementation choices made and why.)',
|
|
444
|
+
'',
|
|
445
|
+
'## Blockers/Errors',
|
|
446
|
+
'(Unresolved errors, failed attempts, things that did not work.)',
|
|
447
|
+
'',
|
|
448
|
+
'## Next Steps',
|
|
449
|
+
'(What should be done next, in priority order.)',
|
|
450
|
+
'',
|
|
451
|
+
'Be specific. Name files, functions, and line numbers. Do not summarize vaguely.',
|
|
452
|
+
'Keep your response under 2000 characters.',
|
|
453
|
+
'',
|
|
454
|
+
'---',
|
|
455
|
+
'',
|
|
456
|
+
`### Session Log for ${agent.name} (${agent.role})`,
|
|
457
|
+
'',
|
|
458
|
+
];
|
|
459
|
+
|
|
460
|
+
let totalChars = 0;
|
|
461
|
+
const cap = 30_000;
|
|
462
|
+
for (const entry of entries.slice(-200)) {
|
|
463
|
+
const line = this.formatEntry(entry);
|
|
464
|
+
if (totalChars + line.length > cap) break;
|
|
465
|
+
parts.push(line);
|
|
466
|
+
totalChars += line.length;
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
return parts.join('\n');
|
|
470
|
+
}
|
|
471
|
+
|
|
418
472
|
formatEntry(entry) {
|
|
419
473
|
switch (entry.type) {
|
|
420
474
|
case 'tool': {
|
|
@@ -739,35 +793,10 @@ export class Journalist {
|
|
|
739
793
|
// --- Handoff Brief for Context Rotation ---
|
|
740
794
|
|
|
741
795
|
async generateHandoffBrief(agent, options = {}) {
|
|
742
|
-
const filteredLogs = this.collectFilteredLogs([agent]);
|
|
796
|
+
const filteredLogs = this.collectFilteredLogs([agent], { since: agent.spawnedAt });
|
|
743
797
|
const agentLog = filteredLogs[agent.id];
|
|
744
798
|
const entries = agentLog?.entries || [];
|
|
745
799
|
|
|
746
|
-
const errorSummary = entries
|
|
747
|
-
.filter((e) => e.type === 'error')
|
|
748
|
-
.map((e) => `- ${e.text}`)
|
|
749
|
-
.slice(-10)
|
|
750
|
-
.join('\n');
|
|
751
|
-
|
|
752
|
-
const resultSummary = entries
|
|
753
|
-
.filter((e) => e.type === 'result')
|
|
754
|
-
.map((e) => e.text)
|
|
755
|
-
.slice(-3)
|
|
756
|
-
.join('\n');
|
|
757
|
-
|
|
758
|
-
// Build file changes section — group Edit/Write operations by file path
|
|
759
|
-
const fileChanges = {};
|
|
760
|
-
for (const e of entries.filter((e) => e.type === 'tool' && (e.tool === 'Edit' || e.tool === 'Write'))) {
|
|
761
|
-
const file = e.input || 'unknown';
|
|
762
|
-
if (!fileChanges[file]) fileChanges[file] = [];
|
|
763
|
-
if (e.diff) fileChanges[file].push(e.diff);
|
|
764
|
-
else fileChanges[file].push(e.tool === 'Write' ? 'created' : 'modified');
|
|
765
|
-
}
|
|
766
|
-
const fileChangesSummary = Object.entries(fileChanges)
|
|
767
|
-
.map(([file, changes]) => `- **${file}**: ${changes.slice(0, 3).join('; ')}`)
|
|
768
|
-
.slice(0, 20)
|
|
769
|
-
.join('\n');
|
|
770
|
-
|
|
771
800
|
// Layer 7 memory: discoveries, constraints, specializations
|
|
772
801
|
const discoveries = this.daemon.memory?.getDiscoveriesMarkdown(agent.role, 10, 2000) || '';
|
|
773
802
|
const constraints = this.daemon.memory?.getConstraintsMarkdown(2000) || '';
|
|
@@ -776,26 +805,58 @@ export class Journalist {
|
|
|
776
805
|
? `- Quality profile: ${specialization.avgQualityScore}/100 across ${specialization.sessionCount} sessions`
|
|
777
806
|
: '';
|
|
778
807
|
|
|
779
|
-
// Pull recent rotation history from persistent memory (Layer 7).
|
|
780
|
-
// Gives the new agent causal continuity: what the last 3 agents struggled
|
|
781
|
-
// with, decided, and solved — not just what the current session did.
|
|
782
808
|
const recentChain = this.daemon.memory?.getRecentHandoffMarkdown(agent.role, 3, 3000, agent.workingDir, agent.teamId) || '';
|
|
783
809
|
|
|
784
|
-
// Pull the user's recent messages scoped to this agent
|
|
785
810
|
const agentFeedback = this.getUserFeedback(agent.id).slice(-5);
|
|
786
811
|
const conversationSummary = agentFeedback.length > 0
|
|
787
812
|
? agentFeedback.map((fb) => `- "${fb.message}"`).join('\n')
|
|
788
813
|
: '';
|
|
789
814
|
|
|
790
|
-
//
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
.
|
|
794
|
-
|
|
795
|
-
|
|
815
|
+
// Try AI-synthesized session summary
|
|
816
|
+
let sessionSummary = '';
|
|
817
|
+
try {
|
|
818
|
+
const prompt = this.buildRotationSynthesisPrompt(agent, entries, options);
|
|
819
|
+
sessionSummary = await this.callHeadless(prompt, { trackAs: '__rotation__' });
|
|
820
|
+
} catch {
|
|
821
|
+
// Fallback: structural summary from raw logs
|
|
822
|
+
const errorSummary = entries
|
|
823
|
+
.filter((e) => e.type === 'error')
|
|
824
|
+
.map((e) => `- ${e.text}`)
|
|
825
|
+
.slice(-10)
|
|
826
|
+
.join('\n');
|
|
827
|
+
|
|
828
|
+
const resultSummary = entries
|
|
829
|
+
.filter((e) => e.type === 'result')
|
|
830
|
+
.map((e) => e.text)
|
|
831
|
+
.slice(-3)
|
|
832
|
+
.join('\n');
|
|
833
|
+
|
|
834
|
+
const fileChanges = {};
|
|
835
|
+
for (const e of entries.filter((e) => e.type === 'tool' && (e.tool === 'Edit' || e.tool === 'Write'))) {
|
|
836
|
+
const file = e.input || 'unknown';
|
|
837
|
+
if (!fileChanges[file]) fileChanges[file] = [];
|
|
838
|
+
if (e.diff) fileChanges[file].push(e.diff);
|
|
839
|
+
else fileChanges[file].push(e.tool === 'Write' ? 'created' : 'modified');
|
|
840
|
+
}
|
|
841
|
+
const fileChangesSummary = Object.entries(fileChanges)
|
|
842
|
+
.map(([file, changes]) => `- **${file}**: ${changes.slice(0, 3).join('; ')}`)
|
|
843
|
+
.slice(0, 20)
|
|
844
|
+
.join('\n');
|
|
845
|
+
|
|
846
|
+
const recentTools = entries
|
|
847
|
+
.filter((e) => e.type === 'tool' || e.type === 'error')
|
|
848
|
+
.slice(-5)
|
|
849
|
+
.map((e) => `- ${e.type === 'error' ? 'ERROR ' : ''}${e.tool}: ${(e.input || '').slice(0, 80)}`)
|
|
850
|
+
.join('\n');
|
|
851
|
+
|
|
852
|
+
const fallbackParts = [];
|
|
853
|
+
if (errorSummary) fallbackParts.push(`## Unresolved Errors\n\n${errorSummary}`);
|
|
854
|
+
if (recentTools) fallbackParts.push(`## Last 5 Tool Calls\n\n${recentTools}`);
|
|
855
|
+
if (fileChangesSummary) fallbackParts.push(`## Files Modified\n\n${fileChangesSummary}`);
|
|
856
|
+
if (resultSummary) fallbackParts.push(`## Accomplishments\n\n${resultSummary}`);
|
|
857
|
+
sessionSummary = fallbackParts.join('\n\n');
|
|
858
|
+
}
|
|
796
859
|
|
|
797
|
-
// Brief priority: errors > constraints > recent tools > accomplishments
|
|
798
|
-
// The rotator already wraps this with session continuation context
|
|
799
860
|
return [
|
|
800
861
|
`# Handoff Brief — ${agent.name} (${agent.role})`,
|
|
801
862
|
``,
|
|
@@ -804,13 +865,10 @@ export class Journalist {
|
|
|
804
865
|
`Rotation: ${options.reason || 'manual'}${options.qualityScore ? ` (quality: ${options.qualityScore}/100)` : ''} | Tokens: ${agent.tokensUsed}`,
|
|
805
866
|
specLine,
|
|
806
867
|
``,
|
|
807
|
-
|
|
868
|
+
sessionSummary ? `## Session Summary\n\n${sessionSummary}\n` : '',
|
|
808
869
|
constraints ? `## Project Constraints (must follow)\n\n${constraints}\n` : '',
|
|
809
870
|
discoveries ? `## Known Issues & Fixes\n\n${discoveries}\n` : '',
|
|
810
871
|
conversationSummary ? `## Recent User Messages\n\n${conversationSummary}\n` : '',
|
|
811
|
-
recentTools ? `## Last 5 Tool Calls\n\n${recentTools}\n` : '',
|
|
812
|
-
fileChangesSummary ? `## Files Modified\n\n${fileChangesSummary}\n` : '',
|
|
813
|
-
resultSummary ? `## Accomplishments\n\n${resultSummary}\n` : '',
|
|
814
872
|
recentChain ? `## Rotation History\n\n${recentChain}\n` : '',
|
|
815
873
|
agent.prompt ? `## Original Task\n\n${agent.prompt}\n` : '',
|
|
816
874
|
``,
|