openclaw-scheduler 0.2.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/AGENTS.md +302 -0
- package/BEST-PRACTICES.md +506 -0
- package/CHANGELOG.md +82 -0
- package/CODE_OF_CONDUCT.md +22 -0
- package/CONTEXT.md +26 -0
- package/CONTRIBUTING.md +73 -0
- package/IMPLEMENTATION_SPEC.md +170 -0
- package/INSTALL-ADDITIONAL-HOST.md +333 -0
- package/INSTALL-LINUX.md +419 -0
- package/INSTALL-WINDOWS.md +305 -0
- package/INSTALL.md +364 -0
- package/JOB-QUICK-REF.md +222 -0
- package/LICENSE +21 -0
- package/QUICK-START.md +256 -0
- package/README.md +2170 -0
- package/SECURITY.md +34 -0
- package/UNINSTALL.md +129 -0
- package/UPGRADING.md +436 -0
- package/agents.js +67 -0
- package/approval.js +107 -0
- package/backup.js +390 -0
- package/bin/openclaw-scheduler.js +138 -0
- package/cli.js +1083 -0
- package/db.js +122 -0
- package/dispatch/529-recovery.mjs +204 -0
- package/dispatch/README.md +372 -0
- package/dispatch/config.example.json +24 -0
- package/dispatch/deliver-watcher.sh +57 -0
- package/dispatch/hooks.mjs +171 -0
- package/dispatch/index.mjs +1836 -0
- package/dispatch/watcher.mjs +1396 -0
- package/dispatch-queue.js +112 -0
- package/dispatcher-approvals.js +96 -0
- package/dispatcher-delivery.js +43 -0
- package/dispatcher-maintenance.js +242 -0
- package/dispatcher-shell.js +29 -0
- package/dispatcher-strategies.js +1280 -0
- package/dispatcher-utils.js +81 -0
- package/dispatcher.js +855 -0
- package/docs/adr-schedule-ownership.md +73 -0
- package/docs/gateway-contract.md +904 -0
- package/docs/plans/2026-03-09-fix-typescript-types.md +91 -0
- package/docs/plans/2026-03-09-test-coverage-gaps.md +83 -0
- package/docs/plans/2026-03-10-dispatcher-refactor.md +801 -0
- package/docs/trust-architecture.md +266 -0
- package/gateway.js +473 -0
- package/idempotency.js +119 -0
- package/index.d.ts +864 -0
- package/index.js +17 -0
- package/jobs.js +1224 -0
- package/messages.js +357 -0
- package/migrate-consolidate.js +694 -0
- package/migrate.js +125 -0
- package/package.json +130 -0
- package/paths.js +79 -0
- package/prompt-context.js +94 -0
- package/retrieval.js +176 -0
- package/runs.js +270 -0
- package/scheduler-schema.js +101 -0
- package/schema.sql +480 -0
- package/scripts/dispatch-cli-utils.mjs +65 -0
- package/scripts/inbox-consumer.mjs +288 -0
- package/scripts/stuck-detector.sh +18 -0
- package/scripts/stuck-run-detector.mjs +333 -0
- package/scripts/telegram-webhook-check.mjs +238 -0
- package/setup.mjs +724 -0
- package/shell-result.js +214 -0
- package/task-tracker.js +300 -0
- package/team-adapter.js +335 -0
- package/v02-runtime.js +599 -0
|
@@ -0,0 +1,694 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* migrate-consolidate.js -- Single idempotent migration for existing databases
|
|
3
|
+
*
|
|
4
|
+
* Brings any DB from any prior version up to the current schema (v23).
|
|
5
|
+
* Fresh installs get everything from schema.sql directly -- this only
|
|
6
|
+
* runs ALTER TABLEs needed for DBs created before the current schema.
|
|
7
|
+
*
|
|
8
|
+
* Replaces: migrate-v3.js, migrate-v3b.js, migrate-v5.js, migrate-v6.js,
|
|
9
|
+
* migrate-v7.js, migrate-v8.js, migrate-v9.js, migrate-v10.js, migrate-v11.js, migrate-v12.js, migrate-v13.js, migrate-v14.js, migrate-v15.js, migrate-v16.js, migrate-v17.js, migrate-v18.js, migrate-v19.js, migrate-v20.js
|
|
10
|
+
*
|
|
11
|
+
* Safe to run multiple times -- all operations are idempotent.
|
|
12
|
+
* Note: schedule_cron NOT NULL constraint cannot be dropped via ALTER TABLE in SQLite.
|
|
13
|
+
* At-jobs on existing DBs use sentinel '0 0 31 2 *' to satisfy the constraint.
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
import { Cron } from 'croner';
|
|
17
|
+
import { getDb } from './db.js';
|
|
18
|
+
|
|
19
|
+
function nextRunFromCron(cronExpr, tz) {
|
|
20
|
+
const cron = new Cron(cronExpr, { timezone: tz || 'UTC' });
|
|
21
|
+
const next = cron.nextRun();
|
|
22
|
+
if (!next) return null;
|
|
23
|
+
return next.toISOString().replace('T', ' ').replace(/\.\d{3}Z$/, '');
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export default function migrateConsolidate() {
|
|
27
|
+
const db = getDb();
|
|
28
|
+
const hasTable = (name) => !!db.prepare(`
|
|
29
|
+
SELECT 1
|
|
30
|
+
FROM sqlite_master
|
|
31
|
+
WHERE type = 'table' AND name = ?
|
|
32
|
+
LIMIT 1
|
|
33
|
+
`).get(name);
|
|
34
|
+
|
|
35
|
+
// Already fully up to date?
|
|
36
|
+
// Note: we can't just check schema_migrations version -- schema.sql inserts
|
|
37
|
+
// version markers via INSERT OR IGNORE, but CREATE TABLE IF NOT EXISTS
|
|
38
|
+
// doesn't add new columns to existing tables. So we also check if the
|
|
39
|
+
// latest column actually exists before skipping.
|
|
40
|
+
const current = hasTable('schema_migrations')
|
|
41
|
+
? (db.prepare('SELECT MAX(version) as v FROM schema_migrations').get()?.v ?? 0)
|
|
42
|
+
: 0;
|
|
43
|
+
// SQLite PRAGMA does not support bound parameters; table names here are all hardcoded literals.
|
|
44
|
+
const columnsFor = (table) => new Set(db.prepare(`PRAGMA table_info(${table})`).all().map((c) => c.name));
|
|
45
|
+
const hasColumns = (actual, required) => required.every((name) => actual.has(name));
|
|
46
|
+
const jobColumns = columnsFor('jobs');
|
|
47
|
+
const runColumns = columnsFor('runs');
|
|
48
|
+
const agentColumns = columnsFor('agents');
|
|
49
|
+
const msgColumns = columnsFor('messages');
|
|
50
|
+
const approvalColumns = columnsFor('approvals');
|
|
51
|
+
const trackerColumns = columnsFor('task_tracker');
|
|
52
|
+
const trackerAgentColumns = columnsFor('task_tracker_agents');
|
|
53
|
+
const hasLatestColumns =
|
|
54
|
+
hasColumns(jobColumns, [
|
|
55
|
+
'job_type', 'execution_intent', 'execution_read_only',
|
|
56
|
+
'agent_id', 'payload_model', 'payload_thinking', 'payload_timeout_seconds',
|
|
57
|
+
'overlap_policy', 'max_queued_dispatches', 'max_pending_approvals',
|
|
58
|
+
'max_trigger_fanout', 'output_store_limit_bytes',
|
|
59
|
+
'output_excerpt_limit_bytes', 'output_summary_limit_bytes',
|
|
60
|
+
'output_offload_threshold_bytes', 'ttl_hours', 'auth_profile',
|
|
61
|
+
'schedule_kind', 'schedule_at', 'delivery_channel', 'delivery_to',
|
|
62
|
+
'delivery_opt_out_reason', 'origin', 'parent_id', 'created_at',
|
|
63
|
+
'updated_at', 'delete_after_run', 'next_run_at', 'last_run_at',
|
|
64
|
+
'last_status', 'consecutive_errors',
|
|
65
|
+
'identity_principal', 'identity_run_as', 'identity_attestation',
|
|
66
|
+
'identity_ref', 'identity_subject_kind', 'identity_subject_principal',
|
|
67
|
+
'identity_trust_level', 'identity_delegation_mode', 'identity',
|
|
68
|
+
'authorization_proof_ref', 'authorization_proof',
|
|
69
|
+
'authorization_ref', 'authorization',
|
|
70
|
+
'evidence_ref', 'evidence',
|
|
71
|
+
'contract_required_trust_level', 'contract_trust_enforcement',
|
|
72
|
+
'contract_sandbox', 'contract_allowed_paths', 'contract_network',
|
|
73
|
+
'contract_max_cost_usd', 'contract_audit',
|
|
74
|
+
'child_credential_policy',
|
|
75
|
+
])
|
|
76
|
+
&& hasColumns(runColumns, [
|
|
77
|
+
'dispatch_queue_id', 'shell_exit_code', 'shell_signal', 'shell_timed_out',
|
|
78
|
+
'shell_stdout', 'shell_stderr', 'shell_stdout_path', 'shell_stderr_path',
|
|
79
|
+
'shell_stdout_bytes', 'shell_stderr_bytes', 'idempotency_key',
|
|
80
|
+
'summary', 'error_message', 'session_key', 'session_id',
|
|
81
|
+
'dispatched_at', 'last_heartbeat',
|
|
82
|
+
'identity_resolved', 'trust_evaluation', 'authorization_decision',
|
|
83
|
+
'authorization_proof_verification', 'evidence_record',
|
|
84
|
+
'credential_handoff_summary',
|
|
85
|
+
])
|
|
86
|
+
&& hasColumns(agentColumns, ['delivery_channel', 'delivery_to', 'brand_name'])
|
|
87
|
+
&& hasColumns(msgColumns, [
|
|
88
|
+
'from_agent', 'to_agent', 'reply_to', 'kind', 'subject', 'body',
|
|
89
|
+
'metadata', 'priority', 'channel', 'delivery_to', 'status',
|
|
90
|
+
'delivered_at', 'read_at', 'expires_at', 'created_at', 'job_id',
|
|
91
|
+
'run_id', 'owner', 'team_id', 'member_id', 'task_id',
|
|
92
|
+
'ack_required', 'ack_at', 'delivery_attempts', 'last_error',
|
|
93
|
+
'team_mapped_at',
|
|
94
|
+
])
|
|
95
|
+
&& hasColumns(approvalColumns, [
|
|
96
|
+
'job_id', 'run_id', 'dispatch_queue_id', 'status', 'requested_at',
|
|
97
|
+
'resolved_at', 'resolved_by', 'notes',
|
|
98
|
+
])
|
|
99
|
+
&& hasColumns(trackerColumns, [
|
|
100
|
+
'name', 'created_at', 'created_by', 'expected_agents', 'timeout_s',
|
|
101
|
+
'status', 'completed_at', 'delivery_channel', 'delivery_to', 'summary',
|
|
102
|
+
])
|
|
103
|
+
&& hasColumns(trackerAgentColumns, [
|
|
104
|
+
'tracker_id', 'agent_label', 'status', 'started_at', 'finished_at',
|
|
105
|
+
'exit_message', 'error', 'session_key', 'last_heartbeat',
|
|
106
|
+
]);
|
|
107
|
+
const legacyAtIsoCount = (jobColumns.has('schedule_kind') && jobColumns.has('schedule_at'))
|
|
108
|
+
? (db.prepare(`
|
|
109
|
+
SELECT COUNT(*) AS cnt
|
|
110
|
+
FROM jobs
|
|
111
|
+
WHERE schedule_kind = 'at'
|
|
112
|
+
AND schedule_at IS NOT NULL
|
|
113
|
+
AND instr(schedule_at, 'T') > 0
|
|
114
|
+
`).get()?.cnt ?? 0)
|
|
115
|
+
: 0;
|
|
116
|
+
const legacyPayloadMismatchCount = (jobColumns.has('session_target') && jobColumns.has('payload_kind'))
|
|
117
|
+
? (db.prepare(`
|
|
118
|
+
SELECT COUNT(*) AS cnt
|
|
119
|
+
FROM jobs
|
|
120
|
+
WHERE (session_target = 'shell' AND payload_kind != 'shellCommand')
|
|
121
|
+
OR (session_target = 'main' AND payload_kind != 'systemEvent')
|
|
122
|
+
`).get()?.cnt ?? 0)
|
|
123
|
+
: 0;
|
|
124
|
+
const legacyMissingDeliveryOptOutCount = (
|
|
125
|
+
jobColumns.has('parent_id')
|
|
126
|
+
&& jobColumns.has('payload_kind')
|
|
127
|
+
&& jobColumns.has('delivery_mode')
|
|
128
|
+
&& jobColumns.has('delivery_opt_out_reason')
|
|
129
|
+
)
|
|
130
|
+
? (db.prepare(`
|
|
131
|
+
SELECT COUNT(*) AS cnt
|
|
132
|
+
FROM jobs
|
|
133
|
+
WHERE parent_id IS NULL
|
|
134
|
+
AND payload_kind = 'agentTurn'
|
|
135
|
+
AND delivery_mode = 'none'
|
|
136
|
+
AND (delivery_opt_out_reason IS NULL OR trim(delivery_opt_out_reason) = '')
|
|
137
|
+
`).get()?.cnt ?? 0)
|
|
138
|
+
: 0;
|
|
139
|
+
if (
|
|
140
|
+
current >= 23
|
|
141
|
+
&& hasLatestColumns
|
|
142
|
+
&& legacyAtIsoCount === 0
|
|
143
|
+
&& legacyPayloadMismatchCount === 0
|
|
144
|
+
&& legacyMissingDeliveryOptOutCount === 0
|
|
145
|
+
) {
|
|
146
|
+
return false;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
// -- Column additions (all idempotent -- column already exists = silent ignore) -
|
|
150
|
+
|
|
151
|
+
const alters = [
|
|
152
|
+
// Legacy partial-table backfills for jobs
|
|
153
|
+
`ALTER TABLE jobs ADD COLUMN agent_id TEXT DEFAULT 'main'`,
|
|
154
|
+
`ALTER TABLE jobs ADD COLUMN payload_model TEXT`,
|
|
155
|
+
`ALTER TABLE jobs ADD COLUMN payload_thinking TEXT`,
|
|
156
|
+
`ALTER TABLE jobs ADD COLUMN payload_timeout_seconds INTEGER DEFAULT 120`,
|
|
157
|
+
`ALTER TABLE jobs ADD COLUMN overlap_policy TEXT NOT NULL DEFAULT 'skip'`,
|
|
158
|
+
`ALTER TABLE jobs ADD COLUMN delivery_channel TEXT`,
|
|
159
|
+
`ALTER TABLE jobs ADD COLUMN delivery_to TEXT`,
|
|
160
|
+
`ALTER TABLE jobs ADD COLUMN created_at TEXT DEFAULT CURRENT_TIMESTAMP`,
|
|
161
|
+
`ALTER TABLE jobs ADD COLUMN updated_at TEXT DEFAULT CURRENT_TIMESTAMP`,
|
|
162
|
+
`ALTER TABLE jobs ADD COLUMN delete_after_run INTEGER NOT NULL DEFAULT 0`,
|
|
163
|
+
`ALTER TABLE jobs ADD COLUMN next_run_at TEXT`,
|
|
164
|
+
`ALTER TABLE jobs ADD COLUMN last_run_at TEXT`,
|
|
165
|
+
`ALTER TABLE jobs ADD COLUMN last_status TEXT`,
|
|
166
|
+
`ALTER TABLE jobs ADD COLUMN consecutive_errors INTEGER NOT NULL DEFAULT 0`,
|
|
167
|
+
// Legacy partial-table backfills for messages
|
|
168
|
+
`ALTER TABLE messages ADD COLUMN to_agent TEXT`,
|
|
169
|
+
`ALTER TABLE messages ADD COLUMN from_agent TEXT`,
|
|
170
|
+
`ALTER TABLE messages ADD COLUMN reply_to TEXT`,
|
|
171
|
+
`ALTER TABLE messages ADD COLUMN kind TEXT`,
|
|
172
|
+
`ALTER TABLE messages ADD COLUMN subject TEXT`,
|
|
173
|
+
`ALTER TABLE messages ADD COLUMN body TEXT`,
|
|
174
|
+
`ALTER TABLE messages ADD COLUMN metadata TEXT`,
|
|
175
|
+
`ALTER TABLE messages ADD COLUMN content TEXT`,
|
|
176
|
+
`ALTER TABLE messages ADD COLUMN priority INTEGER NOT NULL DEFAULT 0`,
|
|
177
|
+
`ALTER TABLE messages ADD COLUMN channel TEXT`,
|
|
178
|
+
`ALTER TABLE messages ADD COLUMN status TEXT NOT NULL DEFAULT 'pending'`,
|
|
179
|
+
`ALTER TABLE messages ADD COLUMN delivered_at TEXT`,
|
|
180
|
+
`ALTER TABLE messages ADD COLUMN read_at TEXT`,
|
|
181
|
+
`ALTER TABLE messages ADD COLUMN expires_at TEXT`,
|
|
182
|
+
`ALTER TABLE messages ADD COLUMN created_at TEXT DEFAULT CURRENT_TIMESTAMP`,
|
|
183
|
+
`ALTER TABLE messages ADD COLUMN job_id TEXT`,
|
|
184
|
+
`ALTER TABLE messages ADD COLUMN run_id TEXT`,
|
|
185
|
+
// Legacy partial-table backfills for approvals
|
|
186
|
+
`ALTER TABLE approvals ADD COLUMN job_id TEXT`,
|
|
187
|
+
`ALTER TABLE approvals ADD COLUMN run_id TEXT`,
|
|
188
|
+
`ALTER TABLE approvals ADD COLUMN status TEXT NOT NULL DEFAULT 'pending'`,
|
|
189
|
+
`ALTER TABLE approvals ADD COLUMN requested_at TEXT DEFAULT CURRENT_TIMESTAMP`,
|
|
190
|
+
`ALTER TABLE approvals ADD COLUMN resolved_at TEXT`,
|
|
191
|
+
`ALTER TABLE approvals ADD COLUMN resolved_by TEXT`,
|
|
192
|
+
`ALTER TABLE approvals ADD COLUMN notes TEXT`,
|
|
193
|
+
// Legacy partial-table backfills for task tracking
|
|
194
|
+
`ALTER TABLE task_tracker ADD COLUMN name TEXT NOT NULL DEFAULT ''`,
|
|
195
|
+
`ALTER TABLE task_tracker ADD COLUMN created_at TEXT DEFAULT CURRENT_TIMESTAMP`,
|
|
196
|
+
`ALTER TABLE task_tracker ADD COLUMN created_by TEXT NOT NULL DEFAULT 'main'`,
|
|
197
|
+
`ALTER TABLE task_tracker ADD COLUMN expected_agents TEXT NOT NULL DEFAULT '[]'`,
|
|
198
|
+
`ALTER TABLE task_tracker ADD COLUMN timeout_s INTEGER NOT NULL DEFAULT 600`,
|
|
199
|
+
`ALTER TABLE task_tracker ADD COLUMN status TEXT NOT NULL DEFAULT 'active'`,
|
|
200
|
+
`ALTER TABLE task_tracker ADD COLUMN completed_at TEXT`,
|
|
201
|
+
`ALTER TABLE task_tracker ADD COLUMN delivery_channel TEXT`,
|
|
202
|
+
`ALTER TABLE task_tracker ADD COLUMN delivery_to TEXT`,
|
|
203
|
+
`ALTER TABLE task_tracker ADD COLUMN summary TEXT`,
|
|
204
|
+
`ALTER TABLE task_tracker_agents ADD COLUMN tracker_id TEXT`,
|
|
205
|
+
`ALTER TABLE task_tracker_agents ADD COLUMN agent_label TEXT NOT NULL DEFAULT ''`,
|
|
206
|
+
`ALTER TABLE task_tracker_agents ADD COLUMN status TEXT NOT NULL DEFAULT 'pending'`,
|
|
207
|
+
`ALTER TABLE task_tracker_agents ADD COLUMN started_at TEXT`,
|
|
208
|
+
`ALTER TABLE task_tracker_agents ADD COLUMN finished_at TEXT`,
|
|
209
|
+
`ALTER TABLE task_tracker_agents ADD COLUMN exit_message TEXT`,
|
|
210
|
+
`ALTER TABLE task_tracker_agents ADD COLUMN error TEXT`,
|
|
211
|
+
// v3: workflow chaining
|
|
212
|
+
`ALTER TABLE jobs ADD COLUMN parent_id TEXT`,
|
|
213
|
+
`ALTER TABLE jobs ADD COLUMN trigger_on TEXT`,
|
|
214
|
+
`ALTER TABLE jobs ADD COLUMN trigger_delay_s INTEGER DEFAULT 0`,
|
|
215
|
+
// v3b: retry logic
|
|
216
|
+
`ALTER TABLE jobs ADD COLUMN max_retries INTEGER DEFAULT 0`,
|
|
217
|
+
`ALTER TABLE runs ADD COLUMN finished_at TEXT`,
|
|
218
|
+
`ALTER TABLE runs ADD COLUMN duration_ms INTEGER`,
|
|
219
|
+
`ALTER TABLE runs ADD COLUMN last_heartbeat TEXT DEFAULT CURRENT_TIMESTAMP`,
|
|
220
|
+
`ALTER TABLE runs ADD COLUMN session_key TEXT`,
|
|
221
|
+
`ALTER TABLE runs ADD COLUMN session_id TEXT`,
|
|
222
|
+
`ALTER TABLE runs ADD COLUMN summary TEXT`,
|
|
223
|
+
`ALTER TABLE runs ADD COLUMN error_message TEXT`,
|
|
224
|
+
`ALTER TABLE runs ADD COLUMN dispatched_at TEXT`,
|
|
225
|
+
`ALTER TABLE runs ADD COLUMN run_timeout_ms INTEGER NOT NULL DEFAULT 300000`,
|
|
226
|
+
`ALTER TABLE runs ADD COLUMN retry_count INTEGER DEFAULT 0`,
|
|
227
|
+
`ALTER TABLE runs ADD COLUMN retry_of TEXT`,
|
|
228
|
+
`ALTER TABLE runs ADD COLUMN triggered_by_run TEXT`,
|
|
229
|
+
`ALTER TABLE runs ADD COLUMN dispatch_queue_id TEXT`,
|
|
230
|
+
// v3c: queue overlap + scope
|
|
231
|
+
`ALTER TABLE jobs ADD COLUMN queued_count INTEGER DEFAULT 0`,
|
|
232
|
+
`ALTER TABLE jobs ADD COLUMN payload_scope TEXT NOT NULL DEFAULT 'own'`,
|
|
233
|
+
`ALTER TABLE jobs ADD COLUMN resource_pool TEXT DEFAULT NULL`,
|
|
234
|
+
`ALTER TABLE jobs ADD COLUMN trigger_condition TEXT DEFAULT NULL`,
|
|
235
|
+
// v5: delivery semantics + approval gates + context retrieval
|
|
236
|
+
`ALTER TABLE jobs ADD COLUMN delivery_guarantee TEXT DEFAULT 'at-most-once'`,
|
|
237
|
+
`ALTER TABLE jobs ADD COLUMN job_class TEXT DEFAULT 'standard'`,
|
|
238
|
+
`ALTER TABLE jobs ADD COLUMN approval_required INTEGER DEFAULT 0`,
|
|
239
|
+
`ALTER TABLE jobs ADD COLUMN approval_timeout_s INTEGER DEFAULT 3600`,
|
|
240
|
+
`ALTER TABLE jobs ADD COLUMN approval_auto TEXT DEFAULT 'reject'`,
|
|
241
|
+
`ALTER TABLE jobs ADD COLUMN context_retrieval TEXT DEFAULT 'none'`,
|
|
242
|
+
`ALTER TABLE jobs ADD COLUMN context_retrieval_limit INTEGER DEFAULT 5`,
|
|
243
|
+
`ALTER TABLE runs ADD COLUMN context_summary TEXT`,
|
|
244
|
+
`ALTER TABLE runs ADD COLUMN replay_of TEXT`,
|
|
245
|
+
`ALTER TABLE messages ADD COLUMN owner TEXT`,
|
|
246
|
+
// v7: idempotency
|
|
247
|
+
`ALTER TABLE runs ADD COLUMN idempotency_key TEXT`,
|
|
248
|
+
// v8: task tracker session correlation
|
|
249
|
+
`ALTER TABLE task_tracker_agents ADD COLUMN session_key TEXT`,
|
|
250
|
+
`ALTER TABLE task_tracker_agents ADD COLUMN last_heartbeat TEXT`,
|
|
251
|
+
// v9: session reuse
|
|
252
|
+
`ALTER TABLE jobs ADD COLUMN preferred_session_key TEXT DEFAULT NULL`,
|
|
253
|
+
// v10: team routing + receipts on messages
|
|
254
|
+
`ALTER TABLE messages ADD COLUMN team_id TEXT`,
|
|
255
|
+
`ALTER TABLE messages ADD COLUMN member_id TEXT`,
|
|
256
|
+
`ALTER TABLE messages ADD COLUMN task_id TEXT`,
|
|
257
|
+
`ALTER TABLE messages ADD COLUMN ack_required INTEGER NOT NULL DEFAULT 0`,
|
|
258
|
+
`ALTER TABLE messages ADD COLUMN ack_at TEXT`,
|
|
259
|
+
`ALTER TABLE messages ADD COLUMN delivery_attempts INTEGER NOT NULL DEFAULT 0`,
|
|
260
|
+
`ALTER TABLE messages ADD COLUMN last_error TEXT`,
|
|
261
|
+
`ALTER TABLE messages ADD COLUMN team_mapped_at TEXT`,
|
|
262
|
+
// v11: durable non-cron dispatches
|
|
263
|
+
`ALTER TABLE approvals ADD COLUMN dispatch_queue_id TEXT`,
|
|
264
|
+
// v12: structured shell results
|
|
265
|
+
`ALTER TABLE runs ADD COLUMN shell_exit_code INTEGER`,
|
|
266
|
+
`ALTER TABLE runs ADD COLUMN shell_signal TEXT`,
|
|
267
|
+
`ALTER TABLE runs ADD COLUMN shell_timed_out INTEGER NOT NULL DEFAULT 0`,
|
|
268
|
+
`ALTER TABLE runs ADD COLUMN shell_stdout TEXT`,
|
|
269
|
+
`ALTER TABLE runs ADD COLUMN shell_stderr TEXT`,
|
|
270
|
+
// v13: watchdog monitoring
|
|
271
|
+
`ALTER TABLE jobs ADD COLUMN job_type TEXT NOT NULL DEFAULT 'standard'`,
|
|
272
|
+
`ALTER TABLE jobs ADD COLUMN watchdog_target_label TEXT`,
|
|
273
|
+
`ALTER TABLE jobs ADD COLUMN watchdog_check_cmd TEXT`,
|
|
274
|
+
`ALTER TABLE jobs ADD COLUMN watchdog_timeout_min INTEGER`,
|
|
275
|
+
`ALTER TABLE jobs ADD COLUMN watchdog_alert_channel TEXT`,
|
|
276
|
+
`ALTER TABLE jobs ADD COLUMN watchdog_alert_target TEXT`,
|
|
277
|
+
`ALTER TABLE jobs ADD COLUMN watchdog_self_destruct INTEGER NOT NULL DEFAULT 1`,
|
|
278
|
+
`ALTER TABLE jobs ADD COLUMN watchdog_started_at TEXT`,
|
|
279
|
+
// v14: execution intent, budgets, and shell-output offloading
|
|
280
|
+
`ALTER TABLE jobs ADD COLUMN execution_intent TEXT NOT NULL DEFAULT 'execute'`,
|
|
281
|
+
`ALTER TABLE jobs ADD COLUMN execution_read_only INTEGER NOT NULL DEFAULT 0`,
|
|
282
|
+
`ALTER TABLE jobs ADD COLUMN max_queued_dispatches INTEGER NOT NULL DEFAULT 25`,
|
|
283
|
+
`ALTER TABLE jobs ADD COLUMN max_pending_approvals INTEGER NOT NULL DEFAULT 10`,
|
|
284
|
+
`ALTER TABLE jobs ADD COLUMN max_trigger_fanout INTEGER NOT NULL DEFAULT 25`,
|
|
285
|
+
`ALTER TABLE jobs ADD COLUMN output_store_limit_bytes INTEGER NOT NULL DEFAULT 65536`,
|
|
286
|
+
`ALTER TABLE jobs ADD COLUMN output_excerpt_limit_bytes INTEGER NOT NULL DEFAULT 2000`,
|
|
287
|
+
`ALTER TABLE jobs ADD COLUMN output_summary_limit_bytes INTEGER NOT NULL DEFAULT 5000`,
|
|
288
|
+
`ALTER TABLE jobs ADD COLUMN output_offload_threshold_bytes INTEGER NOT NULL DEFAULT 65536`,
|
|
289
|
+
`ALTER TABLE runs ADD COLUMN shell_stdout_path TEXT`,
|
|
290
|
+
`ALTER TABLE runs ADD COLUMN shell_stderr_path TEXT`,
|
|
291
|
+
`ALTER TABLE runs ADD COLUMN shell_stdout_bytes INTEGER NOT NULL DEFAULT 0`,
|
|
292
|
+
`ALTER TABLE runs ADD COLUMN shell_stderr_bytes INTEGER NOT NULL DEFAULT 0`,
|
|
293
|
+
// v15: TTL-based auto-deletion
|
|
294
|
+
`ALTER TABLE jobs ADD COLUMN ttl_hours INTEGER DEFAULT NULL`,
|
|
295
|
+
// v16: auth profile override
|
|
296
|
+
`ALTER TABLE jobs ADD COLUMN auth_profile TEXT DEFAULT NULL`,
|
|
297
|
+
// v17: agent delivery config
|
|
298
|
+
`ALTER TABLE agents ADD COLUMN delivery_channel TEXT`,
|
|
299
|
+
`ALTER TABLE agents ADD COLUMN delivery_to TEXT`,
|
|
300
|
+
`ALTER TABLE agents ADD COLUMN brand_name TEXT`,
|
|
301
|
+
// v18: one-shot 'at'-style scheduling
|
|
302
|
+
// Note: schedule_cron NOT NULL constraint cannot be dropped in SQLite via ALTER TABLE.
|
|
303
|
+
// At-jobs on existing DBs must use sentinel cron '0 0 31 2 *' to satisfy the constraint.
|
|
304
|
+
`ALTER TABLE jobs ADD COLUMN schedule_kind TEXT NOT NULL DEFAULT 'cron'`,
|
|
305
|
+
`ALTER TABLE jobs ADD COLUMN schedule_at TEXT DEFAULT NULL`,
|
|
306
|
+
// v19: delivery opt-out reason
|
|
307
|
+
`ALTER TABLE jobs ADD COLUMN delivery_opt_out_reason TEXT DEFAULT NULL`,
|
|
308
|
+
// v20: origin tracking
|
|
309
|
+
`ALTER TABLE jobs ADD COLUMN origin TEXT DEFAULT NULL`,
|
|
310
|
+
// v21: per-message delivery routing
|
|
311
|
+
`ALTER TABLE messages ADD COLUMN delivery_to TEXT`,
|
|
312
|
+
// v22: v0.2 identity
|
|
313
|
+
`ALTER TABLE jobs ADD COLUMN identity_principal TEXT DEFAULT NULL`,
|
|
314
|
+
`ALTER TABLE jobs ADD COLUMN identity_run_as TEXT DEFAULT NULL`,
|
|
315
|
+
`ALTER TABLE jobs ADD COLUMN identity_attestation TEXT DEFAULT NULL`,
|
|
316
|
+
`ALTER TABLE jobs ADD COLUMN identity_ref TEXT DEFAULT NULL`,
|
|
317
|
+
`ALTER TABLE jobs ADD COLUMN identity_subject_kind TEXT DEFAULT NULL`,
|
|
318
|
+
`ALTER TABLE jobs ADD COLUMN identity_subject_principal TEXT DEFAULT NULL`,
|
|
319
|
+
`ALTER TABLE jobs ADD COLUMN identity_trust_level TEXT DEFAULT NULL`,
|
|
320
|
+
`ALTER TABLE jobs ADD COLUMN identity_delegation_mode TEXT DEFAULT NULL`,
|
|
321
|
+
`ALTER TABLE jobs ADD COLUMN identity TEXT DEFAULT NULL`,
|
|
322
|
+
// v22: v0.2 authorization proof
|
|
323
|
+
`ALTER TABLE jobs ADD COLUMN authorization_proof_ref TEXT DEFAULT NULL`,
|
|
324
|
+
`ALTER TABLE jobs ADD COLUMN authorization_proof TEXT DEFAULT NULL`,
|
|
325
|
+
// v22: v0.2 authorization
|
|
326
|
+
`ALTER TABLE jobs ADD COLUMN authorization_ref TEXT DEFAULT NULL`,
|
|
327
|
+
`ALTER TABLE jobs ADD COLUMN authorization TEXT DEFAULT NULL`,
|
|
328
|
+
// v22: v0.2 evidence
|
|
329
|
+
`ALTER TABLE jobs ADD COLUMN evidence_ref TEXT DEFAULT NULL`,
|
|
330
|
+
`ALTER TABLE jobs ADD COLUMN evidence TEXT DEFAULT NULL`,
|
|
331
|
+
// v22: v0.2 contract
|
|
332
|
+
`ALTER TABLE jobs ADD COLUMN contract_required_trust_level TEXT DEFAULT NULL`,
|
|
333
|
+
`ALTER TABLE jobs ADD COLUMN contract_trust_enforcement TEXT DEFAULT NULL`,
|
|
334
|
+
`ALTER TABLE jobs ADD COLUMN contract_sandbox TEXT DEFAULT NULL`,
|
|
335
|
+
`ALTER TABLE jobs ADD COLUMN contract_allowed_paths TEXT DEFAULT NULL`,
|
|
336
|
+
`ALTER TABLE jobs ADD COLUMN contract_network TEXT DEFAULT NULL`,
|
|
337
|
+
`ALTER TABLE jobs ADD COLUMN contract_max_cost_usd REAL DEFAULT NULL`,
|
|
338
|
+
`ALTER TABLE jobs ADD COLUMN contract_audit TEXT DEFAULT NULL`,
|
|
339
|
+
// v22: v0.2 outcomes (runs table)
|
|
340
|
+
`ALTER TABLE runs ADD COLUMN identity_resolved TEXT DEFAULT NULL`,
|
|
341
|
+
`ALTER TABLE runs ADD COLUMN trust_evaluation TEXT DEFAULT NULL`,
|
|
342
|
+
`ALTER TABLE runs ADD COLUMN authorization_decision TEXT DEFAULT NULL`,
|
|
343
|
+
`ALTER TABLE runs ADD COLUMN authorization_proof_verification TEXT DEFAULT NULL`,
|
|
344
|
+
`ALTER TABLE runs ADD COLUMN evidence_record TEXT DEFAULT NULL`,
|
|
345
|
+
`ALTER TABLE runs ADD COLUMN credential_handoff_summary TEXT DEFAULT NULL`,
|
|
346
|
+
// v23: child credential policy
|
|
347
|
+
`ALTER TABLE jobs ADD COLUMN child_credential_policy TEXT DEFAULT NULL`,
|
|
348
|
+
];
|
|
349
|
+
|
|
350
|
+
for (const sql of alters) {
|
|
351
|
+
try {
|
|
352
|
+
db.exec(sql);
|
|
353
|
+
} catch (err) {
|
|
354
|
+
const msg = err.message || '';
|
|
355
|
+
if (msg.includes('duplicate column name') || msg.includes('no such table')) continue;
|
|
356
|
+
throw err;
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
// Wrap all backfill statements, table creation, index creation, and version
|
|
361
|
+
// inserts in a single transaction so that partial backfill cannot occur.
|
|
362
|
+
// ALTER TABLE stays outside because some SQLite builds reject DDL in transactions.
|
|
363
|
+
db.transaction(() => {
|
|
364
|
+
|
|
365
|
+
// Normalize legacy ISO schedule_at / next_run_at values for at-jobs so due checks
|
|
366
|
+
// use a consistent SQLite UTC datetime format after upgrades.
|
|
367
|
+
try {
|
|
368
|
+
db.exec(`
|
|
369
|
+
UPDATE jobs
|
|
370
|
+
SET schedule_at = strftime('%Y-%m-%d %H:%M:%S', schedule_at)
|
|
371
|
+
WHERE schedule_kind = 'at'
|
|
372
|
+
AND schedule_at IS NOT NULL
|
|
373
|
+
AND instr(schedule_at, 'T') > 0
|
|
374
|
+
AND strftime('%Y-%m-%d %H:%M:%S', schedule_at) IS NOT NULL;
|
|
375
|
+
|
|
376
|
+
UPDATE jobs
|
|
377
|
+
SET next_run_at = strftime('%Y-%m-%d %H:%M:%S', next_run_at)
|
|
378
|
+
WHERE schedule_kind = 'at'
|
|
379
|
+
AND next_run_at IS NOT NULL
|
|
380
|
+
AND instr(next_run_at, 'T') > 0
|
|
381
|
+
AND strftime('%Y-%m-%d %H:%M:%S', next_run_at) IS NOT NULL;
|
|
382
|
+
`);
|
|
383
|
+
} catch {
|
|
384
|
+
/* best-effort normalization for legacy rows */
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
// Backfill modern message body from the old content column when present.
|
|
388
|
+
try {
|
|
389
|
+
db.exec(`
|
|
390
|
+
UPDATE messages
|
|
391
|
+
SET body = COALESCE(body, content)
|
|
392
|
+
WHERE content IS NOT NULL
|
|
393
|
+
AND (body IS NULL OR trim(body) = '');
|
|
394
|
+
`);
|
|
395
|
+
} catch {
|
|
396
|
+
/* best-effort normalization for legacy rows */
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
// Backfill root scheduling state for legacy jobs that gained next_run_at late.
|
|
400
|
+
try {
|
|
401
|
+
const rowsNeedingNextRun = db.prepare(`
|
|
402
|
+
SELECT id, schedule_kind, schedule_at, schedule_cron, schedule_tz, parent_id
|
|
403
|
+
FROM jobs
|
|
404
|
+
WHERE next_run_at IS NULL
|
|
405
|
+
`).all();
|
|
406
|
+
const updateNextRun = db.prepare('UPDATE jobs SET next_run_at = ? WHERE id = ?');
|
|
407
|
+
for (const row of rowsNeedingNextRun) {
|
|
408
|
+
let nextRun = null;
|
|
409
|
+
if (!row.parent_id) {
|
|
410
|
+
if (row.schedule_kind === 'at') {
|
|
411
|
+
nextRun = row.schedule_at || null;
|
|
412
|
+
} else if (row.schedule_cron) {
|
|
413
|
+
try {
|
|
414
|
+
nextRun = nextRunFromCron(row.schedule_cron, row.schedule_tz || 'UTC');
|
|
415
|
+
} catch {
|
|
416
|
+
nextRun = null;
|
|
417
|
+
}
|
|
418
|
+
}
|
|
419
|
+
}
|
|
420
|
+
if (nextRun !== null) {
|
|
421
|
+
updateNextRun.run(nextRun, row.id);
|
|
422
|
+
}
|
|
423
|
+
}
|
|
424
|
+
} catch {
|
|
425
|
+
/* best-effort normalization for legacy rows */
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
// Normalize legacy session_target/payload_kind mismatches left behind by older
|
|
429
|
+
// imports or hand-edited rows so current validation/dispatch rules behave
|
|
430
|
+
// consistently on upgraded installs.
|
|
431
|
+
try {
|
|
432
|
+
db.exec(`
|
|
433
|
+
UPDATE jobs
|
|
434
|
+
SET payload_kind = 'shellCommand'
|
|
435
|
+
WHERE session_target = 'shell'
|
|
436
|
+
AND payload_kind != 'shellCommand';
|
|
437
|
+
|
|
438
|
+
UPDATE jobs
|
|
439
|
+
SET payload_kind = 'systemEvent'
|
|
440
|
+
WHERE session_target = 'main'
|
|
441
|
+
AND payload_kind != 'systemEvent';
|
|
442
|
+
|
|
443
|
+
UPDATE jobs
|
|
444
|
+
SET delivery_opt_out_reason = 'legacy scheduler job intentionally suppresses automatic delivery'
|
|
445
|
+
WHERE parent_id IS NULL
|
|
446
|
+
AND payload_kind = 'agentTurn'
|
|
447
|
+
AND delivery_mode = 'none'
|
|
448
|
+
AND (delivery_opt_out_reason IS NULL OR trim(delivery_opt_out_reason) = '');
|
|
449
|
+
`);
|
|
450
|
+
} catch {
|
|
451
|
+
/* best-effort normalization for legacy rows */
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
// -- Tables that may be absent on very old installs ---------------------
|
|
455
|
+
|
|
456
|
+
db.exec(`
|
|
457
|
+
CREATE TABLE IF NOT EXISTS approvals (
|
|
458
|
+
id TEXT PRIMARY KEY,
|
|
459
|
+
job_id TEXT NOT NULL REFERENCES jobs(id) ON DELETE CASCADE,
|
|
460
|
+
run_id TEXT REFERENCES runs(id) ON DELETE SET NULL,
|
|
461
|
+
dispatch_queue_id TEXT REFERENCES job_dispatch_queue(id) ON DELETE SET NULL,
|
|
462
|
+
status TEXT NOT NULL DEFAULT 'pending',
|
|
463
|
+
requested_at TEXT NOT NULL DEFAULT (datetime('now')),
|
|
464
|
+
resolved_at TEXT,
|
|
465
|
+
resolved_by TEXT,
|
|
466
|
+
notes TEXT
|
|
467
|
+
);
|
|
468
|
+
CREATE INDEX IF NOT EXISTS idx_approvals_status ON approvals(status) WHERE status = 'pending';
|
|
469
|
+
CREATE INDEX IF NOT EXISTS idx_approvals_job ON approvals(job_id);
|
|
470
|
+
|
|
471
|
+
CREATE TABLE IF NOT EXISTS task_tracker (
|
|
472
|
+
id TEXT PRIMARY KEY,
|
|
473
|
+
name TEXT NOT NULL,
|
|
474
|
+
created_at TEXT NOT NULL DEFAULT (datetime('now')),
|
|
475
|
+
created_by TEXT NOT NULL DEFAULT 'main',
|
|
476
|
+
expected_agents TEXT NOT NULL,
|
|
477
|
+
timeout_s INTEGER NOT NULL DEFAULT 600,
|
|
478
|
+
status TEXT NOT NULL DEFAULT 'active',
|
|
479
|
+
completed_at TEXT,
|
|
480
|
+
delivery_channel TEXT,
|
|
481
|
+
delivery_to TEXT,
|
|
482
|
+
summary TEXT
|
|
483
|
+
);
|
|
484
|
+
CREATE INDEX IF NOT EXISTS idx_task_tracker_status ON task_tracker(status) WHERE status = 'active';
|
|
485
|
+
|
|
486
|
+
CREATE TABLE IF NOT EXISTS task_tracker_agents (
|
|
487
|
+
id TEXT PRIMARY KEY,
|
|
488
|
+
tracker_id TEXT NOT NULL REFERENCES task_tracker(id) ON DELETE CASCADE,
|
|
489
|
+
agent_label TEXT NOT NULL,
|
|
490
|
+
status TEXT NOT NULL DEFAULT 'pending',
|
|
491
|
+
started_at TEXT,
|
|
492
|
+
finished_at TEXT,
|
|
493
|
+
exit_message TEXT,
|
|
494
|
+
error TEXT,
|
|
495
|
+
session_key TEXT,
|
|
496
|
+
last_heartbeat TEXT
|
|
497
|
+
);
|
|
498
|
+
CREATE INDEX IF NOT EXISTS idx_tta_tracker ON task_tracker_agents(tracker_id);
|
|
499
|
+
CREATE INDEX IF NOT EXISTS idx_tta_status ON task_tracker_agents(status) WHERE status IN ('pending','running');
|
|
500
|
+
|
|
501
|
+
CREATE TABLE IF NOT EXISTS idempotency_ledger (
|
|
502
|
+
key TEXT PRIMARY KEY,
|
|
503
|
+
job_id TEXT NOT NULL,
|
|
504
|
+
run_id TEXT NOT NULL,
|
|
505
|
+
status TEXT NOT NULL DEFAULT 'claimed',
|
|
506
|
+
claimed_at TEXT NOT NULL DEFAULT (datetime('now')),
|
|
507
|
+
released_at TEXT,
|
|
508
|
+
result_hash TEXT,
|
|
509
|
+
expires_at TEXT NOT NULL
|
|
510
|
+
);
|
|
511
|
+
CREATE INDEX IF NOT EXISTS idx_idem_expires ON idempotency_ledger(expires_at);
|
|
512
|
+
CREATE INDEX IF NOT EXISTS idx_idem_job ON idempotency_ledger(job_id);
|
|
513
|
+
|
|
514
|
+
CREATE TABLE IF NOT EXISTS message_receipts (
|
|
515
|
+
id TEXT PRIMARY KEY,
|
|
516
|
+
message_id TEXT NOT NULL REFERENCES messages(id) ON DELETE CASCADE,
|
|
517
|
+
event_type TEXT NOT NULL,
|
|
518
|
+
attempt INTEGER,
|
|
519
|
+
actor TEXT,
|
|
520
|
+
detail TEXT,
|
|
521
|
+
created_at TEXT NOT NULL DEFAULT (datetime('now'))
|
|
522
|
+
);
|
|
523
|
+
|
|
524
|
+
CREATE TABLE IF NOT EXISTS team_tasks (
|
|
525
|
+
team_id TEXT NOT NULL,
|
|
526
|
+
id TEXT NOT NULL,
|
|
527
|
+
member_id TEXT,
|
|
528
|
+
source_message_id TEXT REFERENCES messages(id) ON DELETE SET NULL,
|
|
529
|
+
title TEXT,
|
|
530
|
+
status TEXT NOT NULL DEFAULT 'open',
|
|
531
|
+
gate_tracker_id TEXT REFERENCES task_tracker(id) ON DELETE SET NULL,
|
|
532
|
+
gate_status TEXT,
|
|
533
|
+
last_error TEXT,
|
|
534
|
+
created_at TEXT NOT NULL DEFAULT (datetime('now')),
|
|
535
|
+
updated_at TEXT NOT NULL DEFAULT (datetime('now')),
|
|
536
|
+
completed_at TEXT,
|
|
537
|
+
PRIMARY KEY (team_id, id)
|
|
538
|
+
);
|
|
539
|
+
|
|
540
|
+
CREATE TABLE IF NOT EXISTS team_mailbox_events (
|
|
541
|
+
id TEXT PRIMARY KEY,
|
|
542
|
+
team_id TEXT NOT NULL,
|
|
543
|
+
member_id TEXT,
|
|
544
|
+
task_id TEXT,
|
|
545
|
+
message_id TEXT REFERENCES messages(id) ON DELETE SET NULL,
|
|
546
|
+
event_type TEXT NOT NULL,
|
|
547
|
+
payload TEXT,
|
|
548
|
+
created_at TEXT NOT NULL DEFAULT (datetime('now'))
|
|
549
|
+
);
|
|
550
|
+
|
|
551
|
+
CREATE TABLE IF NOT EXISTS job_dispatch_queue (
|
|
552
|
+
id TEXT PRIMARY KEY,
|
|
553
|
+
job_id TEXT NOT NULL REFERENCES jobs(id) ON DELETE CASCADE,
|
|
554
|
+
dispatch_kind TEXT NOT NULL,
|
|
555
|
+
status TEXT NOT NULL DEFAULT 'pending',
|
|
556
|
+
scheduled_for TEXT NOT NULL,
|
|
557
|
+
source_run_id TEXT REFERENCES runs(id) ON DELETE SET NULL,
|
|
558
|
+
retry_of_run_id TEXT REFERENCES runs(id) ON DELETE SET NULL,
|
|
559
|
+
created_at TEXT NOT NULL DEFAULT (datetime('now')),
|
|
560
|
+
claimed_at TEXT,
|
|
561
|
+
processed_at TEXT
|
|
562
|
+
);
|
|
563
|
+
`);
|
|
564
|
+
|
|
565
|
+
// -- Indexes that may be absent ----------------------------------------
|
|
566
|
+
|
|
567
|
+
try {
|
|
568
|
+
db.exec(`
|
|
569
|
+
CREATE UNIQUE INDEX IF NOT EXISTS idx_runs_idempotency
|
|
570
|
+
ON runs(idempotency_key) WHERE idempotency_key IS NOT NULL
|
|
571
|
+
`);
|
|
572
|
+
} catch { /* index may already exist */ }
|
|
573
|
+
|
|
574
|
+
try {
|
|
575
|
+
db.exec(`
|
|
576
|
+
CREATE INDEX IF NOT EXISTS idx_tta_session_key
|
|
577
|
+
ON task_tracker_agents(session_key) WHERE session_key IS NOT NULL
|
|
578
|
+
`);
|
|
579
|
+
} catch { /* index may already exist */ }
|
|
580
|
+
|
|
581
|
+
try {
|
|
582
|
+
db.exec(`
|
|
583
|
+
CREATE INDEX IF NOT EXISTS idx_messages_team
|
|
584
|
+
ON messages(team_id, member_id, status) WHERE team_id IS NOT NULL
|
|
585
|
+
`);
|
|
586
|
+
} catch { /* index may already exist */ }
|
|
587
|
+
|
|
588
|
+
try {
|
|
589
|
+
db.exec(`
|
|
590
|
+
CREATE INDEX IF NOT EXISTS idx_messages_task
|
|
591
|
+
ON messages(team_id, task_id, created_at)
|
|
592
|
+
WHERE team_id IS NOT NULL AND task_id IS NOT NULL
|
|
593
|
+
`);
|
|
594
|
+
} catch { /* index may already exist */ }
|
|
595
|
+
|
|
596
|
+
try {
|
|
597
|
+
db.exec(`
|
|
598
|
+
CREATE INDEX IF NOT EXISTS idx_messages_ack_pending
|
|
599
|
+
ON messages(ack_required, ack_at, status)
|
|
600
|
+
WHERE ack_required = 1 AND ack_at IS NULL
|
|
601
|
+
`);
|
|
602
|
+
} catch { /* index may already exist */ }
|
|
603
|
+
|
|
604
|
+
try {
|
|
605
|
+
db.exec(`
|
|
606
|
+
CREATE INDEX IF NOT EXISTS idx_receipts_message
|
|
607
|
+
ON message_receipts(message_id, created_at DESC)
|
|
608
|
+
`);
|
|
609
|
+
} catch { /* index may already exist */ }
|
|
610
|
+
|
|
611
|
+
try {
|
|
612
|
+
db.exec(`
|
|
613
|
+
CREATE INDEX IF NOT EXISTS idx_team_tasks_status
|
|
614
|
+
ON team_tasks(team_id, status, updated_at DESC)
|
|
615
|
+
`);
|
|
616
|
+
} catch { /* index may already exist */ }
|
|
617
|
+
|
|
618
|
+
try {
|
|
619
|
+
db.exec(`
|
|
620
|
+
CREATE INDEX IF NOT EXISTS idx_team_tasks_gate
|
|
621
|
+
ON team_tasks(gate_tracker_id) WHERE gate_tracker_id IS NOT NULL
|
|
622
|
+
`);
|
|
623
|
+
} catch { /* index may already exist */ }
|
|
624
|
+
|
|
625
|
+
try {
|
|
626
|
+
db.exec(`
|
|
627
|
+
CREATE INDEX IF NOT EXISTS idx_team_events_team
|
|
628
|
+
ON team_mailbox_events(team_id, created_at DESC)
|
|
629
|
+
`);
|
|
630
|
+
} catch { /* index may already exist */ }
|
|
631
|
+
|
|
632
|
+
try {
|
|
633
|
+
db.exec(`
|
|
634
|
+
CREATE INDEX IF NOT EXISTS idx_team_events_task
|
|
635
|
+
ON team_mailbox_events(team_id, task_id, created_at DESC)
|
|
636
|
+
WHERE task_id IS NOT NULL
|
|
637
|
+
`);
|
|
638
|
+
} catch { /* index may already exist */ }
|
|
639
|
+
|
|
640
|
+
try {
|
|
641
|
+
db.exec(`
|
|
642
|
+
CREATE INDEX IF NOT EXISTS idx_runs_dispatch_queue
|
|
643
|
+
ON runs(dispatch_queue_id) WHERE dispatch_queue_id IS NOT NULL
|
|
644
|
+
`);
|
|
645
|
+
} catch { /* index may already exist */ }
|
|
646
|
+
|
|
647
|
+
try {
|
|
648
|
+
db.exec(`
|
|
649
|
+
CREATE INDEX IF NOT EXISTS idx_approvals_dispatch_queue
|
|
650
|
+
ON approvals(dispatch_queue_id) WHERE dispatch_queue_id IS NOT NULL
|
|
651
|
+
`);
|
|
652
|
+
} catch { /* index may already exist */ }
|
|
653
|
+
|
|
654
|
+
try {
|
|
655
|
+
db.exec(`
|
|
656
|
+
CREATE INDEX IF NOT EXISTS idx_dispatch_queue_due
|
|
657
|
+
ON job_dispatch_queue(status, scheduled_for)
|
|
658
|
+
`);
|
|
659
|
+
} catch { /* index may already exist */ }
|
|
660
|
+
|
|
661
|
+
try {
|
|
662
|
+
db.exec(`
|
|
663
|
+
CREATE INDEX IF NOT EXISTS idx_dispatch_queue_job
|
|
664
|
+
ON job_dispatch_queue(job_id, created_at DESC)
|
|
665
|
+
`);
|
|
666
|
+
} catch { /* index may already exist */ }
|
|
667
|
+
|
|
668
|
+
try {
|
|
669
|
+
db.exec(`
|
|
670
|
+
CREATE INDEX IF NOT EXISTS idx_dispatch_queue_source_run
|
|
671
|
+
ON job_dispatch_queue(source_run_id) WHERE source_run_id IS NOT NULL
|
|
672
|
+
`);
|
|
673
|
+
} catch { /* index may already exist */ }
|
|
674
|
+
|
|
675
|
+
// -- Record all versions -----------------------------------------------
|
|
676
|
+
|
|
677
|
+
const stmt = db.prepare('INSERT OR IGNORE INTO schema_migrations (version) VALUES (?)');
|
|
678
|
+
for (const v of [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23]) {
|
|
679
|
+
stmt.run(v);
|
|
680
|
+
}
|
|
681
|
+
|
|
682
|
+
})(); // end backfill + version-insert transaction
|
|
683
|
+
|
|
684
|
+
return true;
|
|
685
|
+
}
|
|
686
|
+
|
|
687
|
+
// Allow running as standalone script: node migrate-consolidate.js
|
|
688
|
+
if (process.argv[1] && process.argv[1].endsWith('migrate-consolidate.js')) {
|
|
689
|
+
const applied = migrateConsolidate();
|
|
690
|
+
console.log(applied
|
|
691
|
+
? 'Consolidation migration applied -- DB is now at schema v23'
|
|
692
|
+
: 'DB already at v23 -- nothing to do'
|
|
693
|
+
);
|
|
694
|
+
}
|