neoagent 3.0.1-beta.24 → 3.0.1-beta.25
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/flutter_app/lib/src/recording_chunk_queue_io.dart +29 -11
- package/package.json +1 -1
- package/server/db/database.js +25 -8
- package/server/public/.last_build_id +1 -1
- package/server/public/flutter_bootstrap.js +1 -1
- package/server/public/main.dart.js +4 -4
- package/server/services/timeline/service.js +38 -1
|
@@ -29,21 +29,19 @@ class DiskChunkUploadQueue {
|
|
|
29
29
|
bool _draining = false;
|
|
30
30
|
bool _disposed = false;
|
|
31
31
|
Timer? _retryTimer;
|
|
32
|
+
final Set<Future<void>> _pendingEnqueues = <Future<void>>{};
|
|
32
33
|
|
|
33
34
|
Future<void> enqueue(PendingChunk chunk) async {
|
|
34
35
|
if (_disposed) {
|
|
35
36
|
return;
|
|
36
37
|
}
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
await metaTmp.writeAsString(jsonEncode(chunk.toMeta()), flush: true);
|
|
45
|
-
await audioTmp.rename(audioFile.path);
|
|
46
|
-
await metaTmp.rename(metaFile.path);
|
|
38
|
+
final write = _persistChunk(chunk);
|
|
39
|
+
_pendingEnqueues.add(write);
|
|
40
|
+
try {
|
|
41
|
+
await write;
|
|
42
|
+
} finally {
|
|
43
|
+
_pendingEnqueues.remove(write);
|
|
44
|
+
}
|
|
47
45
|
unawaited(drain());
|
|
48
46
|
}
|
|
49
47
|
|
|
@@ -129,6 +127,25 @@ class DiskChunkUploadQueue {
|
|
|
129
127
|
|
|
130
128
|
Future<int> pendingCount() async => (await _listMetaFiles()).length;
|
|
131
129
|
|
|
130
|
+
Future<void> _persistChunk(PendingChunk chunk) async {
|
|
131
|
+
await _dir.create(recursive: true);
|
|
132
|
+
final base = '${chunk.sourceKey}-${chunk.sequence.toString().padLeft(6, '0')}';
|
|
133
|
+
final audioFile = File('${_dir.path}/$base.bin');
|
|
134
|
+
final metaFile = File('${_dir.path}/$base.json');
|
|
135
|
+
final audioTmp = File('${audioFile.path}.tmp');
|
|
136
|
+
final metaTmp = File('${metaFile.path}.tmp');
|
|
137
|
+
await audioTmp.writeAsBytes(chunk.bytes, flush: true);
|
|
138
|
+
await metaTmp.writeAsString(jsonEncode(chunk.toMeta()), flush: true);
|
|
139
|
+
await audioTmp.rename(audioFile.path);
|
|
140
|
+
await metaTmp.rename(metaFile.path);
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
Future<void> _waitForPendingEnqueues() async {
|
|
144
|
+
while (_pendingEnqueues.isNotEmpty && !_disposed) {
|
|
145
|
+
await Future.wait<void>(List<Future<void>>.from(_pendingEnqueues));
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
|
|
132
149
|
void _scheduleRetry() {
|
|
133
150
|
_retryTimer?.cancel();
|
|
134
151
|
_retryTimer = Timer(retryBackoff, () => unawaited(drain()));
|
|
@@ -148,8 +165,9 @@ class DiskChunkUploadQueue {
|
|
|
148
165
|
Future<void> flush({Duration timeout = const Duration(seconds: 30)}) async {
|
|
149
166
|
final deadline = DateTime.now().add(timeout);
|
|
150
167
|
while (!_disposed && DateTime.now().isBefore(deadline)) {
|
|
168
|
+
await _waitForPendingEnqueues();
|
|
151
169
|
await drain();
|
|
152
|
-
if (await pendingCount() == 0) {
|
|
170
|
+
if (_pendingEnqueues.isEmpty && await pendingCount() == 0) {
|
|
153
171
|
break;
|
|
154
172
|
}
|
|
155
173
|
await Future<void>.delayed(const Duration(milliseconds: 200));
|
package/package.json
CHANGED
package/server/db/database.js
CHANGED
|
@@ -506,7 +506,6 @@ db.exec(`
|
|
|
506
506
|
CREATE INDEX IF NOT EXISTS idx_browser_extension_pairing_status ON browser_extension_pairing_requests(status, expires_at);
|
|
507
507
|
CREATE INDEX IF NOT EXISTS idx_browser_extension_tokens_user ON browser_extension_tokens(user_id, status, created_at DESC);
|
|
508
508
|
CREATE INDEX IF NOT EXISTS idx_browser_extension_tokens_hash_status ON browser_extension_tokens(token_hash, status);
|
|
509
|
-
CREATE INDEX IF NOT EXISTS idx_desktop_companion_devices_user ON desktop_companion_devices(user_id, status, created_at DESC);
|
|
510
509
|
CREATE INDEX IF NOT EXISTS idx_messages_user ON messages(user_id, created_at DESC);
|
|
511
510
|
CREATE INDEX IF NOT EXISTS idx_messages_platform ON messages(platform, platform_chat_id);
|
|
512
511
|
CREATE INDEX IF NOT EXISTS idx_messages_dedup ON messages(user_id, platform, platform_msg_id) WHERE platform_msg_id IS NOT NULL;
|
|
@@ -1074,7 +1073,6 @@ db.exec(`
|
|
|
1074
1073
|
);
|
|
1075
1074
|
|
|
1076
1075
|
CREATE INDEX IF NOT EXISTS idx_screen_history_user ON screen_history(user_id, timestamp DESC);
|
|
1077
|
-
CREATE INDEX IF NOT EXISTS idx_screen_history_device ON screen_history(user_id, device_id, captured_at DESC);
|
|
1078
1076
|
CREATE INDEX IF NOT EXISTS idx_notification_history_user ON notification_history(user_id, timestamp DESC);
|
|
1079
1077
|
CREATE INDEX IF NOT EXISTS idx_timeline_events_user ON timeline_events(user_id, occurred_at DESC, id DESC);
|
|
1080
1078
|
CREATE INDEX IF NOT EXISTS idx_timeline_events_source ON timeline_events(user_id, source_kind, occurred_at DESC, id DESC);
|
|
@@ -1377,6 +1375,30 @@ function tableHasColumn(tableName, columnName) {
|
|
|
1377
1375
|
}
|
|
1378
1376
|
}
|
|
1379
1377
|
|
|
1378
|
+
function createLegacyCompatibleIndexes() {
|
|
1379
|
+
const deferredIndexes = [
|
|
1380
|
+
{
|
|
1381
|
+
table: 'desktop_companion_devices',
|
|
1382
|
+
columns: ['user_id', 'status', 'created_at'],
|
|
1383
|
+
sql: 'CREATE INDEX IF NOT EXISTS idx_desktop_companion_devices_user ON desktop_companion_devices(user_id, status, created_at DESC)',
|
|
1384
|
+
},
|
|
1385
|
+
{
|
|
1386
|
+
table: 'screen_history',
|
|
1387
|
+
columns: ['user_id', 'device_id', 'captured_at'],
|
|
1388
|
+
sql: 'CREATE INDEX IF NOT EXISTS idx_screen_history_device ON screen_history(user_id, device_id, captured_at DESC)',
|
|
1389
|
+
},
|
|
1390
|
+
];
|
|
1391
|
+
|
|
1392
|
+
for (const { table, columns, sql } of deferredIndexes) {
|
|
1393
|
+
if (!columns.every((column) => tableHasColumn(table, column))) continue;
|
|
1394
|
+
try {
|
|
1395
|
+
db.exec(sql);
|
|
1396
|
+
} catch {
|
|
1397
|
+
// Keep startup resilient for partially migrated local databases.
|
|
1398
|
+
}
|
|
1399
|
+
}
|
|
1400
|
+
}
|
|
1401
|
+
|
|
1380
1402
|
function createAgentScopedIndexes() {
|
|
1381
1403
|
const statements = [
|
|
1382
1404
|
['agent_runs', 'CREATE INDEX IF NOT EXISTS idx_agent_runs_agent ON agent_runs(user_id, agent_id, created_at DESC)'],
|
|
@@ -2031,14 +2053,9 @@ rebuildCoreMemoryForAgents();
|
|
|
2031
2053
|
migrateIntegrationConnectionsTable();
|
|
2032
2054
|
migrateIntegrationOauthStatesTable();
|
|
2033
2055
|
migrateIntegrationProviderConfigsTable();
|
|
2056
|
+
createLegacyCompatibleIndexes();
|
|
2034
2057
|
createAgentScopedIndexes();
|
|
2035
2058
|
|
|
2036
|
-
try {
|
|
2037
|
-
db.exec('CREATE INDEX IF NOT EXISTS idx_screen_history_device ON screen_history(user_id, device_id, captured_at DESC)');
|
|
2038
|
-
} catch {
|
|
2039
|
-
// Keep startup resilient for partially migrated local databases.
|
|
2040
|
-
}
|
|
2041
|
-
|
|
2042
2059
|
try {
|
|
2043
2060
|
db.exec(`
|
|
2044
2061
|
CREATE TABLE IF NOT EXISTS timeline_events (
|
|
@@ -1 +1 @@
|
|
|
1
|
-
|
|
1
|
+
5bdb494f3a900af0165cb368a571ff54
|
|
@@ -37,6 +37,6 @@ _flutter.buildConfig = {"engineRevision":"a4ce257c68517c1410f4b48ac9852ab5642a3f
|
|
|
37
37
|
|
|
38
38
|
_flutter.loader.load({
|
|
39
39
|
serviceWorkerSettings: {
|
|
40
|
-
serviceWorkerVersion: "
|
|
40
|
+
serviceWorkerVersion: "3511135638" /* Flutter's service worker is deprecated and will be removed in a future Flutter release. */
|
|
41
41
|
}
|
|
42
42
|
});
|
|
@@ -138523,7 +138523,7 @@ r===$&&A.b()
|
|
|
138523
138523
|
p.push(A.iR(q,A.jq(!1,new A.Y(B.vo,A.cR(new A.cA(B.jR,new A.a9_(r,q),q),q,q),q),!1,B.I,!0),q,q,0,0,0,q))}r=!1
|
|
138524
138524
|
if(!s.ay)if(!s.ch){r=s.e
|
|
138525
138525
|
r===$&&A.b()
|
|
138526
|
-
r=B.b.t("
|
|
138526
|
+
r=B.b.t("mqr8ict9-258fe74").length!==0&&r.b}if(r){r=s.d
|
|
138527
138527
|
r===$&&A.b()
|
|
138528
138528
|
r=r.aD&&!r.av?84:0
|
|
138529
138529
|
s=s.e
|
|
@@ -144618,7 +144618,7 @@ $S:0}
|
|
|
144618
144618
|
A.a0a.prototype={}
|
|
144619
144619
|
A.TU.prototype={
|
|
144620
144620
|
nl(a){var s=this
|
|
144621
|
-
if(B.b.t("
|
|
144621
|
+
if(B.b.t("mqr8ict9-258fe74").length===0||s.a!=null)return
|
|
144622
144622
|
s.Be()
|
|
144623
144623
|
s.a=A.m1(B.T0,new A.bfY(s))},
|
|
144624
144624
|
Be(){var s=0,r=A.l(t.H),q,p=2,o=[],n=this,m,l,k,j,i,h,g,f
|
|
@@ -144636,7 +144636,7 @@ if(!t.f.b(k)){s=1
|
|
|
144636
144636
|
break}i=J.a2(k,"buildId")
|
|
144637
144637
|
h=i==null?null:B.b.t(J.p(i))
|
|
144638
144638
|
j=h==null?"":h
|
|
144639
|
-
if(J.bd(j)===0||J.f(j,"
|
|
144639
|
+
if(J.bd(j)===0||J.f(j,"mqr8ict9-258fe74")){s=1
|
|
144640
144640
|
break}n.b=!0
|
|
144641
144641
|
n.E()
|
|
144642
144642
|
p=2
|
|
@@ -144653,7 +144653,7 @@ case 2:return A.i(o.at(-1),r)}})
|
|
|
144653
144653
|
return A.k($async$Be,r)},
|
|
144654
144654
|
vT(){var s=0,r=A.l(t.H),q,p=2,o=[],n=this,m,l,k,j,i,h,g,f,e,d,c,b,a,a0,a1
|
|
144655
144655
|
var $async$vT=A.h(function(a2,a3){if(a2===1){o.push(a3)
|
|
144656
|
-
s=p}for(;;)switch(s){case 0:if(B.b.t("
|
|
144656
|
+
s=p}for(;;)switch(s){case 0:if(B.b.t("mqr8ict9-258fe74").length===0||n.c){s=1
|
|
144657
144657
|
break}n.c=!0
|
|
144658
144658
|
n.E()
|
|
144659
144659
|
p=4
|
|
@@ -347,9 +347,45 @@ class TimelineService {
|
|
|
347
347
|
return { insertedCount: 0, timelineItems: [] };
|
|
348
348
|
}
|
|
349
349
|
|
|
350
|
+
const batchDedupKeys = new Set();
|
|
351
|
+
const findExistingEntry = this.db.prepare(
|
|
352
|
+
`SELECT id
|
|
353
|
+
FROM screen_history
|
|
354
|
+
WHERE user_id = ?
|
|
355
|
+
AND captured_at = ?
|
|
356
|
+
AND device_id = ?
|
|
357
|
+
AND COALESCE(app_name, '') = ?
|
|
358
|
+
AND COALESCE(window_title, '') = ?
|
|
359
|
+
AND text_content = ?
|
|
360
|
+
LIMIT 1`
|
|
361
|
+
);
|
|
362
|
+
|
|
350
363
|
const tx = this.db.transaction(() => {
|
|
351
364
|
const timelineItems = [];
|
|
365
|
+
let insertedCount = 0;
|
|
352
366
|
for (const entry of normalizedEntries) {
|
|
367
|
+
const dedupKey = JSON.stringify([
|
|
368
|
+
entry.capturedAt,
|
|
369
|
+
normalizedDeviceId,
|
|
370
|
+
entry.appName,
|
|
371
|
+
entry.windowTitle,
|
|
372
|
+
entry.text,
|
|
373
|
+
]);
|
|
374
|
+
if (batchDedupKeys.has(dedupKey)) {
|
|
375
|
+
continue;
|
|
376
|
+
}
|
|
377
|
+
batchDedupKeys.add(dedupKey);
|
|
378
|
+
const existing = findExistingEntry.get(
|
|
379
|
+
userId,
|
|
380
|
+
entry.capturedAt,
|
|
381
|
+
normalizedDeviceId,
|
|
382
|
+
entry.appName || '',
|
|
383
|
+
entry.windowTitle || '',
|
|
384
|
+
entry.text,
|
|
385
|
+
);
|
|
386
|
+
if (existing) {
|
|
387
|
+
continue;
|
|
388
|
+
}
|
|
353
389
|
const insert = this.db.prepare(
|
|
354
390
|
`INSERT INTO screen_history (
|
|
355
391
|
user_id, timestamp, captured_at, device_id, device_label, app_name, window_title, text_content, ocr_engine, ocr_confidence
|
|
@@ -366,6 +402,7 @@ class TimelineService {
|
|
|
366
402
|
ocrEngine,
|
|
367
403
|
entry.ocrConfidence,
|
|
368
404
|
);
|
|
405
|
+
insertedCount += 1;
|
|
369
406
|
const screenHistoryId = Number(insert.lastInsertRowid);
|
|
370
407
|
const timelineItem = this._upsertScreenSession({
|
|
371
408
|
userId,
|
|
@@ -383,7 +420,7 @@ class TimelineService {
|
|
|
383
420
|
}
|
|
384
421
|
}
|
|
385
422
|
return {
|
|
386
|
-
insertedCount
|
|
423
|
+
insertedCount,
|
|
387
424
|
timelineItems,
|
|
388
425
|
};
|
|
389
426
|
});
|