@saadnvd1/agent-os 0.3.596 → 0.3.600
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/app/api/bus/spawn/route.ts +35 -16
- package/app/api/lumifyhub/cards/[cardId]/run/route.ts +3 -0
- package/app/api/tasks/route.ts +3 -0
- package/app/page.tsx +2 -1
- package/components/SessionList/SessionRow.tsx +2 -1
- package/lib/agents/spawn.ts +16 -4
- package/lib/chat/refresh.ts +2 -1
- package/lib/chat/runner.ts +29 -0
- package/lib/chat/store.ts +53 -0
- package/lib/db/migration-types.ts +15 -0
- package/lib/db/migrations/0001_add_group_path_to_sessions.ts +11 -0
- package/lib/db/migrations/0002_add_agent_type_to_sessions.ts +11 -0
- package/lib/db/migrations/0003_add_worktree_columns_to_sessions.ts +12 -0
- package/lib/db/migrations/0004_add_pr_tracking_to_sessions.ts +11 -0
- package/lib/db/migrations/0005_add_group_path_index.ts +11 -0
- package/lib/db/migrations/0006_add_orchestration_columns_to_sessions.ts +16 -0
- package/lib/db/migrations/0007_add_auto_approve_to_sessions.ts +11 -0
- package/lib/db/migrations/0008_add_dev_server_columns.ts +19 -0
- package/lib/db/migrations/0009_add_project_id_to_sessions.ts +17 -0
- package/lib/db/migrations/0010_add_project_id_to_dev_servers.ts +36 -0
- package/lib/db/migrations/0011_add_tmux_name_to_sessions.ts +13 -0
- package/lib/db/migrations/0012_add_initial_prompt_to_projects.ts +9 -0
- package/lib/db/migrations/0013_add_project_repositories_table.ts +22 -0
- package/lib/db/migrations/0014_add_hosts.ts +23 -0
- package/lib/db/migrations/0015_add_workspaces.ts +18 -0
- package/lib/db/migrations/0016_add_task_columns_to_sessions.ts +13 -0
- package/lib/db/migrations/0017_add_bus_messages.ts +24 -0
- package/lib/db/migrations/0018_add_chat.ts +23 -0
- package/lib/db/migrations/0019_add_lumifyhub.ts +36 -0
- package/lib/db/migrations/0020_add_lumifyhub_published_docs.ts +18 -0
- package/lib/db/migrations/0021_add_chat_access.ts +14 -0
- package/lib/db/migrations/0022_add_last_seen.ts +10 -0
- package/lib/db/migrations/0023_mark_existing_sessions_seen.ts +12 -0
- package/lib/db/migrations/0024_add_stacks.ts +49 -0
- package/lib/db/migrations/0025_add_stack_item_attempts.ts +16 -0
- package/lib/db/migrations/0026_add_workspace_orchestrator.ts +47 -0
- package/lib/db/migrations/0027_add_devices_and_settings.ts +28 -0
- package/lib/db/migrations/0028_add_orchestrator_acting.ts +70 -0
- package/lib/db/migrations/0029_add_orchestrator_asks_and_pause.ts +81 -0
- package/lib/db/migrations/0030_add_sessions_archived_at.ts +14 -0
- package/lib/db/migrations/0031_add_stack_item_restacked_heads.ts +22 -0
- package/lib/db/migrations/0032_add_sessions_pinned.ts +12 -0
- package/lib/db/migrations/0033_add_program_status.ts +18 -0
- package/lib/db/migrations/0034_add_session_names.ts +21 -0
- package/lib/db/migrations/0035_add_artifacts.ts +23 -0
- package/lib/db/migrations/0036_add_chat_queue.ts +26 -0
- package/lib/db/migrations/0037_add_chat_plan_and_turns.ts +39 -0
- package/lib/db/migrations/0038_add_schedules.ts +56 -0
- package/lib/db/migrations/0039_add_session_name_source.ts +21 -0
- package/lib/db/migrations/0040_add_schedule_targets_and_phone_notify.ts +41 -0
- package/lib/db/migrations/0041_add_task_setup.ts +17 -0
- package/lib/db/migrations/0042_add_host_links_and_moved_tasks.ts +29 -0
- package/lib/db/migrations/0043_scratch_project.ts +14 -0
- package/lib/db/migrations/0044_chat_item_kind.ts +21 -0
- package/lib/db/migrations/0045_change_versions.ts +13 -0
- package/lib/db/migrations/0046_session_port_slots.ts +21 -0
- package/lib/db/migrations/0047_pin_orchestrators.ts +12 -0
- package/lib/db/migrations/0048_session_database.ts +15 -0
- package/lib/db/migrations/0049_peer_mirror.ts +14 -0
- package/lib/db/migrations/0050_task_queue.ts +44 -0
- package/lib/db/migrations/0051_chat_queue_sent_by.ts +16 -0
- package/lib/db/migrations/0052_sessions_chat_restarted.ts +17 -0
- package/lib/db/migrations/0053_brief_amendments_failure_sha_ask_raised_by.ts +37 -0
- package/lib/db/migrations/0054_sessions_chat_tools_digest.ts +15 -0
- package/lib/db/migrations/0055_sessions_merge_armed_and_rebase.ts +25 -0
- package/lib/db/migrations/0056_orchestrator_rules.ts +39 -0
- package/lib/db/migrations/0057_task_pr_wakes.ts +34 -0
- package/lib/db/migrations/0058_away_mode.ts +71 -0
- package/lib/db/migrations/0059_gate_failure_commits.ts +33 -0
- package/lib/db/migrations/0060_check_runs_and_compactions.ts +44 -0
- package/lib/db/migrations/0061_model_routing.ts +30 -0
- package/lib/db/migrations/0062_sessions_quick_of.ts +19 -0
- package/lib/db/migrations/0063_one_off_schedules.ts +16 -0
- package/lib/db/migrations/0064_chat_compactions_phase.ts +15 -0
- package/lib/db/migrations/0065_orchestrator_triage.ts +23 -0
- package/lib/db/migrations/0066_workspace_orch_mcp_servers.ts +16 -0
- package/lib/db/migrations/0067_orchestrator_event_log_suppressed.ts +14 -0
- package/lib/db/migrations/0068_usage_calls_and_rollups.ts +74 -0
- package/lib/db/migrations/0069_session_logs.ts +26 -0
- package/lib/db/migrations/0070_chat_items_running.ts +15 -0
- package/lib/db/migrations/0071_devices_push.ts +29 -0
- package/lib/db/migrations/0072_push_outbox.ts +33 -0
- package/lib/db/migrations/0073_orchestrated_task_moves.ts +27 -0
- package/lib/db/migrations/0074_held_task_replies.ts +27 -0
- package/lib/db/migrations/0075_plan_only_tasks.ts +17 -0
- package/lib/db/migrations/0076_welcome_backfill.ts +16 -0
- package/lib/db/migrations/0077_simulator_leases.ts +21 -0
- package/lib/db/migrations/0078_orchestrator_questions.ts +34 -0
- package/lib/db/migrations/0079_task_queue_agent.ts +13 -0
- package/lib/db/migrations/0080_recipes.ts +129 -0
- package/lib/db/migrations/0081_schedules_into_recipes.ts +18 -0
- package/lib/db/migrations/0082_session_shelf_times.ts +23 -0
- package/lib/db/migrations/0083_native_devices_can_approve.ts +18 -0
- package/lib/db/migrations/0084_approval_audit.ts +20 -0
- package/lib/db/migrations/0085_experiment_usage.ts +35 -0
- package/lib/db/migrations/0086_learning_cards.ts +28 -0
- package/lib/db/migrations/0087_plan_pieces_reconcile.ts +29 -0
- package/lib/db/migrations/0088_shared_links.ts +25 -0
- package/lib/db/migrations/0089_passkeys_device_keys.ts +23 -0
- package/lib/db/migrations/20261014T0900_task_scheduling.ts +57 -0
- package/lib/db/migrations/20261014T1235_placed_mirror_started_by.ts +19 -0
- package/lib/db/migrations/20261014T1600_queued_task_messages.ts +21 -0
- package/lib/db/migrations/20261014T1800_task_starter_device.ts +20 -0
- package/lib/db/migrations/20261014T1900_pause_note.ts +13 -0
- package/lib/db/migrations/20261014T2000_chat_bookmarks.ts +33 -0
- package/lib/db/migrations/20261014T2100_gate_failure_ask_held.ts +18 -0
- package/lib/db/migrations/20261014T2110_task_start_record.ts +21 -0
- package/lib/db/migrations/20261014T2200_end_orphaned_tasks.ts +23 -0
- package/lib/db/migrations/README.md +35 -0
- package/lib/db/migrations-legacy-checksums.json +9 -8
- package/lib/db/migrations-split-snapshot.json +1759 -0
- package/lib/db/migrations.ts +76 -2337
- package/lib/db/types.ts +2 -0
- package/lib/learning-cards/apply.ts +7 -1
- package/lib/models/plan-step.ts +25 -8
- package/lib/models/resolve.ts +1 -1
- package/lib/models/resume.ts +1 -1
- package/lib/models/roles.ts +0 -16
- package/lib/models/session-role.ts +19 -0
- package/lib/orchestrator/act.ts +22 -7
- package/lib/orchestrator/amendments.ts +10 -3
- package/lib/orchestrator/serve.ts +1 -0
- package/lib/orchestrator/testing.ts +8 -0
- package/lib/orchestrator/tool-schemas.ts +9 -0
- package/lib/orchestrator/tools.ts +1 -1
- package/lib/plans/claim.ts +131 -0
- package/lib/plans/release.ts +5 -2
- package/lib/recipes/run.ts +6 -0
- package/lib/schedules/start.ts +1 -0
- package/lib/sessions/fork.ts +10 -0
- package/lib/sessions/launch.ts +5 -1
- package/lib/stacks/start.ts +1 -0
- package/lib/tasks/held-remote.ts +4 -2
- package/lib/tasks/held-replies.ts +5 -2
- package/lib/tasks/held.ts +2 -1
- package/lib/tasks/index.ts +85 -46
- package/lib/tasks/move-carry.ts +12 -1
- package/lib/tasks/move-guard.ts +2 -1
- package/lib/tasks/move.ts +7 -5
- package/lib/tasks/pause.ts +1 -1
- package/lib/tasks/queue.ts +54 -31
- package/lib/tasks/remote.ts +3 -1
- package/lib/tasks/starter.ts +124 -0
- package/lib/usage/calls.ts +2 -1
- package/lib/usage/turns.ts +2 -1
- package/package.json +6 -3
- package/scripts/check +1 -0
- package/scripts/gen-migrations-index.mjs +44 -0
- package/scripts/screenshots/demo.ts +5 -1
- package/scripts/screenshots/run.ts +5 -1
package/lib/db/migrations.ts
CHANGED
|
@@ -1,42 +1,86 @@
|
|
|
1
|
-
import {
|
|
2
|
-
installChangeTriggers,
|
|
3
|
-
TABLES_WATCHED_45,
|
|
4
|
-
TABLES_WATCHED_50,
|
|
5
|
-
TABLES_WATCHED_56,
|
|
6
|
-
TABLES_WATCHED_58,
|
|
7
|
-
TABLES_WATCHED_78,
|
|
8
|
-
TABLES_WATCHED_80,
|
|
9
|
-
TABLES_WATCHED_CHAT_BOOKMARKS,
|
|
10
|
-
} from "./changes";
|
|
11
1
|
import type Database from "better-sqlite3";
|
|
12
|
-
import {
|
|
2
|
+
import type { Migration } from "./migration-types";
|
|
3
|
+
import { migrationFiles } from "./migrations.generated";
|
|
13
4
|
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
//
|
|
17
|
-
//
|
|
18
|
-
//
|
|
5
|
+
export type { Migration } from "./migration-types";
|
|
6
|
+
|
|
7
|
+
// HOW TO ADD A MIGRATION: add ONE file, lib/db/migrations/<key>.ts, and edit
|
|
8
|
+
// nothing else. The key is a UTC timestamp, never an integer id:
|
|
9
|
+
// "YYYYMMDDTHHMM_snake_name" (e.g. "20261014T0130_share_links"), and the file
|
|
10
|
+
// is named exactly the key:
|
|
11
|
+
//
|
|
12
|
+
// import type { Migration } from "../migration-types";
|
|
13
|
+
// export default {
|
|
14
|
+
// key: "20261014T0130_share_links",
|
|
15
|
+
// up: (db) => { ... },
|
|
16
|
+
// } satisfies Migration;
|
|
17
|
+
//
|
|
18
|
+
// Hand-picked numbers collide when two tasks run in parallel and the loser is
|
|
19
|
+
// silently skipped on databases that already applied the winner. Ids 1..89 are
|
|
20
|
+
// legacy (files 0001_<name>.ts..0089_<name>.ts): they run first, by number, and
|
|
19
21
|
// never change. Timestamp keys run after them, in lexical order. Never edit a
|
|
20
|
-
// migration that has shipped; add a new one.
|
|
21
|
-
//
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
}
|
|
28
|
-
interface KeyedMigration {
|
|
29
|
-
key: string;
|
|
30
|
-
id?: undefined;
|
|
31
|
-
name?: undefined;
|
|
32
|
-
up: (db: Database.Database) => void;
|
|
33
|
-
}
|
|
34
|
-
export type Migration = LegacyMigration | KeyedMigration;
|
|
22
|
+
// migration that has shipped; add a new one.
|
|
23
|
+
//
|
|
24
|
+
// lib/db/migrations.generated.ts is built from the folder by
|
|
25
|
+
// scripts/gen-migrations-index.mjs (npm runs it before dev, start, build,
|
|
26
|
+
// typecheck, lint and test) and is not committed. A duplicate id or key, or a
|
|
27
|
+
// file whose name does not match its id, throws when this module loads.
|
|
28
|
+
// lib/db/migrations.test.ts enforces the rest.
|
|
35
29
|
|
|
36
30
|
export const migrationKey = (m: Migration): string => m.key ?? String(m.id);
|
|
37
31
|
const migrationName = (m: Migration): string =>
|
|
38
32
|
m.name ?? (m.key as string).replace(/^\d{8}T\d{4}_/, "");
|
|
39
33
|
|
|
34
|
+
const KEY_RE = /^\d{8}T\d{4}_[a-z0-9]+(_[a-z0-9]+)*$/;
|
|
35
|
+
const LEGACY_MAX = 89;
|
|
36
|
+
|
|
37
|
+
// The migrations in run order, checked against their file names: legacy ids by
|
|
38
|
+
// number, then timestamp keys lexically.
|
|
39
|
+
export function buildRegistry(files: Record<string, Migration>): Migration[] {
|
|
40
|
+
const seen = new Map<string, string>();
|
|
41
|
+
const list: Migration[] = [];
|
|
42
|
+
for (const [file, m] of Object.entries(files)) {
|
|
43
|
+
const where = `lib/db/migrations/${file}.ts`;
|
|
44
|
+
if (!m || typeof m.up !== "function")
|
|
45
|
+
throw new Error(`${where} must default-export { id|key, up }.`);
|
|
46
|
+
let expected: string;
|
|
47
|
+
if (m.key !== undefined) {
|
|
48
|
+
if (!KEY_RE.test(m.key))
|
|
49
|
+
throw new Error(
|
|
50
|
+
`${where}: key "${m.key}" is malformed. Use "YYYYMMDDTHHMM_snake_name" in UTC, e.g. "20261014T0130_share_links".`
|
|
51
|
+
);
|
|
52
|
+
expected = m.key;
|
|
53
|
+
} else if (typeof m.id === "number" && typeof m.name === "string") {
|
|
54
|
+
if (!Number.isInteger(m.id) || m.id < 1 || m.id > LEGACY_MAX)
|
|
55
|
+
throw new Error(
|
|
56
|
+
`${where}: do not add numbered migrations; use a timestamp key (YYYYMMDDTHHMM_snake_name, UTC).`
|
|
57
|
+
);
|
|
58
|
+
expected = `${String(m.id).padStart(4, "0")}_${m.name}`;
|
|
59
|
+
} else {
|
|
60
|
+
throw new Error(`${where} needs a "key" (or a legacy "id" and "name").`);
|
|
61
|
+
}
|
|
62
|
+
if (file !== expected)
|
|
63
|
+
throw new Error(
|
|
64
|
+
`${where}: the file name must match its id. Rename it to ${expected}.ts.`
|
|
65
|
+
);
|
|
66
|
+
const k = migrationKey(m);
|
|
67
|
+
const first = seen.get(k);
|
|
68
|
+
if (first)
|
|
69
|
+
throw new Error(
|
|
70
|
+
`Duplicate migration ${k} in lib/db/migrations/${first}.ts and ${where}. Two tasks picked the same one: give yours a fresh timestamp key.`
|
|
71
|
+
);
|
|
72
|
+
seen.set(k, file);
|
|
73
|
+
list.push(m);
|
|
74
|
+
}
|
|
75
|
+
const legacy = list
|
|
76
|
+
.filter((m) => m.id !== undefined)
|
|
77
|
+
.sort((a, b) => (a.id as number) - (b.id as number));
|
|
78
|
+
const keyed = list
|
|
79
|
+
.filter((m) => m.key !== undefined)
|
|
80
|
+
.sort((a, b) => ((a.key as string) < (b.key as string) ? -1 : 1));
|
|
81
|
+
return [...legacy, ...keyed];
|
|
82
|
+
}
|
|
83
|
+
|
|
40
84
|
// Legacy ids sort before every timestamp key.
|
|
41
85
|
function compareToUpTo(m: Migration, upTo: number | string): number {
|
|
42
86
|
if (typeof upTo === "number") {
|
|
@@ -47,2312 +91,7 @@ function compareToUpTo(m: Migration, upTo: number | string): number {
|
|
|
47
91
|
}
|
|
48
92
|
|
|
49
93
|
// All migrations in order - never modify existing ones, only add new
|
|
50
|
-
export const migrations: Migration[] =
|
|
51
|
-
{
|
|
52
|
-
id: 1,
|
|
53
|
-
name: "add_group_path_to_sessions",
|
|
54
|
-
up: (db) => {
|
|
55
|
-
db.exec(
|
|
56
|
-
`ALTER TABLE sessions ADD COLUMN group_path TEXT NOT NULL DEFAULT 'sessions'`
|
|
57
|
-
);
|
|
58
|
-
},
|
|
59
|
-
},
|
|
60
|
-
{
|
|
61
|
-
id: 2,
|
|
62
|
-
name: "add_agent_type_to_sessions",
|
|
63
|
-
up: (db) => {
|
|
64
|
-
db.exec(
|
|
65
|
-
`ALTER TABLE sessions ADD COLUMN agent_type TEXT NOT NULL DEFAULT 'claude'`
|
|
66
|
-
);
|
|
67
|
-
},
|
|
68
|
-
},
|
|
69
|
-
{
|
|
70
|
-
id: 3,
|
|
71
|
-
name: "add_worktree_columns_to_sessions",
|
|
72
|
-
up: (db) => {
|
|
73
|
-
db.exec(`ALTER TABLE sessions ADD COLUMN worktree_path TEXT`);
|
|
74
|
-
db.exec(`ALTER TABLE sessions ADD COLUMN branch_name TEXT`);
|
|
75
|
-
db.exec(`ALTER TABLE sessions ADD COLUMN base_branch TEXT`);
|
|
76
|
-
db.exec(`ALTER TABLE sessions ADD COLUMN dev_server_port INTEGER`);
|
|
77
|
-
},
|
|
78
|
-
},
|
|
79
|
-
{
|
|
80
|
-
id: 4,
|
|
81
|
-
name: "add_pr_tracking_to_sessions",
|
|
82
|
-
up: (db) => {
|
|
83
|
-
db.exec(`ALTER TABLE sessions ADD COLUMN pr_url TEXT`);
|
|
84
|
-
db.exec(`ALTER TABLE sessions ADD COLUMN pr_number INTEGER`);
|
|
85
|
-
db.exec(`ALTER TABLE sessions ADD COLUMN pr_status TEXT`);
|
|
86
|
-
},
|
|
87
|
-
},
|
|
88
|
-
{
|
|
89
|
-
id: 5,
|
|
90
|
-
name: "add_group_path_index",
|
|
91
|
-
up: (db) => {
|
|
92
|
-
db.exec(
|
|
93
|
-
`CREATE INDEX IF NOT EXISTS idx_sessions_group ON sessions(group_path)`
|
|
94
|
-
);
|
|
95
|
-
},
|
|
96
|
-
},
|
|
97
|
-
{
|
|
98
|
-
id: 6,
|
|
99
|
-
name: "add_orchestration_columns_to_sessions",
|
|
100
|
-
up: (db) => {
|
|
101
|
-
db.exec(
|
|
102
|
-
`ALTER TABLE sessions ADD COLUMN conductor_session_id TEXT REFERENCES sessions(id)`
|
|
103
|
-
);
|
|
104
|
-
db.exec(`ALTER TABLE sessions ADD COLUMN worker_task TEXT`);
|
|
105
|
-
db.exec(`ALTER TABLE sessions ADD COLUMN worker_status TEXT`);
|
|
106
|
-
db.exec(
|
|
107
|
-
`CREATE INDEX IF NOT EXISTS idx_sessions_conductor ON sessions(conductor_session_id)`
|
|
108
|
-
);
|
|
109
|
-
},
|
|
110
|
-
},
|
|
111
|
-
{
|
|
112
|
-
id: 7,
|
|
113
|
-
name: "add_auto_approve_to_sessions",
|
|
114
|
-
up: (db) => {
|
|
115
|
-
db.exec(
|
|
116
|
-
`ALTER TABLE sessions ADD COLUMN auto_approve INTEGER NOT NULL DEFAULT 0`
|
|
117
|
-
);
|
|
118
|
-
},
|
|
119
|
-
},
|
|
120
|
-
{
|
|
121
|
-
id: 8,
|
|
122
|
-
name: "add_dev_server_columns",
|
|
123
|
-
up: (db) => {
|
|
124
|
-
db.exec(
|
|
125
|
-
`ALTER TABLE dev_servers ADD COLUMN type TEXT NOT NULL DEFAULT 'node'`
|
|
126
|
-
);
|
|
127
|
-
db.exec(
|
|
128
|
-
`ALTER TABLE dev_servers ADD COLUMN name TEXT NOT NULL DEFAULT ''`
|
|
129
|
-
);
|
|
130
|
-
db.exec(
|
|
131
|
-
`ALTER TABLE dev_servers ADD COLUMN command TEXT NOT NULL DEFAULT ''`
|
|
132
|
-
);
|
|
133
|
-
db.exec(`ALTER TABLE dev_servers ADD COLUMN pid INTEGER`);
|
|
134
|
-
db.exec(
|
|
135
|
-
`ALTER TABLE dev_servers ADD COLUMN working_directory TEXT NOT NULL DEFAULT ''`
|
|
136
|
-
);
|
|
137
|
-
},
|
|
138
|
-
},
|
|
139
|
-
{
|
|
140
|
-
id: 9,
|
|
141
|
-
name: "add_project_id_to_sessions",
|
|
142
|
-
up: (db) => {
|
|
143
|
-
db.exec(
|
|
144
|
-
`ALTER TABLE sessions ADD COLUMN project_id TEXT REFERENCES projects(id)`
|
|
145
|
-
);
|
|
146
|
-
db.exec(
|
|
147
|
-
`UPDATE sessions SET project_id = 'uncategorized' WHERE project_id IS NULL`
|
|
148
|
-
);
|
|
149
|
-
db.exec(
|
|
150
|
-
`CREATE INDEX IF NOT EXISTS idx_sessions_project ON sessions(project_id)`
|
|
151
|
-
);
|
|
152
|
-
},
|
|
153
|
-
},
|
|
154
|
-
{
|
|
155
|
-
id: 10,
|
|
156
|
-
name: "add_project_id_to_dev_servers",
|
|
157
|
-
up: (db) => {
|
|
158
|
-
// Check if column exists first
|
|
159
|
-
const cols = db.prepare(`PRAGMA table_info(dev_servers)`).all() as {
|
|
160
|
-
name: string;
|
|
161
|
-
}[];
|
|
162
|
-
if (cols.some((c) => c.name === "project_id")) return;
|
|
163
|
-
|
|
164
|
-
db.exec(
|
|
165
|
-
`ALTER TABLE dev_servers ADD COLUMN project_id TEXT REFERENCES projects(id)`
|
|
166
|
-
);
|
|
167
|
-
// Migrate from session_id if it exists
|
|
168
|
-
const hasSessionId = cols.some((c) => c.name === "session_id");
|
|
169
|
-
if (hasSessionId) {
|
|
170
|
-
db.exec(`
|
|
171
|
-
UPDATE dev_servers
|
|
172
|
-
SET project_id = (
|
|
173
|
-
SELECT COALESCE(s.project_id, 'uncategorized')
|
|
174
|
-
FROM sessions s
|
|
175
|
-
WHERE s.id = dev_servers.session_id
|
|
176
|
-
)
|
|
177
|
-
WHERE project_id IS NULL
|
|
178
|
-
`);
|
|
179
|
-
}
|
|
180
|
-
db.exec(
|
|
181
|
-
`UPDATE dev_servers SET project_id = 'uncategorized' WHERE project_id IS NULL`
|
|
182
|
-
);
|
|
183
|
-
db.exec(
|
|
184
|
-
`CREATE INDEX IF NOT EXISTS idx_dev_servers_project ON dev_servers(project_id)`
|
|
185
|
-
);
|
|
186
|
-
},
|
|
187
|
-
},
|
|
188
|
-
{
|
|
189
|
-
id: 11,
|
|
190
|
-
name: "add_tmux_name_to_sessions",
|
|
191
|
-
up: (db) => {
|
|
192
|
-
db.exec(`ALTER TABLE sessions ADD COLUMN tmux_name TEXT`);
|
|
193
|
-
// Backfill existing sessions with computed tmux name
|
|
194
|
-
db.exec(
|
|
195
|
-
`UPDATE sessions SET tmux_name = agent_type || '-' || id WHERE tmux_name IS NULL`
|
|
196
|
-
);
|
|
197
|
-
},
|
|
198
|
-
},
|
|
199
|
-
{
|
|
200
|
-
id: 12,
|
|
201
|
-
name: "add_initial_prompt_to_projects",
|
|
202
|
-
up: (db) => {
|
|
203
|
-
db.exec(`ALTER TABLE projects ADD COLUMN initial_prompt TEXT`);
|
|
204
|
-
},
|
|
205
|
-
},
|
|
206
|
-
{
|
|
207
|
-
id: 13,
|
|
208
|
-
name: "add_project_repositories_table",
|
|
209
|
-
up: (db) => {
|
|
210
|
-
db.exec(`
|
|
211
|
-
CREATE TABLE IF NOT EXISTS project_repositories (
|
|
212
|
-
id TEXT PRIMARY KEY,
|
|
213
|
-
project_id TEXT NOT NULL,
|
|
214
|
-
name TEXT NOT NULL,
|
|
215
|
-
path TEXT NOT NULL,
|
|
216
|
-
is_primary INTEGER NOT NULL DEFAULT 0,
|
|
217
|
-
sort_order INTEGER NOT NULL DEFAULT 0,
|
|
218
|
-
FOREIGN KEY (project_id) REFERENCES projects(id) ON DELETE CASCADE
|
|
219
|
-
)
|
|
220
|
-
`);
|
|
221
|
-
db.exec(
|
|
222
|
-
`CREATE INDEX IF NOT EXISTS idx_project_repositories_project ON project_repositories(project_id)`
|
|
223
|
-
);
|
|
224
|
-
},
|
|
225
|
-
},
|
|
226
|
-
{
|
|
227
|
-
id: 14,
|
|
228
|
-
name: "add_hosts",
|
|
229
|
-
up: (db) => {
|
|
230
|
-
db.exec(`
|
|
231
|
-
CREATE TABLE IF NOT EXISTS hosts (
|
|
232
|
-
id TEXT PRIMARY KEY,
|
|
233
|
-
name TEXT NOT NULL,
|
|
234
|
-
ssh_target TEXT NOT NULL,
|
|
235
|
-
sort_order INTEGER NOT NULL DEFAULT 0,
|
|
236
|
-
created_at TEXT NOT NULL DEFAULT (datetime('now'))
|
|
237
|
-
)
|
|
238
|
-
`);
|
|
239
|
-
db.exec(
|
|
240
|
-
`ALTER TABLE projects ADD COLUMN host_id TEXT NOT NULL DEFAULT 'local'`
|
|
241
|
-
);
|
|
242
|
-
db.exec(
|
|
243
|
-
`ALTER TABLE sessions ADD COLUMN host_id TEXT NOT NULL DEFAULT 'local'`
|
|
244
|
-
);
|
|
245
|
-
},
|
|
246
|
-
},
|
|
247
|
-
{
|
|
248
|
-
id: 15,
|
|
249
|
-
name: "add_workspaces",
|
|
250
|
-
up: (db) => {
|
|
251
|
-
db.exec(`
|
|
252
|
-
CREATE TABLE IF NOT EXISTS workspaces (
|
|
253
|
-
id TEXT PRIMARY KEY,
|
|
254
|
-
name TEXT NOT NULL,
|
|
255
|
-
sort_order INTEGER NOT NULL DEFAULT 0,
|
|
256
|
-
collapsed INTEGER NOT NULL DEFAULT 0,
|
|
257
|
-
created_at TEXT NOT NULL DEFAULT (datetime('now'))
|
|
258
|
-
)
|
|
259
|
-
`);
|
|
260
|
-
db.exec(`ALTER TABLE projects ADD COLUMN workspace_id TEXT`);
|
|
261
|
-
},
|
|
262
|
-
},
|
|
263
|
-
{
|
|
264
|
-
id: 16,
|
|
265
|
-
name: "add_task_columns_to_sessions",
|
|
266
|
-
up: (db) => {
|
|
267
|
-
db.exec(`ALTER TABLE sessions ADD COLUMN task_prompt TEXT`);
|
|
268
|
-
db.exec(`ALTER TABLE sessions ADD COLUMN task_status TEXT`);
|
|
269
|
-
db.exec(
|
|
270
|
-
`CREATE INDEX IF NOT EXISTS idx_sessions_task_status ON sessions(task_status)`
|
|
271
|
-
);
|
|
272
|
-
},
|
|
273
|
-
},
|
|
274
|
-
{
|
|
275
|
-
id: 17,
|
|
276
|
-
name: "add_bus_messages",
|
|
277
|
-
up: (db) => {
|
|
278
|
-
db.exec(`
|
|
279
|
-
CREATE TABLE IF NOT EXISTS bus_messages (
|
|
280
|
-
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
281
|
-
from_id TEXT,
|
|
282
|
-
from_name TEXT NOT NULL,
|
|
283
|
-
to_id TEXT NOT NULL,
|
|
284
|
-
to_name TEXT NOT NULL,
|
|
285
|
-
body TEXT NOT NULL,
|
|
286
|
-
created_at TEXT NOT NULL DEFAULT (datetime('now')),
|
|
287
|
-
delivered_at TEXT,
|
|
288
|
-
read_at TEXT
|
|
289
|
-
)
|
|
290
|
-
`);
|
|
291
|
-
db.exec(
|
|
292
|
-
`CREATE INDEX IF NOT EXISTS idx_bus_to ON bus_messages(to_id, read_at)`
|
|
293
|
-
);
|
|
294
|
-
},
|
|
295
|
-
},
|
|
296
|
-
{
|
|
297
|
-
id: 18,
|
|
298
|
-
name: "add_chat",
|
|
299
|
-
up: (db) => {
|
|
300
|
-
db.exec(
|
|
301
|
-
`ALTER TABLE sessions ADD COLUMN view TEXT NOT NULL DEFAULT 'terminal'`
|
|
302
|
-
);
|
|
303
|
-
db.exec(`
|
|
304
|
-
CREATE TABLE IF NOT EXISTS chat_items (
|
|
305
|
-
session_id TEXT NOT NULL,
|
|
306
|
-
item_id TEXT NOT NULL,
|
|
307
|
-
seq INTEGER NOT NULL,
|
|
308
|
-
data TEXT NOT NULL,
|
|
309
|
-
PRIMARY KEY (session_id, item_id)
|
|
310
|
-
)
|
|
311
|
-
`);
|
|
312
|
-
db.exec(
|
|
313
|
-
`CREATE INDEX IF NOT EXISTS idx_chat_items_seq ON chat_items(session_id, seq)`
|
|
314
|
-
);
|
|
315
|
-
},
|
|
316
|
-
},
|
|
317
|
-
{
|
|
318
|
-
id: 19,
|
|
319
|
-
name: "add_lumifyhub",
|
|
320
|
-
up: (db) => {
|
|
321
|
-
db.exec(`
|
|
322
|
-
CREATE TABLE IF NOT EXISTS lumifyhub_connection (
|
|
323
|
-
id INTEGER PRIMARY KEY CHECK (id = 1),
|
|
324
|
-
base_url TEXT NOT NULL,
|
|
325
|
-
token TEXT NOT NULL,
|
|
326
|
-
user_id TEXT,
|
|
327
|
-
user_email TEXT,
|
|
328
|
-
user_name TEXT,
|
|
329
|
-
connected_at TEXT NOT NULL DEFAULT (datetime('now'))
|
|
330
|
-
)
|
|
331
|
-
`);
|
|
332
|
-
db.exec(`
|
|
333
|
-
CREATE TABLE IF NOT EXISTS lumifyhub_connect_attempts (
|
|
334
|
-
state TEXT PRIMARY KEY,
|
|
335
|
-
verifier TEXT NOT NULL,
|
|
336
|
-
redirect_uri TEXT NOT NULL,
|
|
337
|
-
created_at TEXT NOT NULL DEFAULT (datetime('now'))
|
|
338
|
-
)
|
|
339
|
-
`);
|
|
340
|
-
db.exec(`ALTER TABLE workspaces ADD COLUMN lh_workspace_id TEXT`);
|
|
341
|
-
db.exec(`ALTER TABLE workspaces ADD COLUMN lh_workspace_slug TEXT`);
|
|
342
|
-
db.exec(`ALTER TABLE workspaces ADD COLUMN lh_workspace_name TEXT`);
|
|
343
|
-
db.exec(`ALTER TABLE projects ADD COLUMN lh_board_id TEXT`);
|
|
344
|
-
db.exec(`ALTER TABLE projects ADD COLUMN lh_board_name TEXT`);
|
|
345
|
-
db.exec(`ALTER TABLE projects ADD COLUMN lh_board_page_id TEXT`);
|
|
346
|
-
db.exec(`ALTER TABLE sessions ADD COLUMN lh_card_id TEXT`);
|
|
347
|
-
db.exec(`ALTER TABLE sessions ADD COLUMN lh_board_id TEXT`);
|
|
348
|
-
db.exec(`ALTER TABLE sessions ADD COLUMN lh_card_list TEXT`);
|
|
349
|
-
},
|
|
350
|
-
},
|
|
351
|
-
{
|
|
352
|
-
id: 20,
|
|
353
|
-
name: "add_lumifyhub_published_docs",
|
|
354
|
-
up: (db) => {
|
|
355
|
-
db.exec(`
|
|
356
|
-
CREATE TABLE IF NOT EXISTS lumifyhub_published_docs (
|
|
357
|
-
project_id TEXT NOT NULL REFERENCES projects(id) ON DELETE CASCADE,
|
|
358
|
-
repo_path TEXT NOT NULL,
|
|
359
|
-
page_id TEXT NOT NULL,
|
|
360
|
-
content_hash TEXT NOT NULL,
|
|
361
|
-
published_at TEXT NOT NULL DEFAULT (datetime('now')),
|
|
362
|
-
PRIMARY KEY (project_id, repo_path)
|
|
363
|
-
)
|
|
364
|
-
`);
|
|
365
|
-
},
|
|
366
|
-
},
|
|
367
|
-
{
|
|
368
|
-
id: 21,
|
|
369
|
-
name: "add_chat_access",
|
|
370
|
-
up: (db) => {
|
|
371
|
-
// What a chat agent may do without asking; full is the old behavior.
|
|
372
|
-
db.exec(
|
|
373
|
-
`ALTER TABLE sessions ADD COLUMN chat_access TEXT NOT NULL DEFAULT 'full'`
|
|
374
|
-
);
|
|
375
|
-
// After an undo, where the next start resumes the conversation from.
|
|
376
|
-
db.exec(`ALTER TABLE sessions ADD COLUMN chat_resume_at TEXT`);
|
|
377
|
-
},
|
|
378
|
-
},
|
|
379
|
-
{
|
|
380
|
-
id: 22,
|
|
381
|
-
name: "add_last_seen",
|
|
382
|
-
up: (db) => {
|
|
383
|
-
// When the reader last looked, so "needs you" means something new.
|
|
384
|
-
db.exec(`ALTER TABLE sessions ADD COLUMN last_seen_at TEXT`);
|
|
385
|
-
},
|
|
386
|
-
},
|
|
387
|
-
{
|
|
388
|
-
id: 23,
|
|
389
|
-
name: "mark_existing_sessions_seen",
|
|
390
|
-
up: (db) => {
|
|
391
|
-
// Start quiet: what finished before "needs you" meant news is old news.
|
|
392
|
-
db.exec(
|
|
393
|
-
`UPDATE sessions SET last_seen_at = datetime('now') WHERE last_seen_at IS NULL`
|
|
394
|
-
);
|
|
395
|
-
},
|
|
396
|
-
},
|
|
397
|
-
{
|
|
398
|
-
id: 24,
|
|
399
|
-
name: "add_stacks",
|
|
400
|
-
up: (db) => {
|
|
401
|
-
// A LumifyHub board's cards run as stacked tasks (lib/stacks).
|
|
402
|
-
db.exec(`
|
|
403
|
-
CREATE TABLE IF NOT EXISTS stacks (
|
|
404
|
-
id TEXT PRIMARY KEY,
|
|
405
|
-
project_id TEXT NOT NULL REFERENCES projects(id) ON DELETE CASCADE,
|
|
406
|
-
lh_board_id TEXT NOT NULL,
|
|
407
|
-
name TEXT NOT NULL,
|
|
408
|
-
status TEXT NOT NULL DEFAULT 'running',
|
|
409
|
-
max_parallel INTEGER NOT NULL DEFAULT 3,
|
|
410
|
-
progress TEXT,
|
|
411
|
-
error TEXT,
|
|
412
|
-
created_at TEXT NOT NULL DEFAULT (datetime('now')),
|
|
413
|
-
landed_at TEXT
|
|
414
|
-
)
|
|
415
|
-
`);
|
|
416
|
-
db.exec(`
|
|
417
|
-
CREATE TABLE IF NOT EXISTS stack_items (
|
|
418
|
-
id TEXT PRIMARY KEY,
|
|
419
|
-
stack_id TEXT NOT NULL REFERENCES stacks(id) ON DELETE CASCADE,
|
|
420
|
-
position INTEGER NOT NULL,
|
|
421
|
-
lh_card_id TEXT NOT NULL,
|
|
422
|
-
ticket TEXT,
|
|
423
|
-
title TEXT NOT NULL,
|
|
424
|
-
parent_item_id TEXT,
|
|
425
|
-
also_item_ids TEXT NOT NULL DEFAULT '[]',
|
|
426
|
-
blocker_item_ids TEXT NOT NULL DEFAULT '[]',
|
|
427
|
-
status TEXT NOT NULL DEFAULT 'planned',
|
|
428
|
-
session_id TEXT,
|
|
429
|
-
base_branch TEXT,
|
|
430
|
-
base_tip TEXT,
|
|
431
|
-
pr_number INTEGER,
|
|
432
|
-
note TEXT,
|
|
433
|
-
error TEXT
|
|
434
|
-
)
|
|
435
|
-
`);
|
|
436
|
-
db.exec(
|
|
437
|
-
`CREATE INDEX IF NOT EXISTS idx_stack_items_stack ON stack_items(stack_id, position)`
|
|
438
|
-
);
|
|
439
|
-
db.exec(
|
|
440
|
-
`CREATE INDEX IF NOT EXISTS idx_stack_items_session ON stack_items(session_id)`
|
|
441
|
-
);
|
|
442
|
-
},
|
|
443
|
-
},
|
|
444
|
-
{
|
|
445
|
-
id: 25,
|
|
446
|
-
name: "add_stack_item_attempts",
|
|
447
|
-
up: (db) => {
|
|
448
|
-
// Starts retried after a transient failure, and holds the plan made
|
|
449
|
-
// (an open blocker outside the stack) that a retry must not undo.
|
|
450
|
-
db.exec(
|
|
451
|
-
`ALTER TABLE stack_items ADD COLUMN attempts INTEGER NOT NULL DEFAULT 0`
|
|
452
|
-
);
|
|
453
|
-
db.exec(
|
|
454
|
-
`ALTER TABLE stack_items ADD COLUMN held_outside INTEGER NOT NULL DEFAULT 0`
|
|
455
|
-
);
|
|
456
|
-
},
|
|
457
|
-
},
|
|
458
|
-
{
|
|
459
|
-
id: 26,
|
|
460
|
-
name: "add_workspace_orchestrator",
|
|
461
|
-
up: (db) => {
|
|
462
|
-
// One standing orchestrator chat per workspace. Its events are kept
|
|
463
|
-
// by key: a key present means that event is known, delivered or
|
|
464
|
-
// queued. An event is sent once seen on two diffs in a row (hits);
|
|
465
|
-
// a passing one that cleared is remembered (cleared_at) so it can't
|
|
466
|
-
// fire again too soon. The log counts deliveries per subject.
|
|
467
|
-
db.exec(`ALTER TABLE sessions ADD COLUMN role TEXT`);
|
|
468
|
-
db.exec(`ALTER TABLE sessions ADD COLUMN workspace_id TEXT`);
|
|
469
|
-
db.exec(
|
|
470
|
-
`CREATE UNIQUE INDEX IF NOT EXISTS idx_sessions_orchestrator
|
|
471
|
-
ON sessions(workspace_id) WHERE role = 'orchestrator'`
|
|
472
|
-
);
|
|
473
|
-
db.exec(`
|
|
474
|
-
CREATE TABLE IF NOT EXISTS orchestrator_events (
|
|
475
|
-
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
476
|
-
workspace_id TEXT NOT NULL,
|
|
477
|
-
key TEXT NOT NULL,
|
|
478
|
-
subject TEXT,
|
|
479
|
-
line TEXT NOT NULL,
|
|
480
|
-
sticky INTEGER NOT NULL DEFAULT 1,
|
|
481
|
-
low INTEGER NOT NULL DEFAULT 0,
|
|
482
|
-
hits INTEGER NOT NULL DEFAULT 1,
|
|
483
|
-
created_at TEXT NOT NULL,
|
|
484
|
-
delivered_at TEXT,
|
|
485
|
-
cleared_at TEXT,
|
|
486
|
-
UNIQUE (workspace_id, key)
|
|
487
|
-
)
|
|
488
|
-
`);
|
|
489
|
-
db.exec(`
|
|
490
|
-
CREATE TABLE IF NOT EXISTS orchestrator_event_log (
|
|
491
|
-
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
492
|
-
workspace_id TEXT NOT NULL,
|
|
493
|
-
subject TEXT,
|
|
494
|
-
delivered_at TEXT NOT NULL
|
|
495
|
-
)
|
|
496
|
-
`);
|
|
497
|
-
db.exec(
|
|
498
|
-
`CREATE INDEX IF NOT EXISTS idx_orchestrator_event_log
|
|
499
|
-
ON orchestrator_event_log(workspace_id, delivered_at)`
|
|
500
|
-
);
|
|
501
|
-
},
|
|
502
|
-
},
|
|
503
|
-
{
|
|
504
|
-
id: 27,
|
|
505
|
-
name: "add_devices_and_settings",
|
|
506
|
-
up: (db) => {
|
|
507
|
-
// Paired phones, tablets and laptops. Only a hash of each token is kept.
|
|
508
|
-
db.exec(`
|
|
509
|
-
CREATE TABLE IF NOT EXISTS devices (
|
|
510
|
-
id TEXT PRIMARY KEY,
|
|
511
|
-
name TEXT NOT NULL,
|
|
512
|
-
token_hash TEXT NOT NULL UNIQUE,
|
|
513
|
-
user_agent TEXT,
|
|
514
|
-
created_at TEXT NOT NULL DEFAULT (datetime('now')),
|
|
515
|
-
last_seen_at TEXT,
|
|
516
|
-
last_address TEXT,
|
|
517
|
-
revoked_at TEXT
|
|
518
|
-
)
|
|
519
|
-
`);
|
|
520
|
-
// Small app-wide switches (Wi-Fi access, pairing on the tailnet).
|
|
521
|
-
db.exec(`
|
|
522
|
-
CREATE TABLE IF NOT EXISTS settings (
|
|
523
|
-
key TEXT PRIMARY KEY,
|
|
524
|
-
value TEXT NOT NULL
|
|
525
|
-
)
|
|
526
|
-
`);
|
|
527
|
-
},
|
|
528
|
-
},
|
|
529
|
-
{
|
|
530
|
-
id: 28,
|
|
531
|
-
name: "add_orchestrator_acting",
|
|
532
|
-
up: (db) => {
|
|
533
|
-
// The orchestrator's brakes (settings per workspace, and the brake in
|
|
534
|
-
// force so it's noted once), its starts, its decision log, the reviews
|
|
535
|
-
// and scope checks it ran per commit, and gate failures per task.
|
|
536
|
-
db.exec(
|
|
537
|
-
`ALTER TABLE workspaces ADD COLUMN orch_max_running INTEGER NOT NULL DEFAULT 4`
|
|
538
|
-
);
|
|
539
|
-
db.exec(
|
|
540
|
-
`ALTER TABLE workspaces ADD COLUMN orch_max_starts_per_hour INTEGER NOT NULL DEFAULT 6`
|
|
541
|
-
);
|
|
542
|
-
db.exec(`ALTER TABLE workspaces ADD COLUMN orch_brake TEXT`);
|
|
543
|
-
// The secret an orchestrator's worker sends with each tool call.
|
|
544
|
-
db.exec(`ALTER TABLE sessions ADD COLUMN orch_token TEXT`);
|
|
545
|
-
db.exec(`
|
|
546
|
-
CREATE TABLE IF NOT EXISTS orchestrator_starts (
|
|
547
|
-
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
548
|
-
workspace_id TEXT NOT NULL,
|
|
549
|
-
kind TEXT NOT NULL,
|
|
550
|
-
target TEXT,
|
|
551
|
-
created_at TEXT NOT NULL
|
|
552
|
-
)
|
|
553
|
-
`);
|
|
554
|
-
db.exec(
|
|
555
|
-
`CREATE INDEX IF NOT EXISTS idx_orchestrator_starts
|
|
556
|
-
ON orchestrator_starts(workspace_id, created_at)`
|
|
557
|
-
);
|
|
558
|
-
db.exec(`
|
|
559
|
-
CREATE TABLE IF NOT EXISTS orchestrator_notes (
|
|
560
|
-
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
561
|
-
workspace_id TEXT NOT NULL,
|
|
562
|
-
kind TEXT NOT NULL DEFAULT 'note',
|
|
563
|
-
text TEXT NOT NULL,
|
|
564
|
-
created_at TEXT NOT NULL DEFAULT (datetime('now'))
|
|
565
|
-
)
|
|
566
|
-
`);
|
|
567
|
-
db.exec(
|
|
568
|
-
`CREATE INDEX IF NOT EXISTS idx_orchestrator_notes
|
|
569
|
-
ON orchestrator_notes(workspace_id, id)`
|
|
570
|
-
);
|
|
571
|
-
db.exec(`
|
|
572
|
-
CREATE TABLE IF NOT EXISTS orchestrator_checks (
|
|
573
|
-
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
574
|
-
workspace_id TEXT NOT NULL,
|
|
575
|
-
session_id TEXT NOT NULL,
|
|
576
|
-
sha TEXT NOT NULL,
|
|
577
|
-
kind TEXT NOT NULL,
|
|
578
|
-
status TEXT NOT NULL,
|
|
579
|
-
detail TEXT,
|
|
580
|
-
created_at TEXT NOT NULL DEFAULT (datetime('now')),
|
|
581
|
-
UNIQUE (session_id, sha, kind)
|
|
582
|
-
)
|
|
583
|
-
`);
|
|
584
|
-
db.exec(`
|
|
585
|
-
CREATE TABLE IF NOT EXISTS orchestrator_gate_failures (
|
|
586
|
-
session_id TEXT NOT NULL,
|
|
587
|
-
gate TEXT NOT NULL,
|
|
588
|
-
workspace_id TEXT NOT NULL,
|
|
589
|
-
count INTEGER NOT NULL DEFAULT 0,
|
|
590
|
-
last_reason TEXT,
|
|
591
|
-
escalated_at TEXT,
|
|
592
|
-
PRIMARY KEY (session_id, gate)
|
|
593
|
-
)
|
|
594
|
-
`);
|
|
595
|
-
},
|
|
596
|
-
},
|
|
597
|
-
{
|
|
598
|
-
id: 29,
|
|
599
|
-
name: "add_orchestrator_asks_and_pause",
|
|
600
|
-
up: (db) => {
|
|
601
|
-
// Items the orchestrator parks for Saad. One open ask per subject (a
|
|
602
|
-
// task, the brakes, a title it raised), so a repeat escalation updates
|
|
603
|
-
// it instead of adding another. An approval of a gate or brake ask is
|
|
604
|
-
// spent once (used_at), on the commit it was asked about (sha).
|
|
605
|
-
db.exec(`
|
|
606
|
-
CREATE TABLE IF NOT EXISTS orchestrator_asks (
|
|
607
|
-
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
608
|
-
workspace_id TEXT NOT NULL,
|
|
609
|
-
subject TEXT NOT NULL,
|
|
610
|
-
kind TEXT NOT NULL,
|
|
611
|
-
title TEXT NOT NULL,
|
|
612
|
-
detail TEXT NOT NULL DEFAULT '',
|
|
613
|
-
link TEXT,
|
|
614
|
-
sha TEXT,
|
|
615
|
-
status TEXT NOT NULL DEFAULT 'open',
|
|
616
|
-
answer TEXT,
|
|
617
|
-
created_at TEXT NOT NULL DEFAULT (datetime('now')),
|
|
618
|
-
resolved_at TEXT,
|
|
619
|
-
used_at TEXT,
|
|
620
|
-
brake_key TEXT
|
|
621
|
-
)
|
|
622
|
-
`);
|
|
623
|
-
db.exec(
|
|
624
|
-
`CREATE UNIQUE INDEX IF NOT EXISTS idx_orchestrator_asks_open
|
|
625
|
-
ON orchestrator_asks(workspace_id, subject) WHERE status = 'open'`
|
|
626
|
-
);
|
|
627
|
-
db.exec(
|
|
628
|
-
`CREATE INDEX IF NOT EXISTS idx_orchestrator_asks_workspace
|
|
629
|
-
ON orchestrator_asks(workspace_id, status)`
|
|
630
|
-
);
|
|
631
|
-
// While set, the orchestrator acts on nothing: events queue and its
|
|
632
|
-
// acting tools refuse.
|
|
633
|
-
db.exec(`ALTER TABLE workspaces ADD COLUMN orch_paused_at TEXT`);
|
|
634
|
-
// Proof a person approves: a paired device may answer asks only once
|
|
635
|
-
// Saad lets it, and approvals that matter need a passkey assertion
|
|
636
|
-
// (user verification) on a challenge bound to the one item.
|
|
637
|
-
db.exec(
|
|
638
|
-
`ALTER TABLE devices ADD COLUMN can_approve INTEGER NOT NULL DEFAULT 0`
|
|
639
|
-
);
|
|
640
|
-
db.exec(`
|
|
641
|
-
CREATE TABLE IF NOT EXISTS passkeys (
|
|
642
|
-
id TEXT PRIMARY KEY,
|
|
643
|
-
public_key BLOB NOT NULL,
|
|
644
|
-
counter INTEGER NOT NULL DEFAULT 0,
|
|
645
|
-
transports TEXT,
|
|
646
|
-
rp_id TEXT NOT NULL,
|
|
647
|
-
name TEXT NOT NULL,
|
|
648
|
-
registered_via TEXT,
|
|
649
|
-
registered_from TEXT,
|
|
650
|
-
user_agent TEXT,
|
|
651
|
-
created_at TEXT NOT NULL DEFAULT (datetime('now')),
|
|
652
|
-
last_used_at TEXT,
|
|
653
|
-
revoked_at TEXT
|
|
654
|
-
)
|
|
655
|
-
`);
|
|
656
|
-
db.exec(`
|
|
657
|
-
CREATE TABLE IF NOT EXISTS presence_challenges (
|
|
658
|
-
challenge TEXT PRIMARY KEY,
|
|
659
|
-
kind TEXT NOT NULL,
|
|
660
|
-
purpose TEXT NOT NULL,
|
|
661
|
-
binding TEXT NOT NULL,
|
|
662
|
-
rp_id TEXT NOT NULL,
|
|
663
|
-
expires_at INTEGER NOT NULL,
|
|
664
|
-
used_at INTEGER
|
|
665
|
-
)
|
|
666
|
-
`);
|
|
667
|
-
db.exec(`
|
|
668
|
-
CREATE TABLE IF NOT EXISTS passkey_enrollments (
|
|
669
|
-
code_hash TEXT PRIMARY KEY,
|
|
670
|
-
expires_at INTEGER NOT NULL,
|
|
671
|
-
used_at INTEGER
|
|
672
|
-
)
|
|
673
|
-
`);
|
|
674
|
-
},
|
|
675
|
-
},
|
|
676
|
-
{
|
|
677
|
-
id: 30,
|
|
678
|
-
name: "add_sessions_archived_at",
|
|
679
|
-
up: (db) => {
|
|
680
|
-
// Done sessions leave the sidebar but are never deleted, so their
|
|
681
|
-
// history and chat items stay.
|
|
682
|
-
db.exec(`ALTER TABLE sessions ADD COLUMN archived_at TEXT`);
|
|
683
|
-
db.exec(
|
|
684
|
-
`CREATE INDEX IF NOT EXISTS idx_sessions_archived_at ON sessions(archived_at)`
|
|
685
|
-
);
|
|
686
|
-
},
|
|
687
|
-
},
|
|
688
|
-
{
|
|
689
|
-
id: 31,
|
|
690
|
-
name: "add_stack_item_restacked_heads",
|
|
691
|
-
up: (db) => {
|
|
692
|
-
// A branch AgentOS rebased itself: its head before (the commit the
|
|
693
|
-
// task's code review covered) and after (the head AgentOS pushed).
|
|
694
|
-
// Each column checked, so a run cut off between the two can't be
|
|
695
|
-
// marked applied with one missing.
|
|
696
|
-
const cols = new Set(
|
|
697
|
-
(
|
|
698
|
-
db.prepare(`PRAGMA table_info(stack_items)`).all() as {
|
|
699
|
-
name: string;
|
|
700
|
-
}[]
|
|
701
|
-
).map((c) => c.name)
|
|
702
|
-
);
|
|
703
|
-
for (const col of ["restacked_from", "restacked_to"])
|
|
704
|
-
if (!cols.has(col))
|
|
705
|
-
db.exec(`ALTER TABLE stack_items ADD COLUMN ${col} TEXT`);
|
|
706
|
-
},
|
|
707
|
-
},
|
|
708
|
-
{
|
|
709
|
-
id: 32,
|
|
710
|
-
name: "add_sessions_pinned",
|
|
711
|
-
up: (db) => {
|
|
712
|
-
// Pinned sessions sit on their own shelf at the top of the sidebar.
|
|
713
|
-
db.exec(
|
|
714
|
-
`ALTER TABLE sessions ADD COLUMN pinned INTEGER NOT NULL DEFAULT 0`
|
|
715
|
-
);
|
|
716
|
-
},
|
|
717
|
-
},
|
|
718
|
-
{
|
|
719
|
-
id: 33,
|
|
720
|
-
name: "add_program_status",
|
|
721
|
-
up: (db) => {
|
|
722
|
-
// What programs in a terminal report about themselves (OSC 7501),
|
|
723
|
-
// by tmux session: kept so a restart doesn't forget a finished or
|
|
724
|
-
// blocked session.
|
|
725
|
-
db.exec(`
|
|
726
|
-
CREATE TABLE IF NOT EXISTS program_status (
|
|
727
|
-
session_name TEXT PRIMARY KEY,
|
|
728
|
-
records TEXT NOT NULL,
|
|
729
|
-
updated_at TEXT NOT NULL DEFAULT (datetime('now'))
|
|
730
|
-
)
|
|
731
|
-
`);
|
|
732
|
-
},
|
|
733
|
-
},
|
|
734
|
-
{
|
|
735
|
-
id: 34,
|
|
736
|
-
name: "add_session_names",
|
|
737
|
-
up: (db) => {
|
|
738
|
-
// Names a session had before it was renamed, so a message sent to
|
|
739
|
-
// an old name still reaches it.
|
|
740
|
-
db.exec(`
|
|
741
|
-
CREATE TABLE IF NOT EXISTS session_names (
|
|
742
|
-
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
743
|
-
session_id TEXT NOT NULL,
|
|
744
|
-
name TEXT NOT NULL,
|
|
745
|
-
renamed_at TEXT NOT NULL DEFAULT (datetime('now'))
|
|
746
|
-
)
|
|
747
|
-
`);
|
|
748
|
-
db.exec(
|
|
749
|
-
`CREATE INDEX IF NOT EXISTS idx_session_names_session ON session_names(session_id)`
|
|
750
|
-
);
|
|
751
|
-
},
|
|
752
|
-
},
|
|
753
|
-
{
|
|
754
|
-
id: 35,
|
|
755
|
-
name: "add_artifacts",
|
|
756
|
-
up: (db) => {
|
|
757
|
-
// Pages a chat agent showed with html_render. The HTML lives in a file
|
|
758
|
-
// under ~/.agent-os/artifacts; the row says whose it is and what it's
|
|
759
|
-
// called.
|
|
760
|
-
db.exec(`
|
|
761
|
-
CREATE TABLE IF NOT EXISTS artifacts (
|
|
762
|
-
id TEXT PRIMARY KEY,
|
|
763
|
-
session_id TEXT NOT NULL,
|
|
764
|
-
title TEXT NOT NULL,
|
|
765
|
-
path TEXT NOT NULL,
|
|
766
|
-
created_at TEXT NOT NULL DEFAULT (datetime('now'))
|
|
767
|
-
)
|
|
768
|
-
`);
|
|
769
|
-
db.exec(
|
|
770
|
-
`CREATE INDEX IF NOT EXISTS idx_artifacts_session ON artifacts(session_id, created_at)`
|
|
771
|
-
);
|
|
772
|
-
},
|
|
773
|
-
},
|
|
774
|
-
{
|
|
775
|
-
id: 36,
|
|
776
|
-
name: "add_chat_queue",
|
|
777
|
-
up: (db) => {
|
|
778
|
-
// Messages written while a chat turn runs, sent in order once it ends.
|
|
779
|
-
db.exec(`
|
|
780
|
-
CREATE TABLE IF NOT EXISTS chat_queue (
|
|
781
|
-
session_id TEXT NOT NULL,
|
|
782
|
-
id TEXT NOT NULL,
|
|
783
|
-
position REAL NOT NULL,
|
|
784
|
-
text TEXT NOT NULL,
|
|
785
|
-
images TEXT,
|
|
786
|
-
image_count INTEGER NOT NULL DEFAULT 0,
|
|
787
|
-
created_at INTEGER NOT NULL,
|
|
788
|
-
PRIMARY KEY (session_id, id)
|
|
789
|
-
)
|
|
790
|
-
`);
|
|
791
|
-
db.exec(
|
|
792
|
-
`CREATE INDEX IF NOT EXISTS idx_chat_queue_session ON chat_queue(session_id, position)`
|
|
793
|
-
);
|
|
794
|
-
// The agent's guess at the next message, shown in an empty composer.
|
|
795
|
-
db.exec(`ALTER TABLE sessions ADD COLUMN chat_suggestion TEXT`);
|
|
796
|
-
},
|
|
797
|
-
},
|
|
798
|
-
{
|
|
799
|
-
id: 37,
|
|
800
|
-
name: "add_chat_plan_and_turns",
|
|
801
|
-
// One transaction, so a crash part-way leaves nothing to skip as
|
|
802
|
-
// "duplicate column" on the next start.
|
|
803
|
-
up: (db) =>
|
|
804
|
-
db.transaction(() => {
|
|
805
|
-
// Plan mode is remembered per session; the context meter's last
|
|
806
|
-
// reading survives a reload; and the agent's running totals at its
|
|
807
|
-
// last turn tell what the next turn added.
|
|
808
|
-
db.exec(
|
|
809
|
-
`ALTER TABLE sessions ADD COLUMN chat_plan INTEGER NOT NULL DEFAULT 0`
|
|
810
|
-
);
|
|
811
|
-
db.exec(`ALTER TABLE sessions ADD COLUMN chat_context TEXT`);
|
|
812
|
-
db.exec(`ALTER TABLE sessions ADD COLUMN chat_usage TEXT`);
|
|
813
|
-
// What each chat turn cost, kept with the session's name and
|
|
814
|
-
// workspace so the history outlives the session.
|
|
815
|
-
db.exec(`
|
|
816
|
-
CREATE TABLE IF NOT EXISTS chat_turns (
|
|
817
|
-
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
818
|
-
session_id TEXT NOT NULL,
|
|
819
|
-
session_name TEXT NOT NULL,
|
|
820
|
-
workspace_id TEXT,
|
|
821
|
-
at INTEGER NOT NULL,
|
|
822
|
-
cost_usd REAL NOT NULL,
|
|
823
|
-
input_tokens INTEGER NOT NULL,
|
|
824
|
-
output_tokens INTEGER NOT NULL,
|
|
825
|
-
cache_read_tokens INTEGER NOT NULL,
|
|
826
|
-
cache_write_tokens INTEGER NOT NULL
|
|
827
|
-
)
|
|
828
|
-
`);
|
|
829
|
-
db.exec(
|
|
830
|
-
`CREATE INDEX IF NOT EXISTS idx_chat_turns_at ON chat_turns(at)`
|
|
831
|
-
);
|
|
832
|
-
db.exec(
|
|
833
|
-
`CREATE INDEX IF NOT EXISTS idx_chat_turns_session ON chat_turns(session_id, id)`
|
|
834
|
-
);
|
|
835
|
-
})(),
|
|
836
|
-
},
|
|
837
|
-
{
|
|
838
|
-
id: 38,
|
|
839
|
-
name: "add_schedules",
|
|
840
|
-
up: (db) => {
|
|
841
|
-
// Cron schedules that start agent work, and every run they made or
|
|
842
|
-
// skipped. A run's (schedule, slot) is unique: claiming the row is
|
|
843
|
-
// what stops a slot from running twice. Runs are never deleted, and a
|
|
844
|
-
// removed schedule is archived, not deleted, so its history stays.
|
|
845
|
-
db.exec(`
|
|
846
|
-
CREATE TABLE IF NOT EXISTS schedules (
|
|
847
|
-
id TEXT PRIMARY KEY,
|
|
848
|
-
workspace_id TEXT NOT NULL,
|
|
849
|
-
project_id TEXT,
|
|
850
|
-
name TEXT NOT NULL,
|
|
851
|
-
cron TEXT NOT NULL,
|
|
852
|
-
timezone TEXT NOT NULL DEFAULT 'America/Chicago',
|
|
853
|
-
prompt TEXT NOT NULL,
|
|
854
|
-
kind TEXT NOT NULL,
|
|
855
|
-
enabled INTEGER NOT NULL DEFAULT 1,
|
|
856
|
-
armed_at INTEGER NOT NULL,
|
|
857
|
-
archived_at TEXT,
|
|
858
|
-
created_at TEXT NOT NULL DEFAULT (datetime('now')),
|
|
859
|
-
updated_at TEXT NOT NULL DEFAULT (datetime('now'))
|
|
860
|
-
)
|
|
861
|
-
`);
|
|
862
|
-
db.exec(`
|
|
863
|
-
CREATE TABLE IF NOT EXISTS schedule_runs (
|
|
864
|
-
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
865
|
-
schedule_id TEXT NOT NULL,
|
|
866
|
-
slot TEXT NOT NULL,
|
|
867
|
-
slot_at INTEGER NOT NULL,
|
|
868
|
-
trigger TEXT NOT NULL,
|
|
869
|
-
outcome TEXT NOT NULL,
|
|
870
|
-
detail TEXT,
|
|
871
|
-
session_id TEXT,
|
|
872
|
-
created_at TEXT NOT NULL DEFAULT (datetime('now')),
|
|
873
|
-
UNIQUE (schedule_id, slot)
|
|
874
|
-
)
|
|
875
|
-
`);
|
|
876
|
-
db.exec(
|
|
877
|
-
`CREATE INDEX IF NOT EXISTS idx_schedule_runs_schedule ON schedule_runs(schedule_id, id)`
|
|
878
|
-
);
|
|
879
|
-
// Which server process ticks the schedules: one row, renewed each
|
|
880
|
-
// tick, so a second server on the same database never runs them.
|
|
881
|
-
db.exec(`
|
|
882
|
-
CREATE TABLE IF NOT EXISTS scheduler_lease (
|
|
883
|
-
id INTEGER PRIMARY KEY CHECK (id = 1),
|
|
884
|
-
owner TEXT NOT NULL,
|
|
885
|
-
pid INTEGER NOT NULL,
|
|
886
|
-
heartbeat INTEGER NOT NULL
|
|
887
|
-
)
|
|
888
|
-
`);
|
|
889
|
-
},
|
|
890
|
-
},
|
|
891
|
-
{
|
|
892
|
-
id: 39,
|
|
893
|
-
name: "add_session_name_source",
|
|
894
|
-
up: (db) => {
|
|
895
|
-
// Who named a session: "user" (typed or given explicitly, never
|
|
896
|
-
// renamed for them), "generated" (a title picked from its prompt) or
|
|
897
|
-
// "default" ("Session 4", or a placeholder until a title arrives).
|
|
898
|
-
// One transaction, so a crash part-way leaves nothing to skip.
|
|
899
|
-
db.transaction(() => {
|
|
900
|
-
db.exec(
|
|
901
|
-
`ALTER TABLE sessions ADD COLUMN name_source TEXT NOT NULL DEFAULT 'default'`
|
|
902
|
-
);
|
|
903
|
-
// A session renamed by hand before this keeps its name.
|
|
904
|
-
db.exec(
|
|
905
|
-
`UPDATE sessions SET name_source = 'user' WHERE id IN (SELECT session_id FROM session_names)`
|
|
906
|
-
);
|
|
907
|
-
})();
|
|
908
|
-
},
|
|
909
|
-
},
|
|
910
|
-
{
|
|
911
|
-
id: 40,
|
|
912
|
-
name: "add_schedule_targets_and_phone_notify",
|
|
913
|
-
// One transaction, so a crash part-way leaves nothing to skip as
|
|
914
|
-
// "duplicate column" on the next start.
|
|
915
|
-
up: (db) =>
|
|
916
|
-
db.transaction(() => {
|
|
917
|
-
// Where phone notifications go. One row; the bot token is never
|
|
918
|
-
// returned by any route or written to a log.
|
|
919
|
-
db.exec(`
|
|
920
|
-
CREATE TABLE IF NOT EXISTS notify_settings (
|
|
921
|
-
id INTEGER PRIMARY KEY CHECK (id = 1),
|
|
922
|
-
telegram_token TEXT,
|
|
923
|
-
telegram_chat_id TEXT,
|
|
924
|
-
updated_at TEXT NOT NULL DEFAULT (datetime('now'))
|
|
925
|
-
)
|
|
926
|
-
`);
|
|
927
|
-
// Every phone notification, sent or waiting out its source's
|
|
928
|
-
// minute, so one held across a restart still goes.
|
|
929
|
-
db.exec(`
|
|
930
|
-
CREATE TABLE IF NOT EXISTS notify_outbox (
|
|
931
|
-
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
932
|
-
source TEXT NOT NULL,
|
|
933
|
-
text TEXT NOT NULL,
|
|
934
|
-
created_at INTEGER NOT NULL,
|
|
935
|
-
send_after INTEGER NOT NULL,
|
|
936
|
-
sent_at INTEGER,
|
|
937
|
-
error TEXT
|
|
938
|
-
)
|
|
939
|
-
`);
|
|
940
|
-
db.exec(
|
|
941
|
-
`CREATE INDEX IF NOT EXISTS idx_notify_outbox_source ON notify_outbox(source, sent_at)`
|
|
942
|
-
);
|
|
943
|
-
// A "message" schedule's session, by id: names change, ids don't.
|
|
944
|
-
// And the agent session that made a schedule, when one did.
|
|
945
|
-
db.exec(`ALTER TABLE schedules ADD COLUMN target_session_id TEXT`);
|
|
946
|
-
db.exec(`ALTER TABLE schedules ADD COLUMN created_by_session_id TEXT`);
|
|
947
|
-
})(),
|
|
948
|
-
},
|
|
949
|
-
{
|
|
950
|
-
id: 41,
|
|
951
|
-
name: "add_task_setup",
|
|
952
|
-
up: (db) => {
|
|
953
|
-
// How a task's worktree setup (deps, setup commands) went before its
|
|
954
|
-
// agent started: running, ok or failed, how long, and what failed;
|
|
955
|
-
// and the brief its launch needs, so a restart can resume it.
|
|
956
|
-
db.transaction(() => {
|
|
957
|
-
db.exec(`ALTER TABLE sessions ADD COLUMN setup_status TEXT`);
|
|
958
|
-
db.exec(`ALTER TABLE sessions ADD COLUMN setup_ms INTEGER`);
|
|
959
|
-
db.exec(`ALTER TABLE sessions ADD COLUMN setup_error TEXT`);
|
|
960
|
-
db.exec(`ALTER TABLE sessions ADD COLUMN task_brief TEXT`);
|
|
961
|
-
})();
|
|
962
|
-
},
|
|
963
|
-
},
|
|
964
|
-
{
|
|
965
|
-
id: 42,
|
|
966
|
-
name: "add_host_links_and_moved_tasks",
|
|
967
|
-
up: (db) => {
|
|
968
|
-
// How this machine reaches another machine's own AgentOS: its URL and
|
|
969
|
-
// the device token it paired with. Apart from hosts so the token never
|
|
970
|
-
// rides along with a host row to the browser.
|
|
971
|
-
db.transaction(() => {
|
|
972
|
-
db.exec(`
|
|
973
|
-
CREATE TABLE IF NOT EXISTS host_links (
|
|
974
|
-
host_id TEXT PRIMARY KEY REFERENCES hosts(id) ON DELETE CASCADE,
|
|
975
|
-
url TEXT NOT NULL,
|
|
976
|
-
token TEXT NOT NULL,
|
|
977
|
-
linked_at TEXT NOT NULL DEFAULT (datetime('now'))
|
|
978
|
-
)
|
|
979
|
-
`);
|
|
980
|
-
// A task moved to another machine keeps its row, marked where it
|
|
981
|
-
// went; one that arrived names the session it came from, so the
|
|
982
|
-
// same move imported twice finds the first.
|
|
983
|
-
db.exec(`ALTER TABLE sessions ADD COLUMN moved_to TEXT`);
|
|
984
|
-
db.exec(`ALTER TABLE sessions ADD COLUMN moved_from TEXT`);
|
|
985
|
-
db.exec(
|
|
986
|
-
`CREATE INDEX IF NOT EXISTS idx_sessions_moved_from ON sessions(moved_from)`
|
|
987
|
-
);
|
|
988
|
-
})();
|
|
989
|
-
},
|
|
990
|
-
},
|
|
991
|
-
{
|
|
992
|
-
id: 43,
|
|
993
|
-
name: "scratch_project",
|
|
994
|
-
up: (db) => {
|
|
995
|
-
// Chats with no project work in a scratch folder rather than the home
|
|
996
|
-
// directory. Sessions already made keep their own folders.
|
|
997
|
-
db.prepare(
|
|
998
|
-
`UPDATE projects SET name = 'Scratch', working_directory = '~/.agent-os/scratch'
|
|
999
|
-
WHERE is_uncategorized = 1 AND name = 'Uncategorized' AND working_directory = '~'`
|
|
1000
|
-
).run();
|
|
1001
|
-
},
|
|
1002
|
-
},
|
|
1003
|
-
{
|
|
1004
|
-
id: 44,
|
|
1005
|
-
name: "chat_item_kind",
|
|
1006
|
-
up: (db) => {
|
|
1007
|
-
// Items of one kind (a chat's background tasks, its sent messages)
|
|
1008
|
-
// read from an index, not by parsing every row of a long conversation.
|
|
1009
|
-
const columns = db.prepare(`PRAGMA table_xinfo(chat_items)`).all() as {
|
|
1010
|
-
name: string;
|
|
1011
|
-
}[];
|
|
1012
|
-
if (!columns.some((c) => c.name === "kind"))
|
|
1013
|
-
db.exec(
|
|
1014
|
-
`ALTER TABLE chat_items ADD COLUMN kind TEXT
|
|
1015
|
-
GENERATED ALWAYS AS (json_extract(data, '$.kind')) VIRTUAL`
|
|
1016
|
-
);
|
|
1017
|
-
db.exec(
|
|
1018
|
-
`CREATE INDEX IF NOT EXISTS idx_chat_items_kind ON chat_items(session_id, kind, seq)`
|
|
1019
|
-
);
|
|
1020
|
-
},
|
|
1021
|
-
},
|
|
1022
|
-
{
|
|
1023
|
-
id: 45,
|
|
1024
|
-
name: "change_versions",
|
|
1025
|
-
up: (db) => {
|
|
1026
|
-
// What browsers show from these tables is pushed when they change
|
|
1027
|
-
// (lib/db/changes.ts) instead of polled. A table added later gets its
|
|
1028
|
-
// own migration.
|
|
1029
|
-
installChangeTriggers(db, TABLES_WATCHED_45);
|
|
1030
|
-
},
|
|
1031
|
-
},
|
|
1032
|
-
{
|
|
1033
|
-
id: 46,
|
|
1034
|
-
name: "session_port_slots",
|
|
1035
|
-
up: (db) => {
|
|
1036
|
-
// A session's port slot and the ports agentos.json's bases resolve to
|
|
1037
|
-
// with it (lib/ports.ts). The unique index is what keeps two parallel
|
|
1038
|
-
// starts, in any process, off one slot.
|
|
1039
|
-
const columns = db.prepare(`PRAGMA table_info(sessions)`).all() as {
|
|
1040
|
-
name: string;
|
|
1041
|
-
}[];
|
|
1042
|
-
if (!columns.some((c) => c.name === "port_slot"))
|
|
1043
|
-
db.exec(`ALTER TABLE sessions ADD COLUMN port_slot INTEGER`);
|
|
1044
|
-
if (!columns.some((c) => c.name === "ports"))
|
|
1045
|
-
db.exec(`ALTER TABLE sessions ADD COLUMN ports TEXT`);
|
|
1046
|
-
db.exec(
|
|
1047
|
-
`CREATE UNIQUE INDEX IF NOT EXISTS idx_sessions_port_slot ON sessions(port_slot) WHERE port_slot IS NOT NULL`
|
|
1048
|
-
);
|
|
1049
|
-
},
|
|
1050
|
-
},
|
|
1051
|
-
{
|
|
1052
|
-
id: 47,
|
|
1053
|
-
name: "pin_orchestrators",
|
|
1054
|
-
up: (db) => {
|
|
1055
|
-
// A workspace's orchestrator starts pinned to the top of its list;
|
|
1056
|
-
// unpinning it is then remembered on the session, which lives as
|
|
1057
|
-
// long as its workspace.
|
|
1058
|
-
db.exec(`UPDATE sessions SET pinned = 1 WHERE role = 'orchestrator'`);
|
|
1059
|
-
},
|
|
1060
|
-
},
|
|
1061
|
-
{
|
|
1062
|
-
id: 48,
|
|
1063
|
-
name: "session_database",
|
|
1064
|
-
up: (db) => {
|
|
1065
|
-
// The session's private Postgres copy, or why it has none, as JSON
|
|
1066
|
-
// (lib/project-config/database.ts).
|
|
1067
|
-
const columns = db.prepare(`PRAGMA table_info(sessions)`).all() as {
|
|
1068
|
-
name: string;
|
|
1069
|
-
}[];
|
|
1070
|
-
if (!columns.some((c) => c.name === "database"))
|
|
1071
|
-
db.exec(`ALTER TABLE sessions ADD COLUMN database TEXT`);
|
|
1072
|
-
},
|
|
1073
|
-
},
|
|
1074
|
-
{
|
|
1075
|
-
id: 49,
|
|
1076
|
-
name: "peer_mirror",
|
|
1077
|
-
up: (db) => {
|
|
1078
|
-
// A row that only shows a linked machine's own session: actions on
|
|
1079
|
-
// it go to that machine. Rows this machine started there over ssh
|
|
1080
|
-
// are its own, and stay 0.
|
|
1081
|
-
db.exec(
|
|
1082
|
-
`ALTER TABLE sessions ADD COLUMN peer_mirror INTEGER NOT NULL DEFAULT 0`
|
|
1083
|
-
);
|
|
1084
|
-
},
|
|
1085
|
-
},
|
|
1086
|
-
{
|
|
1087
|
-
id: 50,
|
|
1088
|
-
name: "task_queue",
|
|
1089
|
-
up: (db) => {
|
|
1090
|
-
// Tasks waiting to start (lib/tasks/queue.ts): over the workspace's
|
|
1091
|
-
// limit on running tasks, or after another task. A row's id is the
|
|
1092
|
-
// session id it starts as, so a restart never starts one twice.
|
|
1093
|
-
db.exec(`
|
|
1094
|
-
CREATE TABLE IF NOT EXISTS task_queue (
|
|
1095
|
-
id TEXT PRIMARY KEY,
|
|
1096
|
-
project_id TEXT NOT NULL,
|
|
1097
|
-
prompt TEXT NOT NULL,
|
|
1098
|
-
name TEXT,
|
|
1099
|
-
model TEXT,
|
|
1100
|
-
view TEXT,
|
|
1101
|
-
base_branch TEXT,
|
|
1102
|
-
host_id TEXT,
|
|
1103
|
-
after_task TEXT,
|
|
1104
|
-
after_any TEXT,
|
|
1105
|
-
origin_workspace_id TEXT,
|
|
1106
|
-
position INTEGER NOT NULL,
|
|
1107
|
-
status TEXT NOT NULL DEFAULT 'queued',
|
|
1108
|
-
note TEXT,
|
|
1109
|
-
error TEXT,
|
|
1110
|
-
attempts INTEGER NOT NULL DEFAULT 0,
|
|
1111
|
-
created_at TEXT NOT NULL DEFAULT (datetime('now')),
|
|
1112
|
-
started_at TEXT
|
|
1113
|
-
)
|
|
1114
|
-
`);
|
|
1115
|
-
db.exec(
|
|
1116
|
-
`CREATE INDEX IF NOT EXISTS idx_task_queue_status ON task_queue(status, position)`
|
|
1117
|
-
);
|
|
1118
|
-
const columns = db.prepare(`PRAGMA table_info(workspaces)`).all() as {
|
|
1119
|
-
name: string;
|
|
1120
|
-
}[];
|
|
1121
|
-
// Null: no limit.
|
|
1122
|
-
if (!columns.some((c) => c.name === "max_running_tasks"))
|
|
1123
|
-
db.exec(`ALTER TABLE workspaces ADD COLUMN max_running_tasks INTEGER`);
|
|
1124
|
-
installChangeTriggers(db, TABLES_WATCHED_50);
|
|
1125
|
-
},
|
|
1126
|
-
},
|
|
1127
|
-
{
|
|
1128
|
-
// 50 is taken by queued tasks, landing separately.
|
|
1129
|
-
id: 51,
|
|
1130
|
-
name: "chat_queue_sent_by",
|
|
1131
|
-
up: (db) => {
|
|
1132
|
-
// Who sent a queued message the reader didn't type (lib/chat/events
|
|
1133
|
-
// SentBy, as JSON), so it still shows as theirs once it's sent.
|
|
1134
|
-
const columns = db.prepare(`PRAGMA table_info(chat_queue)`).all() as {
|
|
1135
|
-
name: string;
|
|
1136
|
-
}[];
|
|
1137
|
-
if (!columns.some((c) => c.name === "sent_by"))
|
|
1138
|
-
db.exec(`ALTER TABLE chat_queue ADD COLUMN sent_by TEXT`);
|
|
1139
|
-
},
|
|
1140
|
-
},
|
|
1141
|
-
{
|
|
1142
|
-
id: 52,
|
|
1143
|
-
name: "sessions_chat_restarted",
|
|
1144
|
-
up: (db) => {
|
|
1145
|
-
// Set by a chat worker that retired cleanly after a deploy; the next
|
|
1146
|
-
// worker tells the agent so with its first message, then clears it.
|
|
1147
|
-
const columns = db.prepare(`PRAGMA table_info(sessions)`).all() as {
|
|
1148
|
-
name: string;
|
|
1149
|
-
}[];
|
|
1150
|
-
if (!columns.some((c) => c.name === "chat_restarted"))
|
|
1151
|
-
db.exec(
|
|
1152
|
-
`ALTER TABLE sessions ADD COLUMN chat_restarted INTEGER NOT NULL DEFAULT 0`
|
|
1153
|
-
);
|
|
1154
|
-
},
|
|
1155
|
-
},
|
|
1156
|
-
{
|
|
1157
|
-
id: 53,
|
|
1158
|
-
name: "brief_amendments_failure_sha_ask_raised_by",
|
|
1159
|
-
up: (db) => {
|
|
1160
|
-
// Scope changes the orchestrator records on a task after it started
|
|
1161
|
-
// (send with scope_change); its review judges against them.
|
|
1162
|
-
db.exec(`
|
|
1163
|
-
CREATE TABLE IF NOT EXISTS brief_amendments (
|
|
1164
|
-
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
1165
|
-
session_id TEXT NOT NULL,
|
|
1166
|
-
workspace_id TEXT NOT NULL,
|
|
1167
|
-
text TEXT NOT NULL,
|
|
1168
|
-
created_at TEXT NOT NULL DEFAULT (datetime('now'))
|
|
1169
|
-
);
|
|
1170
|
-
CREATE INDEX IF NOT EXISTS idx_brief_amendments_session
|
|
1171
|
-
ON brief_amendments(session_id);
|
|
1172
|
-
`);
|
|
1173
|
-
// The commit a stored verdict's failure was counted for, so reading
|
|
1174
|
-
// the same verdict again isn't a second failure.
|
|
1175
|
-
const columns = db
|
|
1176
|
-
.prepare(`PRAGMA table_info(orchestrator_gate_failures)`)
|
|
1177
|
-
.all() as { name: string }[];
|
|
1178
|
-
if (!columns.some((c) => c.name === "last_sha"))
|
|
1179
|
-
db.exec(
|
|
1180
|
-
`ALTER TABLE orchestrator_gate_failures ADD COLUMN last_sha TEXT`
|
|
1181
|
-
);
|
|
1182
|
-
// Who raised a merge ask: the orchestrator may refresh its own, never
|
|
1183
|
-
// one the gates raised.
|
|
1184
|
-
const asks = db.prepare(`PRAGMA table_info(orchestrator_asks)`).all() as {
|
|
1185
|
-
name: string;
|
|
1186
|
-
}[];
|
|
1187
|
-
if (!asks.some((c) => c.name === "raised_by"))
|
|
1188
|
-
db.exec(`ALTER TABLE orchestrator_asks ADD COLUMN raised_by TEXT`);
|
|
1189
|
-
},
|
|
1190
|
-
},
|
|
1191
|
-
{
|
|
1192
|
-
id: 54,
|
|
1193
|
-
name: "sessions_chat_tools_digest",
|
|
1194
|
-
up: (db) => {
|
|
1195
|
-
// The tools a chat worker last gave the agent (an orchestrator's), so
|
|
1196
|
-
// the next one says they changed only when they did.
|
|
1197
|
-
const columns = db.prepare(`PRAGMA table_info(sessions)`).all() as {
|
|
1198
|
-
name: string;
|
|
1199
|
-
}[];
|
|
1200
|
-
if (!columns.some((c) => c.name === "chat_tools_digest"))
|
|
1201
|
-
db.exec(`ALTER TABLE sessions ADD COLUMN chat_tools_digest TEXT`);
|
|
1202
|
-
},
|
|
1203
|
-
},
|
|
1204
|
-
{
|
|
1205
|
-
id: 55,
|
|
1206
|
-
name: "sessions_merge_armed_and_rebase",
|
|
1207
|
-
up: (db) => {
|
|
1208
|
-
// merge_armed_at: AgentOS merges the task's PR once every gate
|
|
1209
|
-
// passes (orchestrator/armed.ts); merge_armed_seen: the failure it
|
|
1210
|
-
// last reported. rebased_from/to: the heads an automatic rebase onto
|
|
1211
|
-
// the base moved the branch between, for the code-review gate.
|
|
1212
|
-
// rebase_asked_head: the head the task was last asked to rebase.
|
|
1213
|
-
const columns = db.prepare(`PRAGMA table_info(sessions)`).all() as {
|
|
1214
|
-
name: string;
|
|
1215
|
-
}[];
|
|
1216
|
-
for (const name of [
|
|
1217
|
-
"merge_armed_at",
|
|
1218
|
-
"merge_armed_seen",
|
|
1219
|
-
"rebased_from",
|
|
1220
|
-
"rebased_to",
|
|
1221
|
-
"rebase_asked_head",
|
|
1222
|
-
])
|
|
1223
|
-
if (!columns.some((c) => c.name === name))
|
|
1224
|
-
db.exec(`ALTER TABLE sessions ADD COLUMN ${name} TEXT`);
|
|
1225
|
-
},
|
|
1226
|
-
},
|
|
1227
|
-
{
|
|
1228
|
-
id: 56,
|
|
1229
|
-
name: "orchestrator_rules",
|
|
1230
|
-
up: (db) => {
|
|
1231
|
-
// Standing rules an orchestrator keeps across fresh starts
|
|
1232
|
-
// (lib/orchestrator/rules.ts): written by it or by Saad, never
|
|
1233
|
-
// deleted, only marked removed.
|
|
1234
|
-
db.exec(`
|
|
1235
|
-
CREATE TABLE IF NOT EXISTS orchestrator_rules (
|
|
1236
|
-
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
1237
|
-
workspace_id TEXT NOT NULL,
|
|
1238
|
-
text TEXT NOT NULL,
|
|
1239
|
-
added_by TEXT NOT NULL,
|
|
1240
|
-
created_at TEXT NOT NULL DEFAULT (datetime('now')),
|
|
1241
|
-
updated_at TEXT NOT NULL DEFAULT (datetime('now')),
|
|
1242
|
-
removed_at TEXT,
|
|
1243
|
-
removed_by TEXT
|
|
1244
|
-
)
|
|
1245
|
-
`);
|
|
1246
|
-
db.exec(
|
|
1247
|
-
`CREATE INDEX IF NOT EXISTS idx_orchestrator_rules
|
|
1248
|
-
ON orchestrator_rules(workspace_id, removed_at, id)`
|
|
1249
|
-
);
|
|
1250
|
-
// Its last worker ended the conversation at the context threshold:
|
|
1251
|
-
// the tokens it had reached (0 otherwise). The next one starts a new
|
|
1252
|
-
// one and tells the agent so.
|
|
1253
|
-
const columns = db.prepare(`PRAGMA table_info(sessions)`).all() as {
|
|
1254
|
-
name: string;
|
|
1255
|
-
}[];
|
|
1256
|
-
if (!columns.some((c) => c.name === "chat_refreshed"))
|
|
1257
|
-
db.exec(
|
|
1258
|
-
`ALTER TABLE sessions ADD COLUMN chat_refreshed INTEGER NOT NULL DEFAULT 0`
|
|
1259
|
-
);
|
|
1260
|
-
installChangeTriggers(db, TABLES_WATCHED_56);
|
|
1261
|
-
},
|
|
1262
|
-
},
|
|
1263
|
-
{
|
|
1264
|
-
id: 57,
|
|
1265
|
-
name: "task_pr_wakes",
|
|
1266
|
-
up: (db) => {
|
|
1267
|
-
// What a task's agent was told about its PR (orchestrator/pr-wake.ts),
|
|
1268
|
-
// one row per failing head or review/comment id, claimed before the
|
|
1269
|
-
// message goes. kind: check, review or comment (comments are capped).
|
|
1270
|
-
db.exec(`
|
|
1271
|
-
CREATE TABLE IF NOT EXISTS task_pr_wakes (
|
|
1272
|
-
session_id TEXT NOT NULL,
|
|
1273
|
-
key TEXT NOT NULL,
|
|
1274
|
-
kind TEXT NOT NULL,
|
|
1275
|
-
created_at TEXT NOT NULL DEFAULT (datetime('now')),
|
|
1276
|
-
PRIMARY KEY (session_id, key)
|
|
1277
|
-
);
|
|
1278
|
-
`);
|
|
1279
|
-
// pr_wake_since: reviews and comments older than this are taken as
|
|
1280
|
-
// known when a task's PR is first watched.
|
|
1281
|
-
const sessions = db.prepare(`PRAGMA table_info(sessions)`).all() as {
|
|
1282
|
-
name: string;
|
|
1283
|
-
}[];
|
|
1284
|
-
if (!sessions.some((c) => c.name === "pr_wake_since"))
|
|
1285
|
-
db.exec(`ALTER TABLE sessions ADD COLUMN pr_wake_since TEXT`);
|
|
1286
|
-
const workspaces = db.prepare(`PRAGMA table_info(workspaces)`).all() as {
|
|
1287
|
-
name: string;
|
|
1288
|
-
}[];
|
|
1289
|
-
if (!workspaces.some((c) => c.name === "wake_tasks_on_pr"))
|
|
1290
|
-
db.exec(
|
|
1291
|
-
`ALTER TABLE workspaces ADD COLUMN wake_tasks_on_pr INTEGER NOT NULL DEFAULT 1`
|
|
1292
|
-
);
|
|
1293
|
-
},
|
|
1294
|
-
},
|
|
1295
|
-
{
|
|
1296
|
-
id: 58,
|
|
1297
|
-
name: "away_mode",
|
|
1298
|
-
up: (db) => {
|
|
1299
|
-
// Saad's Away mode (lib/away): one row per time he stepped away. The
|
|
1300
|
-
// report is built from the database when it ends and kept here.
|
|
1301
|
-
db.exec(`
|
|
1302
|
-
CREATE TABLE IF NOT EXISTS away_periods (
|
|
1303
|
-
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
1304
|
-
set_by TEXT NOT NULL,
|
|
1305
|
-
started_at TEXT NOT NULL DEFAULT (datetime('now')),
|
|
1306
|
-
expires_at TEXT,
|
|
1307
|
-
ended_at TEXT,
|
|
1308
|
-
ended_by TEXT,
|
|
1309
|
-
last_sweep_at TEXT,
|
|
1310
|
-
asks_notified_at TEXT,
|
|
1311
|
-
report TEXT,
|
|
1312
|
-
report_seen_at TEXT
|
|
1313
|
-
)
|
|
1314
|
-
`);
|
|
1315
|
-
// What the away sweep did, once per key per period: a nudge, a
|
|
1316
|
-
// request to commit, a review run again, a deploy checked.
|
|
1317
|
-
db.exec(`
|
|
1318
|
-
CREATE TABLE IF NOT EXISTS away_actions (
|
|
1319
|
-
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
1320
|
-
away_id INTEGER NOT NULL,
|
|
1321
|
-
workspace_id TEXT NOT NULL,
|
|
1322
|
-
subject TEXT NOT NULL,
|
|
1323
|
-
kind TEXT NOT NULL,
|
|
1324
|
-
key TEXT NOT NULL,
|
|
1325
|
-
detail TEXT,
|
|
1326
|
-
created_at TEXT NOT NULL DEFAULT (datetime('now')),
|
|
1327
|
-
UNIQUE (away_id, key)
|
|
1328
|
-
)
|
|
1329
|
-
`);
|
|
1330
|
-
// When a task merged, whichever path merged it.
|
|
1331
|
-
const columns = db.prepare(`PRAGMA table_info(sessions)`).all() as {
|
|
1332
|
-
name: string;
|
|
1333
|
-
}[];
|
|
1334
|
-
if (!columns.some((c) => c.name === "merged_at"))
|
|
1335
|
-
db.exec(`ALTER TABLE sessions ADD COLUMN merged_at TEXT`);
|
|
1336
|
-
// When a task ended without a merge (dropped, or done with no PR or
|
|
1337
|
-
// its PR closed).
|
|
1338
|
-
if (!columns.some((c) => c.name === "task_ended_at"))
|
|
1339
|
-
db.exec(`ALTER TABLE sessions ADD COLUMN task_ended_at TEXT`);
|
|
1340
|
-
db.exec(`
|
|
1341
|
-
CREATE TRIGGER IF NOT EXISTS sessions_task_ended_at
|
|
1342
|
-
AFTER UPDATE OF task_status ON sessions
|
|
1343
|
-
WHEN NEW.task_status IN ('dropped', 'done')
|
|
1344
|
-
AND OLD.task_status IS NOT NEW.task_status
|
|
1345
|
-
AND NEW.task_ended_at IS OLD.task_ended_at
|
|
1346
|
-
BEGIN
|
|
1347
|
-
UPDATE sessions SET task_ended_at = datetime('now') WHERE id = NEW.id;
|
|
1348
|
-
END
|
|
1349
|
-
`);
|
|
1350
|
-
db.exec(`
|
|
1351
|
-
CREATE TRIGGER IF NOT EXISTS sessions_merged_at
|
|
1352
|
-
AFTER UPDATE OF task_status ON sessions
|
|
1353
|
-
WHEN NEW.task_status = 'merged' AND OLD.task_status IS NOT 'merged'
|
|
1354
|
-
-- unless the same statement set it (an import keeps its own)
|
|
1355
|
-
AND NEW.merged_at IS OLD.merged_at
|
|
1356
|
-
BEGIN
|
|
1357
|
-
UPDATE sessions SET merged_at = datetime('now') WHERE id = NEW.id;
|
|
1358
|
-
END
|
|
1359
|
-
`);
|
|
1360
|
-
installChangeTriggers(db, TABLES_WATCHED_58);
|
|
1361
|
-
},
|
|
1362
|
-
},
|
|
1363
|
-
{
|
|
1364
|
-
id: 59,
|
|
1365
|
-
name: "gate_failure_commits",
|
|
1366
|
-
up: (db) => {
|
|
1367
|
-
// Each head a gate failed on, once: a gate's failure counts once per
|
|
1368
|
-
// commit however many times that commit is judged (the armed watcher,
|
|
1369
|
-
// CI finishing, sign_off, done, land), and the second commit it fails
|
|
1370
|
-
// on goes to Saad. The key is what makes it once, across restarts and
|
|
1371
|
-
// concurrent evaluations.
|
|
1372
|
-
db.exec(`
|
|
1373
|
-
CREATE TABLE IF NOT EXISTS orchestrator_gate_failure_commits (
|
|
1374
|
-
session_id TEXT NOT NULL,
|
|
1375
|
-
gate TEXT NOT NULL,
|
|
1376
|
-
sha TEXT NOT NULL,
|
|
1377
|
-
workspace_id TEXT NOT NULL,
|
|
1378
|
-
reason TEXT,
|
|
1379
|
-
created_at TEXT NOT NULL DEFAULT (datetime('now')),
|
|
1380
|
-
PRIMARY KEY (session_id, gate, sha)
|
|
1381
|
-
);
|
|
1382
|
-
INSERT OR IGNORE INTO orchestrator_gate_failure_commits (session_id, gate, sha, workspace_id, reason)
|
|
1383
|
-
SELECT session_id, gate, last_sha, workspace_id, last_reason
|
|
1384
|
-
FROM orchestrator_gate_failures WHERE last_sha IS NOT NULL;
|
|
1385
|
-
CREATE TRIGGER IF NOT EXISTS gate_failures_clear_commits
|
|
1386
|
-
AFTER DELETE ON orchestrator_gate_failures
|
|
1387
|
-
BEGIN
|
|
1388
|
-
DELETE FROM orchestrator_gate_failure_commits
|
|
1389
|
-
WHERE session_id = OLD.session_id AND gate = OLD.gate;
|
|
1390
|
-
END;
|
|
1391
|
-
`);
|
|
1392
|
-
},
|
|
1393
|
-
},
|
|
1394
|
-
{
|
|
1395
|
-
id: 60,
|
|
1396
|
-
name: "check_runs_and_compactions",
|
|
1397
|
-
up: (db) => {
|
|
1398
|
-
// What each run of the independent reviewer and the scope check cost,
|
|
1399
|
-
// from its claude -p result: it leaves no transcript, so this is the
|
|
1400
|
-
// only record. And each compaction a chat's agent made, so tokens per
|
|
1401
|
-
// task can be compared before and after the threshold moved.
|
|
1402
|
-
db.exec(`
|
|
1403
|
-
CREATE TABLE IF NOT EXISTS check_runs (
|
|
1404
|
-
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
1405
|
-
workspace_id TEXT NOT NULL,
|
|
1406
|
-
session_id TEXT NOT NULL,
|
|
1407
|
-
label TEXT NOT NULL,
|
|
1408
|
-
pr_number INTEGER,
|
|
1409
|
-
sha TEXT NOT NULL,
|
|
1410
|
-
kind TEXT NOT NULL,
|
|
1411
|
-
model TEXT,
|
|
1412
|
-
input_tokens INTEGER NOT NULL,
|
|
1413
|
-
cache_write_tokens INTEGER NOT NULL,
|
|
1414
|
-
cache_read_tokens INTEGER NOT NULL,
|
|
1415
|
-
output_tokens INTEGER NOT NULL,
|
|
1416
|
-
cost_usd REAL NOT NULL,
|
|
1417
|
-
duration_ms INTEGER,
|
|
1418
|
-
at INTEGER NOT NULL
|
|
1419
|
-
);
|
|
1420
|
-
CREATE INDEX IF NOT EXISTS idx_check_runs_at ON check_runs(at);
|
|
1421
|
-
CREATE INDEX IF NOT EXISTS idx_check_runs_sha ON check_runs(session_id, sha, kind);
|
|
1422
|
-
CREATE TABLE IF NOT EXISTS chat_compactions (
|
|
1423
|
-
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
1424
|
-
session_id TEXT NOT NULL,
|
|
1425
|
-
session_name TEXT NOT NULL,
|
|
1426
|
-
workspace_id TEXT,
|
|
1427
|
-
at INTEGER NOT NULL,
|
|
1428
|
-
trigger TEXT NOT NULL,
|
|
1429
|
-
before_tokens INTEGER,
|
|
1430
|
-
after_tokens INTEGER
|
|
1431
|
-
);
|
|
1432
|
-
CREATE INDEX IF NOT EXISTS idx_chat_compactions_at ON chat_compactions(at);
|
|
1433
|
-
`);
|
|
1434
|
-
},
|
|
1435
|
-
},
|
|
1436
|
-
{
|
|
1437
|
-
id: 61,
|
|
1438
|
-
name: "model_routing",
|
|
1439
|
-
// One transaction, so a crash part-way leaves nothing to skip as
|
|
1440
|
-
// "duplicate column" on the next start.
|
|
1441
|
-
up: (db) =>
|
|
1442
|
-
db.transaction(() => {
|
|
1443
|
-
// What a session runs on beyond the model name: its provider (NULL is
|
|
1444
|
-
// Anthropic), a task's own models by role (JSON), and a task's plan
|
|
1445
|
-
// step (planning, then implementing). Each turn keeps the model,
|
|
1446
|
-
// provider and role it ran as, how long it took, and the plan's 5-hour
|
|
1447
|
-
// window when it ended, so spend compares by model. A check run keeps the
|
|
1448
|
-
// provider its model came from.
|
|
1449
|
-
for (const col of [
|
|
1450
|
-
`ALTER TABLE sessions ADD COLUMN model_provider TEXT`,
|
|
1451
|
-
`ALTER TABLE sessions ADD COLUMN model_overrides TEXT`,
|
|
1452
|
-
`ALTER TABLE sessions ADD COLUMN plan_phase TEXT`,
|
|
1453
|
-
`ALTER TABLE task_queue ADD COLUMN model_overrides TEXT`,
|
|
1454
|
-
`ALTER TABLE chat_turns ADD COLUMN model TEXT`,
|
|
1455
|
-
`ALTER TABLE chat_turns ADD COLUMN provider TEXT`,
|
|
1456
|
-
`ALTER TABLE chat_turns ADD COLUMN role TEXT`,
|
|
1457
|
-
`ALTER TABLE chat_turns ADD COLUMN duration_ms INTEGER`,
|
|
1458
|
-
`ALTER TABLE chat_turns ADD COLUMN window_pct REAL`,
|
|
1459
|
-
`ALTER TABLE check_runs ADD COLUMN provider TEXT`,
|
|
1460
|
-
])
|
|
1461
|
-
db.exec(col);
|
|
1462
|
-
})(),
|
|
1463
|
-
},
|
|
1464
|
-
{
|
|
1465
|
-
id: 62,
|
|
1466
|
-
name: "sessions_quick_of",
|
|
1467
|
-
up: (db) => {
|
|
1468
|
-
// A quick change (orchestrator/quick.ts): a task row with no agent,
|
|
1469
|
-
// whose worktree the orchestrator with this session id edits itself.
|
|
1470
|
-
// Set only by quick_change; the code-review gate reads it.
|
|
1471
|
-
const columns = db.prepare(`PRAGMA table_info(sessions)`).all() as {
|
|
1472
|
-
name: string;
|
|
1473
|
-
}[];
|
|
1474
|
-
if (!columns.some((c) => c.name === "quick_of"))
|
|
1475
|
-
db.exec(`ALTER TABLE sessions ADD COLUMN quick_of TEXT`);
|
|
1476
|
-
db.exec(
|
|
1477
|
-
`CREATE INDEX IF NOT EXISTS idx_sessions_quick_of ON sessions(quick_of) WHERE quick_of IS NOT NULL`
|
|
1478
|
-
);
|
|
1479
|
-
},
|
|
1480
|
-
},
|
|
1481
|
-
{
|
|
1482
|
-
id: 63,
|
|
1483
|
-
name: "one_off_schedules",
|
|
1484
|
-
up: (db) =>
|
|
1485
|
-
db.transaction(() => {
|
|
1486
|
-
// A one-off has a run time (epoch ms, UTC) and no cron; done_at is
|
|
1487
|
-
// set once its single run settles. Recurring rows leave both NULL.
|
|
1488
|
-
db.exec(`ALTER TABLE schedules ADD COLUMN run_at INTEGER`);
|
|
1489
|
-
db.exec(`ALTER TABLE schedules ADD COLUMN done_at INTEGER`);
|
|
1490
|
-
db.exec(
|
|
1491
|
-
`CREATE INDEX IF NOT EXISTS idx_schedules_run_at ON schedules(run_at) WHERE run_at IS NOT NULL`
|
|
1492
|
-
);
|
|
1493
|
-
})(),
|
|
1494
|
-
},
|
|
1495
|
-
{
|
|
1496
|
-
id: 64,
|
|
1497
|
-
name: "chat_compactions_phase",
|
|
1498
|
-
up: (db) => {
|
|
1499
|
-
// Whether the agent compacted itself mid-turn or AgentOS (or a typed
|
|
1500
|
-
// /compact) did between turns; null on rows from before.
|
|
1501
|
-
const columns = db
|
|
1502
|
-
.prepare(`PRAGMA table_info(chat_compactions)`)
|
|
1503
|
-
.all() as { name: string }[];
|
|
1504
|
-
if (!columns.some((c) => c.name === "phase"))
|
|
1505
|
-
db.exec(`ALTER TABLE chat_compactions ADD COLUMN phase TEXT`);
|
|
1506
|
-
},
|
|
1507
|
-
},
|
|
1508
|
-
{
|
|
1509
|
-
id: 65,
|
|
1510
|
-
name: "orchestrator_triage",
|
|
1511
|
-
up: (db) => {
|
|
1512
|
-
// started_by: the orchestrator session that started this one (null: a
|
|
1513
|
-
// person). plan_decided: 1 planned, 0 no plan, null not stated.
|
|
1514
|
-
const add = (table: string, col: string, type: string) => {
|
|
1515
|
-
const columns = db.prepare(`PRAGMA table_info(${table})`).all() as {
|
|
1516
|
-
name: string;
|
|
1517
|
-
}[];
|
|
1518
|
-
if (!columns.some((c) => c.name === col))
|
|
1519
|
-
db.exec(`ALTER TABLE ${table} ADD COLUMN ${col} ${type}`);
|
|
1520
|
-
};
|
|
1521
|
-
add("sessions", "started_by", "TEXT");
|
|
1522
|
-
add("sessions", "plan_decided", "INTEGER");
|
|
1523
|
-
add("sessions", "plan_reason", "TEXT");
|
|
1524
|
-
add("task_queue", "plan", "INTEGER");
|
|
1525
|
-
add("task_queue", "plan_reason", "TEXT");
|
|
1526
|
-
add("task_queue", "started_by", "TEXT");
|
|
1527
|
-
},
|
|
1528
|
-
},
|
|
1529
|
-
{
|
|
1530
|
-
id: 66,
|
|
1531
|
-
name: "workspace_orch_mcp_servers",
|
|
1532
|
-
up: (db) => {
|
|
1533
|
-
// The MCP servers (by name) a workspace's orchestrator may use.
|
|
1534
|
-
const columns = db.prepare(`PRAGMA table_info(workspaces)`).all() as {
|
|
1535
|
-
name: string;
|
|
1536
|
-
}[];
|
|
1537
|
-
if (!columns.some((c) => c.name === "orch_mcp_servers"))
|
|
1538
|
-
db.exec(
|
|
1539
|
-
`ALTER TABLE workspaces ADD COLUMN orch_mcp_servers TEXT NOT NULL DEFAULT '["linear"]'`
|
|
1540
|
-
);
|
|
1541
|
-
},
|
|
1542
|
-
},
|
|
1543
|
-
{
|
|
1544
|
-
id: 67,
|
|
1545
|
-
name: "orchestrator_event_log_suppressed",
|
|
1546
|
-
up: (db) => {
|
|
1547
|
-
// A capped event is logged as suppressed so a drop leaves a trace.
|
|
1548
|
-
const columns = db
|
|
1549
|
-
.prepare(`PRAGMA table_info(orchestrator_event_log)`)
|
|
1550
|
-
.all() as { name: string }[];
|
|
1551
|
-
if (!columns.some((c) => c.name === "suppressed"))
|
|
1552
|
-
db.exec(
|
|
1553
|
-
`ALTER TABLE orchestrator_event_log ADD COLUMN suppressed TEXT`
|
|
1554
|
-
);
|
|
1555
|
-
},
|
|
1556
|
-
},
|
|
1557
|
-
{
|
|
1558
|
-
id: 68,
|
|
1559
|
-
name: "usage_calls_and_rollups",
|
|
1560
|
-
up: (db) => {
|
|
1561
|
-
// One row per Claude response (or per one-shot run), the daily rollups
|
|
1562
|
-
// computed from them, the ratchet's baseline and the limit readings.
|
|
1563
|
-
db.transaction(() => {
|
|
1564
|
-
db.exec(`
|
|
1565
|
-
CREATE TABLE IF NOT EXISTS usage_calls (
|
|
1566
|
-
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
1567
|
-
call_key TEXT NOT NULL UNIQUE,
|
|
1568
|
-
at INTEGER NOT NULL,
|
|
1569
|
-
session_id TEXT,
|
|
1570
|
-
task_id TEXT,
|
|
1571
|
-
workspace_id TEXT,
|
|
1572
|
-
project_id TEXT,
|
|
1573
|
-
session_name TEXT,
|
|
1574
|
-
role TEXT,
|
|
1575
|
-
kind TEXT NOT NULL,
|
|
1576
|
-
agent_type TEXT,
|
|
1577
|
-
model TEXT,
|
|
1578
|
-
provider TEXT,
|
|
1579
|
-
input_tokens INTEGER NOT NULL DEFAULT 0,
|
|
1580
|
-
cache_read_tokens INTEGER NOT NULL DEFAULT 0,
|
|
1581
|
-
cache_write_5m_tokens INTEGER NOT NULL DEFAULT 0,
|
|
1582
|
-
cache_write_1h_tokens INTEGER NOT NULL DEFAULT 0,
|
|
1583
|
-
output_tokens INTEGER NOT NULL DEFAULT 0,
|
|
1584
|
-
ctx_tokens INTEGER,
|
|
1585
|
-
source TEXT NOT NULL
|
|
1586
|
-
);
|
|
1587
|
-
CREATE INDEX IF NOT EXISTS idx_usage_calls_at ON usage_calls(at);
|
|
1588
|
-
CREATE INDEX IF NOT EXISTS idx_usage_calls_task ON usage_calls(task_id);
|
|
1589
|
-
CREATE INDEX IF NOT EXISTS idx_usage_calls_session ON usage_calls(session_id);
|
|
1590
|
-
CREATE TABLE IF NOT EXISTS rate_limit_readings (
|
|
1591
|
-
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
1592
|
-
at INTEGER NOT NULL,
|
|
1593
|
-
window TEXT NOT NULL,
|
|
1594
|
-
utilization REAL NOT NULL,
|
|
1595
|
-
raw REAL,
|
|
1596
|
-
resets_at INTEGER,
|
|
1597
|
-
status TEXT,
|
|
1598
|
-
session_id TEXT,
|
|
1599
|
-
source TEXT NOT NULL
|
|
1600
|
-
);
|
|
1601
|
-
CREATE INDEX IF NOT EXISTS idx_rate_limit_readings ON rate_limit_readings(window, at);
|
|
1602
|
-
CREATE TABLE IF NOT EXISTS usage_rollups (
|
|
1603
|
-
day TEXT PRIMARY KEY,
|
|
1604
|
-
data TEXT NOT NULL,
|
|
1605
|
-
final INTEGER NOT NULL DEFAULT 0,
|
|
1606
|
-
computed_at INTEGER NOT NULL
|
|
1607
|
-
);
|
|
1608
|
-
CREATE TABLE IF NOT EXISTS usage_baseline (
|
|
1609
|
-
metric TEXT PRIMARY KEY,
|
|
1610
|
-
value REAL NOT NULL,
|
|
1611
|
-
set_at INTEGER NOT NULL,
|
|
1612
|
-
supporting TEXT
|
|
1613
|
-
);
|
|
1614
|
-
CREATE TABLE IF NOT EXISTS usage_alerts (
|
|
1615
|
-
kind TEXT PRIMARY KEY,
|
|
1616
|
-
opened_at INTEGER NOT NULL,
|
|
1617
|
-
cleared_at INTEGER,
|
|
1618
|
-
message TEXT NOT NULL
|
|
1619
|
-
);
|
|
1620
|
-
CREATE TABLE IF NOT EXISTS usage_import_offsets (
|
|
1621
|
-
path TEXT PRIMARY KEY,
|
|
1622
|
-
offset INTEGER NOT NULL,
|
|
1623
|
-
mtime INTEGER NOT NULL
|
|
1624
|
-
);
|
|
1625
|
-
`);
|
|
1626
|
-
})();
|
|
1627
|
-
},
|
|
1628
|
-
},
|
|
1629
|
-
{
|
|
1630
|
-
id: 69,
|
|
1631
|
-
name: "session_logs",
|
|
1632
|
-
up: (db) => {
|
|
1633
|
-
// One log per merged task, written by AgentOS from data it already
|
|
1634
|
-
// holds (lib/session-logs). Never deleted: the database is the record,
|
|
1635
|
-
// the markdown file on disk is a copy.
|
|
1636
|
-
db.exec(`
|
|
1637
|
-
CREATE TABLE IF NOT EXISTS session_logs (
|
|
1638
|
-
session_id TEXT PRIMARY KEY,
|
|
1639
|
-
project_id TEXT,
|
|
1640
|
-
workspace_id TEXT,
|
|
1641
|
-
created_at TEXT NOT NULL DEFAULT (datetime('now')),
|
|
1642
|
-
markdown TEXT NOT NULL,
|
|
1643
|
-
data TEXT NOT NULL,
|
|
1644
|
-
file_path TEXT
|
|
1645
|
-
);
|
|
1646
|
-
CREATE INDEX IF NOT EXISTS idx_session_logs_project
|
|
1647
|
-
ON session_logs(project_id, created_at);
|
|
1648
|
-
CREATE INDEX IF NOT EXISTS idx_session_logs_workspace
|
|
1649
|
-
ON session_logs(workspace_id, created_at);
|
|
1650
|
-
`);
|
|
1651
|
-
},
|
|
1652
|
-
},
|
|
1653
|
-
{
|
|
1654
|
-
id: 70,
|
|
1655
|
-
name: "chat_items_running",
|
|
1656
|
-
up: (db) => {
|
|
1657
|
-
// What is still running (tools, background tasks) is read on every
|
|
1658
|
-
// attach; the index holds only those rows, so it's a lookup, not a
|
|
1659
|
-
// scan that parses every tool output of a long conversation.
|
|
1660
|
-
db.exec(
|
|
1661
|
-
`CREATE INDEX IF NOT EXISTS idx_chat_items_running ON chat_items(session_id, kind, seq)
|
|
1662
|
-
WHERE json_extract(data, '$.status') = 'running'`
|
|
1663
|
-
);
|
|
1664
|
-
},
|
|
1665
|
-
},
|
|
1666
|
-
{
|
|
1667
|
-
id: 71,
|
|
1668
|
-
name: "devices_push",
|
|
1669
|
-
up: (db) => {
|
|
1670
|
-
// Push belongs to a paired device, so revoking the device clears it.
|
|
1671
|
-
// push_categories is a JSON array; NULL means the defaults.
|
|
1672
|
-
// Each column checked, so a run cut off part way can be run again.
|
|
1673
|
-
const have = new Set(
|
|
1674
|
-
(
|
|
1675
|
-
db.prepare(`PRAGMA table_info(devices)`).all() as { name: string }[]
|
|
1676
|
-
).map((c) => c.name)
|
|
1677
|
-
);
|
|
1678
|
-
for (const col of [
|
|
1679
|
-
"push_token",
|
|
1680
|
-
"push_platform",
|
|
1681
|
-
"push_environment",
|
|
1682
|
-
"push_categories",
|
|
1683
|
-
"push_updated_at",
|
|
1684
|
-
"push_last_error",
|
|
1685
|
-
])
|
|
1686
|
-
if (!have.has(col))
|
|
1687
|
-
db.exec(`ALTER TABLE devices ADD COLUMN ${col} TEXT`);
|
|
1688
|
-
db.exec(
|
|
1689
|
-
`CREATE INDEX IF NOT EXISTS idx_devices_push_token ON devices(push_token)
|
|
1690
|
-
WHERE push_token IS NOT NULL`
|
|
1691
|
-
);
|
|
1692
|
-
},
|
|
1693
|
-
},
|
|
1694
|
-
{
|
|
1695
|
-
id: 72,
|
|
1696
|
-
name: "push_outbox",
|
|
1697
|
-
up: (db) => {
|
|
1698
|
-
// One row per (event, device). device_id is '' for a row folded into
|
|
1699
|
-
// the Away digest. state: pending, sending, sent, failed, gone,
|
|
1700
|
-
// capped, superseded, folded, digested.
|
|
1701
|
-
db.exec(`
|
|
1702
|
-
CREATE TABLE IF NOT EXISTS push_outbox (
|
|
1703
|
-
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
1704
|
-
event_key TEXT NOT NULL,
|
|
1705
|
-
device_id TEXT NOT NULL,
|
|
1706
|
-
category TEXT NOT NULL,
|
|
1707
|
-
level TEXT NOT NULL,
|
|
1708
|
-
payload TEXT NOT NULL,
|
|
1709
|
-
collapse_id TEXT,
|
|
1710
|
-
state TEXT NOT NULL,
|
|
1711
|
-
attempts INTEGER NOT NULL DEFAULT 0,
|
|
1712
|
-
next_attempt_at INTEGER NOT NULL,
|
|
1713
|
-
created_at INTEGER NOT NULL,
|
|
1714
|
-
sent_at INTEGER,
|
|
1715
|
-
error TEXT,
|
|
1716
|
-
fallback_source TEXT,
|
|
1717
|
-
fallback_text TEXT,
|
|
1718
|
-
UNIQUE(event_key, device_id)
|
|
1719
|
-
);
|
|
1720
|
-
CREATE INDEX IF NOT EXISTS idx_push_outbox_due ON push_outbox(state, next_attempt_at);
|
|
1721
|
-
CREATE INDEX IF NOT EXISTS idx_push_outbox_device ON push_outbox(device_id, sent_at);
|
|
1722
|
-
`);
|
|
1723
|
-
},
|
|
1724
|
-
},
|
|
1725
|
-
{
|
|
1726
|
-
id: 73,
|
|
1727
|
-
name: "orchestrated_task_moves",
|
|
1728
|
-
up: (db) => {
|
|
1729
|
-
// task_since: when the task first started, kept across moves (null:
|
|
1730
|
-
// created_at). held_by_device / held_from: the device and machine of
|
|
1731
|
-
// the orchestrator that moved this task here; only that device may
|
|
1732
|
-
// merge, arm, message or move it. plan_note: the "plan written" line
|
|
1733
|
-
// of a held task, for its orchestrator to read.
|
|
1734
|
-
const add = (col: string) => {
|
|
1735
|
-
const columns = db.prepare(`PRAGMA table_info(sessions)`).all() as {
|
|
1736
|
-
name: string;
|
|
1737
|
-
}[];
|
|
1738
|
-
if (!columns.some((c) => c.name === col))
|
|
1739
|
-
db.exec(`ALTER TABLE sessions ADD COLUMN ${col} TEXT`);
|
|
1740
|
-
};
|
|
1741
|
-
for (const col of [
|
|
1742
|
-
"task_since",
|
|
1743
|
-
"held_by_device",
|
|
1744
|
-
"held_from",
|
|
1745
|
-
"plan_note",
|
|
1746
|
-
])
|
|
1747
|
-
add(col);
|
|
1748
|
-
},
|
|
1749
|
-
},
|
|
1750
|
-
{
|
|
1751
|
-
id: 74,
|
|
1752
|
-
name: "held_task_replies",
|
|
1753
|
-
up: (db) => {
|
|
1754
|
-
// held_for / held_for_name: the orchestrator (id, name there) a held
|
|
1755
|
-
// task messages. relay_state: a held task's message to it, waiting for
|
|
1756
|
-
// the holder to pull it (pending) or pulled (relayed). relay_key: a
|
|
1757
|
-
// pulled message's key on the orchestrator's machine, so a repeat pull
|
|
1758
|
-
// is not delivered twice.
|
|
1759
|
-
const has = (table: string, col: string) =>
|
|
1760
|
-
(
|
|
1761
|
-
db.prepare(`PRAGMA table_info(${table})`).all() as { name: string }[]
|
|
1762
|
-
).some((c) => c.name === col);
|
|
1763
|
-
for (const col of ["held_for", "held_for_name"])
|
|
1764
|
-
if (!has("sessions", col))
|
|
1765
|
-
db.exec(`ALTER TABLE sessions ADD COLUMN ${col} TEXT`);
|
|
1766
|
-
for (const col of ["relay_state", "relay_key"])
|
|
1767
|
-
if (!has("bus_messages", col))
|
|
1768
|
-
db.exec(`ALTER TABLE bus_messages ADD COLUMN ${col} TEXT`);
|
|
1769
|
-
db.exec(
|
|
1770
|
-
`CREATE UNIQUE INDEX IF NOT EXISTS idx_bus_relay_key ON bus_messages(relay_key) WHERE relay_key IS NOT NULL;
|
|
1771
|
-
CREATE INDEX IF NOT EXISTS idx_bus_relay_pending ON bus_messages(relay_state) WHERE relay_state = 'pending'`
|
|
1772
|
-
);
|
|
1773
|
-
},
|
|
1774
|
-
},
|
|
1775
|
-
{
|
|
1776
|
-
id: 75,
|
|
1777
|
-
name: "plan_only_tasks",
|
|
1778
|
-
up: (db) => {
|
|
1779
|
-
// plan_only: 1 for a task that stops after its plan and waits for
|
|
1780
|
-
// approval (plan_phase 'awaiting'); queued tasks keep it until they start.
|
|
1781
|
-
const has = (table: string) =>
|
|
1782
|
-
(
|
|
1783
|
-
db.prepare(`PRAGMA table_info(${table})`).all() as { name: string }[]
|
|
1784
|
-
).some((c) => c.name === "plan_only");
|
|
1785
|
-
for (const table of ["sessions", "task_queue"])
|
|
1786
|
-
if (!has(table))
|
|
1787
|
-
db.exec(`ALTER TABLE ${table} ADD COLUMN plan_only INTEGER`);
|
|
1788
|
-
},
|
|
1789
|
-
},
|
|
1790
|
-
{
|
|
1791
|
-
id: 76,
|
|
1792
|
-
name: "welcome_backfill",
|
|
1793
|
-
up: (db) => {
|
|
1794
|
-
// An install that already has a session or a project of its own has
|
|
1795
|
-
// been set up; mark it so the first-run welcome never shows there.
|
|
1796
|
-
db.exec(
|
|
1797
|
-
`INSERT OR IGNORE INTO settings (key, value)
|
|
1798
|
-
SELECT 'welcome.completed_at', 'existing-install'
|
|
1799
|
-
WHERE EXISTS (SELECT 1 FROM sessions)
|
|
1800
|
-
OR EXISTS (SELECT 1 FROM projects WHERE is_uncategorized = 0)`
|
|
1801
|
-
);
|
|
1802
|
-
},
|
|
1803
|
-
},
|
|
1804
|
-
{
|
|
1805
|
-
id: 77,
|
|
1806
|
-
name: "simulator_leases",
|
|
1807
|
-
up: (db) => {
|
|
1808
|
-
// One iOS simulator a task holds (lib/simulator): the device AgentOS
|
|
1809
|
-
// made for it, and where it is in coming up.
|
|
1810
|
-
db.exec(
|
|
1811
|
-
`CREATE TABLE IF NOT EXISTS simulator_leases (
|
|
1812
|
-
session_id TEXT PRIMARY KEY,
|
|
1813
|
-
udid TEXT,
|
|
1814
|
-
name TEXT NOT NULL,
|
|
1815
|
-
state TEXT NOT NULL,
|
|
1816
|
-
error TEXT,
|
|
1817
|
-
created_at INTEGER NOT NULL,
|
|
1818
|
-
last_used_at INTEGER NOT NULL
|
|
1819
|
-
)`
|
|
1820
|
-
);
|
|
1821
|
-
},
|
|
1822
|
-
},
|
|
1823
|
-
{
|
|
1824
|
-
id: 78,
|
|
1825
|
-
name: "orchestrator_questions",
|
|
1826
|
-
up: (db) => {
|
|
1827
|
-
// A question the orchestrator put to Saad in chat, open until he
|
|
1828
|
-
// answers it or the orchestrator clears it. needs_you_dismissed: items
|
|
1829
|
-
// he hid from the Needs-you strip (an ask's or question's key); hiding
|
|
1830
|
-
// never resolves anything.
|
|
1831
|
-
db.exec(`
|
|
1832
|
-
CREATE TABLE IF NOT EXISTS orchestrator_questions (
|
|
1833
|
-
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
1834
|
-
workspace_id TEXT NOT NULL,
|
|
1835
|
-
text TEXT NOT NULL,
|
|
1836
|
-
task TEXT,
|
|
1837
|
-
status TEXT NOT NULL DEFAULT 'open',
|
|
1838
|
-
resolved_by TEXT,
|
|
1839
|
-
created_at TEXT NOT NULL DEFAULT (datetime('now')),
|
|
1840
|
-
resolved_at TEXT
|
|
1841
|
-
);
|
|
1842
|
-
CREATE INDEX IF NOT EXISTS idx_orchestrator_questions_open
|
|
1843
|
-
ON orchestrator_questions(workspace_id, status);
|
|
1844
|
-
CREATE TABLE IF NOT EXISTS needs_you_dismissed (
|
|
1845
|
-
workspace_id TEXT NOT NULL,
|
|
1846
|
-
key TEXT NOT NULL,
|
|
1847
|
-
dismissed_at TEXT NOT NULL DEFAULT (datetime('now')),
|
|
1848
|
-
PRIMARY KEY (workspace_id, key)
|
|
1849
|
-
);
|
|
1850
|
-
`);
|
|
1851
|
-
installChangeTriggers(db, TABLES_WATCHED_78);
|
|
1852
|
-
},
|
|
1853
|
-
},
|
|
1854
|
-
{
|
|
1855
|
-
id: 79,
|
|
1856
|
-
name: "task_queue_agent",
|
|
1857
|
-
up: (db) => {
|
|
1858
|
-
// The agent CLI a queued task runs with; null is Claude Code.
|
|
1859
|
-
const has = (
|
|
1860
|
-
db.prepare(`PRAGMA table_info(task_queue)`).all() as { name: string }[]
|
|
1861
|
-
).some((c) => c.name === "agent_type");
|
|
1862
|
-
if (!has) db.exec(`ALTER TABLE task_queue ADD COLUMN agent_type TEXT`);
|
|
1863
|
-
},
|
|
1864
|
-
},
|
|
1865
|
-
{
|
|
1866
|
-
id: 80,
|
|
1867
|
-
name: "recipes",
|
|
1868
|
-
up: (db) => {
|
|
1869
|
-
// Recipes: a saved brief that starts a fresh task or session each run
|
|
1870
|
-
// (lib/recipes), and the features a plan splits into pieces
|
|
1871
|
-
// (lib/plans). All additive. A run or piece finds its session by
|
|
1872
|
-
// session_id, so sessions gains no column.
|
|
1873
|
-
db.exec(`
|
|
1874
|
-
CREATE TABLE IF NOT EXISTS recipes (
|
|
1875
|
-
id TEXT PRIMARY KEY,
|
|
1876
|
-
workspace_id TEXT NOT NULL,
|
|
1877
|
-
project_id TEXT NOT NULL,
|
|
1878
|
-
name TEXT NOT NULL,
|
|
1879
|
-
kind TEXT NOT NULL,
|
|
1880
|
-
brief TEXT NOT NULL,
|
|
1881
|
-
model TEXT,
|
|
1882
|
-
view TEXT NOT NULL DEFAULT 'chat',
|
|
1883
|
-
auto_done INTEGER NOT NULL DEFAULT 1,
|
|
1884
|
-
max_minutes INTEGER,
|
|
1885
|
-
carry_cleared_at INTEGER,
|
|
1886
|
-
source_session_id TEXT,
|
|
1887
|
-
created_by_session_id TEXT,
|
|
1888
|
-
updated_by_session_id TEXT,
|
|
1889
|
-
archived_at TEXT,
|
|
1890
|
-
created_at TEXT NOT NULL DEFAULT (datetime('now')),
|
|
1891
|
-
updated_at TEXT NOT NULL DEFAULT (datetime('now'))
|
|
1892
|
-
);
|
|
1893
|
-
CREATE UNIQUE INDEX IF NOT EXISTS idx_recipes_name
|
|
1894
|
-
ON recipes(workspace_id, name COLLATE NOCASE) WHERE archived_at IS NULL;
|
|
1895
|
-
|
|
1896
|
-
CREATE TABLE IF NOT EXISTS recipe_runs (
|
|
1897
|
-
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
1898
|
-
recipe_id TEXT NOT NULL,
|
|
1899
|
-
seq INTEGER NOT NULL,
|
|
1900
|
-
trigger TEXT NOT NULL,
|
|
1901
|
-
triggered_by_session_id TEXT,
|
|
1902
|
-
schedule_run_id INTEGER,
|
|
1903
|
-
request_id TEXT,
|
|
1904
|
-
note TEXT,
|
|
1905
|
-
prompt TEXT NOT NULL,
|
|
1906
|
-
revision_id INTEGER,
|
|
1907
|
-
status TEXT NOT NULL,
|
|
1908
|
-
session_id TEXT,
|
|
1909
|
-
pr_number INTEGER,
|
|
1910
|
-
pr_url TEXT,
|
|
1911
|
-
summary TEXT,
|
|
1912
|
-
outputs TEXT,
|
|
1913
|
-
summary_source TEXT,
|
|
1914
|
-
error TEXT,
|
|
1915
|
-
cost_usd REAL,
|
|
1916
|
-
duration_ms INTEGER,
|
|
1917
|
-
started_at TEXT,
|
|
1918
|
-
ended_at TEXT,
|
|
1919
|
-
created_at TEXT NOT NULL DEFAULT (datetime('now')),
|
|
1920
|
-
UNIQUE (recipe_id, seq)
|
|
1921
|
-
);
|
|
1922
|
-
CREATE INDEX IF NOT EXISTS idx_recipe_runs_recipe
|
|
1923
|
-
ON recipe_runs(recipe_id, id DESC);
|
|
1924
|
-
CREATE INDEX IF NOT EXISTS idx_recipe_runs_session
|
|
1925
|
-
ON recipe_runs(session_id);
|
|
1926
|
-
CREATE UNIQUE INDEX IF NOT EXISTS idx_recipe_runs_one_live
|
|
1927
|
-
ON recipe_runs(recipe_id)
|
|
1928
|
-
WHERE status IN ('queued','starting','running');
|
|
1929
|
-
CREATE UNIQUE INDEX IF NOT EXISTS idx_recipe_runs_request
|
|
1930
|
-
ON recipe_runs(request_id) WHERE request_id IS NOT NULL;
|
|
1931
|
-
|
|
1932
|
-
CREATE TABLE IF NOT EXISTS recipe_revisions (
|
|
1933
|
-
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
1934
|
-
recipe_id TEXT NOT NULL,
|
|
1935
|
-
brief TEXT NOT NULL,
|
|
1936
|
-
settings TEXT NOT NULL,
|
|
1937
|
-
edited_by_session_id TEXT,
|
|
1938
|
-
created_at TEXT NOT NULL DEFAULT (datetime('now'))
|
|
1939
|
-
);
|
|
1940
|
-
|
|
1941
|
-
CREATE TABLE IF NOT EXISTS features (
|
|
1942
|
-
id TEXT PRIMARY KEY,
|
|
1943
|
-
workspace_id TEXT NOT NULL,
|
|
1944
|
-
name TEXT NOT NULL,
|
|
1945
|
-
plan_session_id TEXT NOT NULL,
|
|
1946
|
-
plan_text TEXT NOT NULL,
|
|
1947
|
-
status TEXT NOT NULL,
|
|
1948
|
-
done_at TEXT,
|
|
1949
|
-
cost_usd REAL,
|
|
1950
|
-
duration_ms INTEGER,
|
|
1951
|
-
created_at TEXT NOT NULL DEFAULT (datetime('now'))
|
|
1952
|
-
);
|
|
1953
|
-
CREATE INDEX IF NOT EXISTS idx_features_plan
|
|
1954
|
-
ON features(plan_session_id);
|
|
1955
|
-
|
|
1956
|
-
CREATE TABLE IF NOT EXISTS feature_pieces (
|
|
1957
|
-
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
1958
|
-
feature_id TEXT NOT NULL,
|
|
1959
|
-
position INTEGER NOT NULL,
|
|
1960
|
-
name TEXT NOT NULL,
|
|
1961
|
-
slice TEXT NOT NULL,
|
|
1962
|
-
project_id TEXT NOT NULL,
|
|
1963
|
-
after_position INTEGER,
|
|
1964
|
-
after_kind TEXT,
|
|
1965
|
-
reasons TEXT,
|
|
1966
|
-
owns TEXT,
|
|
1967
|
-
status TEXT NOT NULL,
|
|
1968
|
-
session_id TEXT,
|
|
1969
|
-
note TEXT,
|
|
1970
|
-
UNIQUE (feature_id, position)
|
|
1971
|
-
);
|
|
1972
|
-
CREATE INDEX IF NOT EXISTS idx_feature_pieces_session
|
|
1973
|
-
ON feature_pieces(session_id);
|
|
1974
|
-
`);
|
|
1975
|
-
const cols = (
|
|
1976
|
-
db.prepare(`PRAGMA table_info(schedules)`).all() as { name: string }[]
|
|
1977
|
-
).map((c) => c.name);
|
|
1978
|
-
if (!cols.includes("recipe_id"))
|
|
1979
|
-
db.exec(`ALTER TABLE schedules ADD COLUMN recipe_id TEXT`);
|
|
1980
|
-
if (!cols.includes("migrated_from_kind"))
|
|
1981
|
-
db.exec(`ALTER TABLE schedules ADD COLUMN migrated_from_kind TEXT`);
|
|
1982
|
-
const plan = (
|
|
1983
|
-
db.prepare(`PRAGMA table_info(sessions)`).all() as { name: string }[]
|
|
1984
|
-
).map((c) => c.name);
|
|
1985
|
-
// Edits Saad made to a plan's pieces before approving it (merge, drop).
|
|
1986
|
-
if (!plan.includes("plan_edits"))
|
|
1987
|
-
db.exec(`ALTER TABLE sessions ADD COLUMN plan_edits TEXT`);
|
|
1988
|
-
installChangeTriggers(db, TABLES_WATCHED_80);
|
|
1989
|
-
},
|
|
1990
|
-
},
|
|
1991
|
-
{
|
|
1992
|
-
id: 81,
|
|
1993
|
-
name: "schedules_into_recipes",
|
|
1994
|
-
up: (db) => {
|
|
1995
|
-
// Live task and session schedules become recipes with a timer, and
|
|
1996
|
-
// their runs become recipe runs (lib/db/schedules-move). Reversible:
|
|
1997
|
-
// scripts/revert-schedules-move.ts.
|
|
1998
|
-
const cols = (
|
|
1999
|
-
db.prepare(`PRAGMA table_info(schedules)`).all() as { name: string }[]
|
|
2000
|
-
).map((c) => c.name);
|
|
2001
|
-
if (!cols.includes("recipe_id") || !cols.includes("migrated_from_kind"))
|
|
2002
|
-
throw new Error("migration 80 hasn't run");
|
|
2003
|
-
moveSchedulesToRecipes(db);
|
|
2004
|
-
},
|
|
2005
|
-
},
|
|
2006
|
-
{
|
|
2007
|
-
id: 82,
|
|
2008
|
-
name: "session_shelf_times",
|
|
2009
|
-
up: (db) => {
|
|
2010
|
-
const cols = (
|
|
2011
|
-
db.prepare(`PRAGMA table_info(sessions)`).all() as { name: string }[]
|
|
2012
|
-
).map((c) => c.name);
|
|
2013
|
-
// Last real activity, when it was marked done, and the snooze.
|
|
2014
|
-
for (const col of [
|
|
2015
|
-
"active_at",
|
|
2016
|
-
"settled_at",
|
|
2017
|
-
"snoozed_until",
|
|
2018
|
-
"snoozed_at",
|
|
2019
|
-
])
|
|
2020
|
-
if (!cols.includes(col))
|
|
2021
|
-
db.exec(`ALTER TABLE sessions ADD COLUMN ${col} TEXT`);
|
|
2022
|
-
db.exec(
|
|
2023
|
-
`UPDATE sessions SET active_at = updated_at WHERE active_at IS NULL`
|
|
2024
|
-
);
|
|
2025
|
-
},
|
|
2026
|
-
},
|
|
2027
|
-
{
|
|
2028
|
-
id: 83,
|
|
2029
|
-
name: "native_devices_can_approve",
|
|
2030
|
-
up: (db) => {
|
|
2031
|
-
// New native pairings start with Can approve; this gives it once to the
|
|
2032
|
-
// ones paired before. The claim wasn't recorded, so the app is known by
|
|
2033
|
-
// its user agent (`AgentOS/<build> CFNetwork/...` on iOS) or by an
|
|
2034
|
-
// app push token. It only ever turns the flag on.
|
|
2035
|
-
db.exec(
|
|
2036
|
-
`UPDATE devices SET can_approve = 1
|
|
2037
|
-
WHERE revoked_at IS NULL AND can_approve = 0
|
|
2038
|
-
AND (user_agent LIKE 'AgentOS/% CFNetwork/%'
|
|
2039
|
-
OR push_platform IN ('ios', 'android'))`
|
|
2040
|
-
);
|
|
2041
|
-
},
|
|
2042
|
-
},
|
|
2043
|
-
{
|
|
2044
|
-
id: 84,
|
|
2045
|
-
name: "approval_audit",
|
|
2046
|
-
up: (db) => {
|
|
2047
|
-
// Every approval No approvals mode made in place of a person.
|
|
2048
|
-
db.exec(`
|
|
2049
|
-
CREATE TABLE IF NOT EXISTS approval_audit (
|
|
2050
|
-
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
2051
|
-
at TEXT NOT NULL DEFAULT (datetime('now')),
|
|
2052
|
-
source TEXT NOT NULL,
|
|
2053
|
-
workspace_id TEXT,
|
|
2054
|
-
session_id TEXT,
|
|
2055
|
-
subject TEXT NOT NULL,
|
|
2056
|
-
detail TEXT NOT NULL DEFAULT ''
|
|
2057
|
-
)
|
|
2058
|
-
`);
|
|
2059
|
-
},
|
|
2060
|
-
},
|
|
2061
|
-
{
|
|
2062
|
-
id: 85,
|
|
2063
|
-
name: "experiment_usage",
|
|
2064
|
-
up: (db) => {
|
|
2065
|
-
// What each experiment did: model calls with their cost, and the
|
|
2066
|
-
// non-model work (scripts run, suggestions shown and decided).
|
|
2067
|
-
db.exec(`
|
|
2068
|
-
CREATE TABLE IF NOT EXISTS experiment_usage (
|
|
2069
|
-
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
2070
|
-
experiment_id TEXT NOT NULL,
|
|
2071
|
-
event TEXT NOT NULL,
|
|
2072
|
-
trigger TEXT NOT NULL,
|
|
2073
|
-
session_id TEXT,
|
|
2074
|
-
workspace_id TEXT,
|
|
2075
|
-
model TEXT,
|
|
2076
|
-
provider TEXT,
|
|
2077
|
-
input_tokens INTEGER NOT NULL DEFAULT 0,
|
|
2078
|
-
cache_write_tokens INTEGER NOT NULL DEFAULT 0,
|
|
2079
|
-
cache_read_tokens INTEGER NOT NULL DEFAULT 0,
|
|
2080
|
-
output_tokens INTEGER NOT NULL DEFAULT 0,
|
|
2081
|
-
cost_usd REAL NOT NULL DEFAULT 0,
|
|
2082
|
-
duration_ms INTEGER,
|
|
2083
|
-
ok INTEGER NOT NULL DEFAULT 1,
|
|
2084
|
-
detail TEXT,
|
|
2085
|
-
at INTEGER NOT NULL
|
|
2086
|
-
);
|
|
2087
|
-
CREATE INDEX IF NOT EXISTS idx_experiment_usage_exp_at
|
|
2088
|
-
ON experiment_usage(experiment_id, at);
|
|
2089
|
-
CREATE INDEX IF NOT EXISTS idx_experiment_usage_session
|
|
2090
|
-
ON experiment_usage(session_id, experiment_id, event);
|
|
2091
|
-
`);
|
|
2092
|
-
},
|
|
2093
|
-
},
|
|
2094
|
-
{
|
|
2095
|
-
id: 86,
|
|
2096
|
-
name: "learning_cards",
|
|
2097
|
-
up: (db) => {
|
|
2098
|
-
// End-of-session suggestions (save as recipe, keep a lesson). An
|
|
2099
|
-
// accepted lesson row is the record of where it was applied.
|
|
2100
|
-
db.exec(`
|
|
2101
|
-
CREATE TABLE IF NOT EXISTS learning_cards (
|
|
2102
|
-
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
2103
|
-
session_id TEXT NOT NULL,
|
|
2104
|
-
workspace_id TEXT,
|
|
2105
|
-
kind TEXT NOT NULL,
|
|
2106
|
-
status TEXT NOT NULL DEFAULT 'pending',
|
|
2107
|
-
payload TEXT NOT NULL,
|
|
2108
|
-
destination TEXT,
|
|
2109
|
-
applied_ref TEXT,
|
|
2110
|
-
shown_at INTEGER,
|
|
2111
|
-
decided_at INTEGER,
|
|
2112
|
-
created_at INTEGER NOT NULL,
|
|
2113
|
-
UNIQUE (session_id, kind)
|
|
2114
|
-
);
|
|
2115
|
-
CREATE INDEX IF NOT EXISTS idx_learning_cards_dest
|
|
2116
|
-
ON learning_cards(destination, decided_at);
|
|
2117
|
-
`);
|
|
2118
|
-
},
|
|
2119
|
-
},
|
|
2120
|
-
{
|
|
2121
|
-
id: 87,
|
|
2122
|
-
name: "plan_pieces_reconcile",
|
|
2123
|
-
up: (db) => {
|
|
2124
|
-
// How many pieces a plan listed and what the checker folded, so a plan
|
|
2125
|
-
// that builds as fewer than it listed is never silent; the PRs agents
|
|
2126
|
-
// opened (from their `gh pr create` output) so one outside its task's
|
|
2127
|
-
// repo is noticed.
|
|
2128
|
-
const columns = db.prepare(`PRAGMA table_info(features)`).all() as {
|
|
2129
|
-
name: string;
|
|
2130
|
-
}[];
|
|
2131
|
-
if (!columns.some((c) => c.name === "listed"))
|
|
2132
|
-
db.exec(`ALTER TABLE features ADD COLUMN listed INTEGER`);
|
|
2133
|
-
if (!columns.some((c) => c.name === "folded"))
|
|
2134
|
-
db.exec(`ALTER TABLE features ADD COLUMN folded TEXT`);
|
|
2135
|
-
db.exec(`
|
|
2136
|
-
CREATE TABLE IF NOT EXISTS agent_prs (
|
|
2137
|
-
url TEXT PRIMARY KEY,
|
|
2138
|
-
session_id TEXT NOT NULL,
|
|
2139
|
-
repo TEXT NOT NULL,
|
|
2140
|
-
number INTEGER NOT NULL,
|
|
2141
|
-
seen_at TEXT NOT NULL DEFAULT (datetime('now')),
|
|
2142
|
-
told_at TEXT
|
|
2143
|
-
);
|
|
2144
|
-
`);
|
|
2145
|
-
},
|
|
2146
|
-
},
|
|
2147
|
-
{
|
|
2148
|
-
id: 88,
|
|
2149
|
-
name: "shared_links",
|
|
2150
|
-
up: (db) => {
|
|
2151
|
-
// Snapshot links made through Connect. Connect keeps only a digest of
|
|
2152
|
-
// the token, so this is the one place the link can be copied again.
|
|
2153
|
-
db.exec(`
|
|
2154
|
-
CREATE TABLE IF NOT EXISTS shared_links (
|
|
2155
|
-
id TEXT PRIMARY KEY,
|
|
2156
|
-
url TEXT NOT NULL,
|
|
2157
|
-
kind TEXT NOT NULL,
|
|
2158
|
-
title TEXT NOT NULL,
|
|
2159
|
-
session_id TEXT,
|
|
2160
|
-
artifact_id TEXT,
|
|
2161
|
-
expires_at TEXT,
|
|
2162
|
-
created_at TEXT NOT NULL,
|
|
2163
|
-
revoked_at TEXT
|
|
2164
|
-
);
|
|
2165
|
-
CREATE INDEX IF NOT EXISTS idx_shared_links_created
|
|
2166
|
-
ON shared_links(created_at DESC);
|
|
2167
|
-
`);
|
|
2168
|
-
},
|
|
2169
|
-
},
|
|
2170
|
-
{
|
|
2171
|
-
id: 89,
|
|
2172
|
-
name: "passkeys_device_keys",
|
|
2173
|
-
up: (db) => {
|
|
2174
|
-
// A paired device's own approval key (Secure Enclave, Face ID) sits in
|
|
2175
|
-
// the passkeys table beside the browser passkeys: type 'device', bound
|
|
2176
|
-
// to the device that made it. Additive; existing rows read as webauthn.
|
|
2177
|
-
const columns = db.prepare(`PRAGMA table_info(passkeys)`).all() as {
|
|
2178
|
-
name: string;
|
|
2179
|
-
}[];
|
|
2180
|
-
if (!columns.some((c) => c.name === "type"))
|
|
2181
|
-
db.exec(
|
|
2182
|
-
`ALTER TABLE passkeys ADD COLUMN type TEXT NOT NULL DEFAULT 'webauthn'`
|
|
2183
|
-
);
|
|
2184
|
-
if (!columns.some((c) => c.name === "device_id"))
|
|
2185
|
-
db.exec(`ALTER TABLE passkeys ADD COLUMN device_id TEXT`);
|
|
2186
|
-
db.exec(
|
|
2187
|
-
`CREATE INDEX IF NOT EXISTS idx_passkeys_device ON passkeys(device_id)`
|
|
2188
|
-
);
|
|
2189
|
-
},
|
|
2190
|
-
},
|
|
2191
|
-
{
|
|
2192
|
-
key: "20261014T0900_task_scheduling",
|
|
2193
|
-
up: (db) => {
|
|
2194
|
-
// Priority, "after any" consumption, and timed pause. Additive: every
|
|
2195
|
-
// existing row reads as normal priority and not paused.
|
|
2196
|
-
const has = (table: string, col: string) =>
|
|
2197
|
-
(
|
|
2198
|
-
db.prepare(`PRAGMA table_info(${table})`).all() as { name: string }[]
|
|
2199
|
-
).some((c) => c.name === col);
|
|
2200
|
-
const add = (table: string, col: string, def: string) => {
|
|
2201
|
-
if (!has(table, col))
|
|
2202
|
-
db.exec(`ALTER TABLE ${table} ADD COLUMN ${col} ${def}`);
|
|
2203
|
-
};
|
|
2204
|
-
add("task_queue", "priority", "TEXT NOT NULL DEFAULT 'normal'");
|
|
2205
|
-
add("task_queue", "machine_needs", "TEXT");
|
|
2206
|
-
add("task_queue", "after_any_used", "TEXT");
|
|
2207
|
-
add("task_queue", "wait_code", "TEXT");
|
|
2208
|
-
db.exec(
|
|
2209
|
-
`CREATE UNIQUE INDEX IF NOT EXISTS idx_task_queue_after_any_used
|
|
2210
|
-
ON task_queue(after_any_used) WHERE after_any_used IS NOT NULL`
|
|
2211
|
-
);
|
|
2212
|
-
add("sessions", "priority", "TEXT");
|
|
2213
|
-
add("sessions", "pause_state", "TEXT");
|
|
2214
|
-
add("sessions", "paused_until", "TEXT");
|
|
2215
|
-
add("sessions", "pause_id", "INTEGER");
|
|
2216
|
-
// "Started on <machine> · <why>" for a task the scheduler placed.
|
|
2217
|
-
add("sessions", "placed_note", "TEXT");
|
|
2218
|
-
add("bus_messages", "held_pause_id", "INTEGER");
|
|
2219
|
-
add("projects", "machine_needs", "TEXT");
|
|
2220
|
-
add("projects", "machine_needs_source", "TEXT");
|
|
2221
|
-
db.exec(`
|
|
2222
|
-
CREATE TABLE IF NOT EXISTS task_pauses (
|
|
2223
|
-
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
2224
|
-
session_id TEXT NOT NULL,
|
|
2225
|
-
mode TEXT NOT NULL DEFAULT 'turn',
|
|
2226
|
-
by_kind TEXT NOT NULL DEFAULT 'person',
|
|
2227
|
-
by_id TEXT,
|
|
2228
|
-
reason TEXT,
|
|
2229
|
-
until_at TEXT,
|
|
2230
|
-
state TEXT NOT NULL DEFAULT 'pausing',
|
|
2231
|
-
requested_at TEXT NOT NULL DEFAULT (datetime('now')),
|
|
2232
|
-
paused_at TEXT,
|
|
2233
|
-
resume_requested_at TEXT,
|
|
2234
|
-
resume_by_kind TEXT,
|
|
2235
|
-
resumed_at TEXT,
|
|
2236
|
-
ended_at TEXT,
|
|
2237
|
-
end_reason TEXT,
|
|
2238
|
-
interrupted INTEGER NOT NULL DEFAULT 0,
|
|
2239
|
-
held_count INTEGER NOT NULL DEFAULT 0
|
|
2240
|
-
);
|
|
2241
|
-
CREATE INDEX IF NOT EXISTS idx_task_pauses_session
|
|
2242
|
-
ON task_pauses(session_id, state);
|
|
2243
|
-
`);
|
|
2244
|
-
},
|
|
2245
|
-
},
|
|
2246
|
-
{
|
|
2247
|
-
key: "20261014T1235_placed_mirror_started_by",
|
|
2248
|
-
up: (db) => {
|
|
2249
|
-
// A task the queue started on a linked machine kept no record of the
|
|
2250
|
-
// orchestrator that queued it, so it couldn't be read, messaged or
|
|
2251
|
-
// stopped from there. Fills the mirror from the queue row; additive,
|
|
2252
|
-
// only where the mirror has none.
|
|
2253
|
-
db.exec(`
|
|
2254
|
-
UPDATE sessions SET
|
|
2255
|
-
started_by = (SELECT q.started_by FROM task_queue q WHERE q.id = sessions.id),
|
|
2256
|
-
task_since = COALESCE(task_since, created_at)
|
|
2257
|
-
WHERE started_by IS NULL AND task_prompt IS NOT NULL
|
|
2258
|
-
AND host_id IS NOT NULL AND host_id != 'local'
|
|
2259
|
-
AND (SELECT q.started_by FROM task_queue q WHERE q.id = sessions.id) IS NOT NULL
|
|
2260
|
-
`);
|
|
2261
|
-
},
|
|
2262
|
-
},
|
|
2263
|
-
{
|
|
2264
|
-
key: "20261014T1600_queued_task_messages",
|
|
2265
|
-
up: (db) => {
|
|
2266
|
-
// A message for a task still in the start queue, held (addressed to
|
|
2267
|
-
// the queue id, which is the session id it starts as) until its
|
|
2268
|
-
// first prompt. Additive.
|
|
2269
|
-
const has = (
|
|
2270
|
-
db.prepare(`PRAGMA table_info(bus_messages)`).all() as {
|
|
2271
|
-
name: string;
|
|
2272
|
-
}[]
|
|
2273
|
-
).some((c) => c.name === "held_queued");
|
|
2274
|
-
if (!has)
|
|
2275
|
-
db.exec(`ALTER TABLE bus_messages ADD COLUMN held_queued INTEGER`);
|
|
2276
|
-
db.exec(
|
|
2277
|
-
`CREATE INDEX IF NOT EXISTS idx_bus_messages_held_queued
|
|
2278
|
-
ON bus_messages(to_id) WHERE held_queued = 1`
|
|
2279
|
-
);
|
|
2280
|
-
},
|
|
2281
|
-
},
|
|
2282
|
-
{
|
|
2283
|
-
key: "20261014T1800_task_starter_device",
|
|
2284
|
-
up: (db) => {
|
|
2285
|
-
// The paired device that started a task over the API: the only one
|
|
2286
|
-
// that may later hold it. Tasks already running have no record, so
|
|
2287
|
-
// they're marked unrecorded. Additive.
|
|
2288
|
-
const has = (
|
|
2289
|
-
db.prepare(`PRAGMA table_info(sessions)`).all() as { name: string }[]
|
|
2290
|
-
).some((c) => c.name === "started_by_device");
|
|
2291
|
-
if (!has)
|
|
2292
|
-
db.exec(`ALTER TABLE sessions ADD COLUMN started_by_device TEXT`);
|
|
2293
|
-
db.exec(`
|
|
2294
|
-
UPDATE sessions SET started_by_device = 'unrecorded'
|
|
2295
|
-
WHERE started_by_device IS NULL AND started_by IS NULL
|
|
2296
|
-
AND held_by_device IS NULL AND task_status = 'running'
|
|
2297
|
-
AND task_prompt IS NOT NULL AND (host_id IS NULL OR host_id = 'local')
|
|
2298
|
-
`);
|
|
2299
|
-
},
|
|
2300
|
-
},
|
|
2301
|
-
{
|
|
2302
|
-
key: "20261014T1900_pause_note",
|
|
2303
|
-
up: (db) => {
|
|
2304
|
-
// Why a resume that was asked for hasn't happened yet (waiting for a
|
|
2305
|
-
// slot, held by load), shown on the task row. Additive.
|
|
2306
|
-
const has = (
|
|
2307
|
-
db.prepare(`PRAGMA table_info(sessions)`).all() as { name: string }[]
|
|
2308
|
-
).some((c) => c.name === "pause_note");
|
|
2309
|
-
if (!has) db.exec(`ALTER TABLE sessions ADD COLUMN pause_note TEXT`);
|
|
2310
|
-
},
|
|
2311
|
-
},
|
|
2312
|
-
{
|
|
2313
|
-
key: "20261014T2000_chat_bookmarks",
|
|
2314
|
-
up: (db) => {
|
|
2315
|
-
// A person's marks on single chat items. No FK to sessions: a
|
|
2316
|
-
// bookmark outlives a deleted chat with its own snapshot. Additive.
|
|
2317
|
-
db.exec(`
|
|
2318
|
-
CREATE TABLE IF NOT EXISTS chat_bookmarks (
|
|
2319
|
-
id TEXT PRIMARY KEY,
|
|
2320
|
-
session_id TEXT NOT NULL,
|
|
2321
|
-
item_id TEXT NOT NULL,
|
|
2322
|
-
scope TEXT NOT NULL DEFAULT 'item',
|
|
2323
|
-
workspace_id TEXT,
|
|
2324
|
-
kind TEXT NOT NULL,
|
|
2325
|
-
text TEXT NOT NULL,
|
|
2326
|
-
session_name TEXT NOT NULL,
|
|
2327
|
-
note TEXT,
|
|
2328
|
-
created_at TEXT NOT NULL DEFAULT (datetime('now')),
|
|
2329
|
-
UNIQUE (session_id, item_id, scope)
|
|
2330
|
-
)
|
|
2331
|
-
`);
|
|
2332
|
-
db.exec(
|
|
2333
|
-
`CREATE INDEX IF NOT EXISTS idx_chat_bookmarks_ws
|
|
2334
|
-
ON chat_bookmarks(workspace_id, created_at DESC)`
|
|
2335
|
-
);
|
|
2336
|
-
installChangeTriggers(db, TABLES_WATCHED_CHAT_BOOKMARKS);
|
|
2337
|
-
},
|
|
2338
|
-
},
|
|
2339
|
-
{
|
|
2340
|
-
key: "20261014T2100_gate_failure_ask_held",
|
|
2341
|
-
up: (db) => {
|
|
2342
|
-
// When a failed-twice escalation started holding its ask until the
|
|
2343
|
-
// agent is idle and CI has settled; null is not held. Additive.
|
|
2344
|
-
const has = (
|
|
2345
|
-
db.prepare(`PRAGMA table_info(orchestrator_gate_failures)`).all() as {
|
|
2346
|
-
name: string;
|
|
2347
|
-
}[]
|
|
2348
|
-
).some((c) => c.name === "ask_held_at");
|
|
2349
|
-
if (!has)
|
|
2350
|
-
db.exec(
|
|
2351
|
-
`ALTER TABLE orchestrator_gate_failures ADD COLUMN ask_held_at TEXT`
|
|
2352
|
-
);
|
|
2353
|
-
},
|
|
2354
|
-
},
|
|
2355
|
-
];
|
|
94
|
+
export const migrations: Migration[] = buildRegistry(migrationFiles);
|
|
2356
95
|
|
|
2357
96
|
// `upTo`: stop after this legacy id or timestamp key, inclusive (tests that
|
|
2358
97
|
// start from an older database).
|