@myagentroam/node 0.9.120 → 0.9.121

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.
@@ -515,8 +515,7 @@ export class ConversationTemplateNodeService {
515
515
  manifest.entries.length !== numberValue(row, 'file_count') ||
516
516
  manifest.entries.reduce((total, entry) => total + entry.size, 0) !==
517
517
  numberValue(row, 'total_bytes') ||
518
- snapshotContentHash(manifest.directories, manifest.entries, manifest.schemaVersion) !==
519
- manifest.contentHash)
518
+ snapshotContentHash(manifest.directories, manifest.entries) !== manifest.contentHash)
520
519
  throw new Error('CONVERSATION_TEMPLATE_VERSION_CORRUPTED');
521
520
  const expected = new Map(manifest.entries.map((entry) => [entry.relativePath, entry]));
522
521
  const expectedDirectories = new Set([...manifest.directories, 'inbox', 'output']);
@@ -734,17 +733,6 @@ async function listSnapshotEntries(root, expectedDirectories, actualDirectories,
734
733
  }
735
734
  return entries;
736
735
  }
737
- function legacyPublishedDirectories(entries) {
738
- const directories = new Set(['inbox', 'output']);
739
- for (const entry of entries) {
740
- const parts = entry.relativePath.split('/');
741
- for (let index = 1; index < parts.length; index += 1)
742
- directories.add(parts.slice(0, index).join('/'));
743
- }
744
- directories.delete('inbox');
745
- directories.delete('output');
746
- return [...directories].sort((left, right) => left.localeCompare(right, 'en-US'));
747
- }
748
736
  function forcedExcluded(relativePath) {
749
737
  const parts = relativePath.split('/');
750
738
  if (parts.some((part) => {
@@ -814,19 +802,6 @@ function templateVersionManifest(input) {
814
802
  typeof input['mcpConfigHash'] !== 'string' ||
815
803
  typeof input['contentHash'] !== 'string')
816
804
  throw new Error('CONVERSATION_TEMPLATE_VERSION_CORRUPTED');
817
- if (input['schemaVersion'] === 1) {
818
- if (!Array.isArray(input['files']))
819
- throw new Error('CONVERSATION_TEMPLATE_VERSION_CORRUPTED');
820
- const entries = input['files'].map((value) => manifestFile(value));
821
- return {
822
- schemaVersion: 1,
823
- directories: legacyPublishedDirectories(entries),
824
- entries,
825
- contentHash: input['contentHash'],
826
- mcpServers,
827
- mcpConfigHash: input['mcpConfigHash']
828
- };
829
- }
830
805
  if (input['schemaVersion'] !== 2 ||
831
806
  typeof input['sourceWorkspacePath'] !== 'string' ||
832
807
  !Array.isArray(input['directories']) ||
@@ -913,15 +888,7 @@ function safeNonNegativeInteger(input) {
913
888
  throw new Error('CONVERSATION_TEMPLATE_VERSION_CORRUPTED');
914
889
  return input;
915
890
  }
916
- function snapshotContentHash(directories, entries, schemaVersion = 2) {
917
- if (schemaVersion === 1)
918
- return hashText(entries
919
- .map((entry) => {
920
- if (entry.kind !== 'FILE')
921
- throw new Error('CONVERSATION_TEMPLATE_VERSION_CORRUPTED');
922
- return `${entry.relativePath}\0${entry.size}\0${entry.sha256}`;
923
- })
924
- .join('\n'));
891
+ function snapshotContentHash(directories, entries) {
925
892
  return hashText([
926
893
  ...directories.map((directory) => `DIRECTORY\0${directory}`),
927
894
  ...entries.map((entry) => entry.kind === 'FILE'
@@ -9,6 +9,7 @@ export class GoalMcpServer {
9
9
  goals;
10
10
  changed;
11
11
  capabilities = new Map();
12
+ sockets = new Set();
12
13
  endpoint = process.platform === 'win32'
13
14
  ? `\\\\.\\pipe\\mar-goal-${process.pid}-${randomUUID()}`
14
15
  : join(tmpdir(), `mar-goal-${process.pid}-${randomUUID()}.sock`);
@@ -48,7 +49,10 @@ export class GoalMcpServer {
48
49
  return;
49
50
  const server = await this.listening;
50
51
  this.capabilities.clear();
51
- await new Promise((resolve, reject) => server.close((error) => (error === undefined ? resolve() : reject(error))));
52
+ const closed = new Promise((resolve, reject) => server.close((error) => (error === undefined ? resolve() : reject(error))));
53
+ for (const socket of this.sockets)
54
+ socket.destroy();
55
+ await closed;
52
56
  if (process.platform !== 'win32')
53
57
  await unlink(this.endpoint).catch(() => undefined);
54
58
  }
@@ -65,6 +69,8 @@ export class GoalMcpServer {
65
69
  this.capabilities.set(token, { ...capability, sessionId: nextId });
66
70
  }
67
71
  handle(socket) {
72
+ this.sockets.add(socket);
73
+ socket.once('close', () => this.sockets.delete(socket));
68
74
  let body = '';
69
75
  socket.setEncoding('utf8');
70
76
  socket.on('data', (chunk) => {
@@ -1537,9 +1537,7 @@ export class HostedConversationNodeService {
1537
1537
  processExecutor(paths, version, temporaryDirectory = paths.temporaryRootDirectory, generatedImagesDirectory, generatedImagesLogicalPath) {
1538
1538
  return this.options.processExecutorFactory({
1539
1539
  workspaceDirectory: version.templateDirectory,
1540
- ...(version.sourceWorkspacePath === undefined
1541
- ? {}
1542
- : { sourceWorkspacePath: version.sourceWorkspacePath }),
1540
+ sourceWorkspacePath: version.sourceWorkspacePath,
1543
1541
  inboxDirectory: paths.uploadsDirectory,
1544
1542
  outputDirectory: paths.outputDirectory,
1545
1543
  ...(generatedImagesDirectory === undefined || generatedImagesLogicalPath === undefined
@@ -1605,10 +1603,10 @@ export class HostedConversationNodeService {
1605
1603
  if (!record(persisted))
1606
1604
  throw new Error('CONVERSATION_TEMPLATE_VERSION_CORRUPTED');
1607
1605
  const snapshot = publishedTemplateSnapshot(persisted);
1608
- const parsedDraft = publishedTemplateDraft(snapshot.schemaVersion, persisted.draft);
1606
+ const parsedDraft = publishedTemplateDraft(persisted.draft);
1609
1607
  const draft = parsedDraft.draft;
1610
1608
  const mcpServers = publishedMcpServers(persisted.mcpServers);
1611
- const contentHash = publishedTemplateContentHash(snapshot.directories, snapshot.entries, snapshot.schemaVersion);
1609
+ const contentHash = publishedTemplateContentHash(snapshot.directories, snapshot.entries);
1612
1610
  const totalBytes = snapshot.entries.reduce((total, entry) => total + entry.size, 0);
1613
1611
  if (persisted.mcpConfigHash !== row.mcp_config_hash ||
1614
1612
  createHash('sha256').update(JSON.stringify(mcpServers)).digest('hex') !==
@@ -1629,9 +1627,7 @@ export class HostedConversationNodeService {
1629
1627
  templateId,
1630
1628
  storageKey: row.storage_key,
1631
1629
  templateDirectory: contentDirectory,
1632
- ...(snapshot.sourceWorkspacePath === undefined
1633
- ? {}
1634
- : { sourceWorkspacePath: snapshot.sourceWorkspacePath }),
1630
+ sourceWorkspacePath: snapshot.sourceWorkspacePath,
1635
1631
  draft,
1636
1632
  modelPolicy: parsedDraft.modelPolicy,
1637
1633
  mcpServers
@@ -1679,63 +1675,36 @@ function runtimeModels(value) {
1679
1675
  }
1680
1676
  function allowedModels(version, models) {
1681
1677
  const allowed = new Set(version.modelPolicy.allowedIds);
1682
- return models.filter((model) => model.enabled &&
1683
- allowed.has(version.modelPolicy.identity === 'UPSTREAM_MODEL_ID' ? model.modelId : model.id));
1678
+ return models.filter((model) => model.enabled && allowed.has(model.modelId));
1684
1679
  }
1685
1680
  function selectModel(version, models, requestedModelId) {
1686
1681
  const available = allowedModels(version, models);
1687
1682
  const requested = available.find((candidate) => candidate.id === requestedModelId);
1688
1683
  if (requested !== undefined)
1689
1684
  return requested;
1690
- const defaultModel = available.find((candidate) => (version.modelPolicy.identity === 'UPSTREAM_MODEL_ID' ? candidate.modelId : candidate.id) ===
1691
- version.modelPolicy.defaultId);
1685
+ const defaultModel = available.find((candidate) => candidate.modelId === version.modelPolicy.defaultId);
1692
1686
  if (defaultModel !== undefined)
1693
1687
  return defaultModel;
1694
1688
  const model = version.modelPolicy.allowedIds
1695
- .map((allowedId) => available.find((candidate) => (version.modelPolicy.identity === 'UPSTREAM_MODEL_ID'
1696
- ? candidate.modelId
1697
- : candidate.id) === allowedId))
1689
+ .map((allowedId) => available.find((candidate) => candidate.modelId === allowedId))
1698
1690
  .find((candidate) => candidate !== undefined);
1699
1691
  if (model === undefined)
1700
1692
  throw new Error('HOSTED_CONVERSATION_MODEL_NOT_AVAILABLE');
1701
1693
  return model;
1702
1694
  }
1703
- function publishedTemplateDraft(schemaVersion, value) {
1695
+ function publishedTemplateDraft(value) {
1704
1696
  const current = conversationTemplateDraftSchema.safeParse(value);
1705
1697
  if (current.success) {
1706
1698
  const { allowedModels, defaultModelId, ...draft } = current.data;
1707
1699
  return {
1708
1700
  draft,
1709
1701
  modelPolicy: {
1710
- identity: 'UPSTREAM_MODEL_ID',
1711
1702
  allowedIds: allowedModels.map((model) => model.modelId),
1712
1703
  defaultId: defaultModelId
1713
1704
  }
1714
1705
  };
1715
1706
  }
1716
- if (schemaVersion !== 1 ||
1717
- !record(value) ||
1718
- 'allowedModels' in value ||
1719
- !Array.isArray(value.allowedModelIds))
1720
- throw new Error('CONVERSATION_TEMPLATE_VERSION_CORRUPTED');
1721
- const allowedModelIds = value.allowedModelIds;
1722
- const legacy = { ...value };
1723
- delete legacy.allowedModelIds;
1724
- const normalized = conversationTemplateDraftSchema.safeParse({
1725
- ...legacy,
1726
- allowedModels: allowedModelIds.map((modelId) => ({ modelId, name: modelId }))
1727
- });
1728
- if (!normalized.success)
1729
- throw new Error('CONVERSATION_TEMPLATE_VERSION_CORRUPTED');
1730
- const { allowedModels, defaultModelId, ...draft } = normalized.data;
1731
- return {
1732
- draft,
1733
- modelPolicy: {
1734
- identity: 'CONFIGURATION_ID',
1735
- allowedIds: allowedModels.map((model) => model.modelId),
1736
- defaultId: defaultModelId
1737
- }
1738
- };
1707
+ throw new Error('CONVERSATION_TEMPLATE_VERSION_CORRUPTED');
1739
1708
  }
1740
1709
  function networkModels(models, networkMode) {
1741
1710
  if (networkMode !== 'NONE')
@@ -1773,14 +1742,6 @@ function publishedMcpServers(value) {
1773
1742
  });
1774
1743
  }
1775
1744
  function publishedTemplateSnapshot(manifest) {
1776
- if (manifest.schemaVersion === 1) {
1777
- const entries = publishedTemplateFiles(manifest.files);
1778
- return {
1779
- schemaVersion: 1,
1780
- directories: legacyPublishedTemplateDirectories(entries),
1781
- entries
1782
- };
1783
- }
1784
1745
  if (manifest.schemaVersion !== 2 ||
1785
1746
  typeof manifest.sourceWorkspacePath !== 'string' ||
1786
1747
  !Array.isArray(manifest.directories) ||
@@ -1876,30 +1837,14 @@ function safePublishedTemplateSize(value) {
1876
1837
  throw new Error('CONVERSATION_TEMPLATE_VERSION_CORRUPTED');
1877
1838
  return value;
1878
1839
  }
1879
- function legacyPublishedTemplateDirectories(entries) {
1880
- const directories = new Set();
1881
- for (const entry of entries) {
1882
- const parts = entry.relativePath.split('/');
1883
- for (let index = 1; index < parts.length; index += 1)
1884
- directories.add(parts.slice(0, index).join('/'));
1885
- }
1886
- return [...directories].sort((left, right) => left.localeCompare(right, 'en-US'));
1887
- }
1888
- function publishedTemplateContentHash(directories, entries, schemaVersion) {
1889
- const content = schemaVersion === 1
1890
- ? entries
1891
- .map((entry) => {
1892
- if (entry.kind !== 'FILE')
1893
- throw new Error('CONVERSATION_TEMPLATE_VERSION_CORRUPTED');
1894
- return `${entry.relativePath}\0${entry.size}\0${entry.sha256}`;
1895
- })
1896
- .join('\n')
1897
- : [
1898
- ...directories.map((directory) => `DIRECTORY\0${directory}`),
1899
- ...entries.map((entry) => entry.kind === 'FILE'
1900
- ? `${entry.kind}\0${entry.relativePath}\0${entry.size}\0${entry.executable ? 1 : 0}\0${entry.sha256}`
1901
- : `${entry.kind}\0${entry.relativePath}\0${entry.size}\0${entry.linkTarget}\0${entry.targetKind}\0${entry.resolvedTarget}`)
1902
- ].join('\n');
1840
+ /** Current manifests hash directory and entry identities; changing the format requires a new version. */
1841
+ function publishedTemplateContentHash(directories, entries) {
1842
+ const content = [
1843
+ ...directories.map((directory) => `DIRECTORY\0${directory}`),
1844
+ ...entries.map((entry) => entry.kind === 'FILE'
1845
+ ? `${entry.kind}\0${entry.relativePath}\0${entry.size}\0${entry.executable ? 1 : 0}\0${entry.sha256}`
1846
+ : `${entry.kind}\0${entry.relativePath}\0${entry.size}\0${entry.linkTarget}\0${entry.targetKind}\0${entry.resolvedTarget}`)
1847
+ ].join('\n');
1903
1848
  return createHash('sha256').update(content).digest('hex');
1904
1849
  }
1905
1850
  function validatePublishedSourceWorkspacePath(path) {
@@ -32,6 +32,7 @@ export class WorkspaceChangeService {
32
32
  const existing = this.state.changesReads.get(workspace.id);
33
33
  if (existing !== undefined)
34
34
  return existing;
35
+ // Invalidation may arrive during this shared read; its old result must not repopulate the cache.
35
36
  const version = this.state.changesVersions.get(workspace.id) ?? 0;
36
37
  const request = this.state.changesLimiter.run(async () => {
37
38
  const startedAt = performance.now();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@myagentroam/node",
3
- "version": "0.9.120",
3
+ "version": "0.9.121",
4
4
  "description": "MyAgentRoam Node runtime CLI.",
5
5
  "license": "Apache-2.0 AND GPL-2.0-or-later",
6
6
  "type": "module",
@@ -30,9 +30,9 @@
30
30
  "npm": "11.12.1",
31
31
  "ws": "^8.21.3",
32
32
  "zod": "4.4.3",
33
- "@myagentroam/agent": "0.9.120",
34
- "@myagentroam/code-navigation": "0.9.120",
35
- "@myagentroam/protocol": "0.9.120"
33
+ "@myagentroam/agent": "0.9.121",
34
+ "@myagentroam/code-navigation": "0.9.121",
35
+ "@myagentroam/protocol": "0.9.121"
36
36
  },
37
37
  "devDependencies": {
38
38
  "@types/ws": "^8.18.1"
@@ -1,91 +0,0 @@
1
- export const NODE_SCHEMA_VERSION_V4 = 4;
2
- /** Last supported released schema. Fresh databases start from this baseline. */
3
- export const NODE_SCHEMA_V4 = String.raw `CREATE TABLE node_schema_migrations (
4
- version INTEGER PRIMARY KEY, applied_at INTEGER NOT NULL
5
- ) STRICT;
6
- CREATE TABLE workspaces (
7
- id TEXT PRIMARY KEY, path TEXT NOT NULL UNIQUE,
8
- kind TEXT NOT NULL CHECK (kind IN ('DIRECTORY', 'GIT_WORKSPACE')),
9
- git_summary TEXT, created_at INTEGER NOT NULL, updated_at INTEGER NOT NULL
10
- ) STRICT;
11
- CREATE TABLE agent_sessions (
12
- id TEXT PRIMARY KEY,
13
- workspace_id TEXT NOT NULL REFERENCES workspaces(id) ON DELETE CASCADE,
14
- runner TEXT NOT NULL CHECK (runner IN ('codex', 'claude-code', 'opencode')),
15
- pin_order INTEGER CHECK (pin_order IS NULL OR pin_order >= 0),
16
- metadata TEXT NOT NULL CHECK (json_valid(metadata))
17
- ) STRICT;
18
- CREATE TABLE terminal_sessions (
19
- id TEXT PRIMARY KEY, workspace_id TEXT REFERENCES workspaces(id) ON DELETE SET NULL,
20
- profile_id TEXT NOT NULL,
21
- state TEXT NOT NULL CHECK (state IN ('RUNNING', 'EXITED', 'CLOSED')),
22
- exit_code INTEGER, rows INTEGER NOT NULL CHECK (rows > 0 AND rows <= 1000),
23
- cols INTEGER NOT NULL CHECK (cols > 0 AND cols <= 1000),
24
- latest_sequence INTEGER NOT NULL CHECK (latest_sequence >= 0),
25
- created_at INTEGER NOT NULL, updated_at INTEGER NOT NULL
26
- ) STRICT;
27
- CREATE TABLE runner_defaults (
28
- runner TEXT PRIMARY KEY CHECK (runner IN ('codex', 'claude-code', 'opencode')),
29
- model TEXT NOT NULL, effort TEXT NOT NULL, access TEXT NOT NULL
30
- ) STRICT;
31
- CREATE TABLE mcp_installations (
32
- target_kind TEXT NOT NULL CHECK (target_kind IN ('NODE','WORKSPACE')),
33
- target_id TEXT NOT NULL, catalog_entry_id TEXT NOT NULL, server_name TEXT NOT NULL,
34
- display_name TEXT NOT NULL, version TEXT NOT NULL,
35
- enabled INTEGER NOT NULL CHECK (enabled IN (0,1)),
36
- runtime TEXT NOT NULL CHECK (json_valid(runtime)),
37
- configuration TEXT NOT NULL CHECK (json_valid(configuration)), installed_at INTEGER NOT NULL,
38
- PRIMARY KEY (target_kind,target_id,server_name),
39
- CHECK ((target_kind='NODE' AND target_id='') OR (target_kind='WORKSPACE' AND target_id<>''))
40
- ) STRICT;`;
41
- export function assertNodeSchemaV4(database) {
42
- const required = {
43
- node_schema_migrations: ['version', 'applied_at'],
44
- workspaces: ['id', 'path', 'kind', 'git_summary', 'created_at', 'updated_at'],
45
- agent_sessions: ['id', 'workspace_id', 'runner', 'pin_order', 'metadata'],
46
- terminal_sessions: [
47
- 'id',
48
- 'workspace_id',
49
- 'profile_id',
50
- 'state',
51
- 'exit_code',
52
- 'rows',
53
- 'cols',
54
- 'latest_sequence',
55
- 'created_at',
56
- 'updated_at'
57
- ],
58
- runner_defaults: ['runner', 'model', 'effort', 'access'],
59
- mcp_installations: [
60
- 'target_kind',
61
- 'target_id',
62
- 'catalog_entry_id',
63
- 'server_name',
64
- 'display_name',
65
- 'version',
66
- 'enabled',
67
- 'runtime',
68
- 'configuration',
69
- 'installed_at'
70
- ]
71
- };
72
- for (const [table, expected] of Object.entries(required)) {
73
- const columns = database.prepare(`PRAGMA table_info(${table})`).all();
74
- const actual = columns.map((column) => column.name);
75
- // Later schema versions may add columns to this baseline table.
76
- if (expected.some((name) => !actual.includes(name)))
77
- throw new Error(`NODE_DATABASE_SCHEMA_INVALID:${table}`);
78
- }
79
- for (const table of ['agent_sessions', 'runner_defaults']) {
80
- const definition = database
81
- .prepare("SELECT sql FROM sqlite_master WHERE type='table' AND name=?")
82
- .get(table);
83
- if (typeof definition?.sql !== 'string' || !definition.sql.includes("'opencode'"))
84
- throw new Error(`NODE_DATABASE_SCHEMA_INVALID:${table}:runner`);
85
- }
86
- const row = database
87
- .prepare("SELECT sql FROM sqlite_master WHERE type='table' AND name='mcp_installations'")
88
- .get();
89
- if (typeof row?.sql !== 'string' || !row.sql.includes("target_kind IN ('NODE','WORKSPACE')"))
90
- throw new Error('NODE_DATABASE_SCHEMA_INVALID:mcp_installations');
91
- }
@@ -1,33 +0,0 @@
1
- export const NODE_SCHEMA_VERSION_V5 = 5;
2
- /** Adds MAR Agent to every persisted Runner discriminator. */
3
- export const NODE_SCHEMA_MIGRATION_V5 = String.raw `
4
- CREATE TABLE agent_sessions_v5 (
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
- INSERT INTO agent_sessions_v5 (id, workspace_id, runner, pin_order, metadata)
12
- SELECT id, workspace_id, runner, pin_order, metadata FROM agent_sessions;
13
- DROP TABLE agent_sessions;
14
- ALTER TABLE agent_sessions_v5 RENAME TO agent_sessions;
15
-
16
- CREATE TABLE runner_defaults_v5 (
17
- runner TEXT PRIMARY KEY CHECK (runner IN ('mar-agent', 'codex', 'claude-code', 'opencode')),
18
- model TEXT NOT NULL, effort TEXT NOT NULL, access TEXT NOT NULL
19
- ) STRICT;
20
- INSERT INTO runner_defaults_v5 (runner, model, effort, access)
21
- SELECT runner, model, effort, access FROM runner_defaults;
22
- DROP TABLE runner_defaults;
23
- ALTER TABLE runner_defaults_v5 RENAME TO runner_defaults;
24
- `;
25
- export function assertNodeSchemaV5(database) {
26
- for (const table of ['agent_sessions', 'runner_defaults']) {
27
- const definition = database
28
- .prepare("SELECT sql FROM sqlite_master WHERE type='table' AND name=?")
29
- .get(table);
30
- if (typeof definition?.sql !== 'string' || !definition.sql.includes("'mar-agent'"))
31
- throw new Error(`NODE_DATABASE_SCHEMA_INVALID:${table}:runner`);
32
- }
33
- }
@@ -1,191 +0,0 @@
1
- export const NODE_SCHEMA_VERSION_V6 = 6;
2
- /** Adds rebuildable hosted-conversation indexes and immutable template publication metadata. */
3
- export const NODE_SCHEMA_MIGRATION_V6 = String.raw `
4
- CREATE TABLE conversation_template_versions (
5
- id TEXT PRIMARY KEY,
6
- template_id TEXT NOT NULL,
7
- version_number INTEGER NOT NULL CHECK (version_number > 0),
8
- draft_revision INTEGER NOT NULL CHECK (draft_revision > 0),
9
- status TEXT NOT NULL CHECK (status IN ('AVAILABLE','DISABLED','CORRUPTED','RETAINED')),
10
- storage_key TEXT NOT NULL UNIQUE,
11
- manifest TEXT NOT NULL CHECK (json_valid(manifest)),
12
- content_hash TEXT NOT NULL CHECK (length(content_hash) = 64),
13
- manifest_hash TEXT NOT NULL CHECK (length(manifest_hash) = 64),
14
- file_count INTEGER NOT NULL CHECK (file_count >= 0),
15
- total_bytes INTEGER NOT NULL CHECK (total_bytes >= 0),
16
- host_policy_version INTEGER NOT NULL CHECK (host_policy_version > 0),
17
- published_by_user_id INTEGER NOT NULL CHECK (published_by_user_id > 0),
18
- published_at INTEGER NOT NULL CHECK (published_at >= 0),
19
- reference_count INTEGER NOT NULL DEFAULT 0 CHECK (reference_count >= 0),
20
- UNIQUE (template_id, version_number)
21
- ) STRICT;
22
- CREATE INDEX conversation_template_versions_template_status
23
- ON conversation_template_versions(template_id, status, published_at);
24
-
25
- CREATE TABLE conversation_template_publish_jobs (
26
- id TEXT PRIMARY KEY,
27
- template_id TEXT NOT NULL,
28
- draft_revision INTEGER NOT NULL CHECK (draft_revision > 0),
29
- stage TEXT NOT NULL CHECK (
30
- stage IN ('VALIDATING','ENUMERATING','COPYING','SCANNING','MANIFEST','SEALED','REGISTERED')
31
- ),
32
- status TEXT NOT NULL CHECK (status IN ('QUEUED','RUNNING','SUCCEEDED','FAILED')),
33
- version_id TEXT REFERENCES conversation_template_versions(id) ON DELETE SET NULL,
34
- error_code TEXT,
35
- error_summary TEXT,
36
- requested_by_user_id INTEGER NOT NULL CHECK (requested_by_user_id > 0),
37
- draft TEXT NOT NULL CHECK (json_valid(draft)),
38
- created_at INTEGER NOT NULL CHECK (created_at >= 0),
39
- updated_at INTEGER NOT NULL CHECK (updated_at >= 0)
40
- ) STRICT;
41
- CREATE INDEX conversation_template_publish_jobs_template
42
- ON conversation_template_publish_jobs(template_id, created_at, id);
43
- CREATE INDEX conversation_template_publish_jobs_status
44
- ON conversation_template_publish_jobs(status, updated_at);
45
-
46
- CREATE TABLE hosted_conversations (
47
- id TEXT PRIMARY KEY,
48
- user_storage_key TEXT NOT NULL,
49
- conversation_storage_key TEXT NOT NULL,
50
- client_request_id TEXT,
51
- node_id TEXT NOT NULL,
52
- session_id TEXT NOT NULL,
53
- template_id TEXT NOT NULL,
54
- template_name TEXT NOT NULL,
55
- active_version_id TEXT NOT NULL REFERENCES conversation_template_versions(id),
56
- pending_version_id TEXT REFERENCES conversation_template_versions(id),
57
- title TEXT NOT NULL,
58
- status TEXT NOT NULL CHECK (
59
- status IN ('ACTIVE','UPGRADE_PENDING','UPGRADE_FAILED','RECOVERABLE_DELETED','DELETING')
60
- ),
61
- model_id TEXT NOT NULL,
62
- reasoning_effort TEXT,
63
- template_deleted INTEGER NOT NULL DEFAULT 0 CHECK (template_deleted IN (0,1)),
64
- recoverable_until INTEGER,
65
- used_bytes INTEGER NOT NULL DEFAULT 0 CHECK (used_bytes >= 0),
66
- max_bytes INTEGER NOT NULL CHECK (max_bytes > 0),
67
- created_at INTEGER NOT NULL CHECK (created_at >= 0),
68
- updated_at INTEGER NOT NULL CHECK (updated_at >= 0),
69
- last_activity_at INTEGER NOT NULL CHECK (last_activity_at >= 0),
70
- UNIQUE (user_storage_key, conversation_storage_key),
71
- UNIQUE (user_storage_key, client_request_id)
72
- ) STRICT;
73
- CREATE INDEX hosted_conversations_user_activity
74
- ON hosted_conversations(user_storage_key, last_activity_at DESC, id DESC);
75
- CREATE INDEX hosted_conversations_template
76
- ON hosted_conversations(template_id, status, id);
77
-
78
- CREATE TABLE conversation_files (
79
- id TEXT PRIMARY KEY,
80
- conversation_id TEXT NOT NULL REFERENCES hosted_conversations(id) ON DELETE CASCADE,
81
- category TEXT NOT NULL CHECK (category IN ('UPLOAD','WORK','OUTPUT')),
82
- relative_path TEXT NOT NULL,
83
- display_name TEXT NOT NULL,
84
- mime_type TEXT NOT NULL,
85
- size INTEGER NOT NULL CHECK (size >= 0),
86
- source TEXT NOT NULL CHECK (source IN ('USER','AGENT','SYSTEM')),
87
- turn_id TEXT,
88
- item_id TEXT,
89
- created_at INTEGER NOT NULL CHECK (created_at >= 0),
90
- updated_at INTEGER NOT NULL CHECK (updated_at >= 0),
91
- UNIQUE (conversation_id, category, relative_path)
92
- ) STRICT;
93
- CREATE INDEX conversation_files_conversation_updated
94
- ON conversation_files(conversation_id, updated_at DESC, id);
95
-
96
- CREATE TABLE hosted_conversation_messages (
97
- conversation_id TEXT NOT NULL REFERENCES hosted_conversations(id) ON DELETE CASCADE,
98
- client_message_id TEXT NOT NULL,
99
- run_id TEXT NOT NULL,
100
- status TEXT NOT NULL CHECK (status IN ('RUNNING','SUCCEEDED','FAILED','INTERRUPTED')),
101
- created_at INTEGER NOT NULL CHECK (created_at >= 0),
102
- updated_at INTEGER NOT NULL CHECK (updated_at >= 0),
103
- PRIMARY KEY (conversation_id, client_message_id)
104
- ) STRICT;
105
- `;
106
- export function assertNodeSchemaV6(database) {
107
- const required = {
108
- conversation_template_versions: [
109
- 'id',
110
- 'template_id',
111
- 'version_number',
112
- 'draft_revision',
113
- 'status',
114
- 'storage_key',
115
- 'manifest',
116
- 'content_hash',
117
- 'manifest_hash',
118
- 'file_count',
119
- 'total_bytes',
120
- 'host_policy_version',
121
- 'published_by_user_id',
122
- 'published_at',
123
- 'reference_count'
124
- ],
125
- conversation_template_publish_jobs: [
126
- 'id',
127
- 'template_id',
128
- 'draft_revision',
129
- 'stage',
130
- 'status',
131
- 'version_id',
132
- 'error_code',
133
- 'error_summary',
134
- 'requested_by_user_id',
135
- 'draft',
136
- 'created_at',
137
- 'updated_at'
138
- ],
139
- hosted_conversations: [
140
- 'id',
141
- 'user_storage_key',
142
- 'conversation_storage_key',
143
- 'client_request_id',
144
- 'node_id',
145
- 'session_id',
146
- 'template_id',
147
- 'template_name',
148
- 'active_version_id',
149
- 'pending_version_id',
150
- 'title',
151
- 'status',
152
- 'model_id',
153
- 'reasoning_effort',
154
- 'template_deleted',
155
- 'recoverable_until',
156
- 'used_bytes',
157
- 'max_bytes',
158
- 'created_at',
159
- 'updated_at',
160
- 'last_activity_at'
161
- ],
162
- conversation_files: [
163
- 'id',
164
- 'conversation_id',
165
- 'category',
166
- 'relative_path',
167
- 'display_name',
168
- 'mime_type',
169
- 'size',
170
- 'source',
171
- 'turn_id',
172
- 'item_id',
173
- 'created_at',
174
- 'updated_at'
175
- ],
176
- hosted_conversation_messages: [
177
- 'conversation_id',
178
- 'client_message_id',
179
- 'run_id',
180
- 'status',
181
- 'created_at',
182
- 'updated_at'
183
- ]
184
- };
185
- for (const [table, expected] of Object.entries(required)) {
186
- const columns = database.prepare(`PRAGMA table_info(${table})`).all();
187
- const actual = columns.map((column) => column.name);
188
- if (actual.length !== expected.length || expected.some((name) => !actual.includes(name)))
189
- throw new Error(`NODE_DATABASE_SCHEMA_INVALID:${table}`);
190
- }
191
- }
@@ -1,52 +0,0 @@
1
- export const NODE_SCHEMA_VERSION_V7 = 7;
2
- export const EMPTY_MCP_CONFIG_HASH = '4f53cda18c2baa0c0354bb5f9a3ecbe5ed12ab4d8e11ba873c2f11161202b945';
3
- /** Adds immutable MCP fingerprints and removes the retired hosted reasoning preference. */
4
- export const NODE_SCHEMA_MIGRATION_V7 = String.raw `
5
- ALTER TABLE conversation_template_versions
6
- ADD COLUMN mcp_config_hash TEXT NOT NULL DEFAULT '${EMPTY_MCP_CONFIG_HASH}'
7
- CHECK (length(mcp_config_hash) = 64);
8
- ALTER TABLE hosted_conversations DROP COLUMN reasoning_effort;
9
- CREATE TABLE conversation_files_v7 (
10
- id TEXT PRIMARY KEY,
11
- conversation_id TEXT NOT NULL REFERENCES hosted_conversations(id) ON DELETE CASCADE,
12
- category TEXT NOT NULL CHECK (category IN ('UPLOAD','OUTPUT')),
13
- relative_path TEXT NOT NULL,
14
- display_name TEXT NOT NULL,
15
- mime_type TEXT NOT NULL,
16
- size INTEGER NOT NULL CHECK (size >= 0),
17
- source TEXT NOT NULL CHECK (source IN ('USER','AGENT','SYSTEM')),
18
- turn_id TEXT,
19
- item_id TEXT,
20
- created_at INTEGER NOT NULL CHECK (created_at >= 0),
21
- updated_at INTEGER NOT NULL CHECK (updated_at >= 0),
22
- UNIQUE (conversation_id, category, relative_path)
23
- ) STRICT;
24
- INSERT INTO conversation_files_v7
25
- (id,conversation_id,category,relative_path,display_name,mime_type,size,source,
26
- turn_id,item_id,created_at,updated_at)
27
- SELECT id,conversation_id,category,relative_path,display_name,mime_type,size,source,
28
- turn_id,item_id,created_at,updated_at
29
- FROM conversation_files
30
- WHERE category IN ('UPLOAD','OUTPUT');
31
- DROP TABLE conversation_files;
32
- ALTER TABLE conversation_files_v7 RENAME TO conversation_files;
33
- CREATE INDEX conversation_files_conversation_updated
34
- ON conversation_files(conversation_id, updated_at DESC, id);
35
- `;
36
- export function assertNodeSchemaV7(database) {
37
- const templateColumns = database
38
- .prepare('PRAGMA table_info(conversation_template_versions)')
39
- .all();
40
- if (!templateColumns.some((column) => column.name === 'mcp_config_hash'))
41
- throw new Error('NODE_DATABASE_SCHEMA_INVALID:conversation_template_versions');
42
- const conversationColumns = database.prepare('PRAGMA table_info(hosted_conversations)').all();
43
- if (conversationColumns === undefined ||
44
- conversationColumns.some((column) => column.name === 'reasoning_effort'))
45
- throw new Error('NODE_DATABASE_SCHEMA_INVALID:hosted_conversations');
46
- const conversationFiles = database
47
- .prepare("SELECT sql FROM sqlite_master WHERE type='table' AND name='conversation_files'")
48
- .get();
49
- if (typeof conversationFiles?.sql !== 'string' ||
50
- !conversationFiles.sql.includes("category IN ('UPLOAD','OUTPUT')"))
51
- throw new Error('NODE_DATABASE_SCHEMA_INVALID:conversation_files');
52
- }