@adhdev/daemon-core 0.9.82-rc.2 → 0.9.82-rc.200
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/boot/daemon-lifecycle.d.ts +3 -0
- package/dist/boot/process-hardening.d.ts +50 -0
- package/dist/chat/source-machine.d.ts +166 -0
- package/dist/chat/source-resolver.d.ts +104 -0
- package/dist/chat/subscription-updates.d.ts +1 -0
- package/dist/cli-adapter-types.d.ts +23 -2
- package/dist/cli-adapters/cli-script-runner.d.ts +117 -0
- package/dist/cli-adapters/cli-state-engine.d.ts +178 -0
- package/dist/cli-adapters/provider-cli-adapter.d.ts +106 -63
- package/dist/cli-adapters/provider-cli-parse.d.ts +4 -0
- package/dist/cli-adapters/provider-cli-shared.d.ts +29 -0
- package/dist/cli-adapters/raw-terminal-io.d.ts +37 -0
- package/dist/commands/cli-manager.d.ts +2 -1
- package/dist/commands/handler.d.ts +110 -0
- package/dist/commands/mesh-coordinator.d.ts +85 -1
- package/dist/commands/router.d.ts +29 -1
- package/dist/config/chat-history.d.ts +9 -0
- package/dist/config/config.d.ts +5 -0
- package/dist/config/mesh-config.d.ts +72 -1
- package/dist/git/git-commands.d.ts +8 -1
- package/dist/git/git-status.d.ts +5 -0
- package/dist/git/git-types.d.ts +12 -0
- package/dist/index.d.ts +39 -10
- package/dist/index.js +29425 -12026
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +25700 -8378
- package/dist/index.mjs.map +1 -1
- package/dist/installer.d.ts +1 -4
- package/dist/ipc/local-ipc-server.d.ts +91 -0
- package/dist/launch.d.ts +1 -1
- package/dist/logging/async-batch-writer.d.ts +10 -0
- package/dist/mesh/contracts.d.ts +164 -0
- package/dist/mesh/coordinator-prompt.d.ts +30 -0
- package/dist/mesh/coordinator-registry.d.ts +59 -0
- package/dist/mesh/mesh-active-work.d.ts +90 -0
- package/dist/mesh/mesh-delivery-policy.d.ts +126 -0
- package/dist/mesh/mesh-events.d.ts +167 -6
- package/dist/mesh/mesh-fast-forward.d.ts +41 -0
- package/dist/mesh/mesh-host-ownership.d.ts +9 -0
- package/dist/mesh/mesh-ledger.d.ts +58 -1
- package/dist/mesh/mesh-refine-status.d.ts +26 -0
- package/dist/mesh/mesh-runtime-store.d.ts +149 -0
- package/dist/mesh/mesh-work-queue.d.ts +55 -6
- package/dist/mesh/preview-freshness.d.ts +18 -0
- package/dist/mesh/refine-config.d.ts +193 -0
- package/dist/mesh/worktree-bootstrap-config.d.ts +113 -0
- package/dist/providers/approval-utils.d.ts +13 -0
- package/dist/providers/chat-message-normalization.d.ts +1 -0
- package/dist/providers/cli-provider-instance.d.ts +39 -3
- package/dist/providers/contracts.d.ts +130 -6
- package/dist/providers/external-sources.d.ts +71 -0
- package/dist/providers/native-history/antigravity-cli-transcript.d.ts +100 -0
- package/dist/providers/native-history/claude-cli-transcript.d.ts +70 -0
- package/dist/providers/native-history/codex-cli-transcript.d.ts +73 -0
- package/dist/providers/native-history/dispatcher.d.ts +27 -0
- package/dist/providers/native-history/hermes-cli-transcript.d.ts +30 -0
- package/dist/providers/native-history/index.d.ts +13 -0
- package/dist/providers/provider-instance-manager.d.ts +12 -0
- package/dist/providers/provider-instance.d.ts +13 -1
- package/dist/providers/provider-loader.d.ts +26 -4
- package/dist/providers/provider-trust.d.ts +31 -0
- package/dist/providers/read-chat-contract.d.ts +29 -0
- package/dist/providers/sdk/v1/builders/acp/detect-status.d.ts +68 -0
- package/dist/providers/sdk/v1/builders/cli/detect-status.d.ts +85 -0
- package/dist/providers/sdk/v1/builders/cli/parse-approval-squash.d.ts +59 -0
- package/dist/providers/sdk/v1/builders/cli/parse-approval.d.ts +65 -0
- package/dist/providers/sdk/v1/builders/cli/parse-session.d.ts +91 -0
- package/dist/providers/sdk/v1/builders/cli/visible-region.d.ts +42 -0
- package/dist/providers/sdk/v1/fixture-tooling/format.d.ts +126 -0
- package/dist/providers/sdk/v1/fixture-tooling/index.d.ts +8 -0
- package/dist/providers/sdk/v1/fixture-tooling/replay.d.ts +38 -0
- package/dist/providers/sdk/v1/index.d.ts +30 -0
- package/dist/providers/sdk/v1/sandbox/README-design.d.ts +193 -0
- package/dist/providers/sdk/v1/sandbox/require-whitelist.d.ts +74 -0
- package/dist/providers/sdk/v1/sandbox/script-runner.d.ts +98 -0
- package/dist/providers/sdk/v1/types/cli/index.d.ts +277 -0
- package/dist/providers/sdk/v1/types/common/index.d.ts +203 -0
- package/dist/providers/sdk/v1/validators/index.d.ts +5 -0
- package/dist/providers/sdk/v1/validators/manifest.d.ts +40 -0
- package/dist/providers/sdk/v1/validators/taint.d.ts +52 -0
- package/dist/providers/spec/adapter.d.ts +60 -0
- package/dist/providers/spec/cli-adapter.d.ts +98 -0
- package/dist/providers/spec/driver.d.ts +190 -0
- package/dist/providers/spec/evaluator.d.ts +55 -0
- package/dist/providers/spec/loader.d.ts +14 -0
- package/dist/providers/spec/native-history-executor.d.ts +41 -0
- package/dist/providers/spec/route.d.ts +4 -0
- package/dist/providers/spec/schema.gen.d.ts +599 -0
- package/dist/providers/spec/types.d.ts +282 -0
- package/dist/providers/transcript-v2.d.ts +176 -0
- package/dist/providers/types/interactive-prompt.d.ts +48 -0
- package/dist/repo-mesh-types.d.ts +230 -1
- package/dist/sessions/registry.d.ts +3 -0
- package/dist/shared-types-extra.d.ts +1 -1
- package/dist/shared-types.d.ts +42 -1
- package/dist/status/normalize.d.ts +1 -1
- package/dist/status/normalize.js +1 -0
- package/dist/status/normalize.js.map +1 -1
- package/dist/status/normalize.mjs +1 -0
- package/dist/status/normalize.mjs.map +1 -1
- package/dist/status/reporter.d.ts +2 -0
- package/dist/status/snapshot.d.ts +1 -0
- package/dist/types.d.ts +5 -0
- package/package.json +7 -2
- package/src/agent-stream/poller.ts +2 -3
- package/src/boot/daemon-lifecycle.ts +22 -10
- package/src/boot/process-hardening.ts +89 -0
- package/src/chat/source-machine.ts +534 -0
- package/src/chat/source-resolver.ts +0 -0
- package/src/chat/subscription-updates.ts +20 -1
- package/src/cli-adapter-types.d.ts +1 -0
- package/src/cli-adapter-types.ts +22 -2
- package/src/cli-adapters/cli-script-runner.ts +421 -0
- package/src/cli-adapters/cli-state-engine.ts +1138 -0
- package/src/cli-adapters/provider-cli-adapter.d.ts +1 -1
- package/src/cli-adapters/provider-cli-adapter.ts +670 -1137
- package/src/cli-adapters/provider-cli-parse.d.ts +1 -0
- package/src/cli-adapters/provider-cli-parse.ts +13 -0
- package/src/cli-adapters/provider-cli-runtime.ts +3 -1
- package/src/cli-adapters/provider-cli-shared.d.ts +2 -0
- package/src/cli-adapters/provider-cli-shared.ts +59 -11
- package/src/cli-adapters/raw-terminal-io.ts +252 -0
- package/src/cli-adapters/terminal-backends/ghostty-vt-backend.ts +17 -1
- package/src/cli-adapters/terminal-backends/xterm-backend.ts +8 -1
- package/src/commands/chat-commands.ts +1787 -60
- package/src/commands/cli-manager.ts +283 -14
- package/src/commands/handler.ts +809 -2
- package/src/commands/mesh-coordinator.ts +331 -122
- package/src/commands/router.ts +3915 -546
- package/src/config/chat-history.ts +95 -24
- package/src/config/config.ts +12 -0
- package/src/config/mesh-config.ts +280 -2
- package/src/config/recent-activity.ts +8 -2
- package/src/daemon/dev-cli-debug.ts +10 -1
- package/src/detection/ide-detector.ts +26 -16
- package/src/git/git-commands.ts +38 -8
- package/src/git/git-status.ts +132 -12
- package/src/git/git-summary.ts +3 -0
- package/src/git/git-types.ts +13 -0
- package/src/git/git-worktree.ts +8 -1
- package/src/index.ts +127 -9
- package/src/installer.d.ts +1 -1
- package/src/installer.ts +8 -6
- package/src/ipc/local-ipc-server.ts +278 -0
- package/src/launch.d.ts +1 -1
- package/src/launch.ts +37 -28
- package/src/logging/async-batch-writer.ts +55 -0
- package/src/logging/logger.ts +2 -1
- package/src/mesh/contracts.ts +329 -0
- package/src/mesh/coordinator-prompt.ts +204 -30
- package/src/mesh/coordinator-registry.ts +121 -0
- package/src/mesh/mesh-active-work.ts +437 -0
- package/src/mesh/mesh-delivery-policy.ts +298 -0
- package/src/mesh/mesh-events.ts +1352 -109
- package/src/mesh/mesh-fast-forward.ts +438 -0
- package/src/mesh/mesh-host-ownership.ts +73 -0
- package/src/mesh/mesh-ledger.ts +457 -104
- package/src/mesh/mesh-refine-status.ts +144 -0
- package/src/mesh/mesh-runtime-store.ts +769 -0
- package/src/mesh/mesh-work-queue.ts +267 -159
- package/src/mesh/preview-freshness.ts +118 -0
- package/src/mesh/refine-config.ts +366 -0
- package/src/mesh/worktree-bootstrap-config.ts +247 -0
- package/src/providers/approval-utils.d.ts +4 -0
- package/src/providers/approval-utils.ts +47 -5
- package/src/providers/chat-message-normalization.ts +7 -12
- package/src/providers/cli-provider-instance.ts +806 -64
- package/src/providers/contracts.d.ts +55 -0
- package/src/providers/contracts.ts +141 -6
- package/src/providers/external-sources.ts +218 -0
- package/src/providers/ide-provider-instance.ts +19 -5
- package/src/providers/native-history/antigravity-cli-transcript.ts +643 -0
- package/src/providers/native-history/claude-cli-transcript.ts +396 -0
- package/src/providers/native-history/codex-cli-transcript.ts +419 -0
- package/src/providers/native-history/dispatcher.ts +340 -0
- package/src/providers/native-history/hermes-cli-transcript.ts +230 -0
- package/src/providers/native-history/index.ts +30 -0
- package/src/providers/provider-instance-manager.ts +30 -0
- package/src/providers/provider-instance.ts +10 -1
- package/src/providers/provider-loader.ts +582 -50
- package/src/providers/provider-schema.ts +87 -14
- package/src/providers/provider-trust.ts +114 -0
- package/src/providers/read-chat-contract.ts +76 -16
- package/src/providers/sdk/README.md +49 -0
- package/src/providers/sdk/v1/builders/acp/detect-status.ts +144 -0
- package/src/providers/sdk/v1/builders/cli/detect-status.ts +262 -0
- package/src/providers/sdk/v1/builders/cli/parse-approval-squash.ts +158 -0
- package/src/providers/sdk/v1/builders/cli/parse-approval.ts +250 -0
- package/src/providers/sdk/v1/builders/cli/parse-session.ts +276 -0
- package/src/providers/sdk/v1/builders/cli/visible-region.ts +143 -0
- package/src/providers/sdk/v1/fixture-tooling/format.ts +130 -0
- package/src/providers/sdk/v1/fixture-tooling/index.ts +22 -0
- package/src/providers/sdk/v1/fixture-tooling/replay.ts +352 -0
- package/src/providers/sdk/v1/index.ts +152 -0
- package/src/providers/sdk/v1/sandbox/README-design.ts +195 -0
- package/src/providers/sdk/v1/sandbox/require-whitelist.ts +472 -0
- package/src/providers/sdk/v1/sandbox/script-runner.ts +150 -0
- package/src/providers/sdk/v1/schemas/cli/provider.schema.json +504 -0
- package/src/providers/sdk/v1/schemas/primitives/acp-session-protocol-v1.json +131 -0
- package/src/providers/sdk/v1/schemas/primitives/native-history-codex-rollout-v1.json +66 -0
- package/src/providers/sdk/v1/schemas/primitives/tui-approval-squash-v1.json +91 -0
- package/src/providers/sdk/v1/schemas/primitives/tui-assistant-block-v1.json +91 -0
- package/src/providers/sdk/v1/schemas/primitives/tui-cue-ordering-v1.json +47 -0
- package/src/providers/sdk/v1/schemas/primitives/tui-dispatch-order-v1.json +32 -0
- package/src/providers/sdk/v1/schemas/primitives/tui-footer-chrome-v1.json +42 -0
- package/src/providers/sdk/v1/schemas/primitives/tui-index-finder-v1.json +27 -0
- package/src/providers/sdk/v1/schemas/primitives/tui-modal-v1.json +125 -0
- package/src/providers/sdk/v1/schemas/primitives/tui-prompt-marker-v1.json +45 -0
- package/src/providers/sdk/v1/schemas/primitives/tui-session-id-extraction-v1.json +46 -0
- package/src/providers/sdk/v1/schemas/primitives/tui-settled-prompt-v1.json +71 -0
- package/src/providers/sdk/v1/schemas/primitives/tui-spinner-v1.json +83 -0
- package/src/providers/sdk/v1/schemas/primitives/tui-transcript-pty-v1.json +83 -0
- package/src/providers/sdk/v1/schemas/primitives/tui-visible-region-v1.json +57 -0
- package/src/providers/sdk/v1/schemas/primitives/tui-welcome-screen-v1.json +35 -0
- package/src/providers/sdk/v1/types/cli/index.ts +365 -0
- package/src/providers/sdk/v1/types/common/index.ts +229 -0
- package/src/providers/sdk/v1/validators/index.ts +19 -0
- package/src/providers/sdk/v1/validators/manifest.ts +110 -0
- package/src/providers/sdk/v1/validators/taint.ts +309 -0
- package/src/providers/spec/adapter.ts +176 -0
- package/src/providers/spec/cli-adapter.ts +538 -0
- package/src/providers/spec/driver.ts +725 -0
- package/src/providers/spec/evaluator.ts +373 -0
- package/src/providers/spec/loader.ts +130 -0
- package/src/providers/spec/native-history-executor.ts +939 -0
- package/src/providers/spec/route.ts +51 -0
- package/src/providers/spec/schema.gen.ts +559 -0
- package/src/providers/spec/schema.json +237 -0
- package/src/providers/spec/types.ts +301 -0
- package/src/providers/transcript-v2.ts +567 -0
- package/src/providers/types/interactive-prompt.ts +425 -0
- package/src/providers/version-archive.ts +38 -20
- package/src/repo-mesh-types.ts +250 -1
- package/src/sessions/registry.ts +3 -0
- package/src/shared-types-extra.ts +1 -1
- package/src/shared-types.ts +45 -1
- package/src/status/builders.ts +24 -6
- package/src/status/normalize.ts +2 -0
- package/src/status/reporter.ts +15 -0
- package/src/status/snapshot.ts +84 -25
- package/src/system/host-memory.ts +29 -12
- package/src/types.ts +5 -0
- package/dist/mesh/mesh-sync.d.ts +0 -53
- package/src/mesh/mesh-sync.ts +0 -111
|
@@ -0,0 +1,769 @@
|
|
|
1
|
+
import { existsSync, mkdirSync, readFileSync, renameSync, statSync } from 'fs';
|
|
2
|
+
import { dirname, join } from 'path';
|
|
3
|
+
import { createRequire } from 'module';
|
|
4
|
+
import { getLedgerDir } from './mesh-ledger.js';
|
|
5
|
+
import { nodeSatisfiesRequiredTags } from './mesh-work-queue.js';
|
|
6
|
+
import type { MeshTaskStatus, MeshWorkQueueEntry } from './mesh-work-queue.js';
|
|
7
|
+
import type BetterSqlite3 from 'better-sqlite3';
|
|
8
|
+
import type { Database as DatabaseHandle } from 'better-sqlite3';
|
|
9
|
+
|
|
10
|
+
let DatabaseCtor: typeof BetterSqlite3 | undefined;
|
|
11
|
+
|
|
12
|
+
function loadDatabaseCtor(): typeof BetterSqlite3 {
|
|
13
|
+
if (DatabaseCtor) return DatabaseCtor;
|
|
14
|
+
const runtimeRequire = typeof require === 'function'
|
|
15
|
+
? require
|
|
16
|
+
: createRequire(import.meta.url);
|
|
17
|
+
DatabaseCtor = runtimeRequire('better-sqlite3') as typeof BetterSqlite3;
|
|
18
|
+
return DatabaseCtor;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function safeMeshId(meshId: string): string {
|
|
22
|
+
return meshId.replace(/[^a-zA-Z0-9_-]/g, '_');
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function legacyQueuePath(meshId: string): string {
|
|
26
|
+
return join(getLedgerDir(), `${safeMeshId(meshId)}.queue.json`);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function meshRuntimeStorePath(): string {
|
|
30
|
+
const dir = getLedgerDir();
|
|
31
|
+
const nextPath = join(dir, 'mesh-runtime.db');
|
|
32
|
+
if (existsSync(nextPath)) return nextPath;
|
|
33
|
+
|
|
34
|
+
const legacyPath = join(dir, 'beads.db');
|
|
35
|
+
if (!existsSync(legacyPath)) return nextPath;
|
|
36
|
+
|
|
37
|
+
try {
|
|
38
|
+
renameSync(legacyPath, nextPath);
|
|
39
|
+
for (const suffix of ['-wal', '-shm']) {
|
|
40
|
+
const legacyCompanion = `${legacyPath}${suffix}`;
|
|
41
|
+
if (existsSync(legacyCompanion)) {
|
|
42
|
+
renameSync(legacyCompanion, `${nextPath}${suffix}`);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
} catch {
|
|
46
|
+
// Best-effort compatibility for existing installs. If migration fails,
|
|
47
|
+
// opening the new store will create a clean DB instead of blocking boot.
|
|
48
|
+
}
|
|
49
|
+
return nextPath;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export class MeshRuntimeStore {
|
|
53
|
+
private static instance: MeshRuntimeStore | undefined;
|
|
54
|
+
private readonly db: DatabaseHandle;
|
|
55
|
+
private readonly dbPath: string;
|
|
56
|
+
private readonly migratedMeshIds = new Set<string>();
|
|
57
|
+
private fingerprintSweepCounter = 0;
|
|
58
|
+
private walWriteCounter = 0;
|
|
59
|
+
private static readonly WAL_CHECK_INTERVAL = 500;
|
|
60
|
+
private static readonly WAL_MAX_BYTES = 50 * 1024 * 1024; // 50 MB
|
|
61
|
+
|
|
62
|
+
private constructor(dbPath: string) {
|
|
63
|
+
const dir = dirname(dbPath);
|
|
64
|
+
if (!existsSync(dir)) mkdirSync(dir, { recursive: true });
|
|
65
|
+
|
|
66
|
+
this.dbPath = dbPath;
|
|
67
|
+
this.db = new (loadDatabaseCtor())(dbPath);
|
|
68
|
+
this.db.pragma('journal_mode = WAL');
|
|
69
|
+
this.db.pragma('synchronous = NORMAL');
|
|
70
|
+
this.db.pragma('foreign_keys = ON');
|
|
71
|
+
this.db.pragma('busy_timeout = 5000');
|
|
72
|
+
this.migrate();
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
static getInstance(): MeshRuntimeStore {
|
|
76
|
+
if (!this.instance) {
|
|
77
|
+
this.instance = new MeshRuntimeStore(meshRuntimeStorePath());
|
|
78
|
+
}
|
|
79
|
+
return this.instance;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
static resetForTests(): void {
|
|
83
|
+
this.instance?.close();
|
|
84
|
+
this.instance = undefined;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
close(): void {
|
|
88
|
+
this.db.close();
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
transaction<T>(fn: () => T): T {
|
|
92
|
+
return this.db.transaction(fn).immediate();
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
private migrate(): void {
|
|
96
|
+
this.db.exec(`
|
|
97
|
+
CREATE TABLE IF NOT EXISTS mesh_queue (
|
|
98
|
+
id TEXT PRIMARY KEY,
|
|
99
|
+
mesh_id TEXT NOT NULL,
|
|
100
|
+
status TEXT NOT NULL,
|
|
101
|
+
target_node_id TEXT,
|
|
102
|
+
target_session_id TEXT,
|
|
103
|
+
assigned_node_id TEXT,
|
|
104
|
+
assigned_session_id TEXT,
|
|
105
|
+
created_at TEXT NOT NULL,
|
|
106
|
+
updated_at TEXT NOT NULL,
|
|
107
|
+
payload TEXT NOT NULL
|
|
108
|
+
);
|
|
109
|
+
|
|
110
|
+
CREATE INDEX IF NOT EXISTS idx_mesh_queue_mesh_status_created
|
|
111
|
+
ON mesh_queue(mesh_id, status, created_at);
|
|
112
|
+
CREATE INDEX IF NOT EXISTS idx_mesh_queue_assignment
|
|
113
|
+
ON mesh_queue(mesh_id, assigned_node_id, assigned_session_id, status);
|
|
114
|
+
|
|
115
|
+
CREATE TABLE IF NOT EXISTS mesh_completion_fingerprints (
|
|
116
|
+
fingerprint TEXT PRIMARY KEY,
|
|
117
|
+
expires_at INTEGER NOT NULL
|
|
118
|
+
);
|
|
119
|
+
|
|
120
|
+
CREATE TABLE IF NOT EXISTS mesh_direct_dispatches (
|
|
121
|
+
task_id TEXT PRIMARY KEY,
|
|
122
|
+
mesh_id TEXT NOT NULL,
|
|
123
|
+
node_id TEXT,
|
|
124
|
+
session_id TEXT,
|
|
125
|
+
provider_type TEXT,
|
|
126
|
+
message TEXT NOT NULL,
|
|
127
|
+
task_mode TEXT,
|
|
128
|
+
via TEXT NOT NULL,
|
|
129
|
+
status TEXT NOT NULL DEFAULT 'dispatched',
|
|
130
|
+
dispatched_to_idle_session INTEGER NOT NULL DEFAULT 0,
|
|
131
|
+
dispatched_at TEXT NOT NULL,
|
|
132
|
+
updated_at TEXT NOT NULL
|
|
133
|
+
);
|
|
134
|
+
|
|
135
|
+
CREATE INDEX IF NOT EXISTS idx_direct_dispatches_mesh_session
|
|
136
|
+
ON mesh_direct_dispatches(mesh_id, session_id, status);
|
|
137
|
+
|
|
138
|
+
CREATE TABLE IF NOT EXISTS remote_idle_sessions (
|
|
139
|
+
node_id TEXT NOT NULL,
|
|
140
|
+
session_id TEXT NOT NULL,
|
|
141
|
+
provider_type TEXT NOT NULL,
|
|
142
|
+
expires_at INTEGER NOT NULL,
|
|
143
|
+
metadata TEXT,
|
|
144
|
+
PRIMARY KEY (node_id, session_id)
|
|
145
|
+
);
|
|
146
|
+
|
|
147
|
+
CREATE TABLE IF NOT EXISTS mesh_session_delivery (
|
|
148
|
+
id TEXT PRIMARY KEY,
|
|
149
|
+
mesh_id TEXT NOT NULL,
|
|
150
|
+
node_id TEXT,
|
|
151
|
+
session_id TEXT,
|
|
152
|
+
provider_type TEXT,
|
|
153
|
+
task_id TEXT,
|
|
154
|
+
kind TEXT NOT NULL,
|
|
155
|
+
priority INTEGER NOT NULL DEFAULT 0,
|
|
156
|
+
message TEXT NOT NULL,
|
|
157
|
+
status TEXT NOT NULL DEFAULT 'queued',
|
|
158
|
+
deliver_after TEXT,
|
|
159
|
+
expires_at TEXT,
|
|
160
|
+
attempt_count INTEGER NOT NULL DEFAULT 0,
|
|
161
|
+
source_coordinator_session_id TEXT,
|
|
162
|
+
source_coordinator_daemon_id TEXT,
|
|
163
|
+
last_error TEXT,
|
|
164
|
+
created_at TEXT NOT NULL,
|
|
165
|
+
updated_at TEXT NOT NULL
|
|
166
|
+
);
|
|
167
|
+
|
|
168
|
+
CREATE INDEX IF NOT EXISTS idx_mesh_session_delivery_mesh_status
|
|
169
|
+
ON mesh_session_delivery(mesh_id, status, created_at);
|
|
170
|
+
CREATE INDEX IF NOT EXISTS idx_mesh_session_delivery_session
|
|
171
|
+
ON mesh_session_delivery(mesh_id, session_id, status);
|
|
172
|
+
CREATE INDEX IF NOT EXISTS idx_mesh_session_delivery_task
|
|
173
|
+
ON mesh_session_delivery(mesh_id, task_id);
|
|
174
|
+
|
|
175
|
+
CREATE TABLE IF NOT EXISTS mesh_completion_conflicts (
|
|
176
|
+
id TEXT PRIMARY KEY,
|
|
177
|
+
mesh_id TEXT NOT NULL,
|
|
178
|
+
fingerprint TEXT NOT NULL,
|
|
179
|
+
conflicting_task_id TEXT,
|
|
180
|
+
conflicting_session_id TEXT,
|
|
181
|
+
original_task_id TEXT,
|
|
182
|
+
original_session_id TEXT,
|
|
183
|
+
event TEXT NOT NULL,
|
|
184
|
+
created_at TEXT NOT NULL
|
|
185
|
+
);
|
|
186
|
+
|
|
187
|
+
CREATE INDEX IF NOT EXISTS idx_mesh_completion_conflicts_mesh
|
|
188
|
+
ON mesh_completion_conflicts(mesh_id, created_at);
|
|
189
|
+
`);
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
hasCompletionFingerprint(fingerprint: string): boolean {
|
|
193
|
+
const now = Date.now();
|
|
194
|
+
const row = this.db
|
|
195
|
+
.prepare('SELECT 1 FROM mesh_completion_fingerprints WHERE fingerprint = ? AND expires_at > ?')
|
|
196
|
+
.get(fingerprint, now) as { 1: number } | undefined;
|
|
197
|
+
// Sweep expired fingerprints every 100 reads so stale rows don't accumulate
|
|
198
|
+
// even during read-heavy (non-write) periods when recordFingerprintSeen is idle.
|
|
199
|
+
if (++this.fingerprintSweepCounter >= 100) {
|
|
200
|
+
this.fingerprintSweepCounter = 0;
|
|
201
|
+
this.sweepExpiredFingerprints();
|
|
202
|
+
}
|
|
203
|
+
return row !== undefined;
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
recordCompletionFingerprint(fingerprint: string, ttlMs: number): void {
|
|
207
|
+
const expiresAt = Date.now() + ttlMs;
|
|
208
|
+
this.db.prepare('INSERT OR REPLACE INTO mesh_completion_fingerprints (fingerprint, expires_at) VALUES (?, ?)')
|
|
209
|
+
.run(fingerprint, expiresAt);
|
|
210
|
+
this.maybeCheckpointWal();
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
sweepExpiredFingerprints(): void {
|
|
214
|
+
this.db.prepare('DELETE FROM mesh_completion_fingerprints WHERE expires_at <= ?').run(Date.now());
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
private maybeCheckpointWal(): void {
|
|
218
|
+
if (++this.walWriteCounter < MeshRuntimeStore.WAL_CHECK_INTERVAL) return;
|
|
219
|
+
this.walWriteCounter = 0;
|
|
220
|
+
try {
|
|
221
|
+
const walPath = `${this.dbPath}-wal`;
|
|
222
|
+
if (!existsSync(walPath)) return;
|
|
223
|
+
const size = statSync(walPath).size;
|
|
224
|
+
if (size < MeshRuntimeStore.WAL_MAX_BYTES) return;
|
|
225
|
+
process.stderr.write(
|
|
226
|
+
`[adhdev-mesh] WAL file ${Math.round(size / 1024 / 1024)}MB exceeds threshold; forcing checkpoint\n`,
|
|
227
|
+
);
|
|
228
|
+
this.db.pragma('wal_checkpoint(TRUNCATE)');
|
|
229
|
+
} catch { /* best-effort */ }
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
private ensureLegacyQueueMigrated(meshId: string): void {
|
|
233
|
+
if (this.migratedMeshIds.has(meshId)) return;
|
|
234
|
+
this.migratedMeshIds.add(meshId);
|
|
235
|
+
|
|
236
|
+
const count = this.db
|
|
237
|
+
.prepare('SELECT COUNT(*) AS count FROM mesh_queue WHERE mesh_id = ?')
|
|
238
|
+
.get(meshId) as { count: number };
|
|
239
|
+
if (count.count > 0) return;
|
|
240
|
+
|
|
241
|
+
const path = legacyQueuePath(meshId);
|
|
242
|
+
if (!existsSync(path)) return;
|
|
243
|
+
|
|
244
|
+
try {
|
|
245
|
+
const entries = JSON.parse(readFileSync(path, 'utf-8')) as MeshWorkQueueEntry[];
|
|
246
|
+
if (!Array.isArray(entries)) return;
|
|
247
|
+
const insert = this.db.prepare(`
|
|
248
|
+
INSERT OR REPLACE INTO mesh_queue (
|
|
249
|
+
id, mesh_id, status, target_node_id, target_session_id,
|
|
250
|
+
assigned_node_id, assigned_session_id, created_at, updated_at, payload
|
|
251
|
+
) VALUES (
|
|
252
|
+
@id, @meshId, @status, @targetNodeId, @targetSessionId,
|
|
253
|
+
@assignedNodeId, @assignedSessionId, @createdAt, @updatedAt, @payload
|
|
254
|
+
)
|
|
255
|
+
`);
|
|
256
|
+
for (const entry of entries) {
|
|
257
|
+
insert.run(this.toRow(entry));
|
|
258
|
+
}
|
|
259
|
+
} catch {
|
|
260
|
+
return;
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
getQueueEntries(meshId: string, statuses?: MeshTaskStatus[]): MeshWorkQueueEntry[] {
|
|
265
|
+
this.ensureLegacyQueueMigrated(meshId);
|
|
266
|
+
if (statuses?.length) {
|
|
267
|
+
const placeholders = statuses.map(() => '?').join(', ');
|
|
268
|
+
const rows = this.db
|
|
269
|
+
.prepare(`SELECT payload FROM mesh_queue WHERE mesh_id = ? AND status IN (${placeholders}) ORDER BY created_at ASC`)
|
|
270
|
+
.all(meshId, ...statuses) as Array<{ payload: string }>;
|
|
271
|
+
return rows.map(row => JSON.parse(row.payload) as MeshWorkQueueEntry);
|
|
272
|
+
}
|
|
273
|
+
const rows = this.db
|
|
274
|
+
.prepare('SELECT payload FROM mesh_queue WHERE mesh_id = ? ORDER BY created_at ASC')
|
|
275
|
+
.all(meshId) as Array<{ payload: string }>;
|
|
276
|
+
return rows.map(row => JSON.parse(row.payload) as MeshWorkQueueEntry);
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
getQueueRevision(meshId: string): string {
|
|
280
|
+
this.ensureLegacyQueueMigrated(meshId);
|
|
281
|
+
const rows = this.db
|
|
282
|
+
.prepare('SELECT id, status, updated_at FROM mesh_queue WHERE mesh_id = ? ORDER BY id ASC')
|
|
283
|
+
.all(meshId) as Array<{ id: string; status: string; updated_at: string }>;
|
|
284
|
+
// Tab as field delimiter (UUIDs and ISO timestamps never contain tabs).
|
|
285
|
+
return rows.map(row => `${row.id}\t${row.status}\t${row.updated_at}`).join('\n');
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
replaceQueue(meshId: string, queue: MeshWorkQueueEntry[]): void {
|
|
289
|
+
const deleteStmt = this.db.prepare('DELETE FROM mesh_queue WHERE mesh_id = ?');
|
|
290
|
+
const insert = this.db.prepare(`
|
|
291
|
+
INSERT INTO mesh_queue (
|
|
292
|
+
id, mesh_id, status, target_node_id, target_session_id,
|
|
293
|
+
assigned_node_id, assigned_session_id, created_at, updated_at, payload
|
|
294
|
+
) VALUES (
|
|
295
|
+
@id, @meshId, @status, @targetNodeId, @targetSessionId,
|
|
296
|
+
@assignedNodeId, @assignedSessionId, @createdAt, @updatedAt, @payload
|
|
297
|
+
)
|
|
298
|
+
`);
|
|
299
|
+
deleteStmt.run(meshId);
|
|
300
|
+
for (const entry of queue) insert.run(this.toRow(entry));
|
|
301
|
+
this.maybeCheckpointWal();
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
deleteQueue(meshId: string): void {
|
|
305
|
+
this.db.prepare('DELETE FROM mesh_queue WHERE mesh_id = ?').run(meshId);
|
|
306
|
+
this.migratedMeshIds.delete(meshId);
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
insertQueueEntry(entry: MeshWorkQueueEntry): void {
|
|
310
|
+
this.db.prepare(`
|
|
311
|
+
INSERT INTO mesh_queue (
|
|
312
|
+
id, mesh_id, status, target_node_id, target_session_id,
|
|
313
|
+
assigned_node_id, assigned_session_id, created_at, updated_at, payload
|
|
314
|
+
) VALUES (
|
|
315
|
+
@id, @meshId, @status, @targetNodeId, @targetSessionId,
|
|
316
|
+
@assignedNodeId, @assignedSessionId, @createdAt, @updatedAt, @payload
|
|
317
|
+
)
|
|
318
|
+
`).run(this.toRow(entry));
|
|
319
|
+
this.maybeCheckpointWal();
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
updateQueueEntry(entry: MeshWorkQueueEntry): void {
|
|
323
|
+
const now = new Date().toISOString();
|
|
324
|
+
entry.updatedAt = now;
|
|
325
|
+
this.db.prepare(`
|
|
326
|
+
UPDATE mesh_queue SET
|
|
327
|
+
status = @status,
|
|
328
|
+
target_node_id = @targetNodeId,
|
|
329
|
+
target_session_id = @targetSessionId,
|
|
330
|
+
assigned_node_id = @assignedNodeId,
|
|
331
|
+
assigned_session_id = @assignedSessionId,
|
|
332
|
+
updated_at = @updatedAt,
|
|
333
|
+
payload = @payload
|
|
334
|
+
WHERE id = @id AND mesh_id = @meshId
|
|
335
|
+
`).run(this.toRow(entry));
|
|
336
|
+
this.maybeCheckpointWal();
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
findQueueEntryById(meshId: string, id: string): MeshWorkQueueEntry | null {
|
|
340
|
+
this.ensureLegacyQueueMigrated(meshId);
|
|
341
|
+
const row = this.db.prepare(
|
|
342
|
+
'SELECT payload FROM mesh_queue WHERE id = ? AND mesh_id = ?'
|
|
343
|
+
).get(id, meshId) as { payload: string } | undefined;
|
|
344
|
+
return row ? JSON.parse(row.payload) as MeshWorkQueueEntry : null;
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
hasActiveAssignment(meshId: string, sessionId: string, nodeId: string): boolean {
|
|
348
|
+
this.ensureLegacyQueueMigrated(meshId);
|
|
349
|
+
const row = this.db.prepare(`
|
|
350
|
+
SELECT 1 FROM mesh_queue
|
|
351
|
+
WHERE mesh_id = ? AND status = 'assigned'
|
|
352
|
+
AND (assigned_session_id = ? OR assigned_node_id = ?)
|
|
353
|
+
LIMIT 1
|
|
354
|
+
`).get(meshId, sessionId, nodeId);
|
|
355
|
+
return row !== undefined;
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
// O(1) claim: transaction ensures only one session claims a pending task
|
|
359
|
+
claimNextQueueTask(meshId: string, nodeId: string, sessionId: string, capabilityTags: string[] = []): MeshWorkQueueEntry | null {
|
|
360
|
+
return this.transaction(() => {
|
|
361
|
+
this.ensureLegacyQueueMigrated(meshId);
|
|
362
|
+
if (this.hasActiveAssignment(meshId, sessionId, nodeId)) return null;
|
|
363
|
+
|
|
364
|
+
// Priority: session-targeted > node-targeted (no session) > unconstrained
|
|
365
|
+
const rows = [
|
|
366
|
+
...(
|
|
367
|
+
this.db.prepare(`
|
|
368
|
+
SELECT payload FROM mesh_queue
|
|
369
|
+
WHERE mesh_id = ? AND status = 'pending' AND target_session_id = ?
|
|
370
|
+
ORDER BY created_at ASC
|
|
371
|
+
`).all(meshId, sessionId) as Array<{ payload: string }>
|
|
372
|
+
),
|
|
373
|
+
...(
|
|
374
|
+
this.db.prepare(`
|
|
375
|
+
SELECT payload FROM mesh_queue
|
|
376
|
+
WHERE mesh_id = ? AND status = 'pending' AND target_node_id = ? AND target_session_id IS NULL
|
|
377
|
+
ORDER BY created_at ASC
|
|
378
|
+
`).all(meshId, nodeId) as Array<{ payload: string }>
|
|
379
|
+
),
|
|
380
|
+
...(
|
|
381
|
+
this.db.prepare(`
|
|
382
|
+
SELECT payload FROM mesh_queue
|
|
383
|
+
WHERE mesh_id = ? AND status = 'pending' AND target_node_id IS NULL AND target_session_id IS NULL
|
|
384
|
+
ORDER BY created_at ASC
|
|
385
|
+
`).all(meshId) as Array<{ payload: string }>
|
|
386
|
+
),
|
|
387
|
+
];
|
|
388
|
+
const entry = rows
|
|
389
|
+
.map(row => JSON.parse(row.payload) as MeshWorkQueueEntry)
|
|
390
|
+
.find(candidate => nodeSatisfiesRequiredTags(candidate.requiredTags, capabilityTags));
|
|
391
|
+
if (!entry) return null;
|
|
392
|
+
|
|
393
|
+
const now = new Date().toISOString();
|
|
394
|
+
entry.status = 'assigned';
|
|
395
|
+
entry.assignedNodeId = nodeId;
|
|
396
|
+
entry.assignedSessionId = sessionId;
|
|
397
|
+
entry.dispatchTimestamp = now;
|
|
398
|
+
entry.updatedAt = now;
|
|
399
|
+
|
|
400
|
+
this.db.prepare(`
|
|
401
|
+
UPDATE mesh_queue SET
|
|
402
|
+
status = 'assigned', assigned_node_id = ?, assigned_session_id = ?,
|
|
403
|
+
updated_at = ?, payload = ?
|
|
404
|
+
WHERE id = ? AND mesh_id = ?
|
|
405
|
+
`).run(nodeId, sessionId, now, JSON.stringify(entry), entry.id, meshId);
|
|
406
|
+
|
|
407
|
+
this.maybeCheckpointWal();
|
|
408
|
+
return entry;
|
|
409
|
+
});
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
getQueueStatsByStatus(meshId: string): { status: string; count: number }[] {
|
|
413
|
+
this.ensureLegacyQueueMigrated(meshId);
|
|
414
|
+
return this.db.prepare(
|
|
415
|
+
`SELECT status, COUNT(*) as count FROM mesh_queue WHERE mesh_id = ? GROUP BY status`
|
|
416
|
+
).all(meshId) as { status: string; count: number }[];
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
getActiveAssignmentDetails(meshId: string): Array<{ id: string; nodeId?: string; sessionId?: string; message: string }> {
|
|
420
|
+
this.ensureLegacyQueueMigrated(meshId);
|
|
421
|
+
const rows = this.db.prepare(`
|
|
422
|
+
SELECT assigned_node_id, assigned_session_id, payload
|
|
423
|
+
FROM mesh_queue WHERE mesh_id = ? AND status = 'assigned'
|
|
424
|
+
`).all(meshId) as Array<{ assigned_node_id: string | null; assigned_session_id: string | null; payload: string }>;
|
|
425
|
+
return rows.map(r => {
|
|
426
|
+
let id = '', message = '';
|
|
427
|
+
try { const e = JSON.parse(r.payload) as MeshWorkQueueEntry; id = e.id; message = e.message; } catch { /* ignore */ }
|
|
428
|
+
return { id, nodeId: r.assigned_node_id ?? undefined, sessionId: r.assigned_session_id ?? undefined, message };
|
|
429
|
+
});
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
findAssignedBySession(meshId: string, sessionId: string, occurredAtIso?: string): MeshWorkQueueEntry | null {
|
|
433
|
+
this.ensureLegacyQueueMigrated(meshId);
|
|
434
|
+
// Use updated_at (≈ dispatchTimestamp when status='assigned') for the occurredAt filter.
|
|
435
|
+
const sql = occurredAtIso
|
|
436
|
+
? `SELECT payload FROM mesh_queue WHERE mesh_id = ? AND assigned_session_id = ? AND status = 'assigned' AND updated_at <= ? ORDER BY updated_at DESC LIMIT 1`
|
|
437
|
+
: `SELECT payload FROM mesh_queue WHERE mesh_id = ? AND assigned_session_id = ? AND status = 'assigned' ORDER BY updated_at DESC LIMIT 1`;
|
|
438
|
+
const args: string[] = occurredAtIso ? [meshId, sessionId, occurredAtIso] : [meshId, sessionId];
|
|
439
|
+
const row = this.db.prepare(sql).get(...args as [string, string, string?]) as { payload: string } | undefined;
|
|
440
|
+
return row ? JSON.parse(row.payload) as MeshWorkQueueEntry : null;
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
private toRow(entry: MeshWorkQueueEntry): Record<string, unknown> {
|
|
444
|
+
return {
|
|
445
|
+
id: entry.id,
|
|
446
|
+
meshId: entry.meshId,
|
|
447
|
+
status: entry.status,
|
|
448
|
+
targetNodeId: entry.targetNodeId ?? null,
|
|
449
|
+
targetSessionId: entry.targetSessionId ?? null,
|
|
450
|
+
assignedNodeId: entry.assignedNodeId ?? null,
|
|
451
|
+
assignedSessionId: entry.assignedSessionId ?? null,
|
|
452
|
+
createdAt: entry.createdAt,
|
|
453
|
+
updatedAt: entry.updatedAt,
|
|
454
|
+
payload: JSON.stringify(entry),
|
|
455
|
+
};
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
// ── Direct Dispatch Tracking ─────────────────────────────────────────────
|
|
459
|
+
|
|
460
|
+
insertDirectDispatch(entry: {
|
|
461
|
+
taskId: string;
|
|
462
|
+
meshId: string;
|
|
463
|
+
nodeId?: string;
|
|
464
|
+
sessionId?: string;
|
|
465
|
+
providerType?: string;
|
|
466
|
+
message: string;
|
|
467
|
+
taskMode?: string;
|
|
468
|
+
via: string;
|
|
469
|
+
dispatchedToIdleSession?: boolean;
|
|
470
|
+
dispatchedAt: string;
|
|
471
|
+
}): void {
|
|
472
|
+
const now = new Date().toISOString();
|
|
473
|
+
this.db.prepare(`
|
|
474
|
+
INSERT OR REPLACE INTO mesh_direct_dispatches
|
|
475
|
+
(task_id, mesh_id, node_id, session_id, provider_type, message, task_mode, via,
|
|
476
|
+
status, dispatched_to_idle_session, dispatched_at, updated_at)
|
|
477
|
+
VALUES
|
|
478
|
+
(@taskId, @meshId, @nodeId, @sessionId, @providerType, @message, @taskMode, @via,
|
|
479
|
+
'dispatched', @dispatchedToIdle, @dispatchedAt, @updatedAt)
|
|
480
|
+
`).run({
|
|
481
|
+
taskId: entry.taskId,
|
|
482
|
+
meshId: entry.meshId,
|
|
483
|
+
nodeId: entry.nodeId ?? null,
|
|
484
|
+
sessionId: entry.sessionId ?? null,
|
|
485
|
+
providerType: entry.providerType ?? null,
|
|
486
|
+
message: entry.message,
|
|
487
|
+
taskMode: entry.taskMode ?? null,
|
|
488
|
+
via: entry.via,
|
|
489
|
+
dispatchedToIdle: entry.dispatchedToIdleSession ? 1 : 0,
|
|
490
|
+
dispatchedAt: entry.dispatchedAt,
|
|
491
|
+
updatedAt: now,
|
|
492
|
+
});
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
getActiveDirectDispatches(meshId: string): Array<{
|
|
496
|
+
taskId: string;
|
|
497
|
+
meshId: string;
|
|
498
|
+
nodeId: string | null;
|
|
499
|
+
sessionId: string | null;
|
|
500
|
+
providerType: string | null;
|
|
501
|
+
message: string;
|
|
502
|
+
taskMode: string | null;
|
|
503
|
+
via: string;
|
|
504
|
+
status: string;
|
|
505
|
+
dispatchedToIdleSession: boolean;
|
|
506
|
+
dispatchedAt: string;
|
|
507
|
+
updatedAt: string;
|
|
508
|
+
}> {
|
|
509
|
+
const rows = this.db.prepare(`
|
|
510
|
+
SELECT task_id, mesh_id, node_id, session_id, provider_type, message, task_mode, via,
|
|
511
|
+
status, dispatched_to_idle_session, dispatched_at, updated_at
|
|
512
|
+
FROM mesh_direct_dispatches
|
|
513
|
+
WHERE mesh_id = ? AND status NOT IN ('completed', 'failed', 'stale')
|
|
514
|
+
ORDER BY dispatched_at ASC
|
|
515
|
+
`).all(meshId) as Array<Record<string, unknown>>;
|
|
516
|
+
return rows.map(r => ({
|
|
517
|
+
taskId: r.task_id as string,
|
|
518
|
+
meshId: r.mesh_id as string,
|
|
519
|
+
nodeId: r.node_id as string | null,
|
|
520
|
+
sessionId: r.session_id as string | null,
|
|
521
|
+
providerType: r.provider_type as string | null,
|
|
522
|
+
message: r.message as string,
|
|
523
|
+
taskMode: r.task_mode as string | null,
|
|
524
|
+
via: r.via as string,
|
|
525
|
+
status: r.status as string,
|
|
526
|
+
dispatchedToIdleSession: (r.dispatched_to_idle_session as number) === 1,
|
|
527
|
+
dispatchedAt: r.dispatched_at as string,
|
|
528
|
+
updatedAt: r.updated_at as string,
|
|
529
|
+
}));
|
|
530
|
+
}
|
|
531
|
+
|
|
532
|
+
updateDirectDispatchStatus(meshId: string, sessionId: string, status: 'acked' | 'completed' | 'failed' | 'stale'): void {
|
|
533
|
+
if (!sessionId) return; // never update rows without a session binding
|
|
534
|
+
const now = new Date().toISOString();
|
|
535
|
+
this.db.prepare(`
|
|
536
|
+
UPDATE mesh_direct_dispatches
|
|
537
|
+
SET status = @status, updated_at = @updatedAt
|
|
538
|
+
WHERE mesh_id = @meshId AND session_id = @sessionId
|
|
539
|
+
AND session_id IS NOT NULL
|
|
540
|
+
AND status NOT IN ('completed', 'failed')
|
|
541
|
+
`).run({ status, meshId, sessionId, updatedAt: now });
|
|
542
|
+
}
|
|
543
|
+
|
|
544
|
+
cleanupTerminalDirectDispatches(olderThanMs: number): void {
|
|
545
|
+
const cutoff = new Date(Date.now() - olderThanMs).toISOString();
|
|
546
|
+
this.db.prepare(`
|
|
547
|
+
DELETE FROM mesh_direct_dispatches
|
|
548
|
+
WHERE status IN ('completed', 'failed', 'stale') AND updated_at < ?
|
|
549
|
+
`).run(cutoff);
|
|
550
|
+
}
|
|
551
|
+
|
|
552
|
+
deleteDirectDispatches(meshId: string): void {
|
|
553
|
+
this.db.prepare(`DELETE FROM mesh_direct_dispatches WHERE mesh_id = ?`).run(meshId);
|
|
554
|
+
}
|
|
555
|
+
|
|
556
|
+
markStaleDirectDispatches(meshId: string, olderThanMs: number): void {
|
|
557
|
+
const cutoff = new Date(Date.now() - olderThanMs).toISOString();
|
|
558
|
+
const now = new Date().toISOString();
|
|
559
|
+
this.db.prepare(`
|
|
560
|
+
UPDATE mesh_direct_dispatches
|
|
561
|
+
SET status = 'stale', updated_at = ?
|
|
562
|
+
WHERE mesh_id = ? AND status = 'dispatched' AND dispatched_at < ?
|
|
563
|
+
`).run(now, meshId, cutoff);
|
|
564
|
+
}
|
|
565
|
+
|
|
566
|
+
// ── Remote Idle Sessions ─────────────────────────────────────────────────
|
|
567
|
+
|
|
568
|
+
setRemoteIdleSession(nodeId: string, sessionId: string, providerType: string, expiresAt: number, metadata?: any): void {
|
|
569
|
+
this.db.prepare(`
|
|
570
|
+
INSERT OR REPLACE INTO remote_idle_sessions (node_id, session_id, provider_type, expires_at, metadata)
|
|
571
|
+
VALUES (?, ?, ?, ?, ?)
|
|
572
|
+
`).run(nodeId, sessionId, providerType, expiresAt, metadata ? JSON.stringify(metadata) : null);
|
|
573
|
+
}
|
|
574
|
+
|
|
575
|
+
getRemoteIdleSessions(): Array<{ nodeId: string; sessionId: string; providerType: string; expiresAt: number; metadata?: any }> {
|
|
576
|
+
const rows = this.db.prepare('SELECT node_id, session_id, provider_type, expires_at, metadata FROM remote_idle_sessions').all() as Array<any>;
|
|
577
|
+
return rows.map(r => ({
|
|
578
|
+
nodeId: r.node_id,
|
|
579
|
+
sessionId: r.session_id,
|
|
580
|
+
providerType: r.provider_type,
|
|
581
|
+
expiresAt: r.expires_at,
|
|
582
|
+
metadata: r.metadata ? JSON.parse(r.metadata) : undefined,
|
|
583
|
+
}));
|
|
584
|
+
}
|
|
585
|
+
|
|
586
|
+
deleteRemoteIdleSession(nodeId: string, sessionId: string): void {
|
|
587
|
+
this.db.prepare('DELETE FROM remote_idle_sessions WHERE node_id = ? AND session_id = ?').run(nodeId, sessionId);
|
|
588
|
+
}
|
|
589
|
+
|
|
590
|
+
pruneExpiredRemoteIdleSessions(): void {
|
|
591
|
+
this.db.prepare('DELETE FROM remote_idle_sessions WHERE expires_at <= ?').run(Date.now());
|
|
592
|
+
}
|
|
593
|
+
|
|
594
|
+
// ── Session Delivery Queue ───────────────────────────────────────────────
|
|
595
|
+
|
|
596
|
+
insertSessionDelivery(entry: {
|
|
597
|
+
id: string;
|
|
598
|
+
meshId: string;
|
|
599
|
+
nodeId?: string;
|
|
600
|
+
sessionId?: string;
|
|
601
|
+
providerType?: string;
|
|
602
|
+
taskId?: string;
|
|
603
|
+
kind: string;
|
|
604
|
+
priority?: number;
|
|
605
|
+
message: string;
|
|
606
|
+
status: string;
|
|
607
|
+
deliverAfter?: string;
|
|
608
|
+
expiresAt?: string;
|
|
609
|
+
sourceCoordinatorSessionId?: string;
|
|
610
|
+
sourceCoordinatorDaemonId?: string;
|
|
611
|
+
createdAt: string;
|
|
612
|
+
updatedAt: string;
|
|
613
|
+
}): void {
|
|
614
|
+
this.db.prepare(`
|
|
615
|
+
INSERT OR REPLACE INTO mesh_session_delivery (
|
|
616
|
+
id, mesh_id, node_id, session_id, provider_type, task_id, kind, priority,
|
|
617
|
+
message, status, deliver_after, expires_at, attempt_count,
|
|
618
|
+
source_coordinator_session_id, source_coordinator_daemon_id,
|
|
619
|
+
last_error, created_at, updated_at
|
|
620
|
+
) VALUES (
|
|
621
|
+
@id, @meshId, @nodeId, @sessionId, @providerType, @taskId, @kind, @priority,
|
|
622
|
+
@message, @status, @deliverAfter, @expiresAt, 0,
|
|
623
|
+
@sourceCoordinatorSessionId, @sourceCoordinatorDaemonId,
|
|
624
|
+
NULL, @createdAt, @updatedAt
|
|
625
|
+
)
|
|
626
|
+
`).run({
|
|
627
|
+
id: entry.id,
|
|
628
|
+
meshId: entry.meshId,
|
|
629
|
+
nodeId: entry.nodeId ?? null,
|
|
630
|
+
sessionId: entry.sessionId ?? null,
|
|
631
|
+
providerType: entry.providerType ?? null,
|
|
632
|
+
taskId: entry.taskId ?? null,
|
|
633
|
+
kind: entry.kind,
|
|
634
|
+
priority: entry.priority ?? 0,
|
|
635
|
+
message: entry.message,
|
|
636
|
+
status: entry.status,
|
|
637
|
+
deliverAfter: entry.deliverAfter ?? null,
|
|
638
|
+
expiresAt: entry.expiresAt ?? null,
|
|
639
|
+
sourceCoordinatorSessionId: entry.sourceCoordinatorSessionId ?? null,
|
|
640
|
+
sourceCoordinatorDaemonId: entry.sourceCoordinatorDaemonId ?? null,
|
|
641
|
+
createdAt: entry.createdAt,
|
|
642
|
+
updatedAt: entry.updatedAt,
|
|
643
|
+
});
|
|
644
|
+
this.maybeCheckpointWal();
|
|
645
|
+
}
|
|
646
|
+
|
|
647
|
+
updateSessionDeliveryStatus(id: string, status: string, opts?: { lastError?: string; incrementAttempt?: boolean }): void {
|
|
648
|
+
const now = new Date().toISOString();
|
|
649
|
+
if (opts?.incrementAttempt) {
|
|
650
|
+
this.db.prepare(`
|
|
651
|
+
UPDATE mesh_session_delivery
|
|
652
|
+
SET status = @status, last_error = @lastError, attempt_count = attempt_count + 1, updated_at = @updatedAt
|
|
653
|
+
WHERE id = @id
|
|
654
|
+
`).run({ id, status, lastError: opts?.lastError ?? null, updatedAt: now });
|
|
655
|
+
} else {
|
|
656
|
+
this.db.prepare(`
|
|
657
|
+
UPDATE mesh_session_delivery
|
|
658
|
+
SET status = @status, last_error = @lastError, updated_at = @updatedAt
|
|
659
|
+
WHERE id = @id
|
|
660
|
+
`).run({ id, status, lastError: opts?.lastError ?? null, updatedAt: now });
|
|
661
|
+
}
|
|
662
|
+
}
|
|
663
|
+
|
|
664
|
+
getActiveSessionDeliveries(meshId: string, sessionId?: string): Array<{
|
|
665
|
+
id: string; meshId: string; nodeId: string | null; sessionId: string | null;
|
|
666
|
+
providerType: string | null; taskId: string | null; kind: string; priority: number;
|
|
667
|
+
message: string; status: string; deliverAfter: string | null; expiresAt: string | null;
|
|
668
|
+
attemptCount: number; sourceCoordinatorSessionId: string | null;
|
|
669
|
+
sourceCoordinatorDaemonId: string | null; lastError: string | null;
|
|
670
|
+
createdAt: string; updatedAt: string;
|
|
671
|
+
}> {
|
|
672
|
+
const now = new Date().toISOString();
|
|
673
|
+
const sql = sessionId
|
|
674
|
+
? `SELECT * FROM mesh_session_delivery WHERE mesh_id = ? AND session_id = ? AND status NOT IN ('delivered','completed','failed','expired','cancelled') AND (expires_at IS NULL OR expires_at > ?) ORDER BY priority DESC, created_at ASC`
|
|
675
|
+
: `SELECT * FROM mesh_session_delivery WHERE mesh_id = ? AND status NOT IN ('delivered','completed','failed','expired','cancelled') AND (expires_at IS NULL OR expires_at > ?) ORDER BY priority DESC, created_at ASC`;
|
|
676
|
+
const rows = sessionId
|
|
677
|
+
? this.db.prepare(sql).all(meshId, sessionId, now) as Array<Record<string, unknown>>
|
|
678
|
+
: this.db.prepare(sql).all(meshId, now) as Array<Record<string, unknown>>;
|
|
679
|
+
return rows.map(r => ({
|
|
680
|
+
id: r.id as string,
|
|
681
|
+
meshId: r.mesh_id as string,
|
|
682
|
+
nodeId: r.node_id as string | null,
|
|
683
|
+
sessionId: r.session_id as string | null,
|
|
684
|
+
providerType: r.provider_type as string | null,
|
|
685
|
+
taskId: r.task_id as string | null,
|
|
686
|
+
kind: r.kind as string,
|
|
687
|
+
priority: r.priority as number,
|
|
688
|
+
message: r.message as string,
|
|
689
|
+
status: r.status as string,
|
|
690
|
+
deliverAfter: r.deliver_after as string | null,
|
|
691
|
+
expiresAt: r.expires_at as string | null,
|
|
692
|
+
attemptCount: r.attempt_count as number,
|
|
693
|
+
sourceCoordinatorSessionId: r.source_coordinator_session_id as string | null,
|
|
694
|
+
sourceCoordinatorDaemonId: r.source_coordinator_daemon_id as string | null,
|
|
695
|
+
lastError: r.last_error as string | null,
|
|
696
|
+
createdAt: r.created_at as string,
|
|
697
|
+
updatedAt: r.updated_at as string,
|
|
698
|
+
}));
|
|
699
|
+
}
|
|
700
|
+
|
|
701
|
+
expireStaleSessionDeliveries(meshId: string): void {
|
|
702
|
+
const now = new Date().toISOString();
|
|
703
|
+
this.db.prepare(`
|
|
704
|
+
UPDATE mesh_session_delivery
|
|
705
|
+
SET status = 'expired', updated_at = ?
|
|
706
|
+
WHERE mesh_id = ? AND expires_at IS NOT NULL AND expires_at <= ?
|
|
707
|
+
AND status NOT IN ('delivered','completed','failed','expired','cancelled')
|
|
708
|
+
`).run(now, meshId, now);
|
|
709
|
+
}
|
|
710
|
+
|
|
711
|
+
deleteSessionDeliveries(meshId: string): void {
|
|
712
|
+
this.db.prepare('DELETE FROM mesh_session_delivery WHERE mesh_id = ?').run(meshId);
|
|
713
|
+
}
|
|
714
|
+
|
|
715
|
+
// ── Completion Conflict Diagnostics ──────────────────────────────────────
|
|
716
|
+
|
|
717
|
+
recordCompletionConflict(entry: {
|
|
718
|
+
id: string;
|
|
719
|
+
meshId: string;
|
|
720
|
+
fingerprint: string;
|
|
721
|
+
conflictingTaskId?: string;
|
|
722
|
+
conflictingSessionId?: string;
|
|
723
|
+
originalTaskId?: string;
|
|
724
|
+
originalSessionId?: string;
|
|
725
|
+
event: string;
|
|
726
|
+
createdAt: string;
|
|
727
|
+
}): void {
|
|
728
|
+
this.db.prepare(`
|
|
729
|
+
INSERT OR IGNORE INTO mesh_completion_conflicts
|
|
730
|
+
(id, mesh_id, fingerprint, conflicting_task_id, conflicting_session_id,
|
|
731
|
+
original_task_id, original_session_id, event, created_at)
|
|
732
|
+
VALUES (@id, @meshId, @fingerprint, @conflictingTaskId, @conflictingSessionId,
|
|
733
|
+
@originalTaskId, @originalSessionId, @event, @createdAt)
|
|
734
|
+
`).run({
|
|
735
|
+
id: entry.id,
|
|
736
|
+
meshId: entry.meshId,
|
|
737
|
+
fingerprint: entry.fingerprint,
|
|
738
|
+
conflictingTaskId: entry.conflictingTaskId ?? null,
|
|
739
|
+
conflictingSessionId: entry.conflictingSessionId ?? null,
|
|
740
|
+
originalTaskId: entry.originalTaskId ?? null,
|
|
741
|
+
originalSessionId: entry.originalSessionId ?? null,
|
|
742
|
+
event: entry.event,
|
|
743
|
+
createdAt: entry.createdAt,
|
|
744
|
+
});
|
|
745
|
+
this.maybeCheckpointWal();
|
|
746
|
+
}
|
|
747
|
+
|
|
748
|
+
getRecentCompletionConflicts(meshId: string, limitMs: number = 60 * 60 * 1000): Array<{
|
|
749
|
+
id: string; meshId: string; fingerprint: string; conflictingTaskId: string | null;
|
|
750
|
+
conflictingSessionId: string | null; originalTaskId: string | null;
|
|
751
|
+
originalSessionId: string | null; event: string; createdAt: string;
|
|
752
|
+
}> {
|
|
753
|
+
const cutoff = new Date(Date.now() - limitMs).toISOString();
|
|
754
|
+
const rows = this.db.prepare(
|
|
755
|
+
'SELECT * FROM mesh_completion_conflicts WHERE mesh_id = ? AND created_at >= ? ORDER BY created_at DESC LIMIT 50'
|
|
756
|
+
).all(meshId, cutoff) as Array<Record<string, unknown>>;
|
|
757
|
+
return rows.map(r => ({
|
|
758
|
+
id: r.id as string,
|
|
759
|
+
meshId: r.mesh_id as string,
|
|
760
|
+
fingerprint: r.fingerprint as string,
|
|
761
|
+
conflictingTaskId: r.conflicting_task_id as string | null,
|
|
762
|
+
conflictingSessionId: r.conflicting_session_id as string | null,
|
|
763
|
+
originalTaskId: r.original_task_id as string | null,
|
|
764
|
+
originalSessionId: r.original_session_id as string | null,
|
|
765
|
+
event: r.event as string,
|
|
766
|
+
createdAt: r.created_at as string,
|
|
767
|
+
}));
|
|
768
|
+
}
|
|
769
|
+
}
|