scene-capability-engine 3.6.2 → 3.6.4
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/CHANGELOG.md +54 -0
- package/README.md +15 -2
- package/README.zh.md +15 -2
- package/bin/scene-capability-engine.js +33 -1
- package/docs/command-reference.md +87 -0
- package/lib/collab/agent-registry.js +38 -1
- package/lib/commands/auth.js +269 -0
- package/lib/commands/session.js +60 -2
- package/lib/commands/state.js +210 -0
- package/lib/commands/studio.js +57 -7
- package/lib/commands/task.js +25 -2
- package/lib/runtime/project-timeline.js +202 -17
- package/lib/runtime/session-store.js +167 -14
- package/lib/security/write-authorization.js +632 -0
- package/lib/state/sce-state-store.js +1029 -1
- package/lib/state/state-migration-manager.js +659 -0
- package/lib/steering/compliance-error-reporter.js +6 -0
- package/lib/steering/steering-compliance-checker.js +43 -8
- package/package.json +2 -1
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
|
|
1
|
+
const path = require('path');
|
|
2
|
+
const crypto = require('crypto');
|
|
2
3
|
const fs = require('fs-extra');
|
|
3
4
|
|
|
4
5
|
const DEFAULT_BACKEND = 'sqlite';
|
|
@@ -20,6 +21,14 @@ function normalizeInteger(value, fallback = 0) {
|
|
|
20
21
|
return parsed;
|
|
21
22
|
}
|
|
22
23
|
|
|
24
|
+
function normalizeNonNegativeInteger(value, fallback = 0) {
|
|
25
|
+
const parsed = Number.parseInt(`${value}`, 10);
|
|
26
|
+
if (!Number.isFinite(parsed) || parsed < 0) {
|
|
27
|
+
return fallback;
|
|
28
|
+
}
|
|
29
|
+
return parsed;
|
|
30
|
+
}
|
|
31
|
+
|
|
23
32
|
function parseJsonSafe(value, fallback) {
|
|
24
33
|
if (typeof value !== 'string' || !value.trim()) {
|
|
25
34
|
return fallback;
|
|
@@ -31,6 +40,27 @@ function parseJsonSafe(value, fallback) {
|
|
|
31
40
|
}
|
|
32
41
|
}
|
|
33
42
|
|
|
43
|
+
function normalizeStringArray(value, fallback = []) {
|
|
44
|
+
if (!Array.isArray(value)) {
|
|
45
|
+
return [...fallback];
|
|
46
|
+
}
|
|
47
|
+
return value
|
|
48
|
+
.map((item) => normalizeString(item))
|
|
49
|
+
.filter(Boolean);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function normalizeIsoTimestamp(value, fallback = '') {
|
|
53
|
+
const normalized = normalizeString(value);
|
|
54
|
+
if (!normalized) {
|
|
55
|
+
return normalizeString(fallback);
|
|
56
|
+
}
|
|
57
|
+
const parsed = Date.parse(normalized);
|
|
58
|
+
if (!Number.isFinite(parsed)) {
|
|
59
|
+
return normalizeString(fallback);
|
|
60
|
+
}
|
|
61
|
+
return new Date(parsed).toISOString();
|
|
62
|
+
}
|
|
63
|
+
|
|
34
64
|
function formatSegment(value) {
|
|
35
65
|
const normalized = normalizeInteger(value, 0);
|
|
36
66
|
if (normalized <= 0) {
|
|
@@ -83,6 +113,12 @@ class SceStateStore {
|
|
|
83
113
|
specs: {},
|
|
84
114
|
tasks: {},
|
|
85
115
|
refs: {},
|
|
116
|
+
timeline_snapshots: {},
|
|
117
|
+
scene_session_cycles: {},
|
|
118
|
+
agent_runtime: {},
|
|
119
|
+
migration_records: {},
|
|
120
|
+
auth_leases: {},
|
|
121
|
+
auth_events: [],
|
|
86
122
|
sequences: {
|
|
87
123
|
scene_next: 1,
|
|
88
124
|
spec_next_by_scene: {},
|
|
@@ -181,6 +217,106 @@ class SceStateStore {
|
|
|
181
217
|
|
|
182
218
|
CREATE INDEX IF NOT EXISTS idx_studio_event_stream_job_ts
|
|
183
219
|
ON studio_event_stream(job_id, event_timestamp);
|
|
220
|
+
|
|
221
|
+
CREATE TABLE IF NOT EXISTS auth_lease_registry (
|
|
222
|
+
lease_id TEXT PRIMARY KEY,
|
|
223
|
+
subject TEXT NOT NULL,
|
|
224
|
+
role TEXT NOT NULL,
|
|
225
|
+
scope_json TEXT NOT NULL,
|
|
226
|
+
reason TEXT,
|
|
227
|
+
metadata_json TEXT,
|
|
228
|
+
issued_at TEXT NOT NULL,
|
|
229
|
+
expires_at TEXT NOT NULL,
|
|
230
|
+
revoked_at TEXT,
|
|
231
|
+
created_at TEXT NOT NULL,
|
|
232
|
+
updated_at TEXT NOT NULL
|
|
233
|
+
);
|
|
234
|
+
|
|
235
|
+
CREATE INDEX IF NOT EXISTS idx_auth_lease_registry_expires
|
|
236
|
+
ON auth_lease_registry(expires_at);
|
|
237
|
+
|
|
238
|
+
CREATE TABLE IF NOT EXISTS auth_event_stream (
|
|
239
|
+
event_id TEXT PRIMARY KEY,
|
|
240
|
+
event_timestamp TEXT NOT NULL,
|
|
241
|
+
event_type TEXT NOT NULL,
|
|
242
|
+
action TEXT,
|
|
243
|
+
actor TEXT,
|
|
244
|
+
lease_id TEXT,
|
|
245
|
+
result TEXT,
|
|
246
|
+
target TEXT,
|
|
247
|
+
detail_json TEXT,
|
|
248
|
+
created_at TEXT NOT NULL
|
|
249
|
+
);
|
|
250
|
+
|
|
251
|
+
CREATE INDEX IF NOT EXISTS idx_auth_event_stream_ts
|
|
252
|
+
ON auth_event_stream(event_timestamp);
|
|
253
|
+
|
|
254
|
+
CREATE TABLE IF NOT EXISTS timeline_snapshot_registry (
|
|
255
|
+
snapshot_id TEXT PRIMARY KEY,
|
|
256
|
+
created_at TEXT NOT NULL,
|
|
257
|
+
trigger TEXT,
|
|
258
|
+
event TEXT,
|
|
259
|
+
summary TEXT,
|
|
260
|
+
scene_id TEXT,
|
|
261
|
+
session_id TEXT,
|
|
262
|
+
command TEXT,
|
|
263
|
+
file_count INTEGER,
|
|
264
|
+
total_bytes INTEGER,
|
|
265
|
+
snapshot_path TEXT,
|
|
266
|
+
git_json TEXT,
|
|
267
|
+
source TEXT,
|
|
268
|
+
updated_at TEXT NOT NULL
|
|
269
|
+
);
|
|
270
|
+
|
|
271
|
+
CREATE INDEX IF NOT EXISTS idx_timeline_snapshot_registry_created
|
|
272
|
+
ON timeline_snapshot_registry(created_at DESC);
|
|
273
|
+
|
|
274
|
+
CREATE TABLE IF NOT EXISTS scene_session_cycle_registry (
|
|
275
|
+
scene_id TEXT NOT NULL,
|
|
276
|
+
cycle INTEGER NOT NULL,
|
|
277
|
+
session_id TEXT NOT NULL,
|
|
278
|
+
status TEXT,
|
|
279
|
+
started_at TEXT,
|
|
280
|
+
completed_at TEXT,
|
|
281
|
+
source TEXT,
|
|
282
|
+
updated_at TEXT NOT NULL,
|
|
283
|
+
PRIMARY KEY (scene_id, cycle)
|
|
284
|
+
);
|
|
285
|
+
|
|
286
|
+
CREATE INDEX IF NOT EXISTS idx_scene_session_cycle_registry_session
|
|
287
|
+
ON scene_session_cycle_registry(session_id);
|
|
288
|
+
|
|
289
|
+
CREATE TABLE IF NOT EXISTS agent_runtime_registry (
|
|
290
|
+
agent_id TEXT PRIMARY KEY,
|
|
291
|
+
machine_id TEXT,
|
|
292
|
+
instance_index INTEGER,
|
|
293
|
+
hostname TEXT,
|
|
294
|
+
registered_at TEXT,
|
|
295
|
+
last_heartbeat TEXT,
|
|
296
|
+
status TEXT,
|
|
297
|
+
current_task_json TEXT,
|
|
298
|
+
source TEXT,
|
|
299
|
+
updated_at TEXT NOT NULL
|
|
300
|
+
);
|
|
301
|
+
|
|
302
|
+
CREATE INDEX IF NOT EXISTS idx_agent_runtime_registry_status
|
|
303
|
+
ON agent_runtime_registry(status);
|
|
304
|
+
|
|
305
|
+
CREATE TABLE IF NOT EXISTS state_migration_registry (
|
|
306
|
+
migration_id TEXT PRIMARY KEY,
|
|
307
|
+
component_id TEXT NOT NULL,
|
|
308
|
+
source_path TEXT,
|
|
309
|
+
mode TEXT NOT NULL,
|
|
310
|
+
status TEXT NOT NULL,
|
|
311
|
+
metrics_json TEXT,
|
|
312
|
+
detail_json TEXT,
|
|
313
|
+
started_at TEXT NOT NULL,
|
|
314
|
+
completed_at TEXT,
|
|
315
|
+
updated_at TEXT NOT NULL
|
|
316
|
+
);
|
|
317
|
+
|
|
318
|
+
CREATE INDEX IF NOT EXISTS idx_state_migration_registry_component_started
|
|
319
|
+
ON state_migration_registry(component_id, started_at DESC);
|
|
184
320
|
`);
|
|
185
321
|
}
|
|
186
322
|
|
|
@@ -264,6 +400,117 @@ class SceStateStore {
|
|
|
264
400
|
};
|
|
265
401
|
}
|
|
266
402
|
|
|
403
|
+
_mapAuthLeaseRow(row) {
|
|
404
|
+
if (!row) {
|
|
405
|
+
return null;
|
|
406
|
+
}
|
|
407
|
+
return {
|
|
408
|
+
lease_id: normalizeString(row.lease_id),
|
|
409
|
+
subject: normalizeString(row.subject),
|
|
410
|
+
role: normalizeString(row.role),
|
|
411
|
+
scope: normalizeStringArray(parseJsonSafe(row.scope_json, []), ['project:*']),
|
|
412
|
+
reason: normalizeString(row.reason) || null,
|
|
413
|
+
metadata: parseJsonSafe(row.metadata_json, {}) || {},
|
|
414
|
+
issued_at: normalizeIsoTimestamp(row.issued_at) || null,
|
|
415
|
+
expires_at: normalizeIsoTimestamp(row.expires_at) || null,
|
|
416
|
+
revoked_at: normalizeIsoTimestamp(row.revoked_at) || null,
|
|
417
|
+
created_at: normalizeIsoTimestamp(row.created_at) || null,
|
|
418
|
+
updated_at: normalizeIsoTimestamp(row.updated_at) || null
|
|
419
|
+
};
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
_mapAuthEventRow(row) {
|
|
423
|
+
if (!row) {
|
|
424
|
+
return null;
|
|
425
|
+
}
|
|
426
|
+
return {
|
|
427
|
+
event_id: normalizeString(row.event_id),
|
|
428
|
+
event_timestamp: normalizeIsoTimestamp(row.event_timestamp) || null,
|
|
429
|
+
event_type: normalizeString(row.event_type),
|
|
430
|
+
action: normalizeString(row.action) || null,
|
|
431
|
+
actor: normalizeString(row.actor) || null,
|
|
432
|
+
lease_id: normalizeString(row.lease_id) || null,
|
|
433
|
+
result: normalizeString(row.result) || null,
|
|
434
|
+
target: normalizeString(row.target) || null,
|
|
435
|
+
detail: parseJsonSafe(row.detail_json, {}) || {},
|
|
436
|
+
created_at: normalizeIsoTimestamp(row.created_at) || null
|
|
437
|
+
};
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
_mapTimelineSnapshotRow(row) {
|
|
441
|
+
if (!row) {
|
|
442
|
+
return null;
|
|
443
|
+
}
|
|
444
|
+
return {
|
|
445
|
+
snapshot_id: normalizeString(row.snapshot_id),
|
|
446
|
+
created_at: normalizeIsoTimestamp(row.created_at) || null,
|
|
447
|
+
trigger: normalizeString(row.trigger) || null,
|
|
448
|
+
event: normalizeString(row.event) || null,
|
|
449
|
+
summary: normalizeString(row.summary) || null,
|
|
450
|
+
scene_id: normalizeString(row.scene_id) || null,
|
|
451
|
+
session_id: normalizeString(row.session_id) || null,
|
|
452
|
+
command: normalizeString(row.command) || null,
|
|
453
|
+
file_count: normalizeNonNegativeInteger(row.file_count, 0),
|
|
454
|
+
total_bytes: normalizeNonNegativeInteger(row.total_bytes, 0),
|
|
455
|
+
snapshot_path: normalizeString(row.snapshot_path) || null,
|
|
456
|
+
git: parseJsonSafe(row.git_json, {}) || {},
|
|
457
|
+
source: normalizeString(row.source) || null,
|
|
458
|
+
updated_at: normalizeIsoTimestamp(row.updated_at) || null
|
|
459
|
+
};
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
_mapSceneSessionCycleRow(row) {
|
|
463
|
+
if (!row) {
|
|
464
|
+
return null;
|
|
465
|
+
}
|
|
466
|
+
return {
|
|
467
|
+
scene_id: normalizeString(row.scene_id),
|
|
468
|
+
cycle: normalizeNonNegativeInteger(row.cycle, 0),
|
|
469
|
+
session_id: normalizeString(row.session_id),
|
|
470
|
+
status: normalizeString(row.status) || null,
|
|
471
|
+
started_at: normalizeIsoTimestamp(row.started_at) || null,
|
|
472
|
+
completed_at: normalizeIsoTimestamp(row.completed_at) || null,
|
|
473
|
+
source: normalizeString(row.source) || null,
|
|
474
|
+
updated_at: normalizeIsoTimestamp(row.updated_at) || null
|
|
475
|
+
};
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
_mapAgentRuntimeRow(row) {
|
|
479
|
+
if (!row) {
|
|
480
|
+
return null;
|
|
481
|
+
}
|
|
482
|
+
return {
|
|
483
|
+
agent_id: normalizeString(row.agent_id),
|
|
484
|
+
machine_id: normalizeString(row.machine_id) || null,
|
|
485
|
+
instance_index: normalizeNonNegativeInteger(row.instance_index, 0),
|
|
486
|
+
hostname: normalizeString(row.hostname) || null,
|
|
487
|
+
registered_at: normalizeIsoTimestamp(row.registered_at) || null,
|
|
488
|
+
last_heartbeat: normalizeIsoTimestamp(row.last_heartbeat) || null,
|
|
489
|
+
status: normalizeString(row.status) || null,
|
|
490
|
+
current_task: parseJsonSafe(row.current_task_json, null),
|
|
491
|
+
source: normalizeString(row.source) || null,
|
|
492
|
+
updated_at: normalizeIsoTimestamp(row.updated_at) || null
|
|
493
|
+
};
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
_mapStateMigrationRow(row) {
|
|
497
|
+
if (!row) {
|
|
498
|
+
return null;
|
|
499
|
+
}
|
|
500
|
+
return {
|
|
501
|
+
migration_id: normalizeString(row.migration_id),
|
|
502
|
+
component_id: normalizeString(row.component_id),
|
|
503
|
+
source_path: normalizeString(row.source_path) || null,
|
|
504
|
+
mode: normalizeString(row.mode) || null,
|
|
505
|
+
status: normalizeString(row.status) || null,
|
|
506
|
+
metrics: parseJsonSafe(row.metrics_json, {}) || {},
|
|
507
|
+
detail: parseJsonSafe(row.detail_json, {}) || {},
|
|
508
|
+
started_at: normalizeIsoTimestamp(row.started_at) || null,
|
|
509
|
+
completed_at: normalizeIsoTimestamp(row.completed_at) || null,
|
|
510
|
+
updated_at: normalizeIsoTimestamp(row.updated_at) || null
|
|
511
|
+
};
|
|
512
|
+
}
|
|
513
|
+
|
|
267
514
|
async resolveOrCreateTaskRef(options = {}) {
|
|
268
515
|
const sceneId = normalizeString(options.sceneId);
|
|
269
516
|
const specId = normalizeString(options.specId);
|
|
@@ -510,6 +757,763 @@ class SceStateStore {
|
|
|
510
757
|
return events;
|
|
511
758
|
}
|
|
512
759
|
|
|
760
|
+
async issueAuthLease(options = {}) {
|
|
761
|
+
const subject = normalizeString(options.subject) || 'unknown';
|
|
762
|
+
const role = normalizeString(options.role) || 'maintainer';
|
|
763
|
+
const scope = normalizeStringArray(options.scope, ['project:*']);
|
|
764
|
+
const reason = normalizeString(options.reason) || null;
|
|
765
|
+
const metadata = options.metadata && typeof options.metadata === 'object'
|
|
766
|
+
? options.metadata
|
|
767
|
+
: {};
|
|
768
|
+
const issuedAt = normalizeIsoTimestamp(options.issued_at || options.issuedAt, this.now()) || this.now();
|
|
769
|
+
const ttlMinutes = normalizeInteger(options.ttl_minutes || options.ttlMinutes, 15);
|
|
770
|
+
const fallbackExpiresAt = new Date(
|
|
771
|
+
(Date.parse(issuedAt) || Date.now()) + (Math.max(ttlMinutes, 1) * 60 * 1000)
|
|
772
|
+
).toISOString();
|
|
773
|
+
const expiresAt = normalizeIsoTimestamp(options.expires_at || options.expiresAt, fallbackExpiresAt) || fallbackExpiresAt;
|
|
774
|
+
const leaseId = normalizeString(options.lease_id || options.leaseId)
|
|
775
|
+
|| `lease-${Date.now()}-${crypto.randomBytes(4).toString('hex')}`;
|
|
776
|
+
const nowIso = this.now();
|
|
777
|
+
|
|
778
|
+
if (this._useMemoryBackend()) {
|
|
779
|
+
return this._issueAuthLeaseInMemory({
|
|
780
|
+
leaseId,
|
|
781
|
+
subject,
|
|
782
|
+
role,
|
|
783
|
+
scope,
|
|
784
|
+
reason,
|
|
785
|
+
metadata,
|
|
786
|
+
issuedAt,
|
|
787
|
+
expiresAt,
|
|
788
|
+
nowIso
|
|
789
|
+
});
|
|
790
|
+
}
|
|
791
|
+
|
|
792
|
+
if (!await this.ensureReady()) {
|
|
793
|
+
return null;
|
|
794
|
+
}
|
|
795
|
+
|
|
796
|
+
this._db
|
|
797
|
+
.prepare(`
|
|
798
|
+
INSERT OR REPLACE INTO auth_lease_registry(
|
|
799
|
+
lease_id, subject, role, scope_json, reason, metadata_json, issued_at, expires_at, revoked_at, created_at, updated_at
|
|
800
|
+
)
|
|
801
|
+
VALUES(?, ?, ?, ?, ?, ?, ?, ?, NULL, ?, ?)
|
|
802
|
+
`)
|
|
803
|
+
.run(
|
|
804
|
+
leaseId,
|
|
805
|
+
subject,
|
|
806
|
+
role,
|
|
807
|
+
JSON.stringify(scope),
|
|
808
|
+
reason,
|
|
809
|
+
JSON.stringify(metadata),
|
|
810
|
+
issuedAt,
|
|
811
|
+
expiresAt,
|
|
812
|
+
nowIso,
|
|
813
|
+
nowIso
|
|
814
|
+
);
|
|
815
|
+
|
|
816
|
+
return this.getAuthLease(leaseId);
|
|
817
|
+
}
|
|
818
|
+
|
|
819
|
+
async getAuthLease(leaseId) {
|
|
820
|
+
const normalizedLeaseId = normalizeString(leaseId);
|
|
821
|
+
if (!normalizedLeaseId) {
|
|
822
|
+
return null;
|
|
823
|
+
}
|
|
824
|
+
|
|
825
|
+
if (this._useMemoryBackend()) {
|
|
826
|
+
const row = this._memory.auth_leases[normalizedLeaseId];
|
|
827
|
+
return row
|
|
828
|
+
? {
|
|
829
|
+
...row,
|
|
830
|
+
scope: normalizeStringArray(row.scope, ['project:*']),
|
|
831
|
+
metadata: { ...(row.metadata || {}) }
|
|
832
|
+
}
|
|
833
|
+
: null;
|
|
834
|
+
}
|
|
835
|
+
|
|
836
|
+
if (!await this.ensureReady()) {
|
|
837
|
+
return null;
|
|
838
|
+
}
|
|
839
|
+
|
|
840
|
+
const row = this._db
|
|
841
|
+
.prepare(`
|
|
842
|
+
SELECT lease_id, subject, role, scope_json, reason, metadata_json, issued_at, expires_at, revoked_at, created_at, updated_at
|
|
843
|
+
FROM auth_lease_registry
|
|
844
|
+
WHERE lease_id = ?
|
|
845
|
+
`)
|
|
846
|
+
.get(normalizedLeaseId);
|
|
847
|
+
return this._mapAuthLeaseRow(row);
|
|
848
|
+
}
|
|
849
|
+
|
|
850
|
+
async listAuthLeases(options = {}) {
|
|
851
|
+
const activeOnly = options.activeOnly !== false;
|
|
852
|
+
const limit = normalizeInteger(options.limit, 20);
|
|
853
|
+
const nowIso = this.now();
|
|
854
|
+
|
|
855
|
+
if (this._useMemoryBackend()) {
|
|
856
|
+
let rows = Object.values(this._memory.auth_leases || {}).map((item) => ({
|
|
857
|
+
...item,
|
|
858
|
+
scope: normalizeStringArray(item.scope, ['project:*']),
|
|
859
|
+
metadata: { ...(item.metadata || {}) }
|
|
860
|
+
}));
|
|
861
|
+
if (activeOnly) {
|
|
862
|
+
const nowTime = Date.parse(nowIso) || Date.now();
|
|
863
|
+
rows = rows.filter((item) => {
|
|
864
|
+
const revokedAt = normalizeString(item.revoked_at);
|
|
865
|
+
if (revokedAt) {
|
|
866
|
+
return false;
|
|
867
|
+
}
|
|
868
|
+
const expiresAt = Date.parse(item.expires_at || '') || 0;
|
|
869
|
+
return expiresAt > nowTime;
|
|
870
|
+
});
|
|
871
|
+
}
|
|
872
|
+
rows.sort((left, right) => (Date.parse(right.created_at || '') || 0) - (Date.parse(left.created_at || '') || 0));
|
|
873
|
+
return limit > 0 ? rows.slice(0, limit) : rows;
|
|
874
|
+
}
|
|
875
|
+
|
|
876
|
+
if (!await this.ensureReady()) {
|
|
877
|
+
return null;
|
|
878
|
+
}
|
|
879
|
+
|
|
880
|
+
const query = activeOnly
|
|
881
|
+
? `
|
|
882
|
+
SELECT lease_id, subject, role, scope_json, reason, metadata_json, issued_at, expires_at, revoked_at, created_at, updated_at
|
|
883
|
+
FROM auth_lease_registry
|
|
884
|
+
WHERE revoked_at IS NULL AND expires_at > ?
|
|
885
|
+
ORDER BY created_at DESC
|
|
886
|
+
LIMIT ?
|
|
887
|
+
`
|
|
888
|
+
: `
|
|
889
|
+
SELECT lease_id, subject, role, scope_json, reason, metadata_json, issued_at, expires_at, revoked_at, created_at, updated_at
|
|
890
|
+
FROM auth_lease_registry
|
|
891
|
+
ORDER BY created_at DESC
|
|
892
|
+
LIMIT ?
|
|
893
|
+
`;
|
|
894
|
+
|
|
895
|
+
const statement = this._db.prepare(query);
|
|
896
|
+
const rows = activeOnly
|
|
897
|
+
? statement.all(nowIso, limit)
|
|
898
|
+
: statement.all(limit);
|
|
899
|
+
return rows
|
|
900
|
+
.map((row) => this._mapAuthLeaseRow(row))
|
|
901
|
+
.filter(Boolean);
|
|
902
|
+
}
|
|
903
|
+
|
|
904
|
+
async revokeAuthLease(leaseId, options = {}) {
|
|
905
|
+
const normalizedLeaseId = normalizeString(leaseId);
|
|
906
|
+
if (!normalizedLeaseId) {
|
|
907
|
+
return null;
|
|
908
|
+
}
|
|
909
|
+
const revokedAt = normalizeIsoTimestamp(options.revoked_at || options.revokedAt, this.now()) || this.now();
|
|
910
|
+
|
|
911
|
+
if (this._useMemoryBackend()) {
|
|
912
|
+
const existing = this._memory.auth_leases[normalizedLeaseId];
|
|
913
|
+
if (!existing) {
|
|
914
|
+
return null;
|
|
915
|
+
}
|
|
916
|
+
existing.revoked_at = revokedAt;
|
|
917
|
+
existing.updated_at = revokedAt;
|
|
918
|
+
this._memory.auth_leases[normalizedLeaseId] = existing;
|
|
919
|
+
return {
|
|
920
|
+
...existing,
|
|
921
|
+
scope: normalizeStringArray(existing.scope, ['project:*']),
|
|
922
|
+
metadata: { ...(existing.metadata || {}) }
|
|
923
|
+
};
|
|
924
|
+
}
|
|
925
|
+
|
|
926
|
+
if (!await this.ensureReady()) {
|
|
927
|
+
return null;
|
|
928
|
+
}
|
|
929
|
+
|
|
930
|
+
this._db
|
|
931
|
+
.prepare('UPDATE auth_lease_registry SET revoked_at = ?, updated_at = ? WHERE lease_id = ?')
|
|
932
|
+
.run(revokedAt, revokedAt, normalizedLeaseId);
|
|
933
|
+
return this.getAuthLease(normalizedLeaseId);
|
|
934
|
+
}
|
|
935
|
+
|
|
936
|
+
async appendAuthEvent(event = {}) {
|
|
937
|
+
const eventType = normalizeString(event.event_type || event.eventType);
|
|
938
|
+
if (!eventType) {
|
|
939
|
+
return false;
|
|
940
|
+
}
|
|
941
|
+
const eventId = normalizeString(event.event_id || event.eventId)
|
|
942
|
+
|| `auth-evt-${Date.now()}-${crypto.randomBytes(3).toString('hex')}`;
|
|
943
|
+
const timestamp = normalizeIsoTimestamp(event.event_timestamp || event.timestamp, this.now()) || this.now();
|
|
944
|
+
const normalizedEvent = {
|
|
945
|
+
event_id: eventId,
|
|
946
|
+
event_timestamp: timestamp,
|
|
947
|
+
event_type: eventType,
|
|
948
|
+
action: normalizeString(event.action) || null,
|
|
949
|
+
actor: normalizeString(event.actor) || null,
|
|
950
|
+
lease_id: normalizeString(event.lease_id || event.leaseId) || null,
|
|
951
|
+
result: normalizeString(event.result) || null,
|
|
952
|
+
target: normalizeString(event.target) || null,
|
|
953
|
+
detail: event.detail && typeof event.detail === 'object'
|
|
954
|
+
? event.detail
|
|
955
|
+
: {}
|
|
956
|
+
};
|
|
957
|
+
|
|
958
|
+
if (this._useMemoryBackend()) {
|
|
959
|
+
const existingIndex = this._memory.auth_events
|
|
960
|
+
.findIndex((item) => normalizeString(item.event_id) === eventId);
|
|
961
|
+
const row = {
|
|
962
|
+
...normalizedEvent,
|
|
963
|
+
created_at: this.now()
|
|
964
|
+
};
|
|
965
|
+
if (existingIndex >= 0) {
|
|
966
|
+
this._memory.auth_events[existingIndex] = row;
|
|
967
|
+
} else {
|
|
968
|
+
this._memory.auth_events.push(row);
|
|
969
|
+
}
|
|
970
|
+
this._memory.auth_events.sort((left, right) => {
|
|
971
|
+
const l = Date.parse(left.event_timestamp || '') || 0;
|
|
972
|
+
const r = Date.parse(right.event_timestamp || '') || 0;
|
|
973
|
+
return l - r;
|
|
974
|
+
});
|
|
975
|
+
return true;
|
|
976
|
+
}
|
|
977
|
+
|
|
978
|
+
if (!await this.ensureReady()) {
|
|
979
|
+
return false;
|
|
980
|
+
}
|
|
981
|
+
|
|
982
|
+
this._db
|
|
983
|
+
.prepare(`
|
|
984
|
+
INSERT OR REPLACE INTO auth_event_stream(
|
|
985
|
+
event_id, event_timestamp, event_type, action, actor, lease_id, result, target, detail_json, created_at
|
|
986
|
+
)
|
|
987
|
+
VALUES(?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
988
|
+
`)
|
|
989
|
+
.run(
|
|
990
|
+
normalizedEvent.event_id,
|
|
991
|
+
normalizedEvent.event_timestamp,
|
|
992
|
+
normalizedEvent.event_type,
|
|
993
|
+
normalizedEvent.action,
|
|
994
|
+
normalizedEvent.actor,
|
|
995
|
+
normalizedEvent.lease_id,
|
|
996
|
+
normalizedEvent.result,
|
|
997
|
+
normalizedEvent.target,
|
|
998
|
+
JSON.stringify(normalizedEvent.detail || {}),
|
|
999
|
+
this.now()
|
|
1000
|
+
);
|
|
1001
|
+
return true;
|
|
1002
|
+
}
|
|
1003
|
+
|
|
1004
|
+
async listAuthEvents(options = {}) {
|
|
1005
|
+
const limit = normalizeInteger(options.limit, 50);
|
|
1006
|
+
|
|
1007
|
+
if (this._useMemoryBackend()) {
|
|
1008
|
+
const rows = [...this._memory.auth_events]
|
|
1009
|
+
.sort((left, right) => (Date.parse(right.event_timestamp || '') || 0) - (Date.parse(left.event_timestamp || '') || 0))
|
|
1010
|
+
.map((row) => ({
|
|
1011
|
+
...row,
|
|
1012
|
+
detail: row.detail && typeof row.detail === 'object' ? row.detail : {}
|
|
1013
|
+
}));
|
|
1014
|
+
return limit > 0 ? rows.slice(0, limit) : rows;
|
|
1015
|
+
}
|
|
1016
|
+
|
|
1017
|
+
if (!await this.ensureReady()) {
|
|
1018
|
+
return null;
|
|
1019
|
+
}
|
|
1020
|
+
|
|
1021
|
+
const query = limit > 0
|
|
1022
|
+
? `
|
|
1023
|
+
SELECT event_id, event_timestamp, event_type, action, actor, lease_id, result, target, detail_json, created_at
|
|
1024
|
+
FROM auth_event_stream
|
|
1025
|
+
ORDER BY event_timestamp DESC
|
|
1026
|
+
LIMIT ?
|
|
1027
|
+
`
|
|
1028
|
+
: `
|
|
1029
|
+
SELECT event_id, event_timestamp, event_type, action, actor, lease_id, result, target, detail_json, created_at
|
|
1030
|
+
FROM auth_event_stream
|
|
1031
|
+
ORDER BY event_timestamp DESC
|
|
1032
|
+
`;
|
|
1033
|
+
|
|
1034
|
+
const statement = this._db.prepare(query);
|
|
1035
|
+
const rows = limit > 0 ? statement.all(limit) : statement.all();
|
|
1036
|
+
return rows
|
|
1037
|
+
.map((row) => this._mapAuthEventRow(row))
|
|
1038
|
+
.filter(Boolean);
|
|
1039
|
+
}
|
|
1040
|
+
|
|
1041
|
+
async upsertTimelineSnapshotIndex(records = [], options = {}) {
|
|
1042
|
+
const source = normalizeString(options.source) || 'file.timeline.index';
|
|
1043
|
+
const nowIso = this.now();
|
|
1044
|
+
const normalizedRecords = Array.isArray(records)
|
|
1045
|
+
? records.map((item) => ({
|
|
1046
|
+
snapshot_id: normalizeString(item && item.snapshot_id),
|
|
1047
|
+
created_at: normalizeIsoTimestamp(item && item.created_at, nowIso) || nowIso,
|
|
1048
|
+
trigger: normalizeString(item && item.trigger) || null,
|
|
1049
|
+
event: normalizeString(item && item.event) || null,
|
|
1050
|
+
summary: normalizeString(item && item.summary) || null,
|
|
1051
|
+
scene_id: normalizeString(item && item.scene_id) || null,
|
|
1052
|
+
session_id: normalizeString(item && item.session_id) || null,
|
|
1053
|
+
command: normalizeString(item && item.command) || null,
|
|
1054
|
+
file_count: normalizeNonNegativeInteger(item && item.file_count, 0),
|
|
1055
|
+
total_bytes: normalizeNonNegativeInteger(item && item.total_bytes, 0),
|
|
1056
|
+
snapshot_path: normalizeString(item && (item.snapshot_path || item.path)) || null,
|
|
1057
|
+
git: item && item.git && typeof item.git === 'object' ? item.git : {},
|
|
1058
|
+
source,
|
|
1059
|
+
updated_at: nowIso
|
|
1060
|
+
}))
|
|
1061
|
+
.filter((item) => item.snapshot_id)
|
|
1062
|
+
: [];
|
|
1063
|
+
|
|
1064
|
+
if (this._useMemoryBackend()) {
|
|
1065
|
+
for (const item of normalizedRecords) {
|
|
1066
|
+
this._memory.timeline_snapshots[item.snapshot_id] = { ...item };
|
|
1067
|
+
}
|
|
1068
|
+
return {
|
|
1069
|
+
success: true,
|
|
1070
|
+
written: normalizedRecords.length,
|
|
1071
|
+
total: Object.keys(this._memory.timeline_snapshots || {}).length
|
|
1072
|
+
};
|
|
1073
|
+
}
|
|
1074
|
+
|
|
1075
|
+
if (!await this.ensureReady()) {
|
|
1076
|
+
return null;
|
|
1077
|
+
}
|
|
1078
|
+
|
|
1079
|
+
const statement = this._db.prepare(`
|
|
1080
|
+
INSERT OR REPLACE INTO timeline_snapshot_registry(
|
|
1081
|
+
snapshot_id, created_at, trigger, event, summary, scene_id, session_id, command,
|
|
1082
|
+
file_count, total_bytes, snapshot_path, git_json, source, updated_at
|
|
1083
|
+
)
|
|
1084
|
+
VALUES(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
1085
|
+
`);
|
|
1086
|
+
|
|
1087
|
+
this._withTransaction(() => {
|
|
1088
|
+
for (const item of normalizedRecords) {
|
|
1089
|
+
statement.run(
|
|
1090
|
+
item.snapshot_id,
|
|
1091
|
+
item.created_at,
|
|
1092
|
+
item.trigger,
|
|
1093
|
+
item.event,
|
|
1094
|
+
item.summary,
|
|
1095
|
+
item.scene_id,
|
|
1096
|
+
item.session_id,
|
|
1097
|
+
item.command,
|
|
1098
|
+
item.file_count,
|
|
1099
|
+
item.total_bytes,
|
|
1100
|
+
item.snapshot_path,
|
|
1101
|
+
JSON.stringify(item.git || {}),
|
|
1102
|
+
item.source,
|
|
1103
|
+
item.updated_at
|
|
1104
|
+
);
|
|
1105
|
+
}
|
|
1106
|
+
});
|
|
1107
|
+
|
|
1108
|
+
const totalRow = this._db
|
|
1109
|
+
.prepare('SELECT COUNT(*) AS total FROM timeline_snapshot_registry')
|
|
1110
|
+
.get();
|
|
1111
|
+
|
|
1112
|
+
return {
|
|
1113
|
+
success: true,
|
|
1114
|
+
written: normalizedRecords.length,
|
|
1115
|
+
total: normalizeNonNegativeInteger(totalRow && totalRow.total, 0)
|
|
1116
|
+
};
|
|
1117
|
+
}
|
|
1118
|
+
|
|
1119
|
+
async listTimelineSnapshotIndex(options = {}) {
|
|
1120
|
+
const limit = normalizeInteger(options.limit, 100);
|
|
1121
|
+
const triggerFilter = normalizeString(options.trigger);
|
|
1122
|
+
const snapshotIdFilter = normalizeString(options.snapshotId);
|
|
1123
|
+
|
|
1124
|
+
if (this._useMemoryBackend()) {
|
|
1125
|
+
let rows = Object.values(this._memory.timeline_snapshots || {}).map((item) => ({ ...item }));
|
|
1126
|
+
if (triggerFilter) {
|
|
1127
|
+
rows = rows.filter((item) => normalizeString(item.trigger) === triggerFilter);
|
|
1128
|
+
}
|
|
1129
|
+
if (snapshotIdFilter) {
|
|
1130
|
+
rows = rows.filter((item) => normalizeString(item.snapshot_id) === snapshotIdFilter);
|
|
1131
|
+
}
|
|
1132
|
+
rows.sort((left, right) => (Date.parse(right.created_at || '') || 0) - (Date.parse(left.created_at || '') || 0));
|
|
1133
|
+
if (limit > 0) {
|
|
1134
|
+
rows = rows.slice(0, limit);
|
|
1135
|
+
}
|
|
1136
|
+
return rows.map((row) => this._mapTimelineSnapshotRow({
|
|
1137
|
+
...row,
|
|
1138
|
+
git_json: JSON.stringify(row.git || {})
|
|
1139
|
+
}));
|
|
1140
|
+
}
|
|
1141
|
+
|
|
1142
|
+
if (!await this.ensureReady()) {
|
|
1143
|
+
return null;
|
|
1144
|
+
}
|
|
1145
|
+
|
|
1146
|
+
let query = `
|
|
1147
|
+
SELECT snapshot_id, created_at, trigger, event, summary, scene_id, session_id, command,
|
|
1148
|
+
file_count, total_bytes, snapshot_path, git_json, source, updated_at
|
|
1149
|
+
FROM timeline_snapshot_registry
|
|
1150
|
+
`;
|
|
1151
|
+
const clauses = [];
|
|
1152
|
+
const params = [];
|
|
1153
|
+
if (triggerFilter) {
|
|
1154
|
+
clauses.push('trigger = ?');
|
|
1155
|
+
params.push(triggerFilter);
|
|
1156
|
+
}
|
|
1157
|
+
if (snapshotIdFilter) {
|
|
1158
|
+
clauses.push('snapshot_id = ?');
|
|
1159
|
+
params.push(snapshotIdFilter);
|
|
1160
|
+
}
|
|
1161
|
+
if (clauses.length > 0) {
|
|
1162
|
+
query += ` WHERE ${clauses.join(' AND ')}`;
|
|
1163
|
+
}
|
|
1164
|
+
query += ' ORDER BY created_at DESC';
|
|
1165
|
+
if (limit > 0) {
|
|
1166
|
+
query += ' LIMIT ?';
|
|
1167
|
+
params.push(limit);
|
|
1168
|
+
}
|
|
1169
|
+
|
|
1170
|
+
const rows = this._db.prepare(query).all(...params);
|
|
1171
|
+
return rows
|
|
1172
|
+
.map((row) => this._mapTimelineSnapshotRow(row))
|
|
1173
|
+
.filter(Boolean);
|
|
1174
|
+
}
|
|
1175
|
+
|
|
1176
|
+
async upsertSceneSessionCycles(records = [], options = {}) {
|
|
1177
|
+
const source = normalizeString(options.source) || 'file.session.scene-index';
|
|
1178
|
+
const nowIso = this.now();
|
|
1179
|
+
const normalizedRecords = Array.isArray(records)
|
|
1180
|
+
? records.map((item) => ({
|
|
1181
|
+
scene_id: normalizeString(item && item.scene_id),
|
|
1182
|
+
cycle: normalizeNonNegativeInteger(item && item.cycle, 0),
|
|
1183
|
+
session_id: normalizeString(item && item.session_id),
|
|
1184
|
+
status: normalizeString(item && item.status) || null,
|
|
1185
|
+
started_at: normalizeIsoTimestamp(item && item.started_at, nowIso) || nowIso,
|
|
1186
|
+
completed_at: normalizeIsoTimestamp(item && item.completed_at, '') || null,
|
|
1187
|
+
source,
|
|
1188
|
+
updated_at: nowIso
|
|
1189
|
+
}))
|
|
1190
|
+
.filter((item) => item.scene_id && item.cycle > 0 && item.session_id)
|
|
1191
|
+
: [];
|
|
1192
|
+
|
|
1193
|
+
if (this._useMemoryBackend()) {
|
|
1194
|
+
for (const item of normalizedRecords) {
|
|
1195
|
+
const key = `${item.scene_id}::${item.cycle}`;
|
|
1196
|
+
this._memory.scene_session_cycles[key] = { ...item };
|
|
1197
|
+
}
|
|
1198
|
+
return {
|
|
1199
|
+
success: true,
|
|
1200
|
+
written: normalizedRecords.length,
|
|
1201
|
+
total: Object.keys(this._memory.scene_session_cycles || {}).length
|
|
1202
|
+
};
|
|
1203
|
+
}
|
|
1204
|
+
|
|
1205
|
+
if (!await this.ensureReady()) {
|
|
1206
|
+
return null;
|
|
1207
|
+
}
|
|
1208
|
+
|
|
1209
|
+
const statement = this._db.prepare(`
|
|
1210
|
+
INSERT OR REPLACE INTO scene_session_cycle_registry(
|
|
1211
|
+
scene_id, cycle, session_id, status, started_at, completed_at, source, updated_at
|
|
1212
|
+
)
|
|
1213
|
+
VALUES(?, ?, ?, ?, ?, ?, ?, ?)
|
|
1214
|
+
`);
|
|
1215
|
+
|
|
1216
|
+
this._withTransaction(() => {
|
|
1217
|
+
for (const item of normalizedRecords) {
|
|
1218
|
+
statement.run(
|
|
1219
|
+
item.scene_id,
|
|
1220
|
+
item.cycle,
|
|
1221
|
+
item.session_id,
|
|
1222
|
+
item.status,
|
|
1223
|
+
item.started_at,
|
|
1224
|
+
item.completed_at,
|
|
1225
|
+
item.source,
|
|
1226
|
+
item.updated_at
|
|
1227
|
+
);
|
|
1228
|
+
}
|
|
1229
|
+
});
|
|
1230
|
+
|
|
1231
|
+
const totalRow = this._db
|
|
1232
|
+
.prepare('SELECT COUNT(*) AS total FROM scene_session_cycle_registry')
|
|
1233
|
+
.get();
|
|
1234
|
+
|
|
1235
|
+
return {
|
|
1236
|
+
success: true,
|
|
1237
|
+
written: normalizedRecords.length,
|
|
1238
|
+
total: normalizeNonNegativeInteger(totalRow && totalRow.total, 0)
|
|
1239
|
+
};
|
|
1240
|
+
}
|
|
1241
|
+
|
|
1242
|
+
async listSceneSessionCycles(options = {}) {
|
|
1243
|
+
const limit = normalizeInteger(options.limit, 100);
|
|
1244
|
+
const sceneId = normalizeString(options.sceneId);
|
|
1245
|
+
|
|
1246
|
+
if (this._useMemoryBackend()) {
|
|
1247
|
+
let rows = Object.values(this._memory.scene_session_cycles || {}).map((item) => ({ ...item }));
|
|
1248
|
+
if (sceneId) {
|
|
1249
|
+
rows = rows.filter((item) => normalizeString(item.scene_id) === sceneId);
|
|
1250
|
+
}
|
|
1251
|
+
rows.sort((left, right) => {
|
|
1252
|
+
const sceneCompare = `${left.scene_id}`.localeCompare(`${right.scene_id}`);
|
|
1253
|
+
if (sceneCompare !== 0) {
|
|
1254
|
+
return sceneCompare;
|
|
1255
|
+
}
|
|
1256
|
+
return right.cycle - left.cycle;
|
|
1257
|
+
});
|
|
1258
|
+
if (limit > 0) {
|
|
1259
|
+
rows = rows.slice(0, limit);
|
|
1260
|
+
}
|
|
1261
|
+
return rows.map((row) => this._mapSceneSessionCycleRow(row));
|
|
1262
|
+
}
|
|
1263
|
+
|
|
1264
|
+
if (!await this.ensureReady()) {
|
|
1265
|
+
return null;
|
|
1266
|
+
}
|
|
1267
|
+
|
|
1268
|
+
let query = `
|
|
1269
|
+
SELECT scene_id, cycle, session_id, status, started_at, completed_at, source, updated_at
|
|
1270
|
+
FROM scene_session_cycle_registry
|
|
1271
|
+
`;
|
|
1272
|
+
const params = [];
|
|
1273
|
+
if (sceneId) {
|
|
1274
|
+
query += ' WHERE scene_id = ?';
|
|
1275
|
+
params.push(sceneId);
|
|
1276
|
+
}
|
|
1277
|
+
query += ' ORDER BY scene_id ASC, cycle DESC';
|
|
1278
|
+
if (limit > 0) {
|
|
1279
|
+
query += ' LIMIT ?';
|
|
1280
|
+
params.push(limit);
|
|
1281
|
+
}
|
|
1282
|
+
|
|
1283
|
+
const rows = this._db.prepare(query).all(...params);
|
|
1284
|
+
return rows
|
|
1285
|
+
.map((row) => this._mapSceneSessionCycleRow(row))
|
|
1286
|
+
.filter(Boolean);
|
|
1287
|
+
}
|
|
1288
|
+
|
|
1289
|
+
async upsertAgentRuntimeRecords(records = [], options = {}) {
|
|
1290
|
+
const source = normalizeString(options.source) || 'file.agent-registry';
|
|
1291
|
+
const nowIso = this.now();
|
|
1292
|
+
const normalizedRecords = Array.isArray(records)
|
|
1293
|
+
? records.map((item) => ({
|
|
1294
|
+
agent_id: normalizeString(item && item.agent_id),
|
|
1295
|
+
machine_id: normalizeString(item && item.machine_id) || null,
|
|
1296
|
+
instance_index: normalizeNonNegativeInteger(item && item.instance_index, 0),
|
|
1297
|
+
hostname: normalizeString(item && item.hostname) || null,
|
|
1298
|
+
registered_at: normalizeIsoTimestamp(item && item.registered_at, nowIso) || nowIso,
|
|
1299
|
+
last_heartbeat: normalizeIsoTimestamp(item && item.last_heartbeat, nowIso) || nowIso,
|
|
1300
|
+
status: normalizeString(item && item.status) || null,
|
|
1301
|
+
current_task: item && item.current_task && typeof item.current_task === 'object'
|
|
1302
|
+
? item.current_task
|
|
1303
|
+
: null,
|
|
1304
|
+
source,
|
|
1305
|
+
updated_at: nowIso
|
|
1306
|
+
}))
|
|
1307
|
+
.filter((item) => item.agent_id)
|
|
1308
|
+
: [];
|
|
1309
|
+
|
|
1310
|
+
if (this._useMemoryBackend()) {
|
|
1311
|
+
for (const item of normalizedRecords) {
|
|
1312
|
+
this._memory.agent_runtime[item.agent_id] = { ...item };
|
|
1313
|
+
}
|
|
1314
|
+
return {
|
|
1315
|
+
success: true,
|
|
1316
|
+
written: normalizedRecords.length,
|
|
1317
|
+
total: Object.keys(this._memory.agent_runtime || {}).length
|
|
1318
|
+
};
|
|
1319
|
+
}
|
|
1320
|
+
|
|
1321
|
+
if (!await this.ensureReady()) {
|
|
1322
|
+
return null;
|
|
1323
|
+
}
|
|
1324
|
+
|
|
1325
|
+
const statement = this._db.prepare(`
|
|
1326
|
+
INSERT OR REPLACE INTO agent_runtime_registry(
|
|
1327
|
+
agent_id, machine_id, instance_index, hostname, registered_at, last_heartbeat, status, current_task_json, source, updated_at
|
|
1328
|
+
)
|
|
1329
|
+
VALUES(?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
1330
|
+
`);
|
|
1331
|
+
|
|
1332
|
+
this._withTransaction(() => {
|
|
1333
|
+
for (const item of normalizedRecords) {
|
|
1334
|
+
statement.run(
|
|
1335
|
+
item.agent_id,
|
|
1336
|
+
item.machine_id,
|
|
1337
|
+
item.instance_index,
|
|
1338
|
+
item.hostname,
|
|
1339
|
+
item.registered_at,
|
|
1340
|
+
item.last_heartbeat,
|
|
1341
|
+
item.status,
|
|
1342
|
+
JSON.stringify(item.current_task),
|
|
1343
|
+
item.source,
|
|
1344
|
+
item.updated_at
|
|
1345
|
+
);
|
|
1346
|
+
}
|
|
1347
|
+
});
|
|
1348
|
+
|
|
1349
|
+
const totalRow = this._db
|
|
1350
|
+
.prepare('SELECT COUNT(*) AS total FROM agent_runtime_registry')
|
|
1351
|
+
.get();
|
|
1352
|
+
|
|
1353
|
+
return {
|
|
1354
|
+
success: true,
|
|
1355
|
+
written: normalizedRecords.length,
|
|
1356
|
+
total: normalizeNonNegativeInteger(totalRow && totalRow.total, 0)
|
|
1357
|
+
};
|
|
1358
|
+
}
|
|
1359
|
+
|
|
1360
|
+
async listAgentRuntimeRecords(options = {}) {
|
|
1361
|
+
const limit = normalizeInteger(options.limit, 100);
|
|
1362
|
+
const status = normalizeString(options.status);
|
|
1363
|
+
|
|
1364
|
+
if (this._useMemoryBackend()) {
|
|
1365
|
+
let rows = Object.values(this._memory.agent_runtime || {}).map((item) => ({ ...item }));
|
|
1366
|
+
if (status) {
|
|
1367
|
+
rows = rows.filter((item) => normalizeString(item.status) === status);
|
|
1368
|
+
}
|
|
1369
|
+
rows.sort((left, right) => (Date.parse(right.last_heartbeat || '') || 0) - (Date.parse(left.last_heartbeat || '') || 0));
|
|
1370
|
+
if (limit > 0) {
|
|
1371
|
+
rows = rows.slice(0, limit);
|
|
1372
|
+
}
|
|
1373
|
+
return rows.map((row) => this._mapAgentRuntimeRow({
|
|
1374
|
+
...row,
|
|
1375
|
+
current_task_json: JSON.stringify(row.current_task || null)
|
|
1376
|
+
}));
|
|
1377
|
+
}
|
|
1378
|
+
|
|
1379
|
+
if (!await this.ensureReady()) {
|
|
1380
|
+
return null;
|
|
1381
|
+
}
|
|
1382
|
+
|
|
1383
|
+
let query = `
|
|
1384
|
+
SELECT agent_id, machine_id, instance_index, hostname, registered_at, last_heartbeat, status, current_task_json, source, updated_at
|
|
1385
|
+
FROM agent_runtime_registry
|
|
1386
|
+
`;
|
|
1387
|
+
const params = [];
|
|
1388
|
+
if (status) {
|
|
1389
|
+
query += ' WHERE status = ?';
|
|
1390
|
+
params.push(status);
|
|
1391
|
+
}
|
|
1392
|
+
query += ' ORDER BY last_heartbeat DESC';
|
|
1393
|
+
if (limit > 0) {
|
|
1394
|
+
query += ' LIMIT ?';
|
|
1395
|
+
params.push(limit);
|
|
1396
|
+
}
|
|
1397
|
+
|
|
1398
|
+
const rows = this._db.prepare(query).all(...params);
|
|
1399
|
+
return rows
|
|
1400
|
+
.map((row) => this._mapAgentRuntimeRow(row))
|
|
1401
|
+
.filter(Boolean);
|
|
1402
|
+
}
|
|
1403
|
+
|
|
1404
|
+
async appendStateMigrationRecord(record = {}) {
|
|
1405
|
+
const componentId = normalizeString(record.component_id || record.componentId);
|
|
1406
|
+
if (!componentId) {
|
|
1407
|
+
return null;
|
|
1408
|
+
}
|
|
1409
|
+
const migrationId = normalizeString(record.migration_id || record.migrationId)
|
|
1410
|
+
|| `migration-${Date.now()}-${crypto.randomBytes(3).toString('hex')}`;
|
|
1411
|
+
const startedAt = normalizeIsoTimestamp(record.started_at || record.startedAt, this.now()) || this.now();
|
|
1412
|
+
const completedAt = normalizeIsoTimestamp(record.completed_at || record.completedAt, '') || null;
|
|
1413
|
+
const nowIso = this.now();
|
|
1414
|
+
const normalized = {
|
|
1415
|
+
migration_id: migrationId,
|
|
1416
|
+
component_id: componentId,
|
|
1417
|
+
source_path: normalizeString(record.source_path || record.sourcePath) || null,
|
|
1418
|
+
mode: normalizeString(record.mode) || 'unknown',
|
|
1419
|
+
status: normalizeString(record.status) || 'completed',
|
|
1420
|
+
metrics: record.metrics && typeof record.metrics === 'object' ? record.metrics : {},
|
|
1421
|
+
detail: record.detail && typeof record.detail === 'object' ? record.detail : {},
|
|
1422
|
+
started_at: startedAt,
|
|
1423
|
+
completed_at: completedAt,
|
|
1424
|
+
updated_at: nowIso
|
|
1425
|
+
};
|
|
1426
|
+
|
|
1427
|
+
if (this._useMemoryBackend()) {
|
|
1428
|
+
this._memory.migration_records[normalized.migration_id] = { ...normalized };
|
|
1429
|
+
return { ...normalized };
|
|
1430
|
+
}
|
|
1431
|
+
|
|
1432
|
+
if (!await this.ensureReady()) {
|
|
1433
|
+
return null;
|
|
1434
|
+
}
|
|
1435
|
+
|
|
1436
|
+
this._db
|
|
1437
|
+
.prepare(`
|
|
1438
|
+
INSERT OR REPLACE INTO state_migration_registry(
|
|
1439
|
+
migration_id, component_id, source_path, mode, status, metrics_json, detail_json, started_at, completed_at, updated_at
|
|
1440
|
+
)
|
|
1441
|
+
VALUES(?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
1442
|
+
`)
|
|
1443
|
+
.run(
|
|
1444
|
+
normalized.migration_id,
|
|
1445
|
+
normalized.component_id,
|
|
1446
|
+
normalized.source_path,
|
|
1447
|
+
normalized.mode,
|
|
1448
|
+
normalized.status,
|
|
1449
|
+
JSON.stringify(normalized.metrics || {}),
|
|
1450
|
+
JSON.stringify(normalized.detail || {}),
|
|
1451
|
+
normalized.started_at,
|
|
1452
|
+
normalized.completed_at,
|
|
1453
|
+
normalized.updated_at
|
|
1454
|
+
);
|
|
1455
|
+
|
|
1456
|
+
return this.listStateMigrations({ migrationId: normalized.migration_id, limit: 1 })
|
|
1457
|
+
.then((rows) => (Array.isArray(rows) && rows.length > 0 ? rows[0] : null));
|
|
1458
|
+
}
|
|
1459
|
+
|
|
1460
|
+
async listStateMigrations(options = {}) {
|
|
1461
|
+
const limit = normalizeInteger(options.limit, 50);
|
|
1462
|
+
const componentId = normalizeString(options.componentId);
|
|
1463
|
+
const migrationId = normalizeString(options.migrationId);
|
|
1464
|
+
|
|
1465
|
+
if (this._useMemoryBackend()) {
|
|
1466
|
+
let rows = Object.values(this._memory.migration_records || {}).map((item) => ({ ...item }));
|
|
1467
|
+
if (componentId) {
|
|
1468
|
+
rows = rows.filter((item) => normalizeString(item.component_id) === componentId);
|
|
1469
|
+
}
|
|
1470
|
+
if (migrationId) {
|
|
1471
|
+
rows = rows.filter((item) => normalizeString(item.migration_id) === migrationId);
|
|
1472
|
+
}
|
|
1473
|
+
rows.sort((left, right) => (Date.parse(right.started_at || '') || 0) - (Date.parse(left.started_at || '') || 0));
|
|
1474
|
+
if (limit > 0) {
|
|
1475
|
+
rows = rows.slice(0, limit);
|
|
1476
|
+
}
|
|
1477
|
+
return rows.map((row) => this._mapStateMigrationRow({
|
|
1478
|
+
...row,
|
|
1479
|
+
metrics_json: JSON.stringify(row.metrics || {}),
|
|
1480
|
+
detail_json: JSON.stringify(row.detail || {})
|
|
1481
|
+
}));
|
|
1482
|
+
}
|
|
1483
|
+
|
|
1484
|
+
if (!await this.ensureReady()) {
|
|
1485
|
+
return null;
|
|
1486
|
+
}
|
|
1487
|
+
|
|
1488
|
+
let query = `
|
|
1489
|
+
SELECT migration_id, component_id, source_path, mode, status, metrics_json, detail_json, started_at, completed_at, updated_at
|
|
1490
|
+
FROM state_migration_registry
|
|
1491
|
+
`;
|
|
1492
|
+
const clauses = [];
|
|
1493
|
+
const params = [];
|
|
1494
|
+
if (componentId) {
|
|
1495
|
+
clauses.push('component_id = ?');
|
|
1496
|
+
params.push(componentId);
|
|
1497
|
+
}
|
|
1498
|
+
if (migrationId) {
|
|
1499
|
+
clauses.push('migration_id = ?');
|
|
1500
|
+
params.push(migrationId);
|
|
1501
|
+
}
|
|
1502
|
+
if (clauses.length > 0) {
|
|
1503
|
+
query += ` WHERE ${clauses.join(' AND ')}`;
|
|
1504
|
+
}
|
|
1505
|
+
query += ' ORDER BY started_at DESC';
|
|
1506
|
+
if (limit > 0) {
|
|
1507
|
+
query += ' LIMIT ?';
|
|
1508
|
+
params.push(limit);
|
|
1509
|
+
}
|
|
1510
|
+
|
|
1511
|
+
const rows = this._db.prepare(query).all(...params);
|
|
1512
|
+
return rows
|
|
1513
|
+
.map((row) => this._mapStateMigrationRow(row))
|
|
1514
|
+
.filter(Boolean);
|
|
1515
|
+
}
|
|
1516
|
+
|
|
513
1517
|
_resolveOrCreateTaskRefInMemory(options = {}) {
|
|
514
1518
|
const sceneId = normalizeString(options.sceneId);
|
|
515
1519
|
const specId = normalizeString(options.specId);
|
|
@@ -566,6 +1570,30 @@ class SceStateStore {
|
|
|
566
1570
|
this._memory.refs[taskRef] = row;
|
|
567
1571
|
return { ...row, metadata: { ...(row.metadata || {}) } };
|
|
568
1572
|
}
|
|
1573
|
+
|
|
1574
|
+
_issueAuthLeaseInMemory(options = {}) {
|
|
1575
|
+
const leaseId = normalizeString(options.leaseId)
|
|
1576
|
+
|| `lease-${Date.now()}-${crypto.randomBytes(4).toString('hex')}`;
|
|
1577
|
+
const row = {
|
|
1578
|
+
lease_id: leaseId,
|
|
1579
|
+
subject: normalizeString(options.subject) || 'unknown',
|
|
1580
|
+
role: normalizeString(options.role) || 'maintainer',
|
|
1581
|
+
scope: normalizeStringArray(options.scope, ['project:*']),
|
|
1582
|
+
reason: normalizeString(options.reason) || null,
|
|
1583
|
+
metadata: options.metadata && typeof options.metadata === 'object' ? { ...options.metadata } : {},
|
|
1584
|
+
issued_at: normalizeIsoTimestamp(options.issuedAt, this.now()) || this.now(),
|
|
1585
|
+
expires_at: normalizeIsoTimestamp(options.expiresAt, this.now()) || this.now(),
|
|
1586
|
+
revoked_at: null,
|
|
1587
|
+
created_at: normalizeIsoTimestamp(options.nowIso, this.now()) || this.now(),
|
|
1588
|
+
updated_at: normalizeIsoTimestamp(options.nowIso, this.now()) || this.now()
|
|
1589
|
+
};
|
|
1590
|
+
this._memory.auth_leases[leaseId] = row;
|
|
1591
|
+
return {
|
|
1592
|
+
...row,
|
|
1593
|
+
scope: normalizeStringArray(row.scope, ['project:*']),
|
|
1594
|
+
metadata: { ...(row.metadata || {}) }
|
|
1595
|
+
};
|
|
1596
|
+
}
|
|
569
1597
|
}
|
|
570
1598
|
|
|
571
1599
|
const STORE_CACHE = new Map();
|