groove-dev 0.27.194 → 0.27.195
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/deliver.js +5 -1
- package/node_modules/@groove-dev/daemon/src/process.js +45 -0
- package/node_modules/@groove-dev/gui/package.json +1 -1
- package/package.json +1 -1
- package/packages/cli/package.json +1 -1
- package/packages/daemon/package.json +1 -1
- package/packages/daemon/src/deliver.js +5 -1
- package/packages/daemon/src/process.js +45 -0
- package/packages/gui/package.json +1 -1
|
@@ -38,7 +38,11 @@ export async function deliverInstruction(daemon, agentId, message, opts = {}) {
|
|
|
38
38
|
if (daemon.rotator) daemon.rotator.recordUserMessage(agentId);
|
|
39
39
|
}
|
|
40
40
|
|
|
41
|
-
|
|
41
|
+
// Prepend a fresh wall-clock anchor so the agent gauges progress by real time
|
|
42
|
+
// and the user's direction, not by how much context has piled up.
|
|
43
|
+
const clock = daemon.processes.sessionClock?.(agent);
|
|
44
|
+
const timedMessage = clock ? `${clock}\n\n${finalMessage}` : finalMessage;
|
|
45
|
+
const wrappedMessage = wrapWithRoleReminder(agent.role, timedMessage);
|
|
42
46
|
|
|
43
47
|
// Agent loop path — send straight to the running loop.
|
|
44
48
|
if (daemon.processes.hasAgentLoop(agentId)) {
|
|
@@ -362,12 +362,37 @@ export function wrapWithRoleReminder(role, message) {
|
|
|
362
362
|
return message;
|
|
363
363
|
}
|
|
364
364
|
|
|
365
|
+
// A fresh wall-clock anchor, regenerated for every message an agent receives.
|
|
366
|
+
// Agents have no innate sense of elapsed time — they infer "how far along are
|
|
367
|
+
// we" from how much context has piled up, so 15 minutes of dense work (and the
|
|
368
|
+
// session-feel that rotations add) reads as a long day and they start winding
|
|
369
|
+
// down. This gives them the real time and duration each turn, plus an explicit
|
|
370
|
+
// frame that the session is live and open-ended.
|
|
371
|
+
export function sessionClockLine(startISO) {
|
|
372
|
+
const now = new Date();
|
|
373
|
+
const when = now.toLocaleString('en-US', {
|
|
374
|
+
weekday: 'short', month: 'short', day: 'numeric',
|
|
375
|
+
hour: 'numeric', minute: '2-digit', hour12: true,
|
|
376
|
+
});
|
|
377
|
+
let dur = '';
|
|
378
|
+
if (startISO) {
|
|
379
|
+
const mins = Math.max(0, Math.round((now - new Date(startISO)) / 60000));
|
|
380
|
+
const human = mins < 60 ? `${mins} min` : `${Math.floor(mins / 60)}h ${mins % 60}m`;
|
|
381
|
+
dur = ` You've been in this work session about ${human}.`;
|
|
382
|
+
}
|
|
383
|
+
return `[Clock — ${when}.${dur} This is a live session with the user present and engaged. `
|
|
384
|
+
+ 'Judge how far along you are by the task and the user\'s direction, not by how much has '
|
|
385
|
+
+ 'been built or a felt sense of time. Do not wind down, "call it a day," or trim scope on '
|
|
386
|
+
+ 'your own — keep working until the user says to stop.]';
|
|
387
|
+
}
|
|
388
|
+
|
|
365
389
|
export class ProcessManager {
|
|
366
390
|
constructor(daemon) {
|
|
367
391
|
this.daemon = daemon;
|
|
368
392
|
this.handles = new Map(); // agentId -> { proc, logStream }
|
|
369
393
|
this.peakContextUsage = new Map(); // agentId -> highest contextUsage seen
|
|
370
394
|
this.pendingMessages = new Map(); // agentId -> [{ message, timestamp }, …] FIFO queue
|
|
395
|
+
this.sessionStarts = new Map(); // agentName -> ISO start; keyed by name so it survives rotation
|
|
371
396
|
this._streamThrottle = new Map(); // agentId -> { timer, pending }
|
|
372
397
|
this._rotatingAgents = new Set(); // agentIds currently being rotated (rotator wrote handoff)
|
|
373
398
|
this._stalledAgents = new Set(); // agentIds already flagged as stalled (avoids duplicate broadcasts)
|
|
@@ -1184,6 +1209,12 @@ For normal file edits within your scope, proceed without review.
|
|
|
1184
1209
|
}
|
|
1185
1210
|
}
|
|
1186
1211
|
|
|
1212
|
+
// Give the agent a wall-clock anchor on its very first turn. sessionClock
|
|
1213
|
+
// records the session start (by name, so a later rotation keeps it).
|
|
1214
|
+
if (!isOneShotProvider) {
|
|
1215
|
+
spawnConfig.prompt = `${this.sessionClock(agent)}\n\n${spawnConfig.prompt}`;
|
|
1216
|
+
}
|
|
1217
|
+
|
|
1187
1218
|
// Set up log capture (shared between CLI and agent loop paths)
|
|
1188
1219
|
const logDir = resolve(this.daemon.grooveDir, 'logs');
|
|
1189
1220
|
mkdirSync(logDir, { recursive: true });
|
|
@@ -3219,6 +3250,20 @@ After fixing all issues, run tests (npm test) and build (npm run build) to verif
|
|
|
3219
3250
|
return !!(handle?.loop);
|
|
3220
3251
|
}
|
|
3221
3252
|
|
|
3253
|
+
// The fresh clock line for an agent, anchored to when its work session first
|
|
3254
|
+
// began. Keyed by name, so a rotation (new agent id, same name) keeps the
|
|
3255
|
+
// original start rather than resetting the clock to "just now".
|
|
3256
|
+
sessionClock(agent) {
|
|
3257
|
+
if (!agent) return sessionClockLine(null);
|
|
3258
|
+
const key = agent.name || agent.id;
|
|
3259
|
+
let start = this.sessionStarts.get(key);
|
|
3260
|
+
if (!start) {
|
|
3261
|
+
start = agent.metadata?.sessionStartedAt || agent.spawnedAt || agent.createdAt || new Date().toISOString();
|
|
3262
|
+
this.sessionStarts.set(key, start);
|
|
3263
|
+
}
|
|
3264
|
+
return sessionClockLine(start);
|
|
3265
|
+
}
|
|
3266
|
+
|
|
3222
3267
|
queueMessage(agentId, message) {
|
|
3223
3268
|
const agent = this.daemon.registry.get(agentId);
|
|
3224
3269
|
const wrapped = agent ? wrapWithRoleReminder(agent.role, message) : message;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "groove-dev",
|
|
3
|
-
"version": "0.27.
|
|
3
|
+
"version": "0.27.195",
|
|
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)",
|
|
@@ -38,7 +38,11 @@ export async function deliverInstruction(daemon, agentId, message, opts = {}) {
|
|
|
38
38
|
if (daemon.rotator) daemon.rotator.recordUserMessage(agentId);
|
|
39
39
|
}
|
|
40
40
|
|
|
41
|
-
|
|
41
|
+
// Prepend a fresh wall-clock anchor so the agent gauges progress by real time
|
|
42
|
+
// and the user's direction, not by how much context has piled up.
|
|
43
|
+
const clock = daemon.processes.sessionClock?.(agent);
|
|
44
|
+
const timedMessage = clock ? `${clock}\n\n${finalMessage}` : finalMessage;
|
|
45
|
+
const wrappedMessage = wrapWithRoleReminder(agent.role, timedMessage);
|
|
42
46
|
|
|
43
47
|
// Agent loop path — send straight to the running loop.
|
|
44
48
|
if (daemon.processes.hasAgentLoop(agentId)) {
|
|
@@ -362,12 +362,37 @@ export function wrapWithRoleReminder(role, message) {
|
|
|
362
362
|
return message;
|
|
363
363
|
}
|
|
364
364
|
|
|
365
|
+
// A fresh wall-clock anchor, regenerated for every message an agent receives.
|
|
366
|
+
// Agents have no innate sense of elapsed time — they infer "how far along are
|
|
367
|
+
// we" from how much context has piled up, so 15 minutes of dense work (and the
|
|
368
|
+
// session-feel that rotations add) reads as a long day and they start winding
|
|
369
|
+
// down. This gives them the real time and duration each turn, plus an explicit
|
|
370
|
+
// frame that the session is live and open-ended.
|
|
371
|
+
export function sessionClockLine(startISO) {
|
|
372
|
+
const now = new Date();
|
|
373
|
+
const when = now.toLocaleString('en-US', {
|
|
374
|
+
weekday: 'short', month: 'short', day: 'numeric',
|
|
375
|
+
hour: 'numeric', minute: '2-digit', hour12: true,
|
|
376
|
+
});
|
|
377
|
+
let dur = '';
|
|
378
|
+
if (startISO) {
|
|
379
|
+
const mins = Math.max(0, Math.round((now - new Date(startISO)) / 60000));
|
|
380
|
+
const human = mins < 60 ? `${mins} min` : `${Math.floor(mins / 60)}h ${mins % 60}m`;
|
|
381
|
+
dur = ` You've been in this work session about ${human}.`;
|
|
382
|
+
}
|
|
383
|
+
return `[Clock — ${when}.${dur} This is a live session with the user present and engaged. `
|
|
384
|
+
+ 'Judge how far along you are by the task and the user\'s direction, not by how much has '
|
|
385
|
+
+ 'been built or a felt sense of time. Do not wind down, "call it a day," or trim scope on '
|
|
386
|
+
+ 'your own — keep working until the user says to stop.]';
|
|
387
|
+
}
|
|
388
|
+
|
|
365
389
|
export class ProcessManager {
|
|
366
390
|
constructor(daemon) {
|
|
367
391
|
this.daemon = daemon;
|
|
368
392
|
this.handles = new Map(); // agentId -> { proc, logStream }
|
|
369
393
|
this.peakContextUsage = new Map(); // agentId -> highest contextUsage seen
|
|
370
394
|
this.pendingMessages = new Map(); // agentId -> [{ message, timestamp }, …] FIFO queue
|
|
395
|
+
this.sessionStarts = new Map(); // agentName -> ISO start; keyed by name so it survives rotation
|
|
371
396
|
this._streamThrottle = new Map(); // agentId -> { timer, pending }
|
|
372
397
|
this._rotatingAgents = new Set(); // agentIds currently being rotated (rotator wrote handoff)
|
|
373
398
|
this._stalledAgents = new Set(); // agentIds already flagged as stalled (avoids duplicate broadcasts)
|
|
@@ -1184,6 +1209,12 @@ For normal file edits within your scope, proceed without review.
|
|
|
1184
1209
|
}
|
|
1185
1210
|
}
|
|
1186
1211
|
|
|
1212
|
+
// Give the agent a wall-clock anchor on its very first turn. sessionClock
|
|
1213
|
+
// records the session start (by name, so a later rotation keeps it).
|
|
1214
|
+
if (!isOneShotProvider) {
|
|
1215
|
+
spawnConfig.prompt = `${this.sessionClock(agent)}\n\n${spawnConfig.prompt}`;
|
|
1216
|
+
}
|
|
1217
|
+
|
|
1187
1218
|
// Set up log capture (shared between CLI and agent loop paths)
|
|
1188
1219
|
const logDir = resolve(this.daemon.grooveDir, 'logs');
|
|
1189
1220
|
mkdirSync(logDir, { recursive: true });
|
|
@@ -3219,6 +3250,20 @@ After fixing all issues, run tests (npm test) and build (npm run build) to verif
|
|
|
3219
3250
|
return !!(handle?.loop);
|
|
3220
3251
|
}
|
|
3221
3252
|
|
|
3253
|
+
// The fresh clock line for an agent, anchored to when its work session first
|
|
3254
|
+
// began. Keyed by name, so a rotation (new agent id, same name) keeps the
|
|
3255
|
+
// original start rather than resetting the clock to "just now".
|
|
3256
|
+
sessionClock(agent) {
|
|
3257
|
+
if (!agent) return sessionClockLine(null);
|
|
3258
|
+
const key = agent.name || agent.id;
|
|
3259
|
+
let start = this.sessionStarts.get(key);
|
|
3260
|
+
if (!start) {
|
|
3261
|
+
start = agent.metadata?.sessionStartedAt || agent.spawnedAt || agent.createdAt || new Date().toISOString();
|
|
3262
|
+
this.sessionStarts.set(key, start);
|
|
3263
|
+
}
|
|
3264
|
+
return sessionClockLine(start);
|
|
3265
|
+
}
|
|
3266
|
+
|
|
3222
3267
|
queueMessage(agentId, message) {
|
|
3223
3268
|
const agent = this.daemon.registry.get(agentId);
|
|
3224
3269
|
const wrapped = agent ? wrapWithRoleReminder(agent.role, message) : message;
|