neoagent 2.3.1-beta.63 → 2.3.1-beta.64

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.
@@ -2,8 +2,13 @@ const crypto = require('crypto');
2
2
  const db = require('../../db/database');
3
3
  const { resolveAgentId } = require('../agents/manager');
4
4
  const { findNextRun, getMinimumIntervalMinutes } = require('../tasks/schedule_utils');
5
+ const {
6
+ buildAssistantFocusSnapshot,
7
+ buildAssistantFocusWidgetPayload,
8
+ } = require('./focus_widget');
5
9
 
6
10
  const MIN_WIDGET_REFRESH_MINUTES = 60;
11
+ const SYSTEM_WIDGET_KEY_ASSISTANT_FOCUS = 'assistant_focus';
7
12
 
8
13
  const TEMPLATE_VARIANTS = {
9
14
  stat: ['hero', 'split', 'compact'],
@@ -102,6 +107,9 @@ function normalizeWidgetInput(input = {}, userId) {
102
107
  layoutVariant,
103
108
  refreshCron,
104
109
  enabled: input.enabled !== false,
110
+ widgetKind: normalizeText(input.widgetKind || input.widget_kind || 'custom', 40).toLowerCase() || 'custom',
111
+ systemKey: normalizeOptionalText(input.systemKey || input.system_key, 80),
112
+ isSystem: input.isSystem === true || input.is_system === true,
105
113
  definition: normalizeDefinition(input.definition || input.definition_json || {
106
114
  prompt: input.prompt || input.refreshPrompt || input.refresh_prompt || '',
107
115
  description: input.description || '',
@@ -226,6 +234,8 @@ function validateSnapshotPayload(widget, snapshot = {}) {
226
234
  class WidgetService {
227
235
  constructor({ app }) {
228
236
  this.app = app;
237
+ this._pendingSystemWidgetTaskSetup = new Set();
238
+ this._pendingSystemWidgetRefresh = new Set();
229
239
  }
230
240
 
231
241
  get taskRuntime() {
@@ -237,6 +247,7 @@ class WidgetService {
237
247
  }
238
248
 
239
249
  getWidget(userId, widgetId) {
250
+ this.ensureSystemFocusWidget(userId);
240
251
  const row = db.prepare(
241
252
  `SELECT *
242
253
  FROM ai_widgets
@@ -251,7 +262,11 @@ class WidgetService {
251
262
  }
252
263
 
253
264
  listWidgets(userId, { agentId = null } = {}) {
265
+ this.ensureSystemFocusWidget(userId);
254
266
  const scopedAgentId = agentId ? resolveAgentId(userId, agentId) : null;
267
+ if (scopedAgentId) {
268
+ this.ensureSystemFocusWidget(userId, { agentId: scopedAgentId });
269
+ }
255
270
  const rows = scopedAgentId
256
271
  ? db.prepare(
257
272
  `SELECT *
@@ -287,14 +302,17 @@ class WidgetService {
287
302
  const tx = db.transaction(() => {
288
303
  db.prepare(
289
304
  `INSERT INTO ai_widgets (
290
- id, user_id, agent_id, name, template, layout_variant, definition_json,
305
+ id, user_id, agent_id, name, widget_kind, system_key, is_system, template, layout_variant, definition_json,
291
306
  refresh_cron, enabled, created_at, updated_at
292
- ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, datetime('now'), datetime('now'))`
307
+ ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, datetime('now'), datetime('now'))`
293
308
  ).run(
294
309
  widgetId,
295
310
  userId,
296
311
  normalized.agentId,
297
312
  normalized.name,
313
+ normalized.widgetKind,
314
+ normalized.systemKey,
315
+ normalized.isSystem ? 1 : 0,
298
316
  normalized.template,
299
317
  normalized.layoutVariant,
300
318
  JSON.stringify(normalized.definition),
@@ -347,6 +365,9 @@ class WidgetService {
347
365
  if (!existingRow) {
348
366
  throw new Error('Widget not found.');
349
367
  }
368
+ if (existingRow.is_system) {
369
+ throw new Error('System widgets cannot be edited directly.');
370
+ }
350
371
 
351
372
  const current = this._serializeWidget(existingRow, null, []);
352
373
  const normalized = normalizeWidgetInput({
@@ -441,6 +462,9 @@ class WidgetService {
441
462
  if (!existingRow) {
442
463
  throw new Error('Widget not found.');
443
464
  }
465
+ if (existingRow.is_system) {
466
+ throw new Error('System widgets cannot be deleted.');
467
+ }
444
468
 
445
469
  const taskRuntime = this.taskRuntime;
446
470
  const tx = db.transaction(() => {
@@ -494,6 +518,31 @@ class WidgetService {
494
518
  return { skipped: true, reason: 'disabled' };
495
519
  }
496
520
  const engine = this.agentEngine;
521
+ if (widget.isSystem && widget.systemKey === SYSTEM_WIDGET_KEY_ASSISTANT_FOCUS) {
522
+ const memoryManager = this.app?.locals?.memoryManager || null;
523
+ const snapshot = buildAssistantFocusSnapshot(memoryManager, userId, widget.agentId);
524
+ if (memoryManager?.updateAssistantSelfState) {
525
+ memoryManager.updateAssistantSelfState(userId, {
526
+ focus: {
527
+ currentFocus: snapshot.currentFocus,
528
+ activeThreads: snapshot.activeThreads,
529
+ nearTermPriorities: snapshot.nearTermPriorities,
530
+ recentSignals: snapshot.recentSignals,
531
+ rememberedContext: snapshot.rememberedContext,
532
+ generatedAt: snapshot.generatedAt,
533
+ },
534
+ }, { agentId: widget.agentId });
535
+ }
536
+ return {
537
+ widgetId,
538
+ snapshot: this.saveSnapshot(
539
+ userId,
540
+ widgetId,
541
+ buildAssistantFocusWidgetPayload(snapshot),
542
+ { sourceRunId: null, status: 'ready' },
543
+ ),
544
+ };
545
+ }
497
546
  if (!engine) {
498
547
  throw new Error('Agent engine not available.');
499
548
  }
@@ -635,6 +684,9 @@ class WidgetService {
635
684
  userId: row.user_id,
636
685
  agentId: row.agent_id || null,
637
686
  name: row.name,
687
+ widgetKind: row.widget_kind || 'custom',
688
+ systemKey: row.system_key || null,
689
+ isSystem: row.is_system !== 0 && row.is_system !== false,
638
690
  template: row.template,
639
691
  layoutVariant: row.layout_variant,
640
692
  definition,
@@ -650,10 +702,86 @@ class WidgetService {
650
702
  tasks,
651
703
  };
652
704
  }
705
+
706
+ ensureSystemFocusWidget(userId, { agentId = null } = {}) {
707
+ const scopedAgentId = resolveAgentId(userId, agentId || null);
708
+ const ensureKey = `${userId}:${scopedAgentId || 'default'}:${SYSTEM_WIDGET_KEY_ASSISTANT_FOCUS}`;
709
+ const ensured = db.transaction(() => {
710
+ db.prepare(
711
+ `INSERT OR IGNORE INTO ai_widgets (
712
+ id, user_id, agent_id, name, widget_kind, system_key, is_system, template, layout_variant,
713
+ definition_json, refresh_cron, enabled, created_at, updated_at
714
+ ) VALUES (?, ?, ?, ?, ?, ?, 1, ?, ?, ?, ?, 1, datetime('now'), datetime('now'))`
715
+ ).run(
716
+ crypto.randomUUID(),
717
+ userId,
718
+ scopedAgentId,
719
+ 'Today / Focus',
720
+ 'system',
721
+ SYSTEM_WIDGET_KEY_ASSISTANT_FOCUS,
722
+ 'summary',
723
+ 'focus',
724
+ JSON.stringify({
725
+ prompt: 'System-managed assistant focus widget.',
726
+ description: 'Always-visible assistant continuity widget.',
727
+ }),
728
+ '0 * * * *',
729
+ );
730
+ const row = db.prepare(
731
+ `SELECT * FROM ai_widgets
732
+ WHERE user_id = ? AND agent_id = ? AND system_key = ?`
733
+ ).get(userId, scopedAgentId, SYSTEM_WIDGET_KEY_ASSISTANT_FOCUS);
734
+ return {
735
+ widgetId: row?.id || null,
736
+ scheduledTaskId: row?.scheduled_task_id || null,
737
+ lastSnapshotAt: row?.last_snapshot_at || null,
738
+ };
739
+ })();
740
+
741
+ if (!ensured.widgetId) return null;
742
+ const taskRuntime = this.taskRuntime;
743
+ if (taskRuntime && !ensured.scheduledTaskId && !this._pendingSystemWidgetTaskSetup.has(ensureKey)) {
744
+ this._pendingSystemWidgetTaskSetup.add(ensureKey);
745
+ Promise.resolve(taskRuntime.createTask(userId, {
746
+ name: buildWidgetRefreshTaskName('Today / Focus'),
747
+ triggerType: 'schedule',
748
+ triggerConfig: {
749
+ mode: 'recurring',
750
+ cronExpression: '0 * * * *',
751
+ },
752
+ enabled: true,
753
+ agentId: scopedAgentId,
754
+ taskType: 'widget_refresh',
755
+ taskConfig: { widgetId: ensured.widgetId },
756
+ })).then((task) => {
757
+ db.prepare(
758
+ `UPDATE ai_widgets
759
+ SET scheduled_task_id = COALESCE(scheduled_task_id, ?), updated_at = datetime('now')
760
+ WHERE id = ?`
761
+ ).run(task.id, ensured.widgetId);
762
+ }).catch((error) => {
763
+ console.error('[widgets] Failed to create Today / Focus refresh task:', error);
764
+ }).finally(() => {
765
+ this._pendingSystemWidgetTaskSetup.delete(ensureKey);
766
+ });
767
+ }
768
+
769
+ if (!ensured.lastSnapshotAt && !this._pendingSystemWidgetRefresh.has(ensureKey)) {
770
+ this._pendingSystemWidgetRefresh.add(ensureKey);
771
+ Promise.resolve(this.refreshWidget(userId, ensured.widgetId)).catch((error) => {
772
+ console.error('[widgets] Failed to refresh Today / Focus widget:', error);
773
+ }).finally(() => {
774
+ this._pendingSystemWidgetRefresh.delete(ensureKey);
775
+ });
776
+ }
777
+
778
+ return ensured.widgetId;
779
+ }
653
780
  }
654
781
 
655
782
  module.exports = {
656
783
  MIN_WIDGET_REFRESH_MINUTES,
784
+ SYSTEM_WIDGET_KEY_ASSISTANT_FOCUS,
657
785
  TEMPLATE_VARIANTS,
658
786
  WidgetService,
659
787
  buildWidgetRefreshTaskName,