@cleocode/core 2026.4.11 → 2026.4.13
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/codebase-map/analyzers/architecture.d.ts.map +1 -1
- package/dist/codebase-map/analyzers/architecture.js +0 -1
- package/dist/codebase-map/analyzers/architecture.js.map +1 -1
- package/dist/conduit/local-transport.d.ts +18 -8
- package/dist/conduit/local-transport.d.ts.map +1 -1
- package/dist/conduit/local-transport.js +23 -13
- package/dist/conduit/local-transport.js.map +1 -1
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +0 -1
- package/dist/config.js.map +1 -1
- package/dist/errors.d.ts +19 -0
- package/dist/errors.d.ts.map +1 -1
- package/dist/errors.js +6 -0
- package/dist/errors.js.map +1 -1
- package/dist/index.js +175 -68950
- package/dist/index.js.map +1 -7
- package/dist/init.d.ts +1 -2
- package/dist/init.d.ts.map +1 -1
- package/dist/init.js +1 -2
- package/dist/init.js.map +1 -1
- package/dist/internal.d.ts +8 -3
- package/dist/internal.d.ts.map +1 -1
- package/dist/internal.js +13 -6
- package/dist/internal.js.map +1 -1
- package/dist/memory/learnings.d.ts +2 -2
- package/dist/memory/patterns.d.ts +6 -6
- package/dist/output.d.ts +32 -11
- package/dist/output.d.ts.map +1 -1
- package/dist/output.js +67 -67
- package/dist/output.js.map +1 -1
- package/dist/paths.js +80 -14
- package/dist/paths.js.map +1 -1
- package/dist/skills/dynamic-skill-generator.d.ts +0 -2
- package/dist/skills/dynamic-skill-generator.d.ts.map +1 -1
- package/dist/skills/dynamic-skill-generator.js.map +1 -1
- package/dist/store/agent-registry-accessor.d.ts +203 -12
- package/dist/store/agent-registry-accessor.d.ts.map +1 -1
- package/dist/store/agent-registry-accessor.js +618 -100
- package/dist/store/agent-registry-accessor.js.map +1 -1
- package/dist/store/api-key-kdf.d.ts +73 -0
- package/dist/store/api-key-kdf.d.ts.map +1 -0
- package/dist/store/api-key-kdf.js +84 -0
- package/dist/store/api-key-kdf.js.map +1 -0
- package/dist/store/cleanup-legacy.js +171 -0
- package/dist/store/cleanup-legacy.js.map +1 -0
- package/dist/store/conduit-sqlite.d.ts +184 -0
- package/dist/store/conduit-sqlite.d.ts.map +1 -0
- package/dist/store/conduit-sqlite.js +570 -0
- package/dist/store/conduit-sqlite.js.map +1 -0
- package/dist/store/global-salt.d.ts +78 -0
- package/dist/store/global-salt.d.ts.map +1 -0
- package/dist/store/global-salt.js +147 -0
- package/dist/store/global-salt.js.map +1 -0
- package/dist/store/migrate-signaldock-to-conduit.d.ts +81 -0
- package/dist/store/migrate-signaldock-to-conduit.d.ts.map +1 -0
- package/dist/store/migrate-signaldock-to-conduit.js +555 -0
- package/dist/store/migrate-signaldock-to-conduit.js.map +1 -0
- package/dist/store/nexus-sqlite.js +28 -3
- package/dist/store/nexus-sqlite.js.map +1 -1
- package/dist/store/signaldock-sqlite.d.ts +122 -19
- package/dist/store/signaldock-sqlite.d.ts.map +1 -1
- package/dist/store/signaldock-sqlite.js +401 -251
- package/dist/store/signaldock-sqlite.js.map +1 -1
- package/dist/store/sqlite-backup.js +122 -4
- package/dist/store/sqlite-backup.js.map +1 -1
- package/dist/system/backup.d.ts +0 -26
- package/dist/system/backup.d.ts.map +1 -1
- package/dist/system/runtime.d.ts +0 -2
- package/dist/system/runtime.d.ts.map +1 -1
- package/dist/system/runtime.js +3 -3
- package/dist/system/runtime.js.map +1 -1
- package/dist/tasks/add.d.ts +1 -1
- package/dist/tasks/add.d.ts.map +1 -1
- package/dist/tasks/add.js +98 -23
- package/dist/tasks/add.js.map +1 -1
- package/dist/tasks/complete.d.ts.map +1 -1
- package/dist/tasks/complete.js +4 -1
- package/dist/tasks/complete.js.map +1 -1
- package/dist/tasks/find.d.ts.map +1 -1
- package/dist/tasks/find.js +4 -1
- package/dist/tasks/find.js.map +1 -1
- package/dist/tasks/labels.d.ts.map +1 -1
- package/dist/tasks/labels.js +4 -1
- package/dist/tasks/labels.js.map +1 -1
- package/dist/tasks/relates.d.ts.map +1 -1
- package/dist/tasks/relates.js +16 -4
- package/dist/tasks/relates.js.map +1 -1
- package/dist/tasks/show.d.ts.map +1 -1
- package/dist/tasks/show.js +4 -1
- package/dist/tasks/show.js.map +1 -1
- package/dist/tasks/update.d.ts.map +1 -1
- package/dist/tasks/update.js +32 -6
- package/dist/tasks/update.js.map +1 -1
- package/dist/validation/engine.d.ts.map +1 -1
- package/dist/validation/engine.js +16 -4
- package/dist/validation/engine.js.map +1 -1
- package/dist/validation/param-utils.d.ts +5 -3
- package/dist/validation/param-utils.d.ts.map +1 -1
- package/dist/validation/param-utils.js +8 -6
- package/dist/validation/param-utils.js.map +1 -1
- package/dist/validation/protocols/_shared.d.ts.map +1 -1
- package/dist/validation/protocols/_shared.js +13 -6
- package/dist/validation/protocols/_shared.js.map +1 -1
- package/package.json +9 -7
- package/src/adapters/__tests__/manager.test.ts +0 -1
- package/src/codebase-map/analyzers/architecture.ts +0 -1
- package/src/conduit/__tests__/local-credential-flow.test.ts +20 -18
- package/src/conduit/__tests__/local-transport.test.ts +14 -12
- package/src/conduit/local-transport.ts +23 -13
- package/src/config.ts +0 -1
- package/src/errors.ts +24 -0
- package/src/hooks/handlers/__tests__/hook-automation-e2e.test.ts +2 -5
- package/src/init.ts +1 -2
- package/src/internal.ts +96 -2
- package/src/lifecycle/cant/lifecycle-rcasd.cant +133 -0
- package/src/memory/__tests__/engine-compat.test.ts +2 -2
- package/src/memory/__tests__/pipeline-manifest-sqlite.test.ts +4 -4
- package/src/observability/__tests__/index.test.ts +4 -4
- package/src/observability/__tests__/log-filter.test.ts +4 -4
- package/src/output.ts +73 -75
- package/src/sessions/__tests__/session-grade.integration.test.ts +1 -1
- package/src/sessions/__tests__/session-grade.test.ts +2 -2
- package/src/skills/__tests__/dynamic-skill-generator.test.ts +0 -2
- package/src/skills/dynamic-skill-generator.ts +0 -2
- package/src/store/__tests__/agent-registry-accessor.test.ts +807 -0
- package/src/store/__tests__/api-key-kdf.test.ts +113 -0
- package/src/store/__tests__/backup-crypto.test.ts +101 -0
- package/src/store/__tests__/backup-pack.test.ts +491 -0
- package/src/store/__tests__/backup-unpack.test.ts +298 -0
- package/src/store/__tests__/conduit-sqlite.test.ts +413 -0
- package/src/store/__tests__/global-salt.test.ts +195 -0
- package/src/store/__tests__/migrate-signaldock-to-conduit.test.ts +715 -0
- package/src/store/__tests__/regenerators.test.ts +234 -0
- package/src/store/__tests__/restore-conflict-report.test.ts +274 -0
- package/src/store/__tests__/restore-json-merge.test.ts +521 -0
- package/src/store/__tests__/signaldock-sqlite.test.ts +652 -0
- package/src/store/__tests__/sqlite-backup-global.test.ts +307 -3
- package/src/store/__tests__/sqlite-backup.test.ts +5 -1
- package/src/store/__tests__/t310-integration.test.ts +1150 -0
- package/src/store/__tests__/t310-readiness.test.ts +111 -0
- package/src/store/__tests__/t311-integration.test.ts +661 -0
- package/src/store/agent-registry-accessor.ts +847 -140
- package/src/store/api-key-kdf.ts +104 -0
- package/src/store/backup-crypto.ts +209 -0
- package/src/store/backup-pack.ts +739 -0
- package/src/store/backup-unpack.ts +583 -0
- package/src/store/conduit-sqlite.ts +655 -0
- package/src/store/global-salt.ts +175 -0
- package/src/store/migrate-signaldock-to-conduit.ts +669 -0
- package/src/store/regenerators.ts +243 -0
- package/src/store/restore-conflict-report.ts +317 -0
- package/src/store/restore-json-merge.ts +653 -0
- package/src/store/signaldock-sqlite.ts +431 -254
- package/src/store/sqlite-backup.ts +185 -10
- package/src/store/t310-readiness.ts +119 -0
- package/src/system/backup.ts +2 -62
- package/src/system/runtime.ts +4 -6
- package/src/tasks/__tests__/error-hints.test.ts +256 -0
- package/src/tasks/add.ts +99 -9
- package/src/tasks/complete.ts +4 -1
- package/src/tasks/find.ts +4 -1
- package/src/tasks/labels.ts +4 -1
- package/src/tasks/relates.ts +16 -4
- package/src/tasks/show.ts +4 -1
- package/src/tasks/update.ts +32 -3
- package/src/validation/__tests__/error-hints.test.ts +97 -0
- package/src/validation/engine.ts +16 -1
- package/src/validation/param-utils.ts +10 -7
- package/src/validation/protocols/_shared.ts +14 -6
- package/src/validation/protocols/cant/architecture-decision.cant +80 -0
- package/src/validation/protocols/cant/artifact-publish.cant +95 -0
- package/src/validation/protocols/cant/consensus.cant +74 -0
- package/src/validation/protocols/cant/contribution.cant +82 -0
- package/src/validation/protocols/cant/decomposition.cant +92 -0
- package/src/validation/protocols/cant/implementation.cant +67 -0
- package/src/validation/protocols/cant/provenance.cant +88 -0
- package/src/validation/protocols/cant/release.cant +96 -0
- package/src/validation/protocols/cant/research.cant +66 -0
- package/src/validation/protocols/cant/specification.cant +67 -0
- package/src/validation/protocols/cant/testing.cant +88 -0
- package/src/validation/protocols/cant/validation.cant +65 -0
- package/src/validation/protocols/protocols-markdown/decomposition.md +0 -4
- package/templates/config.template.json +0 -1
- package/templates/global-config.template.json +0 -1
|
@@ -1,310 +1,426 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* SQLite store for signaldock.db —
|
|
2
|
+
* SQLite store for global-tier signaldock.db — canonical agent identity database.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
4
|
+
* Post-T310 (ADR-037), signaldock.db lives at `$XDG_DATA_HOME/cleo/signaldock.db`
|
|
5
|
+
* (resolved via getCleoHome()). It holds cross-project agent identity, capabilities
|
|
6
|
+
* catalog, and cloud-sync tables. Project-local messaging state has moved to
|
|
7
|
+
* conduit.db (managed by conduit-sqlite.ts, T344).
|
|
7
8
|
*
|
|
8
|
-
* This
|
|
9
|
-
*
|
|
10
|
-
* Locally, we create the DB here so that cleo init scaffolds the full
|
|
11
|
-
* .cleo/ directory with all databases ready.
|
|
9
|
+
* GLOBAL-TIER ONLY. This module MUST NOT resolve paths under any project's .cleo/
|
|
10
|
+
* directory. The path guard in getGlobalSignaldockDbPath() enforces this invariant.
|
|
12
11
|
*
|
|
13
|
-
* @task
|
|
12
|
+
* @task T346
|
|
13
|
+
* @epic T310
|
|
14
|
+
* @related ADR-037
|
|
14
15
|
*/
|
|
15
16
|
|
|
16
17
|
import { existsSync, mkdirSync } from 'node:fs';
|
|
17
18
|
import { createRequire } from 'node:module';
|
|
18
|
-
import {
|
|
19
|
+
import { join } from 'node:path';
|
|
19
20
|
import type { DatabaseSync } from 'node:sqlite';
|
|
20
|
-
import {
|
|
21
|
+
import { getCleoHome } from '../paths.js';
|
|
21
22
|
|
|
22
23
|
const _require = createRequire(import.meta.url);
|
|
23
24
|
const { DatabaseSync: DatabaseSyncClass } = _require('node:sqlite') as {
|
|
24
25
|
DatabaseSync: new (...args: ConstructorParameters<typeof DatabaseSync>) => DatabaseSync;
|
|
25
26
|
};
|
|
26
27
|
|
|
27
|
-
/**
|
|
28
|
-
|
|
28
|
+
/**
|
|
29
|
+
* Database file name within the global cleo home directory.
|
|
30
|
+
*
|
|
31
|
+
* @task T346
|
|
32
|
+
* @epic T310
|
|
33
|
+
*/
|
|
34
|
+
export const GLOBAL_SIGNALDOCK_DB_FILENAME = 'signaldock.db';
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Schema version for global signaldock databases.
|
|
38
|
+
*
|
|
39
|
+
* @task T346
|
|
40
|
+
* @epic T310
|
|
41
|
+
*/
|
|
42
|
+
export const GLOBAL_SIGNALDOCK_SCHEMA_VERSION = '2026.4.12';
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* @deprecated Use GLOBAL_SIGNALDOCK_SCHEMA_VERSION. Retained during T310
|
|
46
|
+
* migration window. Will be removed after all callers migrate (T355).
|
|
47
|
+
*/
|
|
48
|
+
export const SIGNALDOCK_SCHEMA_VERSION = GLOBAL_SIGNALDOCK_SCHEMA_VERSION;
|
|
29
49
|
|
|
30
|
-
|
|
31
|
-
|
|
50
|
+
// ---------------------------------------------------------------------------
|
|
51
|
+
// Path helpers
|
|
52
|
+
// ---------------------------------------------------------------------------
|
|
32
53
|
|
|
33
54
|
/**
|
|
34
|
-
*
|
|
55
|
+
* Returns the GLOBAL-tier signaldock.db path. Post-T310, signaldock.db
|
|
56
|
+
* holds canonical agent identity + cloud-sync tables. Project-local
|
|
57
|
+
* messaging state lives in conduit.db (T344).
|
|
58
|
+
*
|
|
59
|
+
* Resolves to `getCleoHome() + '/signaldock.db'`.
|
|
60
|
+
* Guard: asserts the resolved path starts with getCleoHome() (defense in depth,
|
|
61
|
+
* mirrors the ADR-036 pattern used by getNexusDbPath in nexus-sqlite.ts).
|
|
62
|
+
*
|
|
63
|
+
* @task T346
|
|
64
|
+
* @epic T310
|
|
65
|
+
* @why ADR-037 split single signaldock.db into project conduit + global signaldock
|
|
66
|
+
* @throws {Error} If resolved path is not under getCleoHome() — indicates a code
|
|
67
|
+
* path that bypasses canonical path resolution. Fix the caller, do not suppress.
|
|
68
|
+
*/
|
|
69
|
+
export function getGlobalSignaldockDbPath(): string {
|
|
70
|
+
const cleoHome = getCleoHome();
|
|
71
|
+
const dbPath = join(cleoHome, GLOBAL_SIGNALDOCK_DB_FILENAME);
|
|
72
|
+
if (!dbPath.startsWith(cleoHome)) {
|
|
73
|
+
throw new Error(
|
|
74
|
+
`BUG: getGlobalSignaldockDbPath() resolved to "${dbPath}" which is NOT under ` +
|
|
75
|
+
`getCleoHome() ("${cleoHome}"). signaldock.db is global-only per ADR-037. ` +
|
|
76
|
+
`This indicates a code path that bypasses path resolution — ` +
|
|
77
|
+
`fix the caller, do not suppress this error.`,
|
|
78
|
+
);
|
|
79
|
+
}
|
|
80
|
+
return dbPath;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* @deprecated Use getGlobalSignaldockDbPath() directly. Retained during T310
|
|
85
|
+
* migration window so the TypeScript build does not break until all callers
|
|
86
|
+
* are updated (tracked in T355 accessor refactor).
|
|
87
|
+
*
|
|
88
|
+
* When called WITHOUT arguments: returns the global-tier path (forwards to
|
|
89
|
+
* getGlobalSignaldockDbPath()).
|
|
90
|
+
*
|
|
91
|
+
* When called WITH a non-undefined `cwd` argument: throws a migration error
|
|
92
|
+
* immediately. The project-tier path is now owned by conduit-sqlite.ts (T344).
|
|
93
|
+
*
|
|
94
|
+
* @param cwd - Must be undefined. Any other value throws a migration error.
|
|
95
|
+
* @task T346
|
|
96
|
+
* @epic T310
|
|
35
97
|
*/
|
|
36
98
|
export function getSignaldockDbPath(cwd?: string): string {
|
|
37
|
-
|
|
38
|
-
|
|
99
|
+
if (cwd !== undefined) {
|
|
100
|
+
throw new Error(
|
|
101
|
+
'getSignaldockDbPath(cwd) is removed as of T310 (v2026.4.12). ' +
|
|
102
|
+
'signaldock.db is now global-only at $XDG_DATA_HOME/cleo/signaldock.db. ' +
|
|
103
|
+
'Use getGlobalSignaldockDbPath(), or for project-local messaging use ' +
|
|
104
|
+
'getConduitDbPath() from conduit-sqlite.ts (T344).',
|
|
105
|
+
);
|
|
106
|
+
}
|
|
107
|
+
return getGlobalSignaldockDbPath();
|
|
39
108
|
}
|
|
40
109
|
|
|
41
110
|
// ---------------------------------------------------------------------------
|
|
42
|
-
// Embedded migration SQL —
|
|
43
|
-
//
|
|
44
|
-
//
|
|
111
|
+
// Embedded migration SQL — consolidated global-tier schema.
|
|
112
|
+
// Source: spec §2.2, ADR-037.
|
|
113
|
+
// All incremental ALTER TABLE migrations from the pre-T310 schema are
|
|
114
|
+
// collapsed into the single initial migration below. This avoids the
|
|
115
|
+
// multi-step ALTER pattern and ensures fresh installs get the full schema
|
|
116
|
+
// in one idempotent pass.
|
|
45
117
|
// ---------------------------------------------------------------------------
|
|
46
118
|
|
|
47
119
|
/**
|
|
48
|
-
* Ordered migration entries
|
|
120
|
+
* Ordered migration entries for the global signaldock.db.
|
|
49
121
|
* Add new migrations to the END of this array.
|
|
122
|
+
*
|
|
123
|
+
* @task T346
|
|
124
|
+
* @epic T310
|
|
50
125
|
*/
|
|
51
|
-
const
|
|
126
|
+
const GLOBAL_EMBEDDED_MIGRATIONS: Array<{ name: string; sql: string }> = [
|
|
52
127
|
{
|
|
53
|
-
name: '2026-
|
|
54
|
-
sql: `--
|
|
128
|
+
name: '2026-04-12-000000_initial_global_signaldock',
|
|
129
|
+
sql: `-- Global-tier signaldock.db initial migration (T310, ADR-037 §2.2).
|
|
130
|
+
-- Consolidated from pre-T310 incremental migrations. Global identity and
|
|
131
|
+
-- cloud-sync tables only. Project-local messaging state is in conduit.db.
|
|
132
|
+
|
|
133
|
+
-- Cloud-sync: user accounts (zero rows in pure-local mode).
|
|
55
134
|
CREATE TABLE IF NOT EXISTS users (
|
|
56
|
-
id TEXT PRIMARY KEY,
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
135
|
+
id TEXT PRIMARY KEY,
|
|
136
|
+
email TEXT NOT NULL UNIQUE,
|
|
137
|
+
password_hash TEXT NOT NULL,
|
|
138
|
+
name TEXT,
|
|
139
|
+
slug TEXT,
|
|
140
|
+
default_agent_id TEXT,
|
|
141
|
+
username TEXT,
|
|
142
|
+
display_username TEXT,
|
|
143
|
+
email_verified INTEGER NOT NULL DEFAULT 0,
|
|
144
|
+
image TEXT,
|
|
145
|
+
role TEXT NOT NULL DEFAULT 'user',
|
|
146
|
+
banned INTEGER NOT NULL DEFAULT 0,
|
|
147
|
+
ban_reason TEXT,
|
|
148
|
+
ban_expires TEXT,
|
|
149
|
+
two_factor_enabled INTEGER NOT NULL DEFAULT 0,
|
|
150
|
+
metadata TEXT,
|
|
151
|
+
created_at INTEGER NOT NULL,
|
|
152
|
+
updated_at INTEGER NOT NULL
|
|
62
153
|
);
|
|
63
154
|
CREATE UNIQUE INDEX IF NOT EXISTS idx_users_slug ON users(slug);
|
|
64
155
|
|
|
156
|
+
-- Cloud-sync: organization/team records.
|
|
65
157
|
CREATE TABLE IF NOT EXISTS organization (
|
|
66
|
-
id TEXT PRIMARY KEY NOT NULL,
|
|
67
|
-
|
|
158
|
+
id TEXT PRIMARY KEY NOT NULL,
|
|
159
|
+
name TEXT NOT NULL,
|
|
160
|
+
slug TEXT,
|
|
161
|
+
logo TEXT,
|
|
162
|
+
metadata TEXT,
|
|
163
|
+
owner_id TEXT,
|
|
164
|
+
created_at INTEGER NOT NULL DEFAULT (strftime('%s','now')),
|
|
68
165
|
updated_at INTEGER NOT NULL DEFAULT (strftime('%s','now'))
|
|
69
166
|
);
|
|
70
167
|
CREATE UNIQUE INDEX IF NOT EXISTS idx_organization_slug ON organization(slug);
|
|
71
168
|
|
|
169
|
+
-- Global identity: canonical agent registry (cross-project).
|
|
170
|
+
-- api_key_encrypted uses KDF: HMAC-SHA256(machine-key || global-salt, agentId) — ADR-037 §5.
|
|
171
|
+
-- requires_reauth=1 is set during T310 migration for all pre-existing agents.
|
|
72
172
|
CREATE TABLE IF NOT EXISTS agents (
|
|
73
|
-
id TEXT PRIMARY KEY,
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
173
|
+
id TEXT PRIMARY KEY,
|
|
174
|
+
agent_id TEXT NOT NULL UNIQUE,
|
|
175
|
+
name TEXT NOT NULL,
|
|
176
|
+
description TEXT,
|
|
177
|
+
class TEXT NOT NULL DEFAULT 'custom',
|
|
178
|
+
privacy_tier TEXT NOT NULL DEFAULT 'public',
|
|
179
|
+
owner_id TEXT REFERENCES users(id),
|
|
180
|
+
endpoint TEXT,
|
|
181
|
+
webhook_secret TEXT,
|
|
182
|
+
capabilities TEXT NOT NULL DEFAULT '[]',
|
|
183
|
+
skills TEXT NOT NULL DEFAULT '[]',
|
|
184
|
+
avatar TEXT,
|
|
185
|
+
messages_sent INTEGER NOT NULL DEFAULT 0,
|
|
186
|
+
messages_received INTEGER NOT NULL DEFAULT 0,
|
|
187
|
+
conversation_count INTEGER NOT NULL DEFAULT 0,
|
|
188
|
+
friend_count INTEGER NOT NULL DEFAULT 0,
|
|
189
|
+
status TEXT NOT NULL DEFAULT 'online',
|
|
190
|
+
last_seen INTEGER,
|
|
191
|
+
payment_config TEXT,
|
|
192
|
+
api_key_hash TEXT,
|
|
81
193
|
organization_id TEXT REFERENCES organization(id) ON DELETE SET NULL,
|
|
82
|
-
created_at INTEGER NOT NULL,
|
|
194
|
+
created_at INTEGER NOT NULL,
|
|
195
|
+
updated_at INTEGER NOT NULL,
|
|
196
|
+
transport_type TEXT NOT NULL DEFAULT 'http',
|
|
197
|
+
api_key_encrypted TEXT,
|
|
198
|
+
api_base_url TEXT NOT NULL DEFAULT 'https://api.signaldock.io',
|
|
199
|
+
classification TEXT,
|
|
200
|
+
transport_config TEXT NOT NULL DEFAULT '{}',
|
|
201
|
+
is_active INTEGER NOT NULL DEFAULT 1,
|
|
202
|
+
last_used_at INTEGER,
|
|
203
|
+
requires_reauth INTEGER NOT NULL DEFAULT 0
|
|
83
204
|
);
|
|
84
205
|
CREATE UNIQUE INDEX IF NOT EXISTS agents_agent_id_idx ON agents(agent_id);
|
|
85
206
|
CREATE INDEX IF NOT EXISTS agents_owner_idx ON agents(owner_id);
|
|
86
207
|
CREATE INDEX IF NOT EXISTS agents_class_idx ON agents(class);
|
|
87
208
|
CREATE INDEX IF NOT EXISTS agents_privacy_idx ON agents(privacy_tier);
|
|
88
209
|
CREATE INDEX IF NOT EXISTS agents_org_idx ON agents(organization_id);
|
|
210
|
+
CREATE INDEX IF NOT EXISTS idx_agents_transport_type ON agents(transport_type);
|
|
211
|
+
CREATE INDEX IF NOT EXISTS idx_agents_is_active ON agents(is_active);
|
|
212
|
+
CREATE INDEX IF NOT EXISTS idx_agents_last_used ON agents(last_used_at);
|
|
213
|
+
CREATE INDEX IF NOT EXISTS idx_agents_reauth ON agents(requires_reauth) WHERE requires_reauth = 1;
|
|
89
214
|
|
|
90
|
-
|
|
91
|
-
id TEXT PRIMARY KEY, participants TEXT NOT NULL,
|
|
92
|
-
visibility TEXT NOT NULL DEFAULT 'private', message_count INTEGER NOT NULL DEFAULT 0,
|
|
93
|
-
last_message_at INTEGER, created_at INTEGER NOT NULL, updated_at INTEGER NOT NULL
|
|
94
|
-
);
|
|
95
|
-
|
|
96
|
-
CREATE TABLE IF NOT EXISTS messages (
|
|
97
|
-
id TEXT PRIMARY KEY, conversation_id TEXT NOT NULL REFERENCES conversations(id),
|
|
98
|
-
from_agent_id TEXT NOT NULL, to_agent_id TEXT NOT NULL, content TEXT NOT NULL,
|
|
99
|
-
content_type TEXT NOT NULL DEFAULT 'text', status TEXT NOT NULL DEFAULT 'pending',
|
|
100
|
-
attachments TEXT NOT NULL DEFAULT '[]', group_id TEXT, metadata TEXT DEFAULT '{}',
|
|
101
|
-
reply_to TEXT, created_at INTEGER NOT NULL, delivered_at INTEGER, read_at INTEGER
|
|
102
|
-
);
|
|
103
|
-
CREATE INDEX IF NOT EXISTS messages_conversation_idx ON messages(conversation_id);
|
|
104
|
-
CREATE INDEX IF NOT EXISTS messages_from_agent_idx ON messages(from_agent_id);
|
|
105
|
-
CREATE INDEX IF NOT EXISTS messages_to_agent_idx ON messages(to_agent_id);
|
|
106
|
-
CREATE INDEX IF NOT EXISTS messages_created_at_idx ON messages(created_at);
|
|
107
|
-
CREATE INDEX IF NOT EXISTS idx_messages_group_id ON messages(group_id) WHERE group_id IS NOT NULL;
|
|
108
|
-
CREATE INDEX IF NOT EXISTS idx_messages_reply_to ON messages(reply_to) WHERE reply_to IS NOT NULL;
|
|
109
|
-
|
|
215
|
+
-- Cloud-sync: one-time agent claim tokens (api.signaldock.io provisioning).
|
|
110
216
|
CREATE TABLE IF NOT EXISTS claim_codes (
|
|
111
|
-
id TEXT PRIMARY KEY,
|
|
112
|
-
|
|
113
|
-
|
|
217
|
+
id TEXT PRIMARY KEY,
|
|
218
|
+
agent_id TEXT NOT NULL REFERENCES agents(id),
|
|
219
|
+
code TEXT NOT NULL UNIQUE,
|
|
220
|
+
expires_at INTEGER NOT NULL,
|
|
221
|
+
used_at INTEGER,
|
|
222
|
+
used_by TEXT REFERENCES users(id),
|
|
223
|
+
created_at INTEGER NOT NULL
|
|
114
224
|
);
|
|
115
225
|
CREATE UNIQUE INDEX IF NOT EXISTS claim_codes_code_idx ON claim_codes(code);
|
|
116
226
|
CREATE INDEX IF NOT EXISTS claim_codes_agent_idx ON claim_codes(agent_id);
|
|
117
227
|
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
CREATE TABLE IF NOT EXISTS delivery_jobs (
|
|
127
|
-
id TEXT PRIMARY KEY, message_id TEXT NOT NULL, payload TEXT NOT NULL,
|
|
128
|
-
status TEXT NOT NULL DEFAULT 'pending', attempts INTEGER NOT NULL DEFAULT 0,
|
|
129
|
-
max_attempts INTEGER NOT NULL DEFAULT 6, next_attempt_at INTEGER NOT NULL,
|
|
130
|
-
last_error TEXT, created_at INTEGER NOT NULL, updated_at INTEGER NOT NULL
|
|
131
|
-
);
|
|
132
|
-
CREATE INDEX IF NOT EXISTS idx_delivery_jobs_status ON delivery_jobs(status, next_attempt_at);
|
|
133
|
-
|
|
134
|
-
CREATE TABLE IF NOT EXISTS dead_letters (
|
|
135
|
-
id TEXT PRIMARY KEY, message_id TEXT NOT NULL, job_id TEXT NOT NULL,
|
|
136
|
-
reason TEXT NOT NULL, attempts INTEGER NOT NULL, created_at INTEGER NOT NULL
|
|
137
|
-
);
|
|
138
|
-
CREATE INDEX IF NOT EXISTS idx_dead_letters_message ON dead_letters(message_id);
|
|
139
|
-
|
|
140
|
-
CREATE VIRTUAL TABLE IF NOT EXISTS messages_fts USING fts5(content, from_agent_id, content='messages', content_rowid='rowid');
|
|
141
|
-
INSERT INTO messages_fts(messages_fts) VALUES('rebuild');
|
|
142
|
-
CREATE TRIGGER IF NOT EXISTS messages_ai AFTER INSERT ON messages BEGIN
|
|
143
|
-
INSERT INTO messages_fts(rowid, content, from_agent_id) VALUES (new.rowid, new.content, new.from_agent_id);
|
|
144
|
-
END;
|
|
145
|
-
CREATE TRIGGER IF NOT EXISTS messages_ad AFTER DELETE ON messages BEGIN
|
|
146
|
-
INSERT INTO messages_fts(messages_fts, rowid, content, from_agent_id) VALUES('delete', old.rowid, old.content, old.from_agent_id);
|
|
147
|
-
END;
|
|
148
|
-
CREATE TRIGGER IF NOT EXISTS messages_au AFTER UPDATE ON messages BEGIN
|
|
149
|
-
INSERT INTO messages_fts(messages_fts, rowid, content, from_agent_id) VALUES('delete', old.rowid, old.content, old.from_agent_id);
|
|
150
|
-
INSERT INTO messages_fts(rowid, content, from_agent_id) VALUES (new.rowid, new.content, new.from_agent_id);
|
|
151
|
-
END;
|
|
152
|
-
|
|
153
|
-
CREATE TABLE IF NOT EXISTS message_pins (
|
|
154
|
-
id TEXT PRIMARY KEY, message_id TEXT NOT NULL, conversation_id TEXT NOT NULL,
|
|
155
|
-
pinned_by TEXT NOT NULL, note TEXT, created_at INTEGER NOT NULL, UNIQUE(message_id, pinned_by)
|
|
156
|
-
);
|
|
157
|
-
CREATE INDEX IF NOT EXISTS idx_pins_conversation ON message_pins(conversation_id);
|
|
158
|
-
CREATE INDEX IF NOT EXISTS idx_pins_agent ON message_pins(pinned_by);
|
|
159
|
-
|
|
160
|
-
CREATE TABLE IF NOT EXISTS attachments (
|
|
161
|
-
slug TEXT PRIMARY KEY, conversation_id TEXT NOT NULL, from_agent_id TEXT NOT NULL,
|
|
162
|
-
content BLOB NOT NULL, original_size INTEGER NOT NULL, compressed_size INTEGER NOT NULL,
|
|
163
|
-
content_hash TEXT NOT NULL, format TEXT NOT NULL DEFAULT 'text', title TEXT,
|
|
164
|
-
tokens INTEGER NOT NULL DEFAULT 0, expires_at INTEGER NOT NULL DEFAULT 0,
|
|
165
|
-
storage_key TEXT, mode TEXT NOT NULL DEFAULT 'draft',
|
|
166
|
-
version_count INTEGER NOT NULL DEFAULT 1, current_version INTEGER NOT NULL DEFAULT 1,
|
|
228
|
+
-- Identity catalog: pre-seeded capability slugs (19 entries).
|
|
229
|
+
CREATE TABLE IF NOT EXISTS capabilities (
|
|
230
|
+
id TEXT PRIMARY KEY,
|
|
231
|
+
slug TEXT NOT NULL UNIQUE,
|
|
232
|
+
name TEXT NOT NULL,
|
|
233
|
+
description TEXT NOT NULL,
|
|
234
|
+
category TEXT NOT NULL,
|
|
167
235
|
created_at INTEGER NOT NULL
|
|
168
236
|
);
|
|
169
|
-
CREATE INDEX IF NOT EXISTS attachments_conversation_idx ON attachments(conversation_id);
|
|
170
|
-
CREATE INDEX IF NOT EXISTS attachments_agent_idx ON attachments(from_agent_id);
|
|
171
237
|
|
|
172
|
-
|
|
173
|
-
id TEXT PRIMARY KEY, slug TEXT NOT NULL UNIQUE, name TEXT NOT NULL,
|
|
174
|
-
description TEXT NOT NULL, category TEXT NOT NULL, created_at INTEGER NOT NULL
|
|
175
|
-
);
|
|
238
|
+
-- Identity catalog: pre-seeded skill slugs (36 entries).
|
|
176
239
|
CREATE TABLE IF NOT EXISTS skills (
|
|
177
|
-
id TEXT PRIMARY KEY,
|
|
178
|
-
|
|
240
|
+
id TEXT PRIMARY KEY,
|
|
241
|
+
slug TEXT NOT NULL UNIQUE,
|
|
242
|
+
name TEXT NOT NULL,
|
|
243
|
+
description TEXT NOT NULL,
|
|
244
|
+
category TEXT NOT NULL,
|
|
245
|
+
created_at INTEGER NOT NULL
|
|
179
246
|
);
|
|
247
|
+
|
|
248
|
+
-- Junction: agent <-> capability catalog bindings.
|
|
180
249
|
CREATE TABLE IF NOT EXISTS agent_capabilities (
|
|
181
|
-
agent_id TEXT NOT NULL REFERENCES agents(id),
|
|
250
|
+
agent_id TEXT NOT NULL REFERENCES agents(id),
|
|
251
|
+
capability_id TEXT NOT NULL REFERENCES capabilities(id),
|
|
182
252
|
PRIMARY KEY (agent_id, capability_id)
|
|
183
253
|
);
|
|
254
|
+
|
|
255
|
+
-- Junction: agent <-> skill catalog bindings.
|
|
184
256
|
CREATE TABLE IF NOT EXISTS agent_skills (
|
|
185
|
-
agent_id TEXT NOT NULL REFERENCES agents(id),
|
|
257
|
+
agent_id TEXT NOT NULL REFERENCES agents(id),
|
|
258
|
+
skill_id TEXT NOT NULL REFERENCES skills(id),
|
|
186
259
|
PRIMARY KEY (agent_id, skill_id)
|
|
187
260
|
);
|
|
188
261
|
|
|
262
|
+
-- Live transport connection tracking (heartbeat state).
|
|
263
|
+
CREATE TABLE IF NOT EXISTS agent_connections (
|
|
264
|
+
id TEXT PRIMARY KEY NOT NULL,
|
|
265
|
+
agent_id TEXT NOT NULL,
|
|
266
|
+
transport_type TEXT NOT NULL DEFAULT 'http',
|
|
267
|
+
connection_id TEXT,
|
|
268
|
+
connected_at BIGINT NOT NULL,
|
|
269
|
+
last_heartbeat BIGINT NOT NULL,
|
|
270
|
+
connection_metadata TEXT,
|
|
271
|
+
created_at BIGINT NOT NULL,
|
|
272
|
+
FOREIGN KEY (agent_id) REFERENCES agents(agent_id) ON DELETE CASCADE,
|
|
273
|
+
UNIQUE(agent_id, connection_id)
|
|
274
|
+
);
|
|
275
|
+
CREATE INDEX IF NOT EXISTS idx_agent_connections_agent ON agent_connections(agent_id);
|
|
276
|
+
CREATE INDEX IF NOT EXISTS idx_agent_connections_transport ON agent_connections(transport_type);
|
|
277
|
+
CREATE INDEX IF NOT EXISTS idx_agent_connections_heartbeat ON agent_connections(last_heartbeat);
|
|
278
|
+
|
|
279
|
+
-- Cloud-sync: OAuth/provider accounts.
|
|
189
280
|
CREATE TABLE IF NOT EXISTS accounts (
|
|
190
|
-
id TEXT PRIMARY KEY NOT NULL,
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
281
|
+
id TEXT PRIMARY KEY NOT NULL,
|
|
282
|
+
user_id TEXT NOT NULL REFERENCES users(id) ON DELETE CASCADE,
|
|
283
|
+
account_id TEXT NOT NULL,
|
|
284
|
+
provider_id TEXT NOT NULL,
|
|
285
|
+
access_token TEXT,
|
|
286
|
+
refresh_token TEXT,
|
|
287
|
+
id_token TEXT,
|
|
288
|
+
access_token_expires_at TEXT,
|
|
289
|
+
refresh_token_expires_at TEXT,
|
|
290
|
+
scope TEXT,
|
|
291
|
+
password TEXT,
|
|
292
|
+
created_at TEXT NOT NULL,
|
|
293
|
+
updated_at TEXT NOT NULL
|
|
194
294
|
);
|
|
195
295
|
CREATE INDEX IF NOT EXISTS idx_accounts_user_id ON accounts(user_id);
|
|
196
296
|
CREATE UNIQUE INDEX IF NOT EXISTS idx_accounts_provider ON accounts(provider_id, account_id);
|
|
197
297
|
|
|
298
|
+
-- Cloud-sync: authenticated sessions.
|
|
198
299
|
CREATE TABLE IF NOT EXISTS sessions (
|
|
199
|
-
id TEXT PRIMARY KEY NOT NULL,
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
300
|
+
id TEXT PRIMARY KEY NOT NULL,
|
|
301
|
+
user_id TEXT NOT NULL REFERENCES users(id) ON DELETE CASCADE,
|
|
302
|
+
token TEXT NOT NULL UNIQUE,
|
|
303
|
+
ip_address TEXT,
|
|
304
|
+
user_agent TEXT,
|
|
305
|
+
expires_at TEXT NOT NULL,
|
|
306
|
+
active_organization_id TEXT,
|
|
307
|
+
impersonated_by TEXT,
|
|
308
|
+
active INTEGER NOT NULL DEFAULT 1,
|
|
309
|
+
created_at TEXT NOT NULL,
|
|
310
|
+
updated_at TEXT NOT NULL
|
|
203
311
|
);
|
|
204
312
|
CREATE UNIQUE INDEX IF NOT EXISTS idx_sessions_token ON sessions(token);
|
|
205
313
|
CREATE INDEX IF NOT EXISTS idx_sessions_user_id ON sessions(user_id);
|
|
206
314
|
|
|
315
|
+
-- Cloud-sync: email/2FA verification tokens.
|
|
207
316
|
CREATE TABLE IF NOT EXISTS verifications (
|
|
208
|
-
id TEXT PRIMARY KEY NOT NULL,
|
|
209
|
-
|
|
317
|
+
id TEXT PRIMARY KEY NOT NULL,
|
|
318
|
+
identifier TEXT NOT NULL,
|
|
319
|
+
value TEXT NOT NULL,
|
|
320
|
+
expires_at TEXT NOT NULL,
|
|
321
|
+
created_at TEXT NOT NULL,
|
|
322
|
+
updated_at TEXT NOT NULL
|
|
210
323
|
);
|
|
211
324
|
CREATE INDEX IF NOT EXISTS idx_verifications_identifier ON verifications(identifier);
|
|
212
325
|
|
|
326
|
+
-- Org-scoped agent API keys (cloud use; zero rows locally).
|
|
213
327
|
CREATE TABLE IF NOT EXISTS org_agent_keys (
|
|
214
|
-
id TEXT PRIMARY KEY NOT NULL,
|
|
328
|
+
id TEXT PRIMARY KEY NOT NULL,
|
|
329
|
+
organization_id TEXT NOT NULL REFERENCES organization(id) ON DELETE CASCADE,
|
|
215
330
|
agent_id TEXT NOT NULL REFERENCES agents(id) ON DELETE CASCADE,
|
|
216
|
-
created_by TEXT NOT NULL,
|
|
331
|
+
created_by TEXT NOT NULL,
|
|
332
|
+
created_at INTEGER NOT NULL
|
|
217
333
|
);
|
|
218
334
|
CREATE INDEX IF NOT EXISTS org_agent_keys_org_idx ON org_agent_keys(organization_id);
|
|
219
|
-
CREATE INDEX IF NOT EXISTS org_agent_keys_agent_idx ON org_agent_keys(agent_id)
|
|
220
|
-
|
|
221
|
-
CREATE TABLE IF NOT EXISTS attachment_versions (
|
|
222
|
-
id TEXT PRIMARY KEY, slug TEXT NOT NULL REFERENCES attachments(slug) ON DELETE CASCADE,
|
|
223
|
-
version_number INTEGER NOT NULL, author_agent_id TEXT NOT NULL,
|
|
224
|
-
change_type TEXT NOT NULL DEFAULT 'patch', patch_text TEXT, storage_key TEXT NOT NULL,
|
|
225
|
-
content_hash TEXT NOT NULL, original_size INTEGER NOT NULL, compressed_size INTEGER NOT NULL,
|
|
226
|
-
tokens INTEGER NOT NULL, change_summary TEXT, sections_modified TEXT NOT NULL DEFAULT '[]',
|
|
227
|
-
tokens_added INTEGER NOT NULL DEFAULT 0, tokens_removed INTEGER NOT NULL DEFAULT 0,
|
|
228
|
-
created_at INTEGER NOT NULL, UNIQUE(slug, version_number)
|
|
229
|
-
);
|
|
230
|
-
CREATE INDEX IF NOT EXISTS idx_attachment_versions_slug ON attachment_versions(slug);
|
|
231
|
-
CREATE INDEX IF NOT EXISTS idx_attachment_versions_author ON attachment_versions(author_agent_id);
|
|
232
|
-
|
|
233
|
-
CREATE TABLE IF NOT EXISTS attachment_approvals (
|
|
234
|
-
id TEXT PRIMARY KEY, slug TEXT NOT NULL REFERENCES attachments(slug) ON DELETE CASCADE,
|
|
235
|
-
reviewer_agent_id TEXT NOT NULL, status TEXT NOT NULL DEFAULT 'pending', comment TEXT,
|
|
236
|
-
version_reviewed INTEGER NOT NULL, created_at INTEGER NOT NULL, updated_at INTEGER NOT NULL,
|
|
237
|
-
UNIQUE(slug, reviewer_agent_id)
|
|
238
|
-
);
|
|
239
|
-
CREATE INDEX IF NOT EXISTS idx_attachment_approvals_slug ON attachment_approvals(slug);
|
|
240
|
-
|
|
241
|
-
CREATE TABLE IF NOT EXISTS attachment_contributors (
|
|
242
|
-
slug TEXT NOT NULL REFERENCES attachments(slug) ON DELETE CASCADE,
|
|
243
|
-
agent_id TEXT NOT NULL, version_count INTEGER NOT NULL DEFAULT 0,
|
|
244
|
-
total_tokens_added INTEGER NOT NULL DEFAULT 0, total_tokens_removed INTEGER NOT NULL DEFAULT 0,
|
|
245
|
-
first_contribution_at INTEGER NOT NULL, last_contribution_at INTEGER NOT NULL,
|
|
246
|
-
PRIMARY KEY (slug, agent_id)
|
|
247
|
-
);`,
|
|
248
|
-
},
|
|
249
|
-
{
|
|
250
|
-
name: '2026-03-30-000001_agent_connections',
|
|
251
|
-
sql: `-- Add transport_type to agents table for connection mode classification.
|
|
252
|
-
ALTER TABLE agents ADD COLUMN transport_type TEXT NOT NULL DEFAULT 'http';
|
|
253
|
-
CREATE INDEX idx_agents_transport_type ON agents(transport_type);
|
|
254
|
-
|
|
255
|
-
CREATE TABLE agent_connections (
|
|
256
|
-
id TEXT PRIMARY KEY NOT NULL, agent_id TEXT NOT NULL,
|
|
257
|
-
transport_type TEXT NOT NULL DEFAULT 'http', connection_id TEXT,
|
|
258
|
-
connected_at BIGINT NOT NULL, last_heartbeat BIGINT NOT NULL,
|
|
259
|
-
connection_metadata TEXT, created_at BIGINT NOT NULL,
|
|
260
|
-
FOREIGN KEY (agent_id) REFERENCES agents(agent_id) ON DELETE CASCADE,
|
|
261
|
-
UNIQUE(agent_id, connection_id)
|
|
262
|
-
);
|
|
263
|
-
CREATE INDEX idx_agent_connections_agent ON agent_connections(agent_id);
|
|
264
|
-
CREATE INDEX idx_agent_connections_transport ON agent_connections(transport_type);
|
|
265
|
-
CREATE INDEX idx_agent_connections_heartbeat ON agent_connections(last_heartbeat);`,
|
|
266
|
-
},
|
|
267
|
-
{
|
|
268
|
-
name: '2026-03-31-000001_agent_credentials',
|
|
269
|
-
sql: `-- Move agent credentials into signaldock.db agents table (T234 clean-cut).
|
|
270
|
-
ALTER TABLE agents ADD COLUMN api_key_encrypted TEXT;
|
|
271
|
-
ALTER TABLE agents ADD COLUMN api_base_url TEXT NOT NULL DEFAULT 'https://api.signaldock.io';
|
|
272
|
-
ALTER TABLE agents ADD COLUMN classification TEXT;
|
|
273
|
-
ALTER TABLE agents ADD COLUMN transport_config TEXT NOT NULL DEFAULT '{}';
|
|
274
|
-
ALTER TABLE agents ADD COLUMN is_active INTEGER NOT NULL DEFAULT 1;
|
|
275
|
-
ALTER TABLE agents ADD COLUMN last_used_at INTEGER;
|
|
276
|
-
CREATE INDEX IF NOT EXISTS idx_agents_is_active ON agents(is_active);
|
|
277
|
-
CREATE INDEX IF NOT EXISTS idx_agents_last_used ON agents(last_used_at);`,
|
|
335
|
+
CREATE INDEX IF NOT EXISTS org_agent_keys_agent_idx ON org_agent_keys(agent_id);`,
|
|
278
336
|
},
|
|
279
337
|
];
|
|
280
338
|
|
|
339
|
+
// ---------------------------------------------------------------------------
|
|
340
|
+
// Database lifecycle
|
|
341
|
+
// ---------------------------------------------------------------------------
|
|
342
|
+
|
|
343
|
+
/** Singleton native DatabaseSync handle for the current process. */
|
|
344
|
+
let _globalSignaldockNativeDb: DatabaseSync | null = null;
|
|
345
|
+
|
|
281
346
|
/**
|
|
282
|
-
*
|
|
347
|
+
* Apply the global signaldock schema to an already-open database.
|
|
348
|
+
* Idempotent — uses `CREATE TABLE IF NOT EXISTS` and migration tracking.
|
|
283
349
|
*
|
|
284
|
-
*
|
|
285
|
-
*
|
|
350
|
+
* @param db - An open DatabaseSync instance at the global path
|
|
351
|
+
* @task T346
|
|
352
|
+
* @epic T310
|
|
353
|
+
*/
|
|
354
|
+
function applyGlobalSignaldockSchema(db: DatabaseSync): void {
|
|
355
|
+
// Ensure migration tracking tables exist
|
|
356
|
+
db.exec(`
|
|
357
|
+
CREATE TABLE IF NOT EXISTS _signaldock_meta (
|
|
358
|
+
key TEXT PRIMARY KEY,
|
|
359
|
+
value TEXT NOT NULL,
|
|
360
|
+
updated_at INTEGER NOT NULL DEFAULT (strftime('%s', 'now'))
|
|
361
|
+
)
|
|
362
|
+
`);
|
|
363
|
+
db.exec(`
|
|
364
|
+
CREATE TABLE IF NOT EXISTS _signaldock_migrations (
|
|
365
|
+
name TEXT PRIMARY KEY,
|
|
366
|
+
applied_at INTEGER NOT NULL DEFAULT (strftime('%s', 'now'))
|
|
367
|
+
)
|
|
368
|
+
`);
|
|
369
|
+
|
|
370
|
+
// Apply embedded migrations (skips already-applied ones)
|
|
371
|
+
for (const migration of GLOBAL_EMBEDDED_MIGRATIONS) {
|
|
372
|
+
const applied = db
|
|
373
|
+
.prepare('SELECT name FROM _signaldock_migrations WHERE name = ?')
|
|
374
|
+
.get(migration.name) as { name: string } | undefined;
|
|
375
|
+
if (applied) continue;
|
|
376
|
+
|
|
377
|
+
db.exec('BEGIN TRANSACTION');
|
|
378
|
+
try {
|
|
379
|
+
db.exec(migration.sql);
|
|
380
|
+
db.prepare('INSERT INTO _signaldock_migrations (name) VALUES (?)').run(migration.name);
|
|
381
|
+
db.exec('COMMIT');
|
|
382
|
+
} catch (err) {
|
|
383
|
+
db.exec('ROLLBACK');
|
|
384
|
+
throw err;
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
// Record schema version
|
|
389
|
+
db.exec(`
|
|
390
|
+
INSERT OR REPLACE INTO _signaldock_meta (key, value, updated_at)
|
|
391
|
+
VALUES ('schema_version', '${GLOBAL_SIGNALDOCK_SCHEMA_VERSION}', strftime('%s', 'now'))
|
|
392
|
+
`);
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
/**
|
|
396
|
+
* Ensure global signaldock.db exists with the full global schema applied.
|
|
397
|
+
* Creates the global cleo home directory if it doesn't exist.
|
|
398
|
+
* Idempotent — safe to call multiple times.
|
|
286
399
|
*
|
|
287
|
-
* @returns Object with action ('created' | 'exists') and the database path
|
|
400
|
+
* @returns Object with action ('created' | 'exists') and the database path
|
|
401
|
+
* @task T346
|
|
402
|
+
* @epic T310
|
|
288
403
|
*/
|
|
289
|
-
export async function
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
404
|
+
export async function ensureGlobalSignaldockDb(): Promise<{
|
|
405
|
+
action: 'created' | 'exists';
|
|
406
|
+
path: string;
|
|
407
|
+
}> {
|
|
408
|
+
const dbPath = getGlobalSignaldockDbPath();
|
|
293
409
|
const alreadyExists = existsSync(dbPath);
|
|
294
410
|
|
|
295
|
-
// Ensure
|
|
296
|
-
|
|
411
|
+
// Ensure global cleo home directory exists
|
|
412
|
+
const cleoHome = getCleoHome();
|
|
413
|
+
if (!existsSync(cleoHome)) {
|
|
414
|
+
mkdirSync(cleoHome, { recursive: true });
|
|
415
|
+
}
|
|
297
416
|
|
|
298
|
-
// Open or create the database
|
|
299
417
|
const db = new DatabaseSyncClass(dbPath);
|
|
300
|
-
|
|
301
418
|
try {
|
|
302
|
-
// Set pragmas for optimal performance
|
|
303
419
|
db.exec('PRAGMA journal_mode = WAL');
|
|
304
420
|
db.exec('PRAGMA busy_timeout = 5000');
|
|
305
421
|
db.exec('PRAGMA synchronous = NORMAL');
|
|
306
422
|
db.exec('PRAGMA foreign_keys = ON');
|
|
307
|
-
db.exec('PRAGMA cache_size = -64000'); //
|
|
423
|
+
db.exec('PRAGMA cache_size = -64000'); // 64 MB
|
|
308
424
|
|
|
309
425
|
// Check if schema already applied (agents table as sentinel)
|
|
310
426
|
const hasSchema = (() => {
|
|
@@ -318,63 +434,59 @@ export async function ensureSignaldockDb(
|
|
|
318
434
|
}
|
|
319
435
|
})();
|
|
320
436
|
|
|
321
|
-
|
|
322
|
-
db.exec(`
|
|
323
|
-
CREATE TABLE IF NOT EXISTS _signaldock_meta (
|
|
324
|
-
key TEXT PRIMARY KEY,
|
|
325
|
-
value TEXT NOT NULL,
|
|
326
|
-
updated_at INTEGER NOT NULL DEFAULT (strftime('%s', 'now'))
|
|
327
|
-
)
|
|
328
|
-
`);
|
|
329
|
-
db.exec(`
|
|
330
|
-
CREATE TABLE IF NOT EXISTS _signaldock_migrations (
|
|
331
|
-
name TEXT PRIMARY KEY,
|
|
332
|
-
applied_at INTEGER NOT NULL DEFAULT (strftime('%s', 'now'))
|
|
333
|
-
)
|
|
334
|
-
`);
|
|
437
|
+
applyGlobalSignaldockSchema(db);
|
|
335
438
|
|
|
336
|
-
//
|
|
337
|
-
|
|
338
|
-
// Skip already-applied migrations
|
|
339
|
-
const applied = db
|
|
340
|
-
.prepare('SELECT name FROM _signaldock_migrations WHERE name = ?')
|
|
341
|
-
.get(migration.name) as { name: string } | undefined;
|
|
342
|
-
if (applied) continue;
|
|
343
|
-
|
|
344
|
-
db.exec('BEGIN TRANSACTION');
|
|
345
|
-
try {
|
|
346
|
-
db.exec(migration.sql);
|
|
347
|
-
db.prepare('INSERT INTO _signaldock_migrations (name) VALUES (?)').run(migration.name);
|
|
348
|
-
db.exec('COMMIT');
|
|
349
|
-
} catch (err) {
|
|
350
|
-
db.exec('ROLLBACK');
|
|
351
|
-
throw err;
|
|
352
|
-
}
|
|
353
|
-
}
|
|
354
|
-
|
|
355
|
-
// Record schema version
|
|
356
|
-
db.exec(`
|
|
357
|
-
INSERT OR REPLACE INTO _signaldock_meta (key, value, updated_at)
|
|
358
|
-
VALUES ('schema_version', '${SIGNALDOCK_SCHEMA_VERSION}', strftime('%s', 'now'))
|
|
359
|
-
`);
|
|
439
|
+
// Store native handle for backup integration (getGlobalSignaldockNativeDb)
|
|
440
|
+
_globalSignaldockNativeDb = db;
|
|
360
441
|
|
|
361
442
|
return {
|
|
362
443
|
action: alreadyExists && hasSchema ? 'exists' : 'created',
|
|
363
444
|
path: dbPath,
|
|
364
445
|
};
|
|
365
|
-
}
|
|
446
|
+
} catch (err) {
|
|
366
447
|
db.close();
|
|
448
|
+
_globalSignaldockNativeDb = null;
|
|
449
|
+
throw err;
|
|
367
450
|
}
|
|
451
|
+
// NOTE: We intentionally do NOT close `db` here — the native handle is
|
|
452
|
+
// retained as _globalSignaldockNativeDb for backup integration. Callers
|
|
453
|
+
// that need a short-lived open/close pattern should open the DB themselves.
|
|
368
454
|
}
|
|
369
455
|
|
|
370
456
|
/**
|
|
371
|
-
*
|
|
457
|
+
* @deprecated Use ensureGlobalSignaldockDb(). Retained during T310 migration
|
|
458
|
+
* window for callers in init.ts and agent-registry-accessor.ts.
|
|
372
459
|
*
|
|
373
|
-
*
|
|
460
|
+
* When called WITHOUT arguments: forwards to ensureGlobalSignaldockDb().
|
|
461
|
+
* When called WITH a non-undefined `cwd` argument: throws a migration error.
|
|
374
462
|
*
|
|
375
|
-
* @
|
|
463
|
+
* @param cwd - Must be undefined. Any other value throws a migration error.
|
|
464
|
+
* @task T346
|
|
465
|
+
* @epic T310
|
|
376
466
|
*/
|
|
377
|
-
export async function
|
|
467
|
+
export async function ensureSignaldockDb(
|
|
468
|
+
cwd?: string,
|
|
469
|
+
): Promise<{ action: 'created' | 'exists'; path: string }> {
|
|
470
|
+
if (cwd !== undefined) {
|
|
471
|
+
throw new Error(
|
|
472
|
+
'ensureSignaldockDb(cwd) is removed as of T310 (v2026.4.12). ' +
|
|
473
|
+
'signaldock.db is now global-only. ' +
|
|
474
|
+
'Use ensureGlobalSignaldockDb() for global identity, or ' +
|
|
475
|
+
'ensureConduitDb(cwd) from conduit-sqlite.ts for project messaging (T344).',
|
|
476
|
+
);
|
|
477
|
+
}
|
|
478
|
+
return ensureGlobalSignaldockDb();
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
/**
|
|
482
|
+
* Check global signaldock.db health: table count, WAL mode, schema version.
|
|
483
|
+
* Used by `cleo doctor` to verify global signaldock.db integrity.
|
|
484
|
+
*
|
|
485
|
+
* @returns Health report object, or object with exists=false if the DB does not exist.
|
|
486
|
+
* @task T346
|
|
487
|
+
* @epic T310
|
|
488
|
+
*/
|
|
489
|
+
export async function checkGlobalSignaldockDbHealth(): Promise<{
|
|
378
490
|
exists: boolean;
|
|
379
491
|
path: string;
|
|
380
492
|
tableCount: number;
|
|
@@ -382,7 +494,7 @@ export async function checkSignaldockDbHealth(cwd?: string): Promise<{
|
|
|
382
494
|
schemaVersion: string | null;
|
|
383
495
|
foreignKeysEnabled: boolean;
|
|
384
496
|
} | null> {
|
|
385
|
-
const dbPath =
|
|
497
|
+
const dbPath = getGlobalSignaldockDbPath();
|
|
386
498
|
if (!existsSync(dbPath)) {
|
|
387
499
|
return {
|
|
388
500
|
exists: false,
|
|
@@ -412,7 +524,7 @@ export async function checkSignaldockDbHealth(cwd?: string): Promise<{
|
|
|
412
524
|
.get() as { value: string } | undefined;
|
|
413
525
|
schemaVersion = meta?.value ?? null;
|
|
414
526
|
} catch {
|
|
415
|
-
// Meta table may not exist
|
|
527
|
+
// Meta table may not exist on very old or partially-initialized DBs
|
|
416
528
|
}
|
|
417
529
|
|
|
418
530
|
return {
|
|
@@ -427,3 +539,68 @@ export async function checkSignaldockDbHealth(cwd?: string): Promise<{
|
|
|
427
539
|
db.close();
|
|
428
540
|
}
|
|
429
541
|
}
|
|
542
|
+
|
|
543
|
+
/**
|
|
544
|
+
* @deprecated Use checkGlobalSignaldockDbHealth(). Retained during T310 migration
|
|
545
|
+
* window for callers in `cleo doctor` and other diagnostics.
|
|
546
|
+
*
|
|
547
|
+
* When called WITHOUT arguments: forwards to checkGlobalSignaldockDbHealth().
|
|
548
|
+
* When called WITH a non-undefined `cwd` argument: throws a migration error.
|
|
549
|
+
*
|
|
550
|
+
* @param cwd - Must be undefined. Any other value throws a migration error.
|
|
551
|
+
* @task T346
|
|
552
|
+
* @epic T310
|
|
553
|
+
*/
|
|
554
|
+
export async function checkSignaldockDbHealth(cwd?: string): Promise<{
|
|
555
|
+
exists: boolean;
|
|
556
|
+
path: string;
|
|
557
|
+
tableCount: number;
|
|
558
|
+
walMode: boolean;
|
|
559
|
+
schemaVersion: string | null;
|
|
560
|
+
foreignKeysEnabled: boolean;
|
|
561
|
+
} | null> {
|
|
562
|
+
if (cwd !== undefined) {
|
|
563
|
+
throw new Error(
|
|
564
|
+
'checkSignaldockDbHealth(cwd) is removed as of T310 (v2026.4.12). ' +
|
|
565
|
+
'signaldock.db is now global-only. ' +
|
|
566
|
+
'Use checkGlobalSignaldockDbHealth() for global signaldock health, or ' +
|
|
567
|
+
'checkConduitDbHealth(cwd) from conduit-sqlite.ts for project conduit health (T344).',
|
|
568
|
+
);
|
|
569
|
+
}
|
|
570
|
+
return checkGlobalSignaldockDbHealth();
|
|
571
|
+
}
|
|
572
|
+
|
|
573
|
+
/**
|
|
574
|
+
* Get the underlying node:sqlite DatabaseSync instance for global signaldock.db.
|
|
575
|
+
* Returns the handle stored by the most recent ensureGlobalSignaldockDb() call,
|
|
576
|
+
* or null if the database has not yet been initialized in this process.
|
|
577
|
+
*
|
|
578
|
+
* Used by sqlite-backup.ts to activate the signaldock GLOBAL_SNAPSHOT_TARGET
|
|
579
|
+
* (spec §6.2, T310).
|
|
580
|
+
*
|
|
581
|
+
* @task T346
|
|
582
|
+
* @epic T310
|
|
583
|
+
*/
|
|
584
|
+
export function getGlobalSignaldockNativeDb(): DatabaseSync | null {
|
|
585
|
+
return _globalSignaldockNativeDb;
|
|
586
|
+
}
|
|
587
|
+
|
|
588
|
+
/**
|
|
589
|
+
* Reset the in-process global signaldock.db singleton.
|
|
590
|
+
* ONLY for use in test isolation — never call in production code.
|
|
591
|
+
*
|
|
592
|
+
* @task T346
|
|
593
|
+
* @epic T310
|
|
594
|
+
*/
|
|
595
|
+
export function _resetGlobalSignaldockDb_TESTING_ONLY(): void {
|
|
596
|
+
if (_globalSignaldockNativeDb) {
|
|
597
|
+
try {
|
|
598
|
+
if (_globalSignaldockNativeDb.isOpen) {
|
|
599
|
+
_globalSignaldockNativeDb.close();
|
|
600
|
+
}
|
|
601
|
+
} catch {
|
|
602
|
+
// Ignore close errors
|
|
603
|
+
}
|
|
604
|
+
_globalSignaldockNativeDb = null;
|
|
605
|
+
}
|
|
606
|
+
}
|