@awareness-sdk/local 0.1.6 → 0.1.8
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/package.json +1 -1
- package/src/core/cloud-sync.mjs +228 -8
- package/src/core/indexer.mjs +4 -2
package/package.json
CHANGED
package/src/core/cloud-sync.mjs
CHANGED
|
@@ -437,14 +437,155 @@ export class CloudSync {
|
|
|
437
437
|
}
|
|
438
438
|
|
|
439
439
|
/**
|
|
440
|
-
*
|
|
441
|
-
*
|
|
440
|
+
* Push unsynced knowledge cards to the cloud.
|
|
441
|
+
* Uses POST /memories/{id}/insights/submit with action:"new".
|
|
442
|
+
*
|
|
443
|
+
* @returns {Promise<{ synced: number, errors: number }>}
|
|
444
|
+
*/
|
|
445
|
+
async syncInsightsToCloud() {
|
|
446
|
+
if (!this.isEnabled()) return { synced: 0, errors: 0 };
|
|
447
|
+
|
|
448
|
+
let synced = 0;
|
|
449
|
+
let errors = 0;
|
|
450
|
+
|
|
451
|
+
try {
|
|
452
|
+
const unsynced = this.indexer.db
|
|
453
|
+
.prepare("SELECT * FROM knowledge_cards WHERE synced_to_cloud = 0 AND status = 'active' ORDER BY created_at")
|
|
454
|
+
.all();
|
|
455
|
+
|
|
456
|
+
if (!unsynced.length) return { synced: 0, errors: 0 };
|
|
457
|
+
|
|
458
|
+
// Batch cards in groups of 10 to reduce API calls
|
|
459
|
+
const batchSize = 10;
|
|
460
|
+
for (let i = 0; i < unsynced.length; i += batchSize) {
|
|
461
|
+
const batch = unsynced.slice(i, i + batchSize);
|
|
462
|
+
const cards = batch.map((card) => ({
|
|
463
|
+
title: card.title,
|
|
464
|
+
summary: card.summary || '',
|
|
465
|
+
category: card.category,
|
|
466
|
+
confidence: card.confidence || 0.8,
|
|
467
|
+
tags: this._parseTags(card.tags),
|
|
468
|
+
action: 'new',
|
|
469
|
+
}));
|
|
470
|
+
|
|
471
|
+
try {
|
|
472
|
+
const result = await this._post(
|
|
473
|
+
`/memories/${this.memoryId}/insights/submit`,
|
|
474
|
+
{
|
|
475
|
+
session_id: `local-sync-${this.deviceId}`,
|
|
476
|
+
knowledge_cards: cards,
|
|
477
|
+
metadata: { device_id: this.deviceId, source: 'awareness-local' },
|
|
478
|
+
}
|
|
479
|
+
);
|
|
480
|
+
|
|
481
|
+
if (result) {
|
|
482
|
+
// Mark batch as synced
|
|
483
|
+
const markStmt = this.indexer.db.prepare(
|
|
484
|
+
'UPDATE knowledge_cards SET synced_to_cloud = 1 WHERE id = ?'
|
|
485
|
+
);
|
|
486
|
+
for (const card of batch) {
|
|
487
|
+
markStmt.run(card.id);
|
|
488
|
+
}
|
|
489
|
+
synced += batch.length;
|
|
490
|
+
} else {
|
|
491
|
+
errors += batch.length;
|
|
492
|
+
}
|
|
493
|
+
} catch (err) {
|
|
494
|
+
console.warn(`${LOG_PREFIX} Failed to push insight batch:`, err.message);
|
|
495
|
+
errors += batch.length;
|
|
496
|
+
}
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
if (synced > 0) {
|
|
500
|
+
console.log(`${LOG_PREFIX} Pushed ${synced} knowledge cards to cloud` + (errors ? ` (${errors} errors)` : ''));
|
|
501
|
+
}
|
|
502
|
+
} catch (err) {
|
|
503
|
+
console.error(`${LOG_PREFIX} syncInsightsToCloud failed:`, err.message);
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
return { synced, errors };
|
|
507
|
+
}
|
|
508
|
+
|
|
509
|
+
/**
|
|
510
|
+
* Push unsynced tasks (action items) to the cloud.
|
|
511
|
+
* Uses POST /memories/{id}/insights/submit with action_items.
|
|
512
|
+
*
|
|
513
|
+
* @returns {Promise<{ synced: number, errors: number }>}
|
|
514
|
+
*/
|
|
515
|
+
async syncTasksToCloud() {
|
|
516
|
+
if (!this.isEnabled()) return { synced: 0, errors: 0 };
|
|
517
|
+
|
|
518
|
+
let synced = 0;
|
|
519
|
+
let errors = 0;
|
|
520
|
+
|
|
521
|
+
try {
|
|
522
|
+
const unsynced = this.indexer.db
|
|
523
|
+
.prepare("SELECT * FROM tasks WHERE synced_to_cloud = 0 ORDER BY created_at")
|
|
524
|
+
.all();
|
|
525
|
+
|
|
526
|
+
if (!unsynced.length) return { synced: 0, errors: 0 };
|
|
527
|
+
|
|
528
|
+
const batchSize = 10;
|
|
529
|
+
for (let i = 0; i < unsynced.length; i += batchSize) {
|
|
530
|
+
const batch = unsynced.slice(i, i + batchSize);
|
|
531
|
+
const items = batch.map((task) => ({
|
|
532
|
+
title: task.title,
|
|
533
|
+
detail: task.description || '',
|
|
534
|
+
priority: task.priority || 'medium',
|
|
535
|
+
status: task.status || 'open',
|
|
536
|
+
agent_role: task.agent_role || '',
|
|
537
|
+
}));
|
|
538
|
+
|
|
539
|
+
try {
|
|
540
|
+
const result = await this._post(
|
|
541
|
+
`/memories/${this.memoryId}/insights/submit`,
|
|
542
|
+
{
|
|
543
|
+
session_id: `local-sync-${this.deviceId}`,
|
|
544
|
+
action_items: items,
|
|
545
|
+
metadata: { device_id: this.deviceId, source: 'awareness-local' },
|
|
546
|
+
}
|
|
547
|
+
);
|
|
548
|
+
|
|
549
|
+
if (result) {
|
|
550
|
+
const markStmt = this.indexer.db.prepare(
|
|
551
|
+
'UPDATE tasks SET synced_to_cloud = 1 WHERE id = ?'
|
|
552
|
+
);
|
|
553
|
+
for (const task of batch) {
|
|
554
|
+
markStmt.run(task.id);
|
|
555
|
+
}
|
|
556
|
+
synced += batch.length;
|
|
557
|
+
} else {
|
|
558
|
+
errors += batch.length;
|
|
559
|
+
}
|
|
560
|
+
} catch (err) {
|
|
561
|
+
console.warn(`${LOG_PREFIX} Failed to push task batch:`, err.message);
|
|
562
|
+
errors += batch.length;
|
|
563
|
+
}
|
|
564
|
+
}
|
|
565
|
+
|
|
566
|
+
if (synced > 0) {
|
|
567
|
+
console.log(`${LOG_PREFIX} Pushed ${synced} tasks to cloud` + (errors ? ` (${errors} errors)` : ''));
|
|
568
|
+
}
|
|
569
|
+
} catch (err) {
|
|
570
|
+
console.error(`${LOG_PREFIX} syncTasksToCloud failed:`, err.message);
|
|
571
|
+
}
|
|
572
|
+
|
|
573
|
+
return { synced, errors };
|
|
574
|
+
}
|
|
575
|
+
|
|
576
|
+
/**
|
|
577
|
+
* Full bidirectional sync: push memories + insights + tasks, then pull.
|
|
578
|
+
* @returns {Promise<{ pushed: number, pulled: number, insights_pushed: number, tasks_pushed: number }>}
|
|
442
579
|
*/
|
|
443
580
|
async fullSync() {
|
|
444
581
|
const pushResult = await this.syncToCloud();
|
|
582
|
+
const insightsResult = await this.syncInsightsToCloud();
|
|
583
|
+
const tasksResult = await this.syncTasksToCloud();
|
|
445
584
|
const pullResult = await this.pullFromCloud();
|
|
446
585
|
return {
|
|
447
586
|
pushed: pushResult.synced,
|
|
587
|
+
insights_pushed: insightsResult.synced,
|
|
588
|
+
tasks_pushed: tasksResult.synced,
|
|
448
589
|
pulled: pullResult.pulled,
|
|
449
590
|
};
|
|
450
591
|
}
|
|
@@ -518,9 +659,9 @@ export class CloudSync {
|
|
|
518
659
|
this._periodicTimer = setInterval(async () => {
|
|
519
660
|
try {
|
|
520
661
|
const result = await this.fullSync();
|
|
521
|
-
if (result.pushed > 0 || result.pulled > 0) {
|
|
662
|
+
if (result.pushed > 0 || result.pulled > 0 || result.insights_pushed > 0 || result.tasks_pushed > 0) {
|
|
522
663
|
console.log(
|
|
523
|
-
`${LOG_PREFIX} Periodic sync: pushed
|
|
664
|
+
`${LOG_PREFIX} Periodic sync: memories=${result.pushed}, insights=${result.insights_pushed}, tasks=${result.tasks_pushed}, pulled=${result.pulled}`
|
|
524
665
|
);
|
|
525
666
|
}
|
|
526
667
|
} catch (err) {
|
|
@@ -552,8 +693,10 @@ export class CloudSync {
|
|
|
552
693
|
}
|
|
553
694
|
|
|
554
695
|
try {
|
|
555
|
-
// Push unsynced
|
|
696
|
+
// Push unsynced memories, knowledge cards, and tasks
|
|
556
697
|
await this.syncToCloud();
|
|
698
|
+
await this.syncInsightsToCloud();
|
|
699
|
+
await this.syncTasksToCloud();
|
|
557
700
|
} catch (err) {
|
|
558
701
|
console.warn(`${LOG_PREFIX} Initial push failed (will retry):`, err.message);
|
|
559
702
|
}
|
|
@@ -637,7 +780,8 @@ export class CloudSync {
|
|
|
637
780
|
break;
|
|
638
781
|
}
|
|
639
782
|
|
|
640
|
-
case 'knowledge_extracted':
|
|
783
|
+
case 'knowledge_extracted':
|
|
784
|
+
case 'insight_submitted': {
|
|
641
785
|
if (data.device_id === this.deviceId) return;
|
|
642
786
|
try {
|
|
643
787
|
await this._pullKnowledgeCard(data);
|
|
@@ -647,6 +791,17 @@ export class CloudSync {
|
|
|
647
791
|
break;
|
|
648
792
|
}
|
|
649
793
|
|
|
794
|
+
case 'task_created':
|
|
795
|
+
case 'task_updated': {
|
|
796
|
+
if (data.device_id === this.deviceId) return;
|
|
797
|
+
try {
|
|
798
|
+
await this._pullTask(data);
|
|
799
|
+
} catch (err) {
|
|
800
|
+
console.warn(`${LOG_PREFIX} SSE task pull failed:`, err.message);
|
|
801
|
+
}
|
|
802
|
+
break;
|
|
803
|
+
}
|
|
804
|
+
|
|
650
805
|
case 'heartbeat':
|
|
651
806
|
// Keepalive — no action needed
|
|
652
807
|
break;
|
|
@@ -790,8 +945,8 @@ export class CloudSync {
|
|
|
790
945
|
this.indexer.db
|
|
791
946
|
.prepare(
|
|
792
947
|
`INSERT OR IGNORE INTO knowledge_cards
|
|
793
|
-
(id, category, title, summary, source_memories, confidence, status, tags, created_at, filepath)
|
|
794
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?,
|
|
948
|
+
(id, category, title, summary, source_memories, confidence, status, tags, created_at, filepath, synced_to_cloud)
|
|
949
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, 1)`
|
|
795
950
|
)
|
|
796
951
|
.run(
|
|
797
952
|
kcId,
|
|
@@ -815,6 +970,57 @@ export class CloudSync {
|
|
|
815
970
|
}
|
|
816
971
|
}
|
|
817
972
|
|
|
973
|
+
/**
|
|
974
|
+
* Pull a task from cloud SSE event data and store locally.
|
|
975
|
+
* @param {object} data — { id, title, detail, priority, status, agent_role, ... }
|
|
976
|
+
*/
|
|
977
|
+
async _pullTask(data) {
|
|
978
|
+
if (!data.title) return;
|
|
979
|
+
|
|
980
|
+
// Check if we already have this task
|
|
981
|
+
const existing = this._getSyncState(`cloud_task:${data.id}`);
|
|
982
|
+
if (existing) {
|
|
983
|
+
// Task update — update status/priority if changed
|
|
984
|
+
try {
|
|
985
|
+
this.indexer.db
|
|
986
|
+
.prepare('UPDATE tasks SET status = ?, priority = ?, updated_at = ? WHERE id = ?')
|
|
987
|
+
.run(data.status || 'open', data.priority || 'medium', new Date().toISOString(), existing);
|
|
988
|
+
} catch {
|
|
989
|
+
// ignore update failures
|
|
990
|
+
}
|
|
991
|
+
return;
|
|
992
|
+
}
|
|
993
|
+
|
|
994
|
+
const taskId = `task_${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 6)}`;
|
|
995
|
+
const now = new Date().toISOString();
|
|
996
|
+
|
|
997
|
+
try {
|
|
998
|
+
this.indexer.db
|
|
999
|
+
.prepare(
|
|
1000
|
+
`INSERT OR IGNORE INTO tasks
|
|
1001
|
+
(id, title, description, status, priority, agent_role, created_at, updated_at, filepath, synced_to_cloud)
|
|
1002
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, 1)`
|
|
1003
|
+
)
|
|
1004
|
+
.run(
|
|
1005
|
+
taskId,
|
|
1006
|
+
data.title,
|
|
1007
|
+
data.detail || data.description || '',
|
|
1008
|
+
data.status || 'open',
|
|
1009
|
+
data.priority || 'medium',
|
|
1010
|
+
data.agent_role || '',
|
|
1011
|
+
now,
|
|
1012
|
+
now,
|
|
1013
|
+
`cloud-pull:${data.id}`
|
|
1014
|
+
);
|
|
1015
|
+
|
|
1016
|
+
this._setSyncState(`cloud_task:${data.id}`, taskId);
|
|
1017
|
+
} catch (err) {
|
|
1018
|
+
if (!err.message?.includes('UNIQUE')) {
|
|
1019
|
+
throw err;
|
|
1020
|
+
}
|
|
1021
|
+
}
|
|
1022
|
+
}
|
|
1023
|
+
|
|
818
1024
|
// =========================================================================
|
|
819
1025
|
// Internal — HTTP helpers
|
|
820
1026
|
// =========================================================================
|
|
@@ -909,6 +1115,20 @@ export class CloudSync {
|
|
|
909
1115
|
} catch {
|
|
910
1116
|
// Table likely already exists
|
|
911
1117
|
}
|
|
1118
|
+
|
|
1119
|
+
// Migrate existing tables: add synced_to_cloud column if missing.
|
|
1120
|
+
// DEFAULT 0 means all existing records are marked as unsynced → they'll be
|
|
1121
|
+
// pushed on the next sync cycle, ensuring old data reaches the cloud.
|
|
1122
|
+
for (const table of ['knowledge_cards', 'tasks']) {
|
|
1123
|
+
try {
|
|
1124
|
+
this.indexer.db
|
|
1125
|
+
.prepare(`ALTER TABLE ${table} ADD COLUMN synced_to_cloud INTEGER DEFAULT 0`)
|
|
1126
|
+
.run();
|
|
1127
|
+
console.log(`${LOG_PREFIX} Migrated ${table}: added synced_to_cloud column`);
|
|
1128
|
+
} catch {
|
|
1129
|
+
// Column already exists — expected for fresh installs
|
|
1130
|
+
}
|
|
1131
|
+
}
|
|
912
1132
|
}
|
|
913
1133
|
|
|
914
1134
|
/**
|
package/src/core/indexer.mjs
CHANGED
|
@@ -44,7 +44,8 @@ CREATE TABLE IF NOT EXISTS knowledge_cards (
|
|
|
44
44
|
status TEXT DEFAULT 'active',
|
|
45
45
|
tags TEXT,
|
|
46
46
|
created_at TEXT NOT NULL,
|
|
47
|
-
filepath TEXT NOT NULL UNIQUE
|
|
47
|
+
filepath TEXT NOT NULL UNIQUE,
|
|
48
|
+
synced_to_cloud INTEGER DEFAULT 0
|
|
48
49
|
);
|
|
49
50
|
|
|
50
51
|
CREATE VIRTUAL TABLE IF NOT EXISTS knowledge_fts USING fts5(
|
|
@@ -61,7 +62,8 @@ CREATE TABLE IF NOT EXISTS tasks (
|
|
|
61
62
|
agent_role TEXT,
|
|
62
63
|
created_at TEXT NOT NULL,
|
|
63
64
|
updated_at TEXT NOT NULL,
|
|
64
|
-
filepath TEXT NOT NULL UNIQUE
|
|
65
|
+
filepath TEXT NOT NULL UNIQUE,
|
|
66
|
+
synced_to_cloud INTEGER DEFAULT 0
|
|
65
67
|
);
|
|
66
68
|
|
|
67
69
|
CREATE TABLE IF NOT EXISTS sessions (
|