groove-dev 0.27.194 → 0.27.196

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.
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@groove-dev/cli",
3
- "version": "0.27.194",
3
+ "version": "0.27.196",
4
4
  "description": "GROOVE CLI — manage AI coding agents from your terminal",
5
5
  "license": "FSL-1.1-Apache-2.0",
6
6
  "type": "module",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@groove-dev/daemon",
3
- "version": "0.27.194",
3
+ "version": "0.27.196",
4
4
  "description": "GROOVE daemon — agent orchestration engine",
5
5
  "license": "FSL-1.1-Apache-2.0",
6
6
  "type": "module",
@@ -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
- const wrappedMessage = wrapWithRoleReminder(agent.role, finalMessage);
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;
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@groove-dev/gui",
3
- "version": "0.27.194",
3
+ "version": "0.27.196",
4
4
  "description": "GROOVE GUI — visual agent control plane",
5
5
  "license": "FSL-1.1-Apache-2.0",
6
6
  "type": "module",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "groove-dev",
3
- "version": "0.27.194",
3
+ "version": "0.27.196",
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)",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@groove-dev/cli",
3
- "version": "0.27.194",
3
+ "version": "0.27.196",
4
4
  "description": "GROOVE CLI — manage AI coding agents from your terminal",
5
5
  "license": "FSL-1.1-Apache-2.0",
6
6
  "type": "module",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@groove-dev/daemon",
3
- "version": "0.27.194",
3
+ "version": "0.27.196",
4
4
  "description": "GROOVE daemon — agent orchestration engine",
5
5
  "license": "FSL-1.1-Apache-2.0",
6
6
  "type": "module",
@@ -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
- const wrappedMessage = wrapWithRoleReminder(agent.role, finalMessage);
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;
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@groove-dev/gui",
3
- "version": "0.27.194",
3
+ "version": "0.27.196",
4
4
  "description": "GROOVE GUI — visual agent control plane",
5
5
  "license": "FSL-1.1-Apache-2.0",
6
6
  "type": "module",