@myagentroam/node 0.9.120 → 0.9.122
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/dist/connector.js +10 -3
- package/dist/database.js +53 -167
- package/dist/migrations/demo.js +9 -0
- package/dist/migrations/types.js +1 -0
- package/dist/migrations/v013.js +327 -0
- package/dist/runner/mar-agent/managed-run-controller.js +1 -0
- package/dist/service/conversation-template-node-service.js +2 -35
- package/dist/service/goal-mcp-server.js +7 -1
- package/dist/service/hosted-conversation-node-service.js +17 -72
- package/dist/service/workspace-change-service.js +1 -0
- package/package.json +4 -4
- package/dist/migrations/v004.js +0 -91
- package/dist/migrations/v005.js +0 -33
- package/dist/migrations/v006.js +0 -191
- package/dist/migrations/v007.js +0 -52
- package/dist/migrations/v008.js +0 -28
- package/dist/migrations/v009.js +0 -163
- package/dist/migrations/v010.js +0 -96
- package/dist/migrations/v011.js +0 -67
- package/dist/migrations/v012.js +0 -8
package/dist/connector.js
CHANGED
|
@@ -1778,14 +1778,14 @@ export class NodeConnector {
|
|
|
1778
1778
|
this.hostedConversationRuntime = undefined;
|
|
1779
1779
|
this.hostedConversationOperations = undefined;
|
|
1780
1780
|
this.conversationHostingMaintenanceOperations = undefined;
|
|
1781
|
-
|
|
1781
|
+
const uploadDrain = this.workspaceUploadService.shutdown();
|
|
1782
1782
|
this.workspaceFileService.dispose();
|
|
1783
1783
|
this.workspaceWatchService.clearAll();
|
|
1784
1784
|
this.workspaceWorkbenchService.clear();
|
|
1785
1785
|
this.workspaceSessionService.clear();
|
|
1786
1786
|
this.workspaceChangeService.clear();
|
|
1787
1787
|
const runnerDrain = this.runners.stop();
|
|
1788
|
-
|
|
1788
|
+
const goalMcpDrain = this.goalMcpServer.close();
|
|
1789
1789
|
// close() transitions metadata before awaiting process-tree termination,
|
|
1790
1790
|
// so the lifecycle is persisted before this synchronous shutdown returns.
|
|
1791
1791
|
const terminalDrain = this.terminalManager.shutdown();
|
|
@@ -1796,13 +1796,20 @@ export class NodeConnector {
|
|
|
1796
1796
|
this.controlChannel.stop();
|
|
1797
1797
|
const database = this.database;
|
|
1798
1798
|
this.database = undefined;
|
|
1799
|
+
const drains = Promise.allSettled([
|
|
1800
|
+
marAgentDrain,
|
|
1801
|
+
runnerDrain,
|
|
1802
|
+
terminalDrain,
|
|
1803
|
+
uploadDrain,
|
|
1804
|
+
goalMcpDrain
|
|
1805
|
+
]);
|
|
1799
1806
|
this.stopping = (async () => {
|
|
1800
1807
|
try {
|
|
1801
1808
|
await conversationHostingMaintenanceDrain;
|
|
1802
1809
|
}
|
|
1803
1810
|
finally {
|
|
1804
1811
|
await hostedConversationRuntime?.dispose().catch(() => undefined);
|
|
1805
|
-
await
|
|
1812
|
+
await drains;
|
|
1806
1813
|
await this.gitExecutionService?.close();
|
|
1807
1814
|
database?.close();
|
|
1808
1815
|
}
|
package/dist/database.js
CHANGED
|
@@ -4,16 +4,10 @@ import { dirname } from 'node:path';
|
|
|
4
4
|
import { DatabaseSync } from 'node:sqlite';
|
|
5
5
|
import { runnerDefaultConfigurationSchema } from '@myagentroam/protocol';
|
|
6
6
|
import { mcpCatalogEntrySchema, mcpInstallationConfigurationSchema, mcpRuntimeDescriptorSchema } from '@myagentroam/protocol';
|
|
7
|
-
import {
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
import { assertNodeSchemaV8, NODE_SCHEMA_MIGRATION_V8, NODE_SCHEMA_VERSION_V8 } from './migrations/v008.js';
|
|
12
|
-
import { assertNodeSchemaV9, isDraftNodeSchemaV9, NODE_SCHEMA_MIGRATION_V9, NODE_SCHEMA_MIGRATION_V9_FINALIZE, NODE_SCHEMA_VERSION_V9 } from './migrations/v009.js';
|
|
13
|
-
import { assertNodeSchemaV10, NODE_SCHEMA_MIGRATION_V10, NODE_SCHEMA_VERSION_V10 } from './migrations/v010.js';
|
|
14
|
-
import { assertNodeSchemaV11, NODE_SCHEMA_MIGRATION_V11, NODE_SCHEMA_VERSION_V11 } from './migrations/v011.js';
|
|
15
|
-
import { assertNodeSchemaV12, NODE_SCHEMA_MIGRATION_V12, NODE_SCHEMA_VERSION_V12 } from './migrations/v012.js';
|
|
16
|
-
const NODE_SCHEMA_VERSION = NODE_SCHEMA_VERSION_V12;
|
|
7
|
+
import { assertNodeSchemaV13, NODE_SCHEMA_V13, NODE_SCHEMA_VERSION_V13 } from './migrations/v013.js';
|
|
8
|
+
// Future released migrations must be contiguous, immutable and begin at v14; Demo is excluded.
|
|
9
|
+
export const NODE_SCHEMA_MIGRATIONS = [];
|
|
10
|
+
const LEGACY_NODE_SCHEMA_V12 = NODE_SCHEMA_VERSION_V13 - 1;
|
|
17
11
|
export class NodeDatabase {
|
|
18
12
|
now;
|
|
19
13
|
raw;
|
|
@@ -28,8 +22,15 @@ export class NodeDatabase {
|
|
|
28
22
|
timeout: 5_000,
|
|
29
23
|
defensive: true
|
|
30
24
|
});
|
|
31
|
-
|
|
32
|
-
|
|
25
|
+
try {
|
|
26
|
+
// Refuse unsupported databases before changing the on-disk journal mode.
|
|
27
|
+
migrate(this.raw);
|
|
28
|
+
this.raw.exec('PRAGMA journal_mode = WAL; PRAGMA foreign_keys = ON; PRAGMA busy_timeout = 5000;');
|
|
29
|
+
}
|
|
30
|
+
catch (error) {
|
|
31
|
+
this.raw.close();
|
|
32
|
+
throw error;
|
|
33
|
+
}
|
|
33
34
|
}
|
|
34
35
|
finally {
|
|
35
36
|
releaseMigrationLock();
|
|
@@ -335,7 +336,10 @@ export class NodeDatabase {
|
|
|
335
336
|
return (Number(this.raw.prepare('DELETE FROM agent_sessions WHERE id = ?').run(sessionId).changes) > 0);
|
|
336
337
|
}
|
|
337
338
|
}
|
|
338
|
-
function migrate(database) {
|
|
339
|
+
export function migrate(database, catalog = NODE_SCHEMA_MIGRATIONS) {
|
|
340
|
+
if (catalog.some((entry, index) => entry.version !== NODE_SCHEMA_VERSION_V13 + index + 1))
|
|
341
|
+
throw new Error('NODE_DATABASE_MIGRATION_CATALOG_INVALID');
|
|
342
|
+
const latest = NODE_SCHEMA_VERSION_V13 + catalog.length;
|
|
339
343
|
const existingMigrationTable = database
|
|
340
344
|
.prepare("SELECT 1 FROM sqlite_master WHERE type = 'table' AND name = 'node_schema_migrations'")
|
|
341
345
|
.get();
|
|
@@ -347,10 +351,11 @@ function migrate(database) {
|
|
|
347
351
|
throw new Error('NODE_DATABASE_VERSION_UNSUPPORTED');
|
|
348
352
|
database.exec('BEGIN IMMEDIATE');
|
|
349
353
|
try {
|
|
350
|
-
database.exec(
|
|
354
|
+
database.exec(NODE_SCHEMA_V13);
|
|
351
355
|
database
|
|
352
356
|
.prepare('INSERT INTO node_schema_migrations (version, applied_at) VALUES (?, ?)')
|
|
353
|
-
.run(
|
|
357
|
+
.run(NODE_SCHEMA_VERSION_V13, Date.now());
|
|
358
|
+
assertNodeSchemaV13(database);
|
|
354
359
|
database.exec('COMMIT');
|
|
355
360
|
}
|
|
356
361
|
catch (error) {
|
|
@@ -358,80 +363,24 @@ function migrate(database) {
|
|
|
358
363
|
throw error;
|
|
359
364
|
}
|
|
360
365
|
}
|
|
361
|
-
const
|
|
366
|
+
const versions = database
|
|
362
367
|
.prepare('SELECT version FROM node_schema_migrations ORDER BY version')
|
|
363
|
-
.all();
|
|
364
|
-
const
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
assertNodeSchemaV4(database);
|
|
374
|
-
if (current === NODE_SCHEMA_VERSION_V4) {
|
|
375
|
-
database.exec('BEGIN IMMEDIATE');
|
|
376
|
-
try {
|
|
377
|
-
database.exec(NODE_SCHEMA_MIGRATION_V5);
|
|
378
|
-
database
|
|
379
|
-
.prepare('INSERT INTO node_schema_migrations (version, applied_at) VALUES (?, ?)')
|
|
380
|
-
.run(NODE_SCHEMA_VERSION_V5, Date.now());
|
|
381
|
-
database.exec('COMMIT');
|
|
382
|
-
}
|
|
383
|
-
catch (error) {
|
|
384
|
-
database.exec('ROLLBACK');
|
|
385
|
-
throw error;
|
|
386
|
-
}
|
|
387
|
-
}
|
|
388
|
-
assertNodeSchemaV5(database);
|
|
389
|
-
const migrated = database
|
|
390
|
-
.prepare('SELECT MAX(version) AS version FROM node_schema_migrations')
|
|
391
|
-
.get();
|
|
392
|
-
if (numberValue(migrated, 'version') === NODE_SCHEMA_VERSION_V5) {
|
|
393
|
-
database.exec('BEGIN IMMEDIATE');
|
|
394
|
-
try {
|
|
395
|
-
database.exec(NODE_SCHEMA_MIGRATION_V6);
|
|
396
|
-
database
|
|
397
|
-
.prepare('INSERT INTO node_schema_migrations (version, applied_at) VALUES (?, ?)')
|
|
398
|
-
.run(NODE_SCHEMA_VERSION_V6, Date.now());
|
|
399
|
-
database.exec('COMMIT');
|
|
400
|
-
}
|
|
401
|
-
catch (error) {
|
|
402
|
-
database.exec('ROLLBACK');
|
|
403
|
-
throw error;
|
|
404
|
-
}
|
|
405
|
-
}
|
|
406
|
-
const migratedV6 = database
|
|
407
|
-
.prepare('SELECT MAX(version) AS version FROM node_schema_migrations')
|
|
408
|
-
.get();
|
|
409
|
-
if (numberValue(migratedV6, 'version') === NODE_SCHEMA_VERSION_V6) {
|
|
410
|
-
assertNodeSchemaV6(database);
|
|
411
|
-
database.exec('BEGIN IMMEDIATE');
|
|
412
|
-
try {
|
|
413
|
-
database.exec(NODE_SCHEMA_MIGRATION_V7);
|
|
414
|
-
database
|
|
415
|
-
.prepare('INSERT INTO node_schema_migrations (version, applied_at) VALUES (?, ?)')
|
|
416
|
-
.run(NODE_SCHEMA_VERSION_V7, Date.now());
|
|
417
|
-
database.exec('COMMIT');
|
|
418
|
-
}
|
|
419
|
-
catch (error) {
|
|
420
|
-
database.exec('ROLLBACK');
|
|
421
|
-
throw error;
|
|
422
|
-
}
|
|
423
|
-
}
|
|
424
|
-
assertNodeSchemaV7(database);
|
|
425
|
-
const migratedV7 = database
|
|
426
|
-
.prepare('SELECT MAX(version) AS version FROM node_schema_migrations')
|
|
427
|
-
.get();
|
|
428
|
-
if (numberValue(migratedV7, 'version') === NODE_SCHEMA_VERSION_V7) {
|
|
368
|
+
.all().map((row) => numberValue(row, 'version'));
|
|
369
|
+
const firstLegacyVersion = versions[0];
|
|
370
|
+
if (firstLegacyVersion !== undefined &&
|
|
371
|
+
firstLegacyVersion >= 1 &&
|
|
372
|
+
firstLegacyVersion <= 4 &&
|
|
373
|
+
versions.length === LEGACY_NODE_SCHEMA_V12 - firstLegacyVersion + 1 &&
|
|
374
|
+
versions.every((version, index) => version === firstLegacyVersion + index)) {
|
|
375
|
+
// v13 is a history-compaction baseline. The final v12 schema is still the v13 schema;
|
|
376
|
+
// validate it before atomically replacing only the migration ledger.
|
|
377
|
+
assertNodeSchemaV13(database);
|
|
429
378
|
database.exec('BEGIN IMMEDIATE');
|
|
430
379
|
try {
|
|
431
|
-
database.
|
|
380
|
+
database.prepare('DELETE FROM node_schema_migrations').run();
|
|
432
381
|
database
|
|
433
382
|
.prepare('INSERT INTO node_schema_migrations (version, applied_at) VALUES (?, ?)')
|
|
434
|
-
.run(
|
|
383
|
+
.run(NODE_SCHEMA_VERSION_V13, Date.now());
|
|
435
384
|
database.exec('COMMIT');
|
|
436
385
|
}
|
|
437
386
|
catch (error) {
|
|
@@ -439,37 +388,28 @@ function migrate(database) {
|
|
|
439
388
|
throw error;
|
|
440
389
|
}
|
|
441
390
|
}
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
.
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
.prepare('SELECT MAX(version) AS version FROM node_schema_migrations')
|
|
458
|
-
.get();
|
|
459
|
-
if (numberValue(migratedV10, 'version') === NODE_SCHEMA_VERSION_V10)
|
|
460
|
-
applyNodeSchemaV11Migration(database);
|
|
461
|
-
assertNodeSchemaV11(database);
|
|
462
|
-
const migratedV11 = database
|
|
463
|
-
.prepare('SELECT MAX(version) AS version FROM node_schema_migrations')
|
|
464
|
-
.get();
|
|
465
|
-
if (numberValue(migratedV11, 'version') === NODE_SCHEMA_VERSION_V11) {
|
|
391
|
+
const migratedVersions = database
|
|
392
|
+
.prepare('SELECT version FROM node_schema_migrations ORDER BY version')
|
|
393
|
+
.all().map((row) => numberValue(row, 'version'));
|
|
394
|
+
const current = migratedVersions.at(-1);
|
|
395
|
+
if (current === undefined ||
|
|
396
|
+
current < NODE_SCHEMA_VERSION_V13 ||
|
|
397
|
+
current > latest ||
|
|
398
|
+
migratedVersions[0] !== NODE_SCHEMA_VERSION_V13)
|
|
399
|
+
throw new Error('NODE_DATABASE_VERSION_UNSUPPORTED');
|
|
400
|
+
if (migratedVersions.some((version, index) => version !== NODE_SCHEMA_VERSION_V13 + index))
|
|
401
|
+
throw new Error('NODE_DATABASE_MIGRATION_HISTORY_INVALID');
|
|
402
|
+
assertNodeSchemaV13(database);
|
|
403
|
+
for (const entry of catalog) {
|
|
404
|
+
if (entry.version <= current)
|
|
405
|
+
continue;
|
|
466
406
|
database.exec('BEGIN IMMEDIATE');
|
|
467
407
|
try {
|
|
468
|
-
database.exec(
|
|
408
|
+
database.exec(entry.sql);
|
|
409
|
+
entry.assert(database);
|
|
469
410
|
database
|
|
470
411
|
.prepare('INSERT INTO node_schema_migrations (version, applied_at) VALUES (?, ?)')
|
|
471
|
-
.run(
|
|
472
|
-
assertNodeSchemaV12(database);
|
|
412
|
+
.run(entry.version, Date.now());
|
|
473
413
|
database.exec('COMMIT');
|
|
474
414
|
}
|
|
475
415
|
catch (error) {
|
|
@@ -477,62 +417,8 @@ function migrate(database) {
|
|
|
477
417
|
throw error;
|
|
478
418
|
}
|
|
479
419
|
}
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
function applyNodeSchemaV9Migration(database, migration, recordVersion) {
|
|
483
|
-
database.exec('PRAGMA foreign_keys = OFF');
|
|
484
|
-
try {
|
|
485
|
-
database.exec('BEGIN IMMEDIATE');
|
|
486
|
-
database.exec(migration);
|
|
487
|
-
if (recordVersion)
|
|
488
|
-
database
|
|
489
|
-
.prepare('INSERT INTO node_schema_migrations (version, applied_at) VALUES (?, ?)')
|
|
490
|
-
.run(NODE_SCHEMA_VERSION_V9, Date.now());
|
|
491
|
-
database.exec('COMMIT');
|
|
492
|
-
}
|
|
493
|
-
catch (error) {
|
|
494
|
-
database.exec('ROLLBACK');
|
|
495
|
-
throw error;
|
|
496
|
-
}
|
|
497
|
-
finally {
|
|
498
|
-
database.exec('PRAGMA foreign_keys = ON');
|
|
499
|
-
}
|
|
500
|
-
}
|
|
501
|
-
function applyNodeSchemaV10Migration(database) {
|
|
502
|
-
database.exec('PRAGMA foreign_keys = OFF');
|
|
503
|
-
try {
|
|
504
|
-
database.exec('BEGIN IMMEDIATE');
|
|
505
|
-
database.exec(NODE_SCHEMA_MIGRATION_V10);
|
|
506
|
-
database
|
|
507
|
-
.prepare('INSERT INTO node_schema_migrations (version, applied_at) VALUES (?, ?)')
|
|
508
|
-
.run(NODE_SCHEMA_VERSION_V10, Date.now());
|
|
509
|
-
database.exec('COMMIT');
|
|
510
|
-
}
|
|
511
|
-
catch (error) {
|
|
512
|
-
database.exec('ROLLBACK');
|
|
513
|
-
throw error;
|
|
514
|
-
}
|
|
515
|
-
finally {
|
|
516
|
-
database.exec('PRAGMA foreign_keys = ON');
|
|
517
|
-
}
|
|
518
|
-
}
|
|
519
|
-
function applyNodeSchemaV11Migration(database) {
|
|
520
|
-
database.exec('PRAGMA foreign_keys = OFF');
|
|
521
|
-
try {
|
|
522
|
-
database.exec('BEGIN IMMEDIATE');
|
|
523
|
-
database.exec(NODE_SCHEMA_MIGRATION_V11);
|
|
524
|
-
database
|
|
525
|
-
.prepare('INSERT INTO node_schema_migrations (version, applied_at) VALUES (?, ?)')
|
|
526
|
-
.run(NODE_SCHEMA_VERSION_V11, Date.now());
|
|
527
|
-
database.exec('COMMIT');
|
|
528
|
-
}
|
|
529
|
-
catch (error) {
|
|
530
|
-
database.exec('ROLLBACK');
|
|
531
|
-
throw error;
|
|
532
|
-
}
|
|
533
|
-
finally {
|
|
534
|
-
database.exec('PRAGMA foreign_keys = ON');
|
|
535
|
-
}
|
|
420
|
+
if (catalog.length > 0)
|
|
421
|
+
catalog[catalog.length - 1].assert(database);
|
|
536
422
|
}
|
|
537
423
|
function mcpInstallation(row) {
|
|
538
424
|
const runtime = mcpRuntimeDescriptorSchema.parse(JSON.parse(stringValue(row, 'runtime')));
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/** Example only: never import this migration into the production catalog. */
|
|
2
|
+
export const NODE_SCHEMA_MIGRATION_DEMO = {
|
|
3
|
+
version: 1,
|
|
4
|
+
sql: 'CREATE TABLE migration_demo_example (id INTEGER PRIMARY KEY) STRICT;',
|
|
5
|
+
assert: (database) => {
|
|
6
|
+
if (!database.prepare("SELECT 1 FROM sqlite_master WHERE name='migration_demo_example'").get())
|
|
7
|
+
throw new Error('NODE_DATABASE_SCHEMA_INVALID:migration_demo_example');
|
|
8
|
+
}
|
|
9
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,327 @@
|
|
|
1
|
+
import { DatabaseSync } from 'node:sqlite';
|
|
2
|
+
export const NODE_SCHEMA_VERSION_V13 = 13;
|
|
3
|
+
/** Complete schema at the supported SQLite baseline; future changes use the migration catalog. */
|
|
4
|
+
export const NODE_SCHEMA_V13 = String.raw `CREATE TABLE "agent_sessions" (
|
|
5
|
+
id TEXT PRIMARY KEY,
|
|
6
|
+
workspace_id TEXT NOT NULL REFERENCES workspaces(id) ON DELETE CASCADE,
|
|
7
|
+
runner TEXT NOT NULL CHECK (runner IN ('mar-agent', 'codex', 'claude-code', 'opencode')),
|
|
8
|
+
pin_order INTEGER CHECK (pin_order IS NULL OR pin_order >= 0),
|
|
9
|
+
metadata TEXT NOT NULL CHECK (json_valid(metadata))
|
|
10
|
+
) STRICT;
|
|
11
|
+
|
|
12
|
+
CREATE TABLE "conversation_files" (
|
|
13
|
+
id TEXT PRIMARY KEY,
|
|
14
|
+
conversation_id TEXT NOT NULL REFERENCES hosted_conversations(id) ON DELETE CASCADE,
|
|
15
|
+
category TEXT NOT NULL CHECK (category IN ('UPLOAD','OUTPUT')),
|
|
16
|
+
relative_path TEXT NOT NULL,
|
|
17
|
+
display_name TEXT NOT NULL,
|
|
18
|
+
mime_type TEXT NOT NULL,
|
|
19
|
+
size INTEGER NOT NULL CHECK (size >= 0),
|
|
20
|
+
source TEXT NOT NULL CHECK (source IN ('USER','AGENT','SYSTEM')),
|
|
21
|
+
turn_id TEXT,
|
|
22
|
+
item_id TEXT,
|
|
23
|
+
created_at INTEGER NOT NULL CHECK (created_at >= 0),
|
|
24
|
+
updated_at INTEGER NOT NULL CHECK (updated_at >= 0),
|
|
25
|
+
UNIQUE (conversation_id, category, relative_path)
|
|
26
|
+
) STRICT;
|
|
27
|
+
|
|
28
|
+
CREATE TABLE conversation_template_publish_jobs (
|
|
29
|
+
id TEXT PRIMARY KEY,
|
|
30
|
+
template_id TEXT NOT NULL,
|
|
31
|
+
draft_revision INTEGER NOT NULL CHECK (draft_revision > 0),
|
|
32
|
+
stage TEXT NOT NULL CHECK (
|
|
33
|
+
stage IN ('VALIDATING','ENUMERATING','COPYING','SCANNING','MANIFEST','SEALED','REGISTERED')
|
|
34
|
+
),
|
|
35
|
+
status TEXT NOT NULL CHECK (status IN ('QUEUED','RUNNING','SUCCEEDED','FAILED')),
|
|
36
|
+
version_id TEXT REFERENCES conversation_template_versions(id) ON DELETE SET NULL,
|
|
37
|
+
error_code TEXT,
|
|
38
|
+
error_summary TEXT,
|
|
39
|
+
requested_by_user_id INTEGER NOT NULL CHECK (requested_by_user_id > 0),
|
|
40
|
+
draft TEXT NOT NULL CHECK (json_valid(draft)),
|
|
41
|
+
created_at INTEGER NOT NULL CHECK (created_at >= 0),
|
|
42
|
+
updated_at INTEGER NOT NULL CHECK (updated_at >= 0)
|
|
43
|
+
) STRICT;
|
|
44
|
+
|
|
45
|
+
CREATE TABLE conversation_template_versions (
|
|
46
|
+
id TEXT PRIMARY KEY,
|
|
47
|
+
template_id TEXT NOT NULL,
|
|
48
|
+
version_number INTEGER NOT NULL CHECK (version_number > 0),
|
|
49
|
+
draft_revision INTEGER NOT NULL CHECK (draft_revision > 0),
|
|
50
|
+
status TEXT NOT NULL CHECK (status IN ('AVAILABLE','DISABLED','CORRUPTED','RETAINED')),
|
|
51
|
+
storage_key TEXT NOT NULL UNIQUE,
|
|
52
|
+
manifest TEXT NOT NULL CHECK (json_valid(manifest)),
|
|
53
|
+
content_hash TEXT NOT NULL CHECK (length(content_hash) = 64),
|
|
54
|
+
manifest_hash TEXT NOT NULL CHECK (length(manifest_hash) = 64),
|
|
55
|
+
file_count INTEGER NOT NULL CHECK (file_count >= 0),
|
|
56
|
+
total_bytes INTEGER NOT NULL CHECK (total_bytes >= 0),
|
|
57
|
+
host_policy_version INTEGER NOT NULL CHECK (host_policy_version > 0),
|
|
58
|
+
published_by_user_id INTEGER NOT NULL CHECK (published_by_user_id > 0),
|
|
59
|
+
published_at INTEGER NOT NULL CHECK (published_at >= 0),
|
|
60
|
+
reference_count INTEGER NOT NULL DEFAULT 0 CHECK (reference_count >= 0), mcp_config_hash TEXT NOT NULL DEFAULT '4f53cda18c2baa0c0354bb5f9a3ecbe5ed12ab4d8e11ba873c2f11161202b945'
|
|
61
|
+
CHECK (length(mcp_config_hash) = 64),
|
|
62
|
+
UNIQUE (template_id, version_number)
|
|
63
|
+
) STRICT;
|
|
64
|
+
|
|
65
|
+
CREATE TABLE hosted_conversation_messages (
|
|
66
|
+
conversation_id TEXT NOT NULL REFERENCES hosted_conversations(id) ON DELETE CASCADE,
|
|
67
|
+
client_message_id TEXT NOT NULL,
|
|
68
|
+
run_id TEXT NOT NULL,
|
|
69
|
+
status TEXT NOT NULL CHECK (status IN ('RUNNING','SUCCEEDED','FAILED','INTERRUPTED')),
|
|
70
|
+
created_at INTEGER NOT NULL CHECK (created_at >= 0),
|
|
71
|
+
updated_at INTEGER NOT NULL CHECK (updated_at >= 0), user_text_present INTEGER NOT NULL DEFAULT 1
|
|
72
|
+
CHECK (user_text_present IN (0,1)),
|
|
73
|
+
PRIMARY KEY (conversation_id, client_message_id)
|
|
74
|
+
) STRICT;
|
|
75
|
+
|
|
76
|
+
CREATE TABLE "hosted_conversations" (
|
|
77
|
+
id TEXT PRIMARY KEY,
|
|
78
|
+
user_storage_key TEXT NOT NULL,
|
|
79
|
+
conversation_storage_key TEXT NOT NULL,
|
|
80
|
+
client_request_id TEXT,
|
|
81
|
+
node_id TEXT NOT NULL,
|
|
82
|
+
session_id TEXT NOT NULL,
|
|
83
|
+
template_id TEXT NOT NULL,
|
|
84
|
+
template_name TEXT NOT NULL,
|
|
85
|
+
active_version_id TEXT NOT NULL REFERENCES conversation_template_versions(id),
|
|
86
|
+
title TEXT NOT NULL,
|
|
87
|
+
status TEXT NOT NULL CHECK (status IN ('ACTIVE')),
|
|
88
|
+
model_id TEXT NOT NULL,
|
|
89
|
+
template_deleted INTEGER NOT NULL DEFAULT 0 CHECK (template_deleted IN (0,1)),
|
|
90
|
+
used_bytes INTEGER NOT NULL DEFAULT 0 CHECK (used_bytes >= 0),
|
|
91
|
+
max_bytes INTEGER NOT NULL CHECK (max_bytes > 0),
|
|
92
|
+
created_at INTEGER NOT NULL CHECK (created_at >= 0),
|
|
93
|
+
updated_at INTEGER NOT NULL CHECK (updated_at >= 0),
|
|
94
|
+
last_activity_at INTEGER NOT NULL CHECK (last_activity_at >= 0),
|
|
95
|
+
pinned_at INTEGER CHECK (pinned_at IS NULL OR pinned_at >= 0),
|
|
96
|
+
title_source TEXT NOT NULL DEFAULT 'CUSTOM'
|
|
97
|
+
CHECK (title_source IN ('DEFAULT','AUTO','CUSTOM')),
|
|
98
|
+
UNIQUE (user_storage_key, conversation_storage_key),
|
|
99
|
+
UNIQUE (user_storage_key, client_request_id)
|
|
100
|
+
) STRICT;
|
|
101
|
+
|
|
102
|
+
CREATE TABLE mcp_installations (
|
|
103
|
+
target_kind TEXT NOT NULL CHECK (target_kind IN ('NODE','WORKSPACE')),
|
|
104
|
+
target_id TEXT NOT NULL, catalog_entry_id TEXT NOT NULL, server_name TEXT NOT NULL,
|
|
105
|
+
display_name TEXT NOT NULL, version TEXT NOT NULL,
|
|
106
|
+
enabled INTEGER NOT NULL CHECK (enabled IN (0,1)),
|
|
107
|
+
runtime TEXT NOT NULL CHECK (json_valid(runtime)),
|
|
108
|
+
configuration TEXT NOT NULL CHECK (json_valid(configuration)), installed_at INTEGER NOT NULL,
|
|
109
|
+
PRIMARY KEY (target_kind,target_id,server_name),
|
|
110
|
+
CHECK ((target_kind='NODE' AND target_id='') OR (target_kind='WORKSPACE' AND target_id<>''))
|
|
111
|
+
) STRICT;
|
|
112
|
+
|
|
113
|
+
CREATE TABLE node_schema_migrations (
|
|
114
|
+
version INTEGER PRIMARY KEY, applied_at INTEGER NOT NULL
|
|
115
|
+
) STRICT;
|
|
116
|
+
|
|
117
|
+
CREATE TABLE "runner_defaults" (
|
|
118
|
+
runner TEXT PRIMARY KEY CHECK (runner IN ('mar-agent', 'codex', 'claude-code', 'opencode')),
|
|
119
|
+
model TEXT NOT NULL, effort TEXT NOT NULL, access TEXT NOT NULL
|
|
120
|
+
) STRICT;
|
|
121
|
+
|
|
122
|
+
CREATE TABLE terminal_sessions (
|
|
123
|
+
id TEXT PRIMARY KEY, workspace_id TEXT REFERENCES workspaces(id) ON DELETE SET NULL,
|
|
124
|
+
profile_id TEXT NOT NULL,
|
|
125
|
+
state TEXT NOT NULL CHECK (state IN ('RUNNING', 'EXITED', 'CLOSED')),
|
|
126
|
+
exit_code INTEGER, rows INTEGER NOT NULL CHECK (rows > 0 AND rows <= 1000),
|
|
127
|
+
cols INTEGER NOT NULL CHECK (cols > 0 AND cols <= 1000),
|
|
128
|
+
latest_sequence INTEGER NOT NULL CHECK (latest_sequence >= 0),
|
|
129
|
+
created_at INTEGER NOT NULL, updated_at INTEGER NOT NULL
|
|
130
|
+
) STRICT;
|
|
131
|
+
|
|
132
|
+
CREATE TABLE workspaces (
|
|
133
|
+
id TEXT PRIMARY KEY, path TEXT NOT NULL UNIQUE,
|
|
134
|
+
kind TEXT NOT NULL CHECK (kind IN ('DIRECTORY', 'GIT_WORKSPACE')),
|
|
135
|
+
git_summary TEXT, created_at INTEGER NOT NULL, updated_at INTEGER NOT NULL
|
|
136
|
+
, preserve_git_config INTEGER NOT NULL DEFAULT 1 CHECK (preserve_git_config IN (0,1))) STRICT;
|
|
137
|
+
|
|
138
|
+
CREATE INDEX conversation_files_conversation_updated
|
|
139
|
+
ON conversation_files(conversation_id, updated_at DESC, id);
|
|
140
|
+
|
|
141
|
+
CREATE INDEX conversation_template_publish_jobs_status
|
|
142
|
+
ON conversation_template_publish_jobs(status, updated_at);
|
|
143
|
+
|
|
144
|
+
CREATE INDEX conversation_template_publish_jobs_template
|
|
145
|
+
ON conversation_template_publish_jobs(template_id, created_at, id);
|
|
146
|
+
|
|
147
|
+
CREATE INDEX conversation_template_versions_template_status
|
|
148
|
+
ON conversation_template_versions(template_id, status, published_at);
|
|
149
|
+
|
|
150
|
+
CREATE INDEX hosted_conversations_template
|
|
151
|
+
ON hosted_conversations(template_id, status, id);
|
|
152
|
+
|
|
153
|
+
CREATE INDEX hosted_conversations_user_pin_activity
|
|
154
|
+
ON hosted_conversations(
|
|
155
|
+
user_storage_key,
|
|
156
|
+
(pinned_at IS NOT NULL) DESC,
|
|
157
|
+
last_activity_at DESC,
|
|
158
|
+
id DESC
|
|
159
|
+
);`;
|
|
160
|
+
export function assertNodeSchemaV13(database, exactBaseline = false) {
|
|
161
|
+
const required = {
|
|
162
|
+
agent_sessions: ['id', 'workspace_id', 'runner', 'pin_order', 'metadata'],
|
|
163
|
+
conversation_files: [
|
|
164
|
+
'id',
|
|
165
|
+
'conversation_id',
|
|
166
|
+
'category',
|
|
167
|
+
'relative_path',
|
|
168
|
+
'display_name',
|
|
169
|
+
'mime_type',
|
|
170
|
+
'size',
|
|
171
|
+
'source',
|
|
172
|
+
'turn_id',
|
|
173
|
+
'item_id',
|
|
174
|
+
'created_at',
|
|
175
|
+
'updated_at'
|
|
176
|
+
],
|
|
177
|
+
conversation_template_publish_jobs: [
|
|
178
|
+
'id',
|
|
179
|
+
'template_id',
|
|
180
|
+
'draft_revision',
|
|
181
|
+
'stage',
|
|
182
|
+
'status',
|
|
183
|
+
'version_id',
|
|
184
|
+
'error_code',
|
|
185
|
+
'error_summary',
|
|
186
|
+
'requested_by_user_id',
|
|
187
|
+
'draft',
|
|
188
|
+
'created_at',
|
|
189
|
+
'updated_at'
|
|
190
|
+
],
|
|
191
|
+
conversation_template_versions: [
|
|
192
|
+
'id',
|
|
193
|
+
'template_id',
|
|
194
|
+
'version_number',
|
|
195
|
+
'draft_revision',
|
|
196
|
+
'status',
|
|
197
|
+
'storage_key',
|
|
198
|
+
'manifest',
|
|
199
|
+
'content_hash',
|
|
200
|
+
'manifest_hash',
|
|
201
|
+
'file_count',
|
|
202
|
+
'total_bytes',
|
|
203
|
+
'host_policy_version',
|
|
204
|
+
'published_by_user_id',
|
|
205
|
+
'published_at',
|
|
206
|
+
'reference_count',
|
|
207
|
+
'mcp_config_hash'
|
|
208
|
+
],
|
|
209
|
+
hosted_conversation_messages: [
|
|
210
|
+
'conversation_id',
|
|
211
|
+
'client_message_id',
|
|
212
|
+
'run_id',
|
|
213
|
+
'status',
|
|
214
|
+
'created_at',
|
|
215
|
+
'updated_at',
|
|
216
|
+
'user_text_present'
|
|
217
|
+
],
|
|
218
|
+
hosted_conversations: [
|
|
219
|
+
'id',
|
|
220
|
+
'user_storage_key',
|
|
221
|
+
'conversation_storage_key',
|
|
222
|
+
'client_request_id',
|
|
223
|
+
'node_id',
|
|
224
|
+
'session_id',
|
|
225
|
+
'template_id',
|
|
226
|
+
'template_name',
|
|
227
|
+
'active_version_id',
|
|
228
|
+
'title',
|
|
229
|
+
'status',
|
|
230
|
+
'model_id',
|
|
231
|
+
'template_deleted',
|
|
232
|
+
'used_bytes',
|
|
233
|
+
'max_bytes',
|
|
234
|
+
'created_at',
|
|
235
|
+
'updated_at',
|
|
236
|
+
'last_activity_at',
|
|
237
|
+
'pinned_at',
|
|
238
|
+
'title_source'
|
|
239
|
+
],
|
|
240
|
+
mcp_installations: [
|
|
241
|
+
'target_kind',
|
|
242
|
+
'target_id',
|
|
243
|
+
'catalog_entry_id',
|
|
244
|
+
'server_name',
|
|
245
|
+
'display_name',
|
|
246
|
+
'version',
|
|
247
|
+
'enabled',
|
|
248
|
+
'runtime',
|
|
249
|
+
'configuration',
|
|
250
|
+
'installed_at'
|
|
251
|
+
],
|
|
252
|
+
node_schema_migrations: ['version', 'applied_at'],
|
|
253
|
+
runner_defaults: ['runner', 'model', 'effort', 'access'],
|
|
254
|
+
terminal_sessions: [
|
|
255
|
+
'id',
|
|
256
|
+
'workspace_id',
|
|
257
|
+
'profile_id',
|
|
258
|
+
'state',
|
|
259
|
+
'exit_code',
|
|
260
|
+
'rows',
|
|
261
|
+
'cols',
|
|
262
|
+
'latest_sequence',
|
|
263
|
+
'created_at',
|
|
264
|
+
'updated_at'
|
|
265
|
+
],
|
|
266
|
+
workspaces: [
|
|
267
|
+
'id',
|
|
268
|
+
'path',
|
|
269
|
+
'kind',
|
|
270
|
+
'git_summary',
|
|
271
|
+
'created_at',
|
|
272
|
+
'updated_at',
|
|
273
|
+
'preserve_git_config'
|
|
274
|
+
]
|
|
275
|
+
};
|
|
276
|
+
for (const [table, expected] of Object.entries(required)) {
|
|
277
|
+
const columns = database.prepare(`PRAGMA table_info(${table})`).all();
|
|
278
|
+
if (expected.some((name) => !columns.some((column) => column.name === name)))
|
|
279
|
+
throw new Error(`NODE_DATABASE_SCHEMA_INVALID:${table}`);
|
|
280
|
+
}
|
|
281
|
+
for (const name of [
|
|
282
|
+
'conversation_files_conversation_updated',
|
|
283
|
+
'conversation_template_publish_jobs_status',
|
|
284
|
+
'conversation_template_publish_jobs_template',
|
|
285
|
+
'conversation_template_versions_template_status',
|
|
286
|
+
'hosted_conversations_template',
|
|
287
|
+
'hosted_conversations_user_pin_activity'
|
|
288
|
+
]) {
|
|
289
|
+
if (!database.prepare("SELECT 1 FROM sqlite_master WHERE type='index' AND name=?").get(name))
|
|
290
|
+
throw new Error(`NODE_DATABASE_SCHEMA_INVALID:${name}`);
|
|
291
|
+
}
|
|
292
|
+
const workspace = database
|
|
293
|
+
.prepare("SELECT sql FROM sqlite_master WHERE type='table' AND name='workspaces'")
|
|
294
|
+
.get();
|
|
295
|
+
if (typeof workspace?.sql !== 'string' ||
|
|
296
|
+
!workspace.sql.includes('preserve_git_config INTEGER NOT NULL DEFAULT 1'))
|
|
297
|
+
throw new Error('NODE_DATABASE_SCHEMA_INVALID:workspaces');
|
|
298
|
+
for (const name of ['agent_sessions', 'runner_defaults']) {
|
|
299
|
+
const row = database
|
|
300
|
+
.prepare("SELECT sql FROM sqlite_master WHERE type='table' AND name=?")
|
|
301
|
+
.get(name);
|
|
302
|
+
const definition = row?.sql;
|
|
303
|
+
if (typeof definition !== 'string' ||
|
|
304
|
+
!["'mar-agent'", "'codex'", "'claude-code'", "'opencode'"].every((runner) => definition.includes(runner)))
|
|
305
|
+
throw new Error(`NODE_DATABASE_SCHEMA_INVALID:${name}:runner`);
|
|
306
|
+
}
|
|
307
|
+
if (database.prepare('PRAGMA foreign_key_check').all().length !== 0)
|
|
308
|
+
throw new Error('NODE_DATABASE_SCHEMA_INVALID:foreign_keys');
|
|
309
|
+
if (exactBaseline) {
|
|
310
|
+
// Only compare whole DDL at v13; later additive migrations may extend these tables.
|
|
311
|
+
const reference = new DatabaseSync(':memory:');
|
|
312
|
+
try {
|
|
313
|
+
reference.exec(NODE_SCHEMA_V13);
|
|
314
|
+
const query = "SELECT type,name,sql FROM sqlite_master WHERE sql IS NOT NULL AND name NOT LIKE 'sqlite_%' ORDER BY type,name";
|
|
315
|
+
const expected = reference.prepare(query).all();
|
|
316
|
+
const actual = database.prepare(query).all();
|
|
317
|
+
if (actual.length !== expected.length ||
|
|
318
|
+
expected.some((entry, index) => actual[index]?.type !== entry.type ||
|
|
319
|
+
actual[index]?.name !== entry.name ||
|
|
320
|
+
actual[index]?.sql !== entry.sql))
|
|
321
|
+
throw new Error('NODE_DATABASE_SCHEMA_INVALID:baseline');
|
|
322
|
+
}
|
|
323
|
+
finally {
|
|
324
|
+
reference.close();
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
}
|
|
@@ -244,6 +244,7 @@ export class MarAgentManagedRunController {
|
|
|
244
244
|
let executionModels = [];
|
|
245
245
|
try {
|
|
246
246
|
const sessionKey = input.externalSessionId ?? input.sessionId;
|
|
247
|
+
// Reserve both identities while opening so a concurrent release can await the same runtime.
|
|
247
248
|
const acquisitionKeys = [...new Set([sessionKey, input.sessionId])];
|
|
248
249
|
if (acquisitionKeys.some((key) => this.#runtimeAcquisitions.get(key) !== undefined))
|
|
249
250
|
throw new Error('MAR_AGENT_SESSION_RUNTIME_ACTIVE');
|