@kontextmind/kxm 0.6.0
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/.claude-plugin/marketplace.json +19 -0
- package/.kxm/README.md +14 -0
- package/.kxm/assets/README.md +5 -0
- package/.kxm/assets/retrospectives/README.md +5 -0
- package/.kxm/config/README.md +5 -0
- package/.kxm/config/agents.json +43 -0
- package/.kxm/config/env.example +56 -0
- package/.kxm/config/update.example.yaml +9 -0
- package/.kxm/config/workflows/fix.json +160 -0
- package/.kxm/config/workflows/jira-development.json +116 -0
- package/.kxm/config/workflows/provenance-quorum.json +150 -0
- package/.kxm/config/workflows/v04-dogfood.json +72 -0
- package/CHANGELOG.md +465 -0
- package/LICENSE +21 -0
- package/README.md +306 -0
- package/SECURITY.md +72 -0
- package/docs/README.md +48 -0
- package/docs/agent-communication-envelopes-and-gates.md +553 -0
- package/docs/architecture.md +242 -0
- package/docs/assignment-runner.md +241 -0
- package/docs/configuration.md +361 -0
- package/docs/continuous-improvement.md +114 -0
- package/docs/getting-started.md +253 -0
- package/docs/kxm-handbook.md +1090 -0
- package/docs/operations.md +205 -0
- package/docs/provenance-gates.md +291 -0
- package/docs/skills.md +45 -0
- package/docs/templates/README.md +95 -0
- package/docs/templates/adr.md +88 -0
- package/docs/templates/architecture.md +120 -0
- package/docs/templates/bug-fix.md +109 -0
- package/docs/templates/feature.md +108 -0
- package/docs/templates/handoff.md +72 -0
- package/docs/templates/postmortem.md +77 -0
- package/docs/templates/research.md +100 -0
- package/docs/templates/review.md +85 -0
- package/docs/templates/runbook.md +73 -0
- package/docs/templates/test-plan.md +87 -0
- package/docs/templates/test-report.md +72 -0
- package/docs/test-matrix.md +121 -0
- package/docs/troubleshooting.md +249 -0
- package/docs/vnext/README.md +62 -0
- package/docs/vnext/architecture.md +185 -0
- package/docs/vnext/effects-and-recovery.md +172 -0
- package/docs/vnext/lifecycles.md +235 -0
- package/docs/vnext/migration.md +220 -0
- package/docs/vnext/routing.md +184 -0
- package/docs/vnext/synchronization.md +172 -0
- package/docs/vnext/terminology.md +240 -0
- package/docs/vnext/validation.md +335 -0
- package/docs/webhook-workflows.md +240 -0
- package/docs/workflow-guide.md +1150 -0
- package/examples/README.md +102 -0
- package/examples/provenance-workflow.json +40 -0
- package/examples/requester.ts +30 -0
- package/examples/reviewer-agent.ts +29 -0
- package/examples/roundtrip.ts +46 -0
- package/examples/vnext/.kxm/agents/coordinator.yaml +16 -0
- package/examples/vnext/.kxm/agents/critic-1.yaml +16 -0
- package/examples/vnext/.kxm/agents/critic-2.yaml +15 -0
- package/examples/vnext/.kxm/agents/critic-3.yaml +15 -0
- package/examples/vnext/.kxm/agents/implementer.yaml +15 -0
- package/examples/vnext/.kxm/agents/planner.yaml +13 -0
- package/examples/vnext/.kxm/agents/reproducer.yaml +15 -0
- package/examples/vnext/.kxm/agents/reviewer.yaml +15 -0
- package/examples/vnext/.kxm/gates.yaml +8 -0
- package/examples/vnext/.kxm/models/critic-claude.yaml +11 -0
- package/examples/vnext/.kxm/models/critic-gemini.yaml +11 -0
- package/examples/vnext/.kxm/models/critic-grok.yaml +12 -0
- package/examples/vnext/.kxm/models/implementation.yaml +14 -0
- package/examples/vnext/.kxm/models/primary.yaml +17 -0
- package/examples/vnext/.kxm/prices.yaml +111 -0
- package/examples/vnext/.kxm/project/env.yaml +7 -0
- package/examples/vnext/.kxm/project.yaml +32 -0
- package/examples/vnext/.kxm/repo/repo.yaml +8 -0
- package/examples/vnext/.kxm/workflows/default.yaml +92 -0
- package/examples/vnext/.kxm/workflows/fix.yaml +376 -0
- package/examples/vnext/.kxm/workflows/improve.yaml +57 -0
- package/examples/vnext/README.md +53 -0
- package/examples/vnext/records/assignment-result-recorded.json +63 -0
- package/examples/vnext/records/assignment-result.json +46 -0
- package/examples/vnext/records/context-candidate.json +42 -0
- package/examples/vnext/records/delivery-manifest.json +66 -0
- package/examples/vnext/records/effect-uncertainty-resolved-sync.json +67 -0
- package/examples/vnext/records/effect-uncertainty-resolved.json +62 -0
- package/examples/vnext/records/run-created.json +54 -0
- package/examples/vnext/records/sync-event.json +65 -0
- package/examples/vnext/repositories/api/.kxm/repo/env.yaml +7 -0
- package/examples/vnext/repositories/api/.kxm/repo/repo.yaml +8 -0
- package/examples/vnext/repositories/web/.kxm/repo/repo.yaml +8 -0
- package/examples/workflow-signal.ts +63 -0
- package/package.json +129 -0
- package/plugins/kxm/.claude-plugin/plugin.json +73 -0
- package/plugins/kxm/.mcp.json +19 -0
- package/plugins/kxm/README.md +93 -0
- package/plugins/kxm/dist/cli.js +42853 -0
- package/plugins/kxm/dist/client.js +416 -0
- package/plugins/kxm/dist/core.js +1823 -0
- package/plugins/kxm/dist/extension.js +3797 -0
- package/plugins/kxm/dist/mcp-server.js +17104 -0
- package/plugins/kxm/dist/runtime.js +23361 -0
- package/plugins/kxm/dist/server.js +13640 -0
- package/plugins/kxm/dist/vnext-runtime-supervisor.js +21109 -0
- package/plugins/kxm/package.json +12 -0
- package/plugins/kxm/skills/kxm/SKILL.md +97 -0
- package/plugins/kxm/skills/kxm/references/protocol.md +103 -0
- package/plugins/kxm/skills/kxm-session/SKILL.md +53 -0
- package/plugins/kxm/src/arbiter.ts +355 -0
- package/plugins/kxm/src/artifacts-exist.ts +62 -0
- package/plugins/kxm/src/autocomplete.ts +236 -0
- package/plugins/kxm/src/cli.ts +3707 -0
- package/plugins/kxm/src/client.ts +614 -0
- package/plugins/kxm/src/commands.ts +1063 -0
- package/plugins/kxm/src/config.ts +290 -0
- package/plugins/kxm/src/context/providers.ts +101 -0
- package/plugins/kxm/src/context-packet.ts +332 -0
- package/plugins/kxm/src/context.ts +499 -0
- package/plugins/kxm/src/core.ts +6 -0
- package/plugins/kxm/src/database.ts +563 -0
- package/plugins/kxm/src/diagnostics.ts +184 -0
- package/plugins/kxm/src/envelope.ts +118 -0
- package/plugins/kxm/src/extension.ts +895 -0
- package/plugins/kxm/src/external-effects.ts +299 -0
- package/plugins/kxm/src/github-watch.ts +255 -0
- package/plugins/kxm/src/hub-binding.ts +160 -0
- package/plugins/kxm/src/hub.ts +2502 -0
- package/plugins/kxm/src/improve.ts +383 -0
- package/plugins/kxm/src/inbox.ts +10 -0
- package/plugins/kxm/src/kxm-install-kind.ts +113 -0
- package/plugins/kxm/src/kxm-update-config.ts +39 -0
- package/plugins/kxm/src/kxm-update.ts +238 -0
- package/plugins/kxm/src/local-snapshot.ts +406 -0
- package/plugins/kxm/src/logger.ts +198 -0
- package/plugins/kxm/src/mcp-server.ts +143 -0
- package/plugins/kxm/src/memory.ts +385 -0
- package/plugins/kxm/src/nous-pi.ts +287 -0
- package/plugins/kxm/src/nous-provider.ts +729 -0
- package/plugins/kxm/src/price-calc.ts +87 -0
- package/plugins/kxm/src/prices.ts +121 -0
- package/plugins/kxm/src/protocol.ts +172 -0
- package/plugins/kxm/src/recovery.ts +211 -0
- package/plugins/kxm/src/redact.ts +26 -0
- package/plugins/kxm/src/retrospective.ts +400 -0
- package/plugins/kxm/src/routing.ts +830 -0
- package/plugins/kxm/src/runtime.ts +9 -0
- package/plugins/kxm/src/server.ts +117 -0
- package/plugins/kxm/src/session-work.ts +571 -0
- package/plugins/kxm/src/session.ts +184 -0
- package/plugins/kxm/src/skills.ts +535 -0
- package/plugins/kxm/src/state.ts +326 -0
- package/plugins/kxm/src/store.ts +637 -0
- package/plugins/kxm/src/studio-layout.ts +268 -0
- package/plugins/kxm/src/suggest.ts +162 -0
- package/plugins/kxm/src/task-manager.ts +244 -0
- package/plugins/kxm/src/telemetry.ts +116 -0
- package/plugins/kxm/src/tui.ts +1046 -0
- package/plugins/kxm/src/vnext-bindings.ts +403 -0
- package/plugins/kxm/src/vnext-config.ts +1646 -0
- package/plugins/kxm/src/vnext-engine-artifacts.ts +86 -0
- package/plugins/kxm/src/vnext-engine-command.ts +533 -0
- package/plugins/kxm/src/vnext-engine-compile.ts +722 -0
- package/plugins/kxm/src/vnext-engine-evidence.ts +273 -0
- package/plugins/kxm/src/vnext-engine-fold.ts +1400 -0
- package/plugins/kxm/src/vnext-engine-gate-records.ts +583 -0
- package/plugins/kxm/src/vnext-engine-plan.ts +717 -0
- package/plugins/kxm/src/vnext-engine.ts +2458 -0
- package/plugins/kxm/src/vnext-gate-hash.ts +10 -0
- package/plugins/kxm/src/vnext-harness.ts +1142 -0
- package/plugins/kxm/src/vnext-init.ts +430 -0
- package/plugins/kxm/src/vnext-migrate.ts +1848 -0
- package/plugins/kxm/src/vnext-oneshot-producer.ts +424 -0
- package/plugins/kxm/src/vnext-permission.ts +936 -0
- package/plugins/kxm/src/vnext-pi-producer.ts +628 -0
- package/plugins/kxm/src/vnext-repair.ts +1094 -0
- package/plugins/kxm/src/vnext-runtime-owner.ts +320 -0
- package/plugins/kxm/src/vnext-runtime-store.ts +1560 -0
- package/plugins/kxm/src/vnext-runtime-supervisor.ts +586 -0
- package/plugins/kxm/src/vnext-runtime.ts +663 -0
- package/plugins/kxm/src/vnext-template.ts +247 -0
- package/plugins/kxm/src/wiki.ts +313 -0
- package/plugins/kxm/src/workflow.ts +1548 -0
- package/schemas/vnext/README.md +46 -0
- package/schemas/vnext/agent.schema.json +40 -0
- package/schemas/vnext/assignment-result.schema.json +66 -0
- package/schemas/vnext/backup-manifest.schema.json +89 -0
- package/schemas/vnext/candidate.schema.json +109 -0
- package/schemas/vnext/common.schema.json +422 -0
- package/schemas/vnext/context-candidate.schema.json +76 -0
- package/schemas/vnext/context-packet.schema.json +192 -0
- package/schemas/vnext/delivery-manifest.schema.json +159 -0
- package/schemas/vnext/environment.schema.json +66 -0
- package/schemas/vnext/gate-registry.schema.json +109 -0
- package/schemas/vnext/handoff-manifest.schema.json +146 -0
- package/schemas/vnext/init-operation.schema.json +61 -0
- package/schemas/vnext/local-repository-bindings.schema.json +30 -0
- package/schemas/vnext/memory-record.schema.json +45 -0
- package/schemas/vnext/migration-decision.schema.json +26 -0
- package/schemas/vnext/migration-plan.schema.json +123 -0
- package/schemas/vnext/migration-receipt.schema.json +52 -0
- package/schemas/vnext/model.schema.json +42 -0
- package/schemas/vnext/permission-diff.schema.json +57 -0
- package/schemas/vnext/prices.schema.json +115 -0
- package/schemas/vnext/project.schema.json +85 -0
- package/schemas/vnext/repository.schema.json +24 -0
- package/schemas/vnext/run-event.schema.json +460 -0
- package/schemas/vnext/session-brief.schema.json +153 -0
- package/schemas/vnext/sync-event.schema.json +234 -0
- package/schemas/vnext/template-provenance.schema.json +38 -0
- package/schemas/vnext/workflow.schema.json +248 -0
- package/scripts/assignment-run.d.mts +354 -0
- package/scripts/assignment-run.mjs +4451 -0
- package/scripts/build-runtime.mjs +56 -0
- package/scripts/check-generated.mjs +77 -0
- package/scripts/check-versions.mjs +34 -0
- package/scripts/emit-codex-artifacts.d.mts +9 -0
- package/scripts/emit-codex-artifacts.mjs +91 -0
- package/scripts/harness-run.d.mts +83 -0
- package/scripts/harness-run.mjs +2095 -0
- package/scripts/kxm-hub.mjs +105 -0
- package/scripts/kxm-publish-npm.mjs +327 -0
- package/scripts/kxm-release-github.mjs +472 -0
- package/scripts/kxm-runtime-supervisor.mjs +7 -0
- package/scripts/kxm-worker.mjs +1127 -0
- package/scripts/kxm.mjs +27 -0
- package/scripts/roster-policy.d.mts +20 -0
- package/scripts/roster-policy.mjs +161 -0
- package/scripts/smoke-multi-pi.mjs +479 -0
|
@@ -0,0 +1,637 @@
|
|
|
1
|
+
import { resolve } from "node:path";
|
|
2
|
+
import type { DatabaseSync } from "node:sqlite";
|
|
3
|
+
import type { AgentRecord, MessageRecord } from "./protocol.ts";
|
|
4
|
+
import type { ContextItem } from "./context.ts";
|
|
5
|
+
import type { WorkflowJournalEntry, WorkflowRun } from "./workflow.ts";
|
|
6
|
+
import {
|
|
7
|
+
openDatabase,
|
|
8
|
+
withDatabaseTransaction,
|
|
9
|
+
type DatabaseMigrationStep,
|
|
10
|
+
type DatabaseSchemaSpec,
|
|
11
|
+
} from "./database.ts";
|
|
12
|
+
|
|
13
|
+
export const HUB_STORE_SCHEMA_VERSION = 3;
|
|
14
|
+
|
|
15
|
+
export const HUB_STORE_TABLES: Readonly<Record<string, readonly string[]>> = Object.freeze({
|
|
16
|
+
agents: Object.freeze(["id", "record"]),
|
|
17
|
+
messages: Object.freeze(["id", "record"]),
|
|
18
|
+
consumer_cursors: Object.freeze(["agent_id", "cursor"]),
|
|
19
|
+
agent_sequences: Object.freeze(["agent_id", "next_seq"]),
|
|
20
|
+
workflow_runs: Object.freeze(["id", "definition_id", "delivery_id", "record"]),
|
|
21
|
+
workflow_journal: Object.freeze(["id", "run_id", "category", "area", "record"]),
|
|
22
|
+
context_items: Object.freeze(["id", "project", "kind", "record"]),
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
export const HUB_STORE_SCHEMA_V3 = `
|
|
26
|
+
CREATE TABLE IF NOT EXISTS agents (
|
|
27
|
+
id TEXT PRIMARY KEY,
|
|
28
|
+
record TEXT NOT NULL
|
|
29
|
+
) STRICT;
|
|
30
|
+
CREATE TABLE IF NOT EXISTS messages (
|
|
31
|
+
id TEXT PRIMARY KEY,
|
|
32
|
+
record TEXT NOT NULL
|
|
33
|
+
) STRICT;
|
|
34
|
+
CREATE UNIQUE INDEX IF NOT EXISTS messages_from_idempotency
|
|
35
|
+
ON messages(
|
|
36
|
+
json_extract(record, '$.from'),
|
|
37
|
+
json_extract(record, '$.idempotencyKey')
|
|
38
|
+
) WHERE json_extract(record, '$.idempotencyKey') IS NOT NULL;
|
|
39
|
+
CREATE INDEX IF NOT EXISTS messages_to_seq
|
|
40
|
+
ON messages(
|
|
41
|
+
json_extract(record, '$.to'),
|
|
42
|
+
COALESCE(json_extract(record, '$.seq'), 0)
|
|
43
|
+
);
|
|
44
|
+
CREATE TABLE IF NOT EXISTS consumer_cursors (
|
|
45
|
+
agent_id TEXT PRIMARY KEY,
|
|
46
|
+
cursor INTEGER NOT NULL
|
|
47
|
+
) STRICT;
|
|
48
|
+
CREATE TABLE IF NOT EXISTS agent_sequences (
|
|
49
|
+
agent_id TEXT PRIMARY KEY,
|
|
50
|
+
next_seq INTEGER NOT NULL
|
|
51
|
+
) STRICT;
|
|
52
|
+
CREATE TABLE IF NOT EXISTS workflow_runs (
|
|
53
|
+
id TEXT PRIMARY KEY,
|
|
54
|
+
definition_id TEXT NOT NULL,
|
|
55
|
+
delivery_id TEXT NOT NULL,
|
|
56
|
+
record TEXT NOT NULL,
|
|
57
|
+
UNIQUE(definition_id, delivery_id)
|
|
58
|
+
) STRICT;
|
|
59
|
+
CREATE TABLE IF NOT EXISTS workflow_journal (
|
|
60
|
+
id TEXT PRIMARY KEY,
|
|
61
|
+
run_id TEXT NOT NULL,
|
|
62
|
+
category TEXT NOT NULL,
|
|
63
|
+
area TEXT NOT NULL,
|
|
64
|
+
record TEXT NOT NULL
|
|
65
|
+
) STRICT;
|
|
66
|
+
CREATE INDEX IF NOT EXISTS workflow_journal_run_id ON workflow_journal(run_id);
|
|
67
|
+
CREATE TABLE IF NOT EXISTS context_items (
|
|
68
|
+
id TEXT PRIMARY KEY,
|
|
69
|
+
project TEXT NOT NULL,
|
|
70
|
+
kind TEXT NOT NULL,
|
|
71
|
+
record TEXT NOT NULL
|
|
72
|
+
) STRICT;
|
|
73
|
+
CREATE INDEX IF NOT EXISTS context_items_project ON context_items(project);
|
|
74
|
+
`;
|
|
75
|
+
|
|
76
|
+
export const HUB_STORE_MIGRATIONS: readonly DatabaseMigrationStep[] = Object.freeze([
|
|
77
|
+
{
|
|
78
|
+
fromVersion: 1,
|
|
79
|
+
toVersion: 2,
|
|
80
|
+
migrate: (db: DatabaseSync) => {
|
|
81
|
+
db.exec(`
|
|
82
|
+
CREATE TABLE IF NOT EXISTS agents (
|
|
83
|
+
id TEXT PRIMARY KEY,
|
|
84
|
+
record TEXT NOT NULL
|
|
85
|
+
) STRICT;
|
|
86
|
+
CREATE TABLE IF NOT EXISTS messages (
|
|
87
|
+
id TEXT PRIMARY KEY,
|
|
88
|
+
record TEXT NOT NULL
|
|
89
|
+
) STRICT;
|
|
90
|
+
CREATE TABLE IF NOT EXISTS workflow_runs (
|
|
91
|
+
id TEXT PRIMARY KEY,
|
|
92
|
+
definition_id TEXT NOT NULL,
|
|
93
|
+
delivery_id TEXT NOT NULL,
|
|
94
|
+
record TEXT NOT NULL,
|
|
95
|
+
UNIQUE(definition_id, delivery_id)
|
|
96
|
+
) STRICT;
|
|
97
|
+
CREATE TABLE IF NOT EXISTS workflow_journal (
|
|
98
|
+
id TEXT PRIMARY KEY,
|
|
99
|
+
run_id TEXT NOT NULL,
|
|
100
|
+
category TEXT NOT NULL,
|
|
101
|
+
area TEXT NOT NULL,
|
|
102
|
+
record TEXT NOT NULL
|
|
103
|
+
) STRICT;
|
|
104
|
+
CREATE INDEX IF NOT EXISTS workflow_journal_run_id ON workflow_journal(run_id);
|
|
105
|
+
`);
|
|
106
|
+
},
|
|
107
|
+
},
|
|
108
|
+
{
|
|
109
|
+
fromVersion: 2,
|
|
110
|
+
toVersion: 3,
|
|
111
|
+
migrate: (db: DatabaseSync) => {
|
|
112
|
+
db.exec(`
|
|
113
|
+
CREATE TABLE IF NOT EXISTS agents (
|
|
114
|
+
id TEXT PRIMARY KEY,
|
|
115
|
+
record TEXT NOT NULL
|
|
116
|
+
) STRICT;
|
|
117
|
+
CREATE TABLE IF NOT EXISTS messages (
|
|
118
|
+
id TEXT PRIMARY KEY,
|
|
119
|
+
record TEXT NOT NULL
|
|
120
|
+
) STRICT;
|
|
121
|
+
CREATE TABLE IF NOT EXISTS workflow_runs (
|
|
122
|
+
id TEXT PRIMARY KEY,
|
|
123
|
+
definition_id TEXT NOT NULL,
|
|
124
|
+
delivery_id TEXT NOT NULL,
|
|
125
|
+
record TEXT NOT NULL,
|
|
126
|
+
UNIQUE(definition_id, delivery_id)
|
|
127
|
+
) STRICT;
|
|
128
|
+
CREATE TABLE IF NOT EXISTS workflow_journal (
|
|
129
|
+
id TEXT PRIMARY KEY,
|
|
130
|
+
run_id TEXT NOT NULL,
|
|
131
|
+
category TEXT NOT NULL,
|
|
132
|
+
area TEXT NOT NULL,
|
|
133
|
+
record TEXT NOT NULL
|
|
134
|
+
) STRICT;
|
|
135
|
+
CREATE INDEX IF NOT EXISTS workflow_journal_run_id ON workflow_journal(run_id);
|
|
136
|
+
CREATE TABLE IF NOT EXISTS context_items (
|
|
137
|
+
id TEXT PRIMARY KEY,
|
|
138
|
+
project TEXT NOT NULL,
|
|
139
|
+
kind TEXT NOT NULL,
|
|
140
|
+
record TEXT NOT NULL
|
|
141
|
+
) STRICT;
|
|
142
|
+
CREATE INDEX IF NOT EXISTS context_items_project ON context_items(project);
|
|
143
|
+
CREATE UNIQUE INDEX IF NOT EXISTS messages_from_idempotency
|
|
144
|
+
ON messages(
|
|
145
|
+
json_extract(record, '$.from'),
|
|
146
|
+
json_extract(record, '$.idempotencyKey')
|
|
147
|
+
) WHERE json_extract(record, '$.idempotencyKey') IS NOT NULL;
|
|
148
|
+
CREATE INDEX IF NOT EXISTS messages_to_seq
|
|
149
|
+
ON messages(
|
|
150
|
+
json_extract(record, '$.to'),
|
|
151
|
+
COALESCE(json_extract(record, '$.seq'), 0)
|
|
152
|
+
);
|
|
153
|
+
CREATE TABLE IF NOT EXISTS consumer_cursors (
|
|
154
|
+
agent_id TEXT PRIMARY KEY,
|
|
155
|
+
cursor INTEGER NOT NULL
|
|
156
|
+
) STRICT;
|
|
157
|
+
CREATE TABLE IF NOT EXISTS agent_sequences (
|
|
158
|
+
agent_id TEXT PRIMARY KEY,
|
|
159
|
+
next_seq INTEGER NOT NULL
|
|
160
|
+
) STRICT;
|
|
161
|
+
`);
|
|
162
|
+
},
|
|
163
|
+
},
|
|
164
|
+
]);
|
|
165
|
+
|
|
166
|
+
export const HUB_STORE_SCHEMA_SPEC: DatabaseSchemaSpec = Object.freeze({
|
|
167
|
+
schema: HUB_STORE_SCHEMA_V3,
|
|
168
|
+
version: HUB_STORE_SCHEMA_VERSION,
|
|
169
|
+
tables: HUB_STORE_TABLES,
|
|
170
|
+
migrations: HUB_STORE_MIGRATIONS,
|
|
171
|
+
});
|
|
172
|
+
|
|
173
|
+
export interface StoredAgent extends AgentRecord {
|
|
174
|
+
key: string;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
class MessageMap extends Map<string, MessageRecord> {
|
|
178
|
+
private readonly store: MeshStore;
|
|
179
|
+
|
|
180
|
+
constructor(store: MeshStore) {
|
|
181
|
+
super();
|
|
182
|
+
this.store = store;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
override get(id: string): MessageRecord | undefined {
|
|
186
|
+
const cached = super.get(id);
|
|
187
|
+
if (cached) return cached;
|
|
188
|
+
return this.store.loadMessageFromDb(id);
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
override has(id: string): boolean {
|
|
192
|
+
if (super.has(id)) return true;
|
|
193
|
+
return this.store.hasMessage(id);
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
override delete(id: string): boolean {
|
|
197
|
+
this.store.deleteMessage(id);
|
|
198
|
+
return super.delete(id);
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
override get size(): number {
|
|
202
|
+
return this.store.countMessages();
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
get cachedSize(): number {
|
|
206
|
+
return super.size;
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
export class MeshStore {
|
|
211
|
+
readonly agents = new Map<string, StoredAgent>();
|
|
212
|
+
readonly messages: MessageMap;
|
|
213
|
+
readonly workflowRuns = new Map<string, WorkflowRun>();
|
|
214
|
+
readonly journal = new Map<string, WorkflowJournalEntry>();
|
|
215
|
+
readonly contextItems = new Map<string, ContextItem>();
|
|
216
|
+
readonly path?: string;
|
|
217
|
+
private readonly database?: DatabaseSync;
|
|
218
|
+
private readonly agentSequences = new Map<string, number>();
|
|
219
|
+
private readonly consumerCursors = new Map<string, number>();
|
|
220
|
+
|
|
221
|
+
constructor(path?: string) {
|
|
222
|
+
this.messages = new MessageMap(this);
|
|
223
|
+
if (!path) return;
|
|
224
|
+
this.path = path === ":memory:" ? path : resolve(path);
|
|
225
|
+
this.database = openDatabase(this.path, "hub database", HUB_STORE_SCHEMA_SPEC);
|
|
226
|
+
this.load();
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
get persistent(): boolean {
|
|
230
|
+
return Boolean(this.database && this.path !== ":memory:");
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
saveAgent(agent: StoredAgent): void {
|
|
234
|
+
this.agents.set(agent.id, agent);
|
|
235
|
+
this.database?.prepare(`
|
|
236
|
+
INSERT INTO agents (id, record) VALUES (?, ?)
|
|
237
|
+
ON CONFLICT(id) DO UPDATE SET record = excluded.record
|
|
238
|
+
`).run(agent.id, JSON.stringify(agent));
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
saveMessage(message: MessageRecord): void {
|
|
242
|
+
Map.prototype.set.call(this.messages, message.id, message);
|
|
243
|
+
this.database?.prepare(`
|
|
244
|
+
INSERT INTO messages (id, record) VALUES (?, ?)
|
|
245
|
+
ON CONFLICT(id) DO UPDATE SET record = excluded.record
|
|
246
|
+
`).run(message.id, JSON.stringify(message));
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
deleteMessage(messageId: string): void {
|
|
250
|
+
Map.prototype.delete.call(this.messages, messageId);
|
|
251
|
+
this.database?.prepare("DELETE FROM messages WHERE id = ?").run(messageId);
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
loadMessageFromDb(id: string): MessageRecord | undefined {
|
|
255
|
+
if (!this.database) return undefined;
|
|
256
|
+
const row = this.database.prepare("SELECT record FROM messages WHERE id = ?").get(id) as { record: string } | undefined;
|
|
257
|
+
if (!row) return undefined;
|
|
258
|
+
try {
|
|
259
|
+
const msg = JSON.parse(row.record) as MessageRecord;
|
|
260
|
+
Map.prototype.set.call(this.messages, msg.id, msg);
|
|
261
|
+
return msg;
|
|
262
|
+
} catch {
|
|
263
|
+
return undefined;
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
hasMessage(id: string): boolean {
|
|
268
|
+
if (Map.prototype.has.call(this.messages, id)) return true;
|
|
269
|
+
if (!this.database) return false;
|
|
270
|
+
const row = this.database.prepare("SELECT 1 AS ok FROM messages WHERE id = ?").get(id) as { ok?: number } | undefined;
|
|
271
|
+
return Boolean(row?.ok);
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
countMessages(): number {
|
|
275
|
+
if (!this.database) return [...Map.prototype.keys.call(this.messages)].length;
|
|
276
|
+
const row = this.database.prepare("SELECT COUNT(*) AS total FROM messages").get() as { total: number } | undefined;
|
|
277
|
+
return Number(row?.total ?? 0);
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
nextAgentSequence(agentId: string): number {
|
|
281
|
+
if (!this.database) {
|
|
282
|
+
const current = this.agentSequences.get(agentId) ?? 0;
|
|
283
|
+
const next = current + 1;
|
|
284
|
+
this.agentSequences.set(agentId, next);
|
|
285
|
+
return next;
|
|
286
|
+
}
|
|
287
|
+
this.database.exec("BEGIN IMMEDIATE");
|
|
288
|
+
try {
|
|
289
|
+
const row = this.database.prepare("SELECT next_seq FROM agent_sequences WHERE agent_id = ?").get(agentId) as { next_seq: number } | undefined;
|
|
290
|
+
let next = row?.next_seq;
|
|
291
|
+
if (next === undefined) {
|
|
292
|
+
const maxRow = this.database.prepare(`
|
|
293
|
+
SELECT COALESCE(MAX(json_extract(record, '$.seq')), 0) AS max_seq
|
|
294
|
+
FROM messages WHERE json_extract(record, '$.to') = ?
|
|
295
|
+
`).get(agentId) as { max_seq: number } | undefined;
|
|
296
|
+
next = Number(maxRow?.max_seq ?? 0) + 1;
|
|
297
|
+
}
|
|
298
|
+
this.database.prepare(`
|
|
299
|
+
INSERT INTO agent_sequences (agent_id, next_seq) VALUES (?, ?)
|
|
300
|
+
ON CONFLICT(agent_id) DO UPDATE SET next_seq = excluded.next_seq
|
|
301
|
+
`).run(agentId, next + 1);
|
|
302
|
+
this.database.exec("COMMIT");
|
|
303
|
+
this.agentSequences.set(agentId, next + 1);
|
|
304
|
+
return next;
|
|
305
|
+
} catch (err) {
|
|
306
|
+
try { this.database.exec("ROLLBACK"); } catch { /* rollback */ }
|
|
307
|
+
throw err;
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
getConsumerCursor(agentId: string): number {
|
|
312
|
+
if (!this.database) {
|
|
313
|
+
return this.consumerCursors.get(agentId) ?? 0;
|
|
314
|
+
}
|
|
315
|
+
const row = this.database.prepare("SELECT cursor FROM consumer_cursors WHERE agent_id = ?").get(agentId) as { cursor: number } | undefined;
|
|
316
|
+
const cursor = Number(row?.cursor ?? 0);
|
|
317
|
+
this.consumerCursors.set(agentId, cursor);
|
|
318
|
+
return cursor;
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
advanceCursor(agentId: string, seq: number): number {
|
|
322
|
+
if (!this.database) {
|
|
323
|
+
const current = this.consumerCursors.get(agentId) ?? 0;
|
|
324
|
+
const next = Math.max(current, seq);
|
|
325
|
+
this.consumerCursors.set(agentId, next);
|
|
326
|
+
return next;
|
|
327
|
+
}
|
|
328
|
+
this.database.prepare(`
|
|
329
|
+
INSERT INTO consumer_cursors (agent_id, cursor) VALUES (?, ?)
|
|
330
|
+
ON CONFLICT(agent_id) DO UPDATE SET cursor = MAX(consumer_cursors.cursor, excluded.cursor)
|
|
331
|
+
`).run(agentId, seq);
|
|
332
|
+
return this.getConsumerCursor(agentId);
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
getPendingMessages(agentId: string, cursor = 0): MessageRecord[] {
|
|
336
|
+
if (!this.database) {
|
|
337
|
+
return [...Map.prototype.values.call(this.messages)]
|
|
338
|
+
.filter((m) => m.to === agentId && (m.status === "queued" || m.status === "delivered") && (m.seq ?? 0) > cursor)
|
|
339
|
+
.sort((a, b) => (a.seq ?? 0) - (b.seq ?? 0));
|
|
340
|
+
}
|
|
341
|
+
const rows = this.database.prepare(`
|
|
342
|
+
SELECT record FROM messages
|
|
343
|
+
WHERE json_extract(record, '$.to') = ?
|
|
344
|
+
AND json_extract(record, '$.status') IN ('queued', 'delivered')
|
|
345
|
+
AND COALESCE(json_extract(record, '$.seq'), 0) > ?
|
|
346
|
+
ORDER BY COALESCE(json_extract(record, '$.seq'), 0) ASC
|
|
347
|
+
`).all(agentId, cursor) as Array<{ record: string }>;
|
|
348
|
+
const result: MessageRecord[] = [];
|
|
349
|
+
for (const row of rows) {
|
|
350
|
+
try {
|
|
351
|
+
const msg = JSON.parse(row.record) as MessageRecord;
|
|
352
|
+
Map.prototype.set.call(this.messages, msg.id, msg);
|
|
353
|
+
result.push(msg);
|
|
354
|
+
} catch { /* skip */ }
|
|
355
|
+
}
|
|
356
|
+
return result;
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
findMessageByIdempotency(fromId: string, idempotencyKey: string): MessageRecord | undefined {
|
|
360
|
+
for (const m of Map.prototype.values.call(this.messages)) {
|
|
361
|
+
if (m.from === fromId && m.idempotencyKey === idempotencyKey) return m;
|
|
362
|
+
}
|
|
363
|
+
if (!this.database) return undefined;
|
|
364
|
+
const row = this.database.prepare(`
|
|
365
|
+
SELECT record FROM messages
|
|
366
|
+
WHERE json_extract(record, '$.from') = ? AND json_extract(record, '$.idempotencyKey') = ?
|
|
367
|
+
`).get(fromId, idempotencyKey) as { record: string } | undefined;
|
|
368
|
+
if (!row) return undefined;
|
|
369
|
+
try {
|
|
370
|
+
const msg = JSON.parse(row.record) as MessageRecord;
|
|
371
|
+
Map.prototype.set.call(this.messages, msg.id, msg);
|
|
372
|
+
return msg;
|
|
373
|
+
} catch {
|
|
374
|
+
return undefined;
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
getOpenMessages(project: string): MessageRecord[] {
|
|
379
|
+
if (!this.database) {
|
|
380
|
+
return [...Map.prototype.values.call(this.messages)]
|
|
381
|
+
.filter((m) => m.project === project && (m.status === "queued" || m.status === "delivered"))
|
|
382
|
+
.sort((a, b) => Date.parse(b.createdAt) - Date.parse(a.createdAt));
|
|
383
|
+
}
|
|
384
|
+
const rows = this.database.prepare(`
|
|
385
|
+
SELECT record FROM messages
|
|
386
|
+
WHERE json_extract(record, '$.project') = ?
|
|
387
|
+
AND json_extract(record, '$.status') IN ('queued', 'delivered')
|
|
388
|
+
ORDER BY json_extract(record, '$.createdAt') DESC
|
|
389
|
+
`).all(project) as Array<{ record: string }>;
|
|
390
|
+
const result: MessageRecord[] = [];
|
|
391
|
+
for (const row of rows) {
|
|
392
|
+
try {
|
|
393
|
+
const msg = JSON.parse(row.record) as MessageRecord;
|
|
394
|
+
Map.prototype.set.call(this.messages, msg.id, msg);
|
|
395
|
+
result.push(msg);
|
|
396
|
+
} catch { /* skip */ }
|
|
397
|
+
}
|
|
398
|
+
return result;
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
getExpiringMessages(nowMs = Date.now()): MessageRecord[] {
|
|
402
|
+
const nowIso = new Date(nowMs).toISOString();
|
|
403
|
+
if (!this.database) {
|
|
404
|
+
return [...Map.prototype.values.call(this.messages)]
|
|
405
|
+
.filter((m) => (m.status === "queued" || m.status === "delivered") && Date.parse(m.expiresAt) <= nowMs);
|
|
406
|
+
}
|
|
407
|
+
const rows = this.database.prepare(`
|
|
408
|
+
SELECT record FROM messages
|
|
409
|
+
WHERE json_extract(record, '$.status') IN ('queued', 'delivered')
|
|
410
|
+
AND json_extract(record, '$.expiresAt') <= ?
|
|
411
|
+
`).all(nowIso) as Array<{ record: string }>;
|
|
412
|
+
const result: MessageRecord[] = [];
|
|
413
|
+
for (const row of rows) {
|
|
414
|
+
try {
|
|
415
|
+
const msg = JSON.parse(row.record) as MessageRecord;
|
|
416
|
+
Map.prototype.set.call(this.messages, msg.id, msg);
|
|
417
|
+
result.push(msg);
|
|
418
|
+
} catch { /* skip */ }
|
|
419
|
+
}
|
|
420
|
+
return result;
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
deleteWorkflowRun(runId: string): void {
|
|
424
|
+
this.workflowRuns.delete(runId);
|
|
425
|
+
this.database?.prepare("DELETE FROM workflow_runs WHERE id = ?").run(runId);
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
deleteJournalEntry(id: string): void {
|
|
429
|
+
this.journal.delete(id);
|
|
430
|
+
this.database?.prepare("DELETE FROM workflow_journal WHERE id = ?").run(id);
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
deleteContextItem(id: string): void {
|
|
434
|
+
this.contextItems.delete(id);
|
|
435
|
+
this.database?.prepare("DELETE FROM context_items WHERE id = ?").run(id);
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
sweepRetention(
|
|
439
|
+
messageRetentionMs: number,
|
|
440
|
+
runRetentionMs = 7 * 86_400_000,
|
|
441
|
+
nowMs = Date.now(),
|
|
442
|
+
): {
|
|
443
|
+
purgedMessages: MessageRecord[];
|
|
444
|
+
purgedRuns: string[];
|
|
445
|
+
purgedJournal: string[];
|
|
446
|
+
purgedContextItems: string[];
|
|
447
|
+
} {
|
|
448
|
+
const messageCutoffMs = nowMs - messageRetentionMs;
|
|
449
|
+
const messageCutoffIso = new Date(messageCutoffMs).toISOString();
|
|
450
|
+
const runCutoffMs = nowMs - runRetentionMs;
|
|
451
|
+
const purgedMessages: MessageRecord[] = [];
|
|
452
|
+
const purgedRuns: string[] = [];
|
|
453
|
+
const purgedJournal: string[] = [];
|
|
454
|
+
const purgedContextItems: string[] = [];
|
|
455
|
+
|
|
456
|
+
// 1. Messages
|
|
457
|
+
if (!this.database) {
|
|
458
|
+
for (const m of [...Map.prototype.values.call(this.messages)]) {
|
|
459
|
+
const terminal = m.status === "replied" || m.status === "cancelled" || m.status === "expired" || m.status === "error";
|
|
460
|
+
if (!terminal) continue;
|
|
461
|
+
const terminalAt = m.repliedAt ?? m.cancelledAt ?? m.expiresAt ?? m.createdAt;
|
|
462
|
+
if (Date.parse(terminalAt) <= messageCutoffMs) {
|
|
463
|
+
Map.prototype.delete.call(this.messages, m.id);
|
|
464
|
+
purgedMessages.push(m);
|
|
465
|
+
}
|
|
466
|
+
}
|
|
467
|
+
} else {
|
|
468
|
+
const rows = this.database.prepare(`
|
|
469
|
+
SELECT record FROM messages
|
|
470
|
+
WHERE json_extract(record, '$.status') IN ('replied', 'cancelled', 'expired', 'error')
|
|
471
|
+
AND COALESCE(json_extract(record, '$.repliedAt'), json_extract(record, '$.cancelledAt'), json_extract(record, '$.expiresAt'), json_extract(record, '$.createdAt')) <= ?
|
|
472
|
+
`).all(messageCutoffIso) as Array<{ record: string }>;
|
|
473
|
+
for (const row of rows) {
|
|
474
|
+
try {
|
|
475
|
+
const msg = JSON.parse(row.record) as MessageRecord;
|
|
476
|
+
Map.prototype.delete.call(this.messages, msg.id);
|
|
477
|
+
this.database.prepare("DELETE FROM messages WHERE id = ?").run(msg.id);
|
|
478
|
+
purgedMessages.push(msg);
|
|
479
|
+
} catch { /* skip */ }
|
|
480
|
+
}
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
// 2. Runs & their journal entries
|
|
484
|
+
const terminalRunsToPurge: string[] = [];
|
|
485
|
+
for (const run of this.workflowRuns.values()) {
|
|
486
|
+
const terminal = run.status === "completed" || run.status === "failed";
|
|
487
|
+
if (!terminal) continue;
|
|
488
|
+
const terminalAt = run.updatedAt ?? run.createdAt;
|
|
489
|
+
if (Date.parse(terminalAt) <= runCutoffMs) {
|
|
490
|
+
terminalRunsToPurge.push(run.id);
|
|
491
|
+
}
|
|
492
|
+
}
|
|
493
|
+
for (const runId of terminalRunsToPurge) {
|
|
494
|
+
this.deleteWorkflowRun(runId);
|
|
495
|
+
purgedRuns.push(runId);
|
|
496
|
+
const toDeleteJournal: string[] = [];
|
|
497
|
+
for (const entry of this.journal.values()) {
|
|
498
|
+
if (entry.runId === runId) toDeleteJournal.push(entry.id);
|
|
499
|
+
}
|
|
500
|
+
for (const jid of toDeleteJournal) {
|
|
501
|
+
this.deleteJournalEntry(jid);
|
|
502
|
+
purgedJournal.push(jid);
|
|
503
|
+
}
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
// 3. Orphan journal entries
|
|
507
|
+
const orphanJournal: string[] = [];
|
|
508
|
+
for (const entry of this.journal.values()) {
|
|
509
|
+
if (Date.parse(entry.createdAt) <= runCutoffMs && !this.workflowRuns.has(entry.runId)) {
|
|
510
|
+
orphanJournal.push(entry.id);
|
|
511
|
+
}
|
|
512
|
+
}
|
|
513
|
+
for (const jid of orphanJournal) {
|
|
514
|
+
this.deleteJournalEntry(jid);
|
|
515
|
+
purgedJournal.push(jid);
|
|
516
|
+
}
|
|
517
|
+
|
|
518
|
+
// 4. Context items
|
|
519
|
+
const contextItemsToPurge: string[] = [];
|
|
520
|
+
for (const item of this.contextItems.values()) {
|
|
521
|
+
const terminal = item.status === "superseded" || item.status === "rejected" || (item.validUntil !== undefined && Date.parse(item.validUntil) <= runCutoffMs);
|
|
522
|
+
if (!terminal) continue;
|
|
523
|
+
const terminalAt = item.validUntil ?? item.observedAt;
|
|
524
|
+
if (terminalAt && Date.parse(terminalAt) <= runCutoffMs) {
|
|
525
|
+
contextItemsToPurge.push(item.id);
|
|
526
|
+
}
|
|
527
|
+
}
|
|
528
|
+
for (const id of contextItemsToPurge) {
|
|
529
|
+
this.deleteContextItem(id);
|
|
530
|
+
purgedContextItems.push(id);
|
|
531
|
+
}
|
|
532
|
+
|
|
533
|
+
return { purgedMessages, purgedRuns, purgedJournal, purgedContextItems };
|
|
534
|
+
}
|
|
535
|
+
|
|
536
|
+
saveWorkflowRun(run: WorkflowRun): void {
|
|
537
|
+
this.workflowRuns.set(run.id, run);
|
|
538
|
+
this.database?.prepare(`
|
|
539
|
+
INSERT INTO workflow_runs (id, definition_id, delivery_id, record) VALUES (?, ?, ?, ?)
|
|
540
|
+
ON CONFLICT(id) DO UPDATE SET record = excluded.record
|
|
541
|
+
`).run(run.id, run.definitionId, run.deliveryId, JSON.stringify(run));
|
|
542
|
+
}
|
|
543
|
+
|
|
544
|
+
saveJournalEntry(entry: WorkflowJournalEntry): void {
|
|
545
|
+
this.journal.set(entry.id, entry);
|
|
546
|
+
this.database?.prepare(`
|
|
547
|
+
INSERT INTO workflow_journal (id, run_id, category, area, record) VALUES (?, ?, ?, ?, ?)
|
|
548
|
+
ON CONFLICT(id) DO UPDATE SET record = excluded.record
|
|
549
|
+
`).run(entry.id, entry.runId, entry.category, entry.area, JSON.stringify(entry));
|
|
550
|
+
}
|
|
551
|
+
|
|
552
|
+
saveContextItem(item: ContextItem): void {
|
|
553
|
+
this.contextItems.set(item.id, item);
|
|
554
|
+
this.database?.prepare(`
|
|
555
|
+
INSERT INTO context_items (id, project, kind, record) VALUES (?, ?, ?, ?)
|
|
556
|
+
ON CONFLICT(id) DO UPDATE SET record = excluded.record
|
|
557
|
+
`).run(item.id, item.project, item.kind, JSON.stringify(item));
|
|
558
|
+
}
|
|
559
|
+
|
|
560
|
+
getContextItem(id: string, project?: string): ContextItem | undefined {
|
|
561
|
+
const item = this.contextItems.get(id);
|
|
562
|
+
if (!item) return undefined;
|
|
563
|
+
if (project !== undefined && item.project !== project) return undefined;
|
|
564
|
+
return item;
|
|
565
|
+
}
|
|
566
|
+
|
|
567
|
+
listContextItems(project: string, kinds?: ContextItem["kind"][]): ContextItem[] {
|
|
568
|
+
const wanted = kinds ? new Set(kinds) : undefined;
|
|
569
|
+
return [...this.contextItems.values()]
|
|
570
|
+
.filter((item) => item.project === project)
|
|
571
|
+
.filter((item) => wanted === undefined || wanted.has(item.kind))
|
|
572
|
+
.sort((left, right) => left.id.localeCompare(right.id));
|
|
573
|
+
}
|
|
574
|
+
|
|
575
|
+
saveWorkflowTransition(
|
|
576
|
+
run: WorkflowRun,
|
|
577
|
+
message?: MessageRecord,
|
|
578
|
+
entry?: WorkflowJournalEntry,
|
|
579
|
+
): void {
|
|
580
|
+
if (this.database) {
|
|
581
|
+
withDatabaseTransaction(this.database, () => {
|
|
582
|
+
if (message) {
|
|
583
|
+
this.database!.prepare(`
|
|
584
|
+
INSERT INTO messages (id, record) VALUES (?, ?)
|
|
585
|
+
ON CONFLICT(id) DO UPDATE SET record = excluded.record
|
|
586
|
+
`).run(message.id, JSON.stringify(message));
|
|
587
|
+
}
|
|
588
|
+
if (entry) {
|
|
589
|
+
this.database!.prepare(`
|
|
590
|
+
INSERT INTO workflow_journal (id, run_id, category, area, record) VALUES (?, ?, ?, ?, ?)
|
|
591
|
+
ON CONFLICT(id) DO UPDATE SET record = excluded.record
|
|
592
|
+
`).run(entry.id, entry.runId, entry.category, entry.area, JSON.stringify(entry));
|
|
593
|
+
}
|
|
594
|
+
this.database!.prepare(`
|
|
595
|
+
INSERT INTO workflow_runs (id, definition_id, delivery_id, record) VALUES (?, ?, ?, ?)
|
|
596
|
+
ON CONFLICT(id) DO UPDATE SET record = excluded.record
|
|
597
|
+
`).run(run.id, run.definitionId, run.deliveryId, JSON.stringify(run));
|
|
598
|
+
});
|
|
599
|
+
}
|
|
600
|
+
if (message) Map.prototype.set.call(this.messages, message.id, message);
|
|
601
|
+
if (entry) this.journal.set(entry.id, entry);
|
|
602
|
+
this.workflowRuns.set(run.id, run);
|
|
603
|
+
}
|
|
604
|
+
|
|
605
|
+
healthy(): boolean {
|
|
606
|
+
if (!this.database) return true;
|
|
607
|
+
return this.database.prepare("SELECT 1 AS ok").get() !== undefined;
|
|
608
|
+
}
|
|
609
|
+
|
|
610
|
+
close(): void {
|
|
611
|
+
this.database?.close();
|
|
612
|
+
}
|
|
613
|
+
|
|
614
|
+
private load(): void {
|
|
615
|
+
if (!this.database) return;
|
|
616
|
+
const agentRows = this.database.prepare("SELECT record FROM agents").all() as Array<{ record: string }>;
|
|
617
|
+
const workflowRows = this.database.prepare("SELECT record FROM workflow_runs").all() as Array<{ record: string }>;
|
|
618
|
+
const journalRows = this.database.prepare("SELECT record FROM workflow_journal").all() as Array<{ record: string }>;
|
|
619
|
+
const contextRows = this.database.prepare("SELECT record FROM context_items").all() as Array<{ record: string }>;
|
|
620
|
+
for (const row of agentRows) {
|
|
621
|
+
const agent = JSON.parse(row.record) as StoredAgent;
|
|
622
|
+
this.agents.set(agent.id, agent);
|
|
623
|
+
}
|
|
624
|
+
for (const row of workflowRows) {
|
|
625
|
+
const run = JSON.parse(row.record) as WorkflowRun;
|
|
626
|
+
this.workflowRuns.set(run.id, run);
|
|
627
|
+
}
|
|
628
|
+
for (const row of journalRows) {
|
|
629
|
+
const entry = JSON.parse(row.record) as WorkflowJournalEntry;
|
|
630
|
+
this.journal.set(entry.id, entry);
|
|
631
|
+
}
|
|
632
|
+
for (const row of contextRows) {
|
|
633
|
+
const item = JSON.parse(row.record) as ContextItem;
|
|
634
|
+
this.contextItems.set(item.id, item);
|
|
635
|
+
}
|
|
636
|
+
}
|
|
637
|
+
}
|