@saadnvd1/agent-os 0.3.595 → 0.3.597

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.
Files changed (135) hide show
  1. package/README.md +1 -0
  2. package/app/api/sessions/[id]/artifacts/[artifactId]/route.ts +34 -0
  3. package/app/api/sessions/[id]/artifacts/route.ts +25 -9
  4. package/components/Artifacts/ArtifactPreview.tsx +239 -0
  5. package/components/Artifacts/ArtifactRow.tsx +147 -0
  6. package/components/Artifacts/ArtifactsPanel.tsx +244 -0
  7. package/components/Artifacts/helpers.test.ts +90 -0
  8. package/components/Artifacts/helpers.ts +99 -0
  9. package/components/Artifacts/index.ts +1 -0
  10. package/components/Chat/ArtifactFrame.tsx +4 -1
  11. package/components/Pane/DesktopTabBar.tsx +2 -2
  12. package/components/Pane/MobileTabBar.tsx +14 -2
  13. package/components/Pane/PaneViewToggle.tsx +20 -3
  14. package/components/Pane/index.tsx +35 -3
  15. package/contexts/PaneContext.tsx +7 -1
  16. package/data/session-artifacts/index.ts +2 -0
  17. package/data/session-artifacts/keys.ts +7 -0
  18. package/data/session-artifacts/queries.ts +53 -0
  19. package/lib/chat/task-output.ts +1 -1
  20. package/lib/db/migration-types.ts +15 -0
  21. package/lib/db/migrations/0001_add_group_path_to_sessions.ts +11 -0
  22. package/lib/db/migrations/0002_add_agent_type_to_sessions.ts +11 -0
  23. package/lib/db/migrations/0003_add_worktree_columns_to_sessions.ts +12 -0
  24. package/lib/db/migrations/0004_add_pr_tracking_to_sessions.ts +11 -0
  25. package/lib/db/migrations/0005_add_group_path_index.ts +11 -0
  26. package/lib/db/migrations/0006_add_orchestration_columns_to_sessions.ts +16 -0
  27. package/lib/db/migrations/0007_add_auto_approve_to_sessions.ts +11 -0
  28. package/lib/db/migrations/0008_add_dev_server_columns.ts +19 -0
  29. package/lib/db/migrations/0009_add_project_id_to_sessions.ts +17 -0
  30. package/lib/db/migrations/0010_add_project_id_to_dev_servers.ts +36 -0
  31. package/lib/db/migrations/0011_add_tmux_name_to_sessions.ts +13 -0
  32. package/lib/db/migrations/0012_add_initial_prompt_to_projects.ts +9 -0
  33. package/lib/db/migrations/0013_add_project_repositories_table.ts +22 -0
  34. package/lib/db/migrations/0014_add_hosts.ts +23 -0
  35. package/lib/db/migrations/0015_add_workspaces.ts +18 -0
  36. package/lib/db/migrations/0016_add_task_columns_to_sessions.ts +13 -0
  37. package/lib/db/migrations/0017_add_bus_messages.ts +24 -0
  38. package/lib/db/migrations/0018_add_chat.ts +23 -0
  39. package/lib/db/migrations/0019_add_lumifyhub.ts +36 -0
  40. package/lib/db/migrations/0020_add_lumifyhub_published_docs.ts +18 -0
  41. package/lib/db/migrations/0021_add_chat_access.ts +14 -0
  42. package/lib/db/migrations/0022_add_last_seen.ts +10 -0
  43. package/lib/db/migrations/0023_mark_existing_sessions_seen.ts +12 -0
  44. package/lib/db/migrations/0024_add_stacks.ts +49 -0
  45. package/lib/db/migrations/0025_add_stack_item_attempts.ts +16 -0
  46. package/lib/db/migrations/0026_add_workspace_orchestrator.ts +47 -0
  47. package/lib/db/migrations/0027_add_devices_and_settings.ts +28 -0
  48. package/lib/db/migrations/0028_add_orchestrator_acting.ts +70 -0
  49. package/lib/db/migrations/0029_add_orchestrator_asks_and_pause.ts +81 -0
  50. package/lib/db/migrations/0030_add_sessions_archived_at.ts +14 -0
  51. package/lib/db/migrations/0031_add_stack_item_restacked_heads.ts +22 -0
  52. package/lib/db/migrations/0032_add_sessions_pinned.ts +12 -0
  53. package/lib/db/migrations/0033_add_program_status.ts +18 -0
  54. package/lib/db/migrations/0034_add_session_names.ts +21 -0
  55. package/lib/db/migrations/0035_add_artifacts.ts +23 -0
  56. package/lib/db/migrations/0036_add_chat_queue.ts +26 -0
  57. package/lib/db/migrations/0037_add_chat_plan_and_turns.ts +39 -0
  58. package/lib/db/migrations/0038_add_schedules.ts +56 -0
  59. package/lib/db/migrations/0039_add_session_name_source.ts +21 -0
  60. package/lib/db/migrations/0040_add_schedule_targets_and_phone_notify.ts +41 -0
  61. package/lib/db/migrations/0041_add_task_setup.ts +17 -0
  62. package/lib/db/migrations/0042_add_host_links_and_moved_tasks.ts +29 -0
  63. package/lib/db/migrations/0043_scratch_project.ts +14 -0
  64. package/lib/db/migrations/0044_chat_item_kind.ts +21 -0
  65. package/lib/db/migrations/0045_change_versions.ts +13 -0
  66. package/lib/db/migrations/0046_session_port_slots.ts +21 -0
  67. package/lib/db/migrations/0047_pin_orchestrators.ts +12 -0
  68. package/lib/db/migrations/0048_session_database.ts +15 -0
  69. package/lib/db/migrations/0049_peer_mirror.ts +14 -0
  70. package/lib/db/migrations/0050_task_queue.ts +44 -0
  71. package/lib/db/migrations/0051_chat_queue_sent_by.ts +16 -0
  72. package/lib/db/migrations/0052_sessions_chat_restarted.ts +17 -0
  73. package/lib/db/migrations/0053_brief_amendments_failure_sha_ask_raised_by.ts +37 -0
  74. package/lib/db/migrations/0054_sessions_chat_tools_digest.ts +15 -0
  75. package/lib/db/migrations/0055_sessions_merge_armed_and_rebase.ts +25 -0
  76. package/lib/db/migrations/0056_orchestrator_rules.ts +39 -0
  77. package/lib/db/migrations/0057_task_pr_wakes.ts +34 -0
  78. package/lib/db/migrations/0058_away_mode.ts +71 -0
  79. package/lib/db/migrations/0059_gate_failure_commits.ts +33 -0
  80. package/lib/db/migrations/0060_check_runs_and_compactions.ts +44 -0
  81. package/lib/db/migrations/0061_model_routing.ts +30 -0
  82. package/lib/db/migrations/0062_sessions_quick_of.ts +19 -0
  83. package/lib/db/migrations/0063_one_off_schedules.ts +16 -0
  84. package/lib/db/migrations/0064_chat_compactions_phase.ts +15 -0
  85. package/lib/db/migrations/0065_orchestrator_triage.ts +23 -0
  86. package/lib/db/migrations/0066_workspace_orch_mcp_servers.ts +16 -0
  87. package/lib/db/migrations/0067_orchestrator_event_log_suppressed.ts +14 -0
  88. package/lib/db/migrations/0068_usage_calls_and_rollups.ts +74 -0
  89. package/lib/db/migrations/0069_session_logs.ts +26 -0
  90. package/lib/db/migrations/0070_chat_items_running.ts +15 -0
  91. package/lib/db/migrations/0071_devices_push.ts +29 -0
  92. package/lib/db/migrations/0072_push_outbox.ts +33 -0
  93. package/lib/db/migrations/0073_orchestrated_task_moves.ts +27 -0
  94. package/lib/db/migrations/0074_held_task_replies.ts +27 -0
  95. package/lib/db/migrations/0075_plan_only_tasks.ts +17 -0
  96. package/lib/db/migrations/0076_welcome_backfill.ts +16 -0
  97. package/lib/db/migrations/0077_simulator_leases.ts +21 -0
  98. package/lib/db/migrations/0078_orchestrator_questions.ts +34 -0
  99. package/lib/db/migrations/0079_task_queue_agent.ts +13 -0
  100. package/lib/db/migrations/0080_recipes.ts +129 -0
  101. package/lib/db/migrations/0081_schedules_into_recipes.ts +18 -0
  102. package/lib/db/migrations/0082_session_shelf_times.ts +23 -0
  103. package/lib/db/migrations/0083_native_devices_can_approve.ts +18 -0
  104. package/lib/db/migrations/0084_approval_audit.ts +20 -0
  105. package/lib/db/migrations/0085_experiment_usage.ts +35 -0
  106. package/lib/db/migrations/0086_learning_cards.ts +28 -0
  107. package/lib/db/migrations/0087_plan_pieces_reconcile.ts +29 -0
  108. package/lib/db/migrations/0088_shared_links.ts +25 -0
  109. package/lib/db/migrations/0089_passkeys_device_keys.ts +23 -0
  110. package/lib/db/migrations/20261014T0900_task_scheduling.ts +57 -0
  111. package/lib/db/migrations/20261014T1235_placed_mirror_started_by.ts +19 -0
  112. package/lib/db/migrations/20261014T1600_queued_task_messages.ts +21 -0
  113. package/lib/db/migrations/20261014T1800_task_starter_device.ts +20 -0
  114. package/lib/db/migrations/20261014T1900_pause_note.ts +13 -0
  115. package/lib/db/migrations/20261014T2000_chat_bookmarks.ts +33 -0
  116. package/lib/db/migrations/20261014T2100_gate_failure_ask_held.ts +18 -0
  117. package/lib/db/migrations/README.md +35 -0
  118. package/lib/db/migrations-legacy-checksums.json +9 -8
  119. package/lib/db/migrations-split-snapshot.json +1759 -0
  120. package/lib/db/migrations.ts +76 -2337
  121. package/lib/done/worktree.ts +3 -0
  122. package/lib/security/demo.ts +1 -0
  123. package/lib/session-artifacts/access.ts +81 -0
  124. package/lib/session-artifacts/collect.ts +318 -0
  125. package/lib/session-artifacts/derive.ts +437 -0
  126. package/lib/session-artifacts/keep.ts +119 -0
  127. package/lib/session-artifacts/peer.ts +187 -0
  128. package/lib/session-artifacts/serve.ts +265 -0
  129. package/lib/session-artifacts/types.ts +43 -0
  130. package/lib/tasks/finish.ts +2 -0
  131. package/package.json +6 -3
  132. package/scripts/check +1 -0
  133. package/scripts/gen-migrations-index.mjs +44 -0
  134. package/scripts/screenshots/demo.ts +5 -1
  135. package/scripts/screenshots/run.ts +5 -1
@@ -0,0 +1,16 @@
1
+ import type { Migration } from "../migration-types";
2
+
3
+ export default {
4
+ // 50 is taken by queued tasks, landing separately.
5
+ id: 51,
6
+ name: "chat_queue_sent_by",
7
+ up: (db) => {
8
+ // Who sent a queued message the reader didn't type (lib/chat/events
9
+ // SentBy, as JSON), so it still shows as theirs once it's sent.
10
+ const columns = db.prepare(`PRAGMA table_info(chat_queue)`).all() as {
11
+ name: string;
12
+ }[];
13
+ if (!columns.some((c) => c.name === "sent_by"))
14
+ db.exec(`ALTER TABLE chat_queue ADD COLUMN sent_by TEXT`);
15
+ },
16
+ } satisfies Migration;
@@ -0,0 +1,17 @@
1
+ import type { Migration } from "../migration-types";
2
+
3
+ export default {
4
+ id: 52,
5
+ name: "sessions_chat_restarted",
6
+ up: (db) => {
7
+ // Set by a chat worker that retired cleanly after a deploy; the next
8
+ // worker tells the agent so with its first message, then clears it.
9
+ const columns = db.prepare(`PRAGMA table_info(sessions)`).all() as {
10
+ name: string;
11
+ }[];
12
+ if (!columns.some((c) => c.name === "chat_restarted"))
13
+ db.exec(
14
+ `ALTER TABLE sessions ADD COLUMN chat_restarted INTEGER NOT NULL DEFAULT 0`
15
+ );
16
+ },
17
+ } satisfies Migration;
@@ -0,0 +1,37 @@
1
+ import type { Migration } from "../migration-types";
2
+
3
+ export default {
4
+ id: 53,
5
+ name: "brief_amendments_failure_sha_ask_raised_by",
6
+ up: (db) => {
7
+ // Scope changes the orchestrator records on a task after it started
8
+ // (send with scope_change); its review judges against them.
9
+ db.exec(`
10
+ CREATE TABLE IF NOT EXISTS brief_amendments (
11
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
12
+ session_id TEXT NOT NULL,
13
+ workspace_id TEXT NOT NULL,
14
+ text TEXT NOT NULL,
15
+ created_at TEXT NOT NULL DEFAULT (datetime('now'))
16
+ );
17
+ CREATE INDEX IF NOT EXISTS idx_brief_amendments_session
18
+ ON brief_amendments(session_id);
19
+ `);
20
+ // The commit a stored verdict's failure was counted for, so reading
21
+ // the same verdict again isn't a second failure.
22
+ const columns = db
23
+ .prepare(`PRAGMA table_info(orchestrator_gate_failures)`)
24
+ .all() as { name: string }[];
25
+ if (!columns.some((c) => c.name === "last_sha"))
26
+ db.exec(
27
+ `ALTER TABLE orchestrator_gate_failures ADD COLUMN last_sha TEXT`
28
+ );
29
+ // Who raised a merge ask: the orchestrator may refresh its own, never
30
+ // one the gates raised.
31
+ const asks = db.prepare(`PRAGMA table_info(orchestrator_asks)`).all() as {
32
+ name: string;
33
+ }[];
34
+ if (!asks.some((c) => c.name === "raised_by"))
35
+ db.exec(`ALTER TABLE orchestrator_asks ADD COLUMN raised_by TEXT`);
36
+ },
37
+ } satisfies Migration;
@@ -0,0 +1,15 @@
1
+ import type { Migration } from "../migration-types";
2
+
3
+ export default {
4
+ id: 54,
5
+ name: "sessions_chat_tools_digest",
6
+ up: (db) => {
7
+ // The tools a chat worker last gave the agent (an orchestrator's), so
8
+ // the next one says they changed only when they did.
9
+ const columns = db.prepare(`PRAGMA table_info(sessions)`).all() as {
10
+ name: string;
11
+ }[];
12
+ if (!columns.some((c) => c.name === "chat_tools_digest"))
13
+ db.exec(`ALTER TABLE sessions ADD COLUMN chat_tools_digest TEXT`);
14
+ },
15
+ } satisfies Migration;
@@ -0,0 +1,25 @@
1
+ import type { Migration } from "../migration-types";
2
+
3
+ export default {
4
+ id: 55,
5
+ name: "sessions_merge_armed_and_rebase",
6
+ up: (db) => {
7
+ // merge_armed_at: AgentOS merges the task's PR once every gate
8
+ // passes (orchestrator/armed.ts); merge_armed_seen: the failure it
9
+ // last reported. rebased_from/to: the heads an automatic rebase onto
10
+ // the base moved the branch between, for the code-review gate.
11
+ // rebase_asked_head: the head the task was last asked to rebase.
12
+ const columns = db.prepare(`PRAGMA table_info(sessions)`).all() as {
13
+ name: string;
14
+ }[];
15
+ for (const name of [
16
+ "merge_armed_at",
17
+ "merge_armed_seen",
18
+ "rebased_from",
19
+ "rebased_to",
20
+ "rebase_asked_head",
21
+ ])
22
+ if (!columns.some((c) => c.name === name))
23
+ db.exec(`ALTER TABLE sessions ADD COLUMN ${name} TEXT`);
24
+ },
25
+ } satisfies Migration;
@@ -0,0 +1,39 @@
1
+ import type { Migration } from "../migration-types";
2
+ import { installChangeTriggers, TABLES_WATCHED_56 } from "../changes";
3
+
4
+ export default {
5
+ id: 56,
6
+ name: "orchestrator_rules",
7
+ up: (db) => {
8
+ // Standing rules an orchestrator keeps across fresh starts
9
+ // (lib/orchestrator/rules.ts): written by it or by Saad, never
10
+ // deleted, only marked removed.
11
+ db.exec(`
12
+ CREATE TABLE IF NOT EXISTS orchestrator_rules (
13
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
14
+ workspace_id TEXT NOT NULL,
15
+ text TEXT NOT NULL,
16
+ added_by TEXT NOT NULL,
17
+ created_at TEXT NOT NULL DEFAULT (datetime('now')),
18
+ updated_at TEXT NOT NULL DEFAULT (datetime('now')),
19
+ removed_at TEXT,
20
+ removed_by TEXT
21
+ )
22
+ `);
23
+ db.exec(
24
+ `CREATE INDEX IF NOT EXISTS idx_orchestrator_rules
25
+ ON orchestrator_rules(workspace_id, removed_at, id)`
26
+ );
27
+ // Its last worker ended the conversation at the context threshold:
28
+ // the tokens it had reached (0 otherwise). The next one starts a new
29
+ // one and tells the agent so.
30
+ const columns = db.prepare(`PRAGMA table_info(sessions)`).all() as {
31
+ name: string;
32
+ }[];
33
+ if (!columns.some((c) => c.name === "chat_refreshed"))
34
+ db.exec(
35
+ `ALTER TABLE sessions ADD COLUMN chat_refreshed INTEGER NOT NULL DEFAULT 0`
36
+ );
37
+ installChangeTriggers(db, TABLES_WATCHED_56);
38
+ },
39
+ } satisfies Migration;
@@ -0,0 +1,34 @@
1
+ import type { Migration } from "../migration-types";
2
+
3
+ export default {
4
+ id: 57,
5
+ name: "task_pr_wakes",
6
+ up: (db) => {
7
+ // What a task's agent was told about its PR (orchestrator/pr-wake.ts),
8
+ // one row per failing head or review/comment id, claimed before the
9
+ // message goes. kind: check, review or comment (comments are capped).
10
+ db.exec(`
11
+ CREATE TABLE IF NOT EXISTS task_pr_wakes (
12
+ session_id TEXT NOT NULL,
13
+ key TEXT NOT NULL,
14
+ kind TEXT NOT NULL,
15
+ created_at TEXT NOT NULL DEFAULT (datetime('now')),
16
+ PRIMARY KEY (session_id, key)
17
+ );
18
+ `);
19
+ // pr_wake_since: reviews and comments older than this are taken as
20
+ // known when a task's PR is first watched.
21
+ const sessions = db.prepare(`PRAGMA table_info(sessions)`).all() as {
22
+ name: string;
23
+ }[];
24
+ if (!sessions.some((c) => c.name === "pr_wake_since"))
25
+ db.exec(`ALTER TABLE sessions ADD COLUMN pr_wake_since TEXT`);
26
+ const workspaces = db.prepare(`PRAGMA table_info(workspaces)`).all() as {
27
+ name: string;
28
+ }[];
29
+ if (!workspaces.some((c) => c.name === "wake_tasks_on_pr"))
30
+ db.exec(
31
+ `ALTER TABLE workspaces ADD COLUMN wake_tasks_on_pr INTEGER NOT NULL DEFAULT 1`
32
+ );
33
+ },
34
+ } satisfies Migration;
@@ -0,0 +1,71 @@
1
+ import type { Migration } from "../migration-types";
2
+ import { installChangeTriggers, TABLES_WATCHED_58 } from "../changes";
3
+
4
+ export default {
5
+ id: 58,
6
+ name: "away_mode",
7
+ up: (db) => {
8
+ // Saad's Away mode (lib/away): one row per time he stepped away. The
9
+ // report is built from the database when it ends and kept here.
10
+ db.exec(`
11
+ CREATE TABLE IF NOT EXISTS away_periods (
12
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
13
+ set_by TEXT NOT NULL,
14
+ started_at TEXT NOT NULL DEFAULT (datetime('now')),
15
+ expires_at TEXT,
16
+ ended_at TEXT,
17
+ ended_by TEXT,
18
+ last_sweep_at TEXT,
19
+ asks_notified_at TEXT,
20
+ report TEXT,
21
+ report_seen_at TEXT
22
+ )
23
+ `);
24
+ // What the away sweep did, once per key per period: a nudge, a
25
+ // request to commit, a review run again, a deploy checked.
26
+ db.exec(`
27
+ CREATE TABLE IF NOT EXISTS away_actions (
28
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
29
+ away_id INTEGER NOT NULL,
30
+ workspace_id TEXT NOT NULL,
31
+ subject TEXT NOT NULL,
32
+ kind TEXT NOT NULL,
33
+ key TEXT NOT NULL,
34
+ detail TEXT,
35
+ created_at TEXT NOT NULL DEFAULT (datetime('now')),
36
+ UNIQUE (away_id, key)
37
+ )
38
+ `);
39
+ // When a task merged, whichever path merged it.
40
+ const columns = db.prepare(`PRAGMA table_info(sessions)`).all() as {
41
+ name: string;
42
+ }[];
43
+ if (!columns.some((c) => c.name === "merged_at"))
44
+ db.exec(`ALTER TABLE sessions ADD COLUMN merged_at TEXT`);
45
+ // When a task ended without a merge (dropped, or done with no PR or
46
+ // its PR closed).
47
+ if (!columns.some((c) => c.name === "task_ended_at"))
48
+ db.exec(`ALTER TABLE sessions ADD COLUMN task_ended_at TEXT`);
49
+ db.exec(`
50
+ CREATE TRIGGER IF NOT EXISTS sessions_task_ended_at
51
+ AFTER UPDATE OF task_status ON sessions
52
+ WHEN NEW.task_status IN ('dropped', 'done')
53
+ AND OLD.task_status IS NOT NEW.task_status
54
+ AND NEW.task_ended_at IS OLD.task_ended_at
55
+ BEGIN
56
+ UPDATE sessions SET task_ended_at = datetime('now') WHERE id = NEW.id;
57
+ END
58
+ `);
59
+ db.exec(`
60
+ CREATE TRIGGER IF NOT EXISTS sessions_merged_at
61
+ AFTER UPDATE OF task_status ON sessions
62
+ WHEN NEW.task_status = 'merged' AND OLD.task_status IS NOT 'merged'
63
+ -- unless the same statement set it (an import keeps its own)
64
+ AND NEW.merged_at IS OLD.merged_at
65
+ BEGIN
66
+ UPDATE sessions SET merged_at = datetime('now') WHERE id = NEW.id;
67
+ END
68
+ `);
69
+ installChangeTriggers(db, TABLES_WATCHED_58);
70
+ },
71
+ } satisfies Migration;
@@ -0,0 +1,33 @@
1
+ import type { Migration } from "../migration-types";
2
+
3
+ export default {
4
+ id: 59,
5
+ name: "gate_failure_commits",
6
+ up: (db) => {
7
+ // Each head a gate failed on, once: a gate's failure counts once per
8
+ // commit however many times that commit is judged (the armed watcher,
9
+ // CI finishing, sign_off, done, land), and the second commit it fails
10
+ // on goes to Saad. The key is what makes it once, across restarts and
11
+ // concurrent evaluations.
12
+ db.exec(`
13
+ CREATE TABLE IF NOT EXISTS orchestrator_gate_failure_commits (
14
+ session_id TEXT NOT NULL,
15
+ gate TEXT NOT NULL,
16
+ sha TEXT NOT NULL,
17
+ workspace_id TEXT NOT NULL,
18
+ reason TEXT,
19
+ created_at TEXT NOT NULL DEFAULT (datetime('now')),
20
+ PRIMARY KEY (session_id, gate, sha)
21
+ );
22
+ INSERT OR IGNORE INTO orchestrator_gate_failure_commits (session_id, gate, sha, workspace_id, reason)
23
+ SELECT session_id, gate, last_sha, workspace_id, last_reason
24
+ FROM orchestrator_gate_failures WHERE last_sha IS NOT NULL;
25
+ CREATE TRIGGER IF NOT EXISTS gate_failures_clear_commits
26
+ AFTER DELETE ON orchestrator_gate_failures
27
+ BEGIN
28
+ DELETE FROM orchestrator_gate_failure_commits
29
+ WHERE session_id = OLD.session_id AND gate = OLD.gate;
30
+ END;
31
+ `);
32
+ },
33
+ } satisfies Migration;
@@ -0,0 +1,44 @@
1
+ import type { Migration } from "../migration-types";
2
+
3
+ export default {
4
+ id: 60,
5
+ name: "check_runs_and_compactions",
6
+ up: (db) => {
7
+ // What each run of the independent reviewer and the scope check cost,
8
+ // from its claude -p result: it leaves no transcript, so this is the
9
+ // only record. And each compaction a chat's agent made, so tokens per
10
+ // task can be compared before and after the threshold moved.
11
+ db.exec(`
12
+ CREATE TABLE IF NOT EXISTS check_runs (
13
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
14
+ workspace_id TEXT NOT NULL,
15
+ session_id TEXT NOT NULL,
16
+ label TEXT NOT NULL,
17
+ pr_number INTEGER,
18
+ sha TEXT NOT NULL,
19
+ kind TEXT NOT NULL,
20
+ model TEXT,
21
+ input_tokens INTEGER NOT NULL,
22
+ cache_write_tokens INTEGER NOT NULL,
23
+ cache_read_tokens INTEGER NOT NULL,
24
+ output_tokens INTEGER NOT NULL,
25
+ cost_usd REAL NOT NULL,
26
+ duration_ms INTEGER,
27
+ at INTEGER NOT NULL
28
+ );
29
+ CREATE INDEX IF NOT EXISTS idx_check_runs_at ON check_runs(at);
30
+ CREATE INDEX IF NOT EXISTS idx_check_runs_sha ON check_runs(session_id, sha, kind);
31
+ CREATE TABLE IF NOT EXISTS chat_compactions (
32
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
33
+ session_id TEXT NOT NULL,
34
+ session_name TEXT NOT NULL,
35
+ workspace_id TEXT,
36
+ at INTEGER NOT NULL,
37
+ trigger TEXT NOT NULL,
38
+ before_tokens INTEGER,
39
+ after_tokens INTEGER
40
+ );
41
+ CREATE INDEX IF NOT EXISTS idx_chat_compactions_at ON chat_compactions(at);
42
+ `);
43
+ },
44
+ } satisfies Migration;
@@ -0,0 +1,30 @@
1
+ import type { Migration } from "../migration-types";
2
+
3
+ export default {
4
+ id: 61,
5
+ name: "model_routing",
6
+ // One transaction, so a crash part-way leaves nothing to skip as
7
+ // "duplicate column" on the next start.
8
+ up: (db) =>
9
+ db.transaction(() => {
10
+ // What a session runs on beyond the model name: its provider (NULL is
11
+ // Anthropic), a task's own models by role (JSON), and a task's plan
12
+ // step (planning, then implementing). Each turn keeps the model,
13
+ // provider and role it ran as, how long it took, and the plan's 5-hour
14
+ // window when it ended, so spend compares by model. A check run keeps the
15
+ // provider its model came from.
16
+ for (const col of [
17
+ `ALTER TABLE sessions ADD COLUMN model_provider TEXT`,
18
+ `ALTER TABLE sessions ADD COLUMN model_overrides TEXT`,
19
+ `ALTER TABLE sessions ADD COLUMN plan_phase TEXT`,
20
+ `ALTER TABLE task_queue ADD COLUMN model_overrides TEXT`,
21
+ `ALTER TABLE chat_turns ADD COLUMN model TEXT`,
22
+ `ALTER TABLE chat_turns ADD COLUMN provider TEXT`,
23
+ `ALTER TABLE chat_turns ADD COLUMN role TEXT`,
24
+ `ALTER TABLE chat_turns ADD COLUMN duration_ms INTEGER`,
25
+ `ALTER TABLE chat_turns ADD COLUMN window_pct REAL`,
26
+ `ALTER TABLE check_runs ADD COLUMN provider TEXT`,
27
+ ])
28
+ db.exec(col);
29
+ })(),
30
+ } satisfies Migration;
@@ -0,0 +1,19 @@
1
+ import type { Migration } from "../migration-types";
2
+
3
+ export default {
4
+ id: 62,
5
+ name: "sessions_quick_of",
6
+ up: (db) => {
7
+ // A quick change (orchestrator/quick.ts): a task row with no agent,
8
+ // whose worktree the orchestrator with this session id edits itself.
9
+ // Set only by quick_change; the code-review gate reads it.
10
+ const columns = db.prepare(`PRAGMA table_info(sessions)`).all() as {
11
+ name: string;
12
+ }[];
13
+ if (!columns.some((c) => c.name === "quick_of"))
14
+ db.exec(`ALTER TABLE sessions ADD COLUMN quick_of TEXT`);
15
+ db.exec(
16
+ `CREATE INDEX IF NOT EXISTS idx_sessions_quick_of ON sessions(quick_of) WHERE quick_of IS NOT NULL`
17
+ );
18
+ },
19
+ } satisfies Migration;
@@ -0,0 +1,16 @@
1
+ import type { Migration } from "../migration-types";
2
+
3
+ export default {
4
+ id: 63,
5
+ name: "one_off_schedules",
6
+ up: (db) =>
7
+ db.transaction(() => {
8
+ // A one-off has a run time (epoch ms, UTC) and no cron; done_at is
9
+ // set once its single run settles. Recurring rows leave both NULL.
10
+ db.exec(`ALTER TABLE schedules ADD COLUMN run_at INTEGER`);
11
+ db.exec(`ALTER TABLE schedules ADD COLUMN done_at INTEGER`);
12
+ db.exec(
13
+ `CREATE INDEX IF NOT EXISTS idx_schedules_run_at ON schedules(run_at) WHERE run_at IS NOT NULL`
14
+ );
15
+ })(),
16
+ } satisfies Migration;
@@ -0,0 +1,15 @@
1
+ import type { Migration } from "../migration-types";
2
+
3
+ export default {
4
+ id: 64,
5
+ name: "chat_compactions_phase",
6
+ up: (db) => {
7
+ // Whether the agent compacted itself mid-turn or AgentOS (or a typed
8
+ // /compact) did between turns; null on rows from before.
9
+ const columns = db.prepare(`PRAGMA table_info(chat_compactions)`).all() as {
10
+ name: string;
11
+ }[];
12
+ if (!columns.some((c) => c.name === "phase"))
13
+ db.exec(`ALTER TABLE chat_compactions ADD COLUMN phase TEXT`);
14
+ },
15
+ } satisfies Migration;
@@ -0,0 +1,23 @@
1
+ import type { Migration } from "../migration-types";
2
+
3
+ export default {
4
+ id: 65,
5
+ name: "orchestrator_triage",
6
+ up: (db) => {
7
+ // started_by: the orchestrator session that started this one (null: a
8
+ // person). plan_decided: 1 planned, 0 no plan, null not stated.
9
+ const add = (table: string, col: string, type: string) => {
10
+ const columns = db.prepare(`PRAGMA table_info(${table})`).all() as {
11
+ name: string;
12
+ }[];
13
+ if (!columns.some((c) => c.name === col))
14
+ db.exec(`ALTER TABLE ${table} ADD COLUMN ${col} ${type}`);
15
+ };
16
+ add("sessions", "started_by", "TEXT");
17
+ add("sessions", "plan_decided", "INTEGER");
18
+ add("sessions", "plan_reason", "TEXT");
19
+ add("task_queue", "plan", "INTEGER");
20
+ add("task_queue", "plan_reason", "TEXT");
21
+ add("task_queue", "started_by", "TEXT");
22
+ },
23
+ } satisfies Migration;
@@ -0,0 +1,16 @@
1
+ import type { Migration } from "../migration-types";
2
+
3
+ export default {
4
+ id: 66,
5
+ name: "workspace_orch_mcp_servers",
6
+ up: (db) => {
7
+ // The MCP servers (by name) a workspace's orchestrator may use.
8
+ const columns = db.prepare(`PRAGMA table_info(workspaces)`).all() as {
9
+ name: string;
10
+ }[];
11
+ if (!columns.some((c) => c.name === "orch_mcp_servers"))
12
+ db.exec(
13
+ `ALTER TABLE workspaces ADD COLUMN orch_mcp_servers TEXT NOT NULL DEFAULT '["linear"]'`
14
+ );
15
+ },
16
+ } satisfies Migration;
@@ -0,0 +1,14 @@
1
+ import type { Migration } from "../migration-types";
2
+
3
+ export default {
4
+ id: 67,
5
+ name: "orchestrator_event_log_suppressed",
6
+ up: (db) => {
7
+ // A capped event is logged as suppressed so a drop leaves a trace.
8
+ const columns = db
9
+ .prepare(`PRAGMA table_info(orchestrator_event_log)`)
10
+ .all() as { name: string }[];
11
+ if (!columns.some((c) => c.name === "suppressed"))
12
+ db.exec(`ALTER TABLE orchestrator_event_log ADD COLUMN suppressed TEXT`);
13
+ },
14
+ } satisfies Migration;
@@ -0,0 +1,74 @@
1
+ import type { Migration } from "../migration-types";
2
+
3
+ export default {
4
+ id: 68,
5
+ name: "usage_calls_and_rollups",
6
+ up: (db) => {
7
+ // One row per Claude response (or per one-shot run), the daily rollups
8
+ // computed from them, the ratchet's baseline and the limit readings.
9
+ db.transaction(() => {
10
+ db.exec(`
11
+ CREATE TABLE IF NOT EXISTS usage_calls (
12
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
13
+ call_key TEXT NOT NULL UNIQUE,
14
+ at INTEGER NOT NULL,
15
+ session_id TEXT,
16
+ task_id TEXT,
17
+ workspace_id TEXT,
18
+ project_id TEXT,
19
+ session_name TEXT,
20
+ role TEXT,
21
+ kind TEXT NOT NULL,
22
+ agent_type TEXT,
23
+ model TEXT,
24
+ provider TEXT,
25
+ input_tokens INTEGER NOT NULL DEFAULT 0,
26
+ cache_read_tokens INTEGER NOT NULL DEFAULT 0,
27
+ cache_write_5m_tokens INTEGER NOT NULL DEFAULT 0,
28
+ cache_write_1h_tokens INTEGER NOT NULL DEFAULT 0,
29
+ output_tokens INTEGER NOT NULL DEFAULT 0,
30
+ ctx_tokens INTEGER,
31
+ source TEXT NOT NULL
32
+ );
33
+ CREATE INDEX IF NOT EXISTS idx_usage_calls_at ON usage_calls(at);
34
+ CREATE INDEX IF NOT EXISTS idx_usage_calls_task ON usage_calls(task_id);
35
+ CREATE INDEX IF NOT EXISTS idx_usage_calls_session ON usage_calls(session_id);
36
+ CREATE TABLE IF NOT EXISTS rate_limit_readings (
37
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
38
+ at INTEGER NOT NULL,
39
+ window TEXT NOT NULL,
40
+ utilization REAL NOT NULL,
41
+ raw REAL,
42
+ resets_at INTEGER,
43
+ status TEXT,
44
+ session_id TEXT,
45
+ source TEXT NOT NULL
46
+ );
47
+ CREATE INDEX IF NOT EXISTS idx_rate_limit_readings ON rate_limit_readings(window, at);
48
+ CREATE TABLE IF NOT EXISTS usage_rollups (
49
+ day TEXT PRIMARY KEY,
50
+ data TEXT NOT NULL,
51
+ final INTEGER NOT NULL DEFAULT 0,
52
+ computed_at INTEGER NOT NULL
53
+ );
54
+ CREATE TABLE IF NOT EXISTS usage_baseline (
55
+ metric TEXT PRIMARY KEY,
56
+ value REAL NOT NULL,
57
+ set_at INTEGER NOT NULL,
58
+ supporting TEXT
59
+ );
60
+ CREATE TABLE IF NOT EXISTS usage_alerts (
61
+ kind TEXT PRIMARY KEY,
62
+ opened_at INTEGER NOT NULL,
63
+ cleared_at INTEGER,
64
+ message TEXT NOT NULL
65
+ );
66
+ CREATE TABLE IF NOT EXISTS usage_import_offsets (
67
+ path TEXT PRIMARY KEY,
68
+ offset INTEGER NOT NULL,
69
+ mtime INTEGER NOT NULL
70
+ );
71
+ `);
72
+ })();
73
+ },
74
+ } satisfies Migration;
@@ -0,0 +1,26 @@
1
+ import type { Migration } from "../migration-types";
2
+
3
+ export default {
4
+ id: 69,
5
+ name: "session_logs",
6
+ up: (db) => {
7
+ // One log per merged task, written by AgentOS from data it already
8
+ // holds (lib/session-logs). Never deleted: the database is the record,
9
+ // the markdown file on disk is a copy.
10
+ db.exec(`
11
+ CREATE TABLE IF NOT EXISTS session_logs (
12
+ session_id TEXT PRIMARY KEY,
13
+ project_id TEXT,
14
+ workspace_id TEXT,
15
+ created_at TEXT NOT NULL DEFAULT (datetime('now')),
16
+ markdown TEXT NOT NULL,
17
+ data TEXT NOT NULL,
18
+ file_path TEXT
19
+ );
20
+ CREATE INDEX IF NOT EXISTS idx_session_logs_project
21
+ ON session_logs(project_id, created_at);
22
+ CREATE INDEX IF NOT EXISTS idx_session_logs_workspace
23
+ ON session_logs(workspace_id, created_at);
24
+ `);
25
+ },
26
+ } satisfies Migration;
@@ -0,0 +1,15 @@
1
+ import type { Migration } from "../migration-types";
2
+
3
+ export default {
4
+ id: 70,
5
+ name: "chat_items_running",
6
+ up: (db) => {
7
+ // What is still running (tools, background tasks) is read on every
8
+ // attach; the index holds only those rows, so it's a lookup, not a
9
+ // scan that parses every tool output of a long conversation.
10
+ db.exec(
11
+ `CREATE INDEX IF NOT EXISTS idx_chat_items_running ON chat_items(session_id, kind, seq)
12
+ WHERE json_extract(data, '$.status') = 'running'`
13
+ );
14
+ },
15
+ } satisfies Migration;
@@ -0,0 +1,29 @@
1
+ import type { Migration } from "../migration-types";
2
+
3
+ export default {
4
+ id: 71,
5
+ name: "devices_push",
6
+ up: (db) => {
7
+ // Push belongs to a paired device, so revoking the device clears it.
8
+ // push_categories is a JSON array; NULL means the defaults.
9
+ // Each column checked, so a run cut off part way can be run again.
10
+ const have = new Set(
11
+ (
12
+ db.prepare(`PRAGMA table_info(devices)`).all() as { name: string }[]
13
+ ).map((c) => c.name)
14
+ );
15
+ for (const col of [
16
+ "push_token",
17
+ "push_platform",
18
+ "push_environment",
19
+ "push_categories",
20
+ "push_updated_at",
21
+ "push_last_error",
22
+ ])
23
+ if (!have.has(col)) db.exec(`ALTER TABLE devices ADD COLUMN ${col} TEXT`);
24
+ db.exec(
25
+ `CREATE INDEX IF NOT EXISTS idx_devices_push_token ON devices(push_token)
26
+ WHERE push_token IS NOT NULL`
27
+ );
28
+ },
29
+ } satisfies Migration;
@@ -0,0 +1,33 @@
1
+ import type { Migration } from "../migration-types";
2
+
3
+ export default {
4
+ id: 72,
5
+ name: "push_outbox",
6
+ up: (db) => {
7
+ // One row per (event, device). device_id is '' for a row folded into
8
+ // the Away digest. state: pending, sending, sent, failed, gone,
9
+ // capped, superseded, folded, digested.
10
+ db.exec(`
11
+ CREATE TABLE IF NOT EXISTS push_outbox (
12
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
13
+ event_key TEXT NOT NULL,
14
+ device_id TEXT NOT NULL,
15
+ category TEXT NOT NULL,
16
+ level TEXT NOT NULL,
17
+ payload TEXT NOT NULL,
18
+ collapse_id TEXT,
19
+ state TEXT NOT NULL,
20
+ attempts INTEGER NOT NULL DEFAULT 0,
21
+ next_attempt_at INTEGER NOT NULL,
22
+ created_at INTEGER NOT NULL,
23
+ sent_at INTEGER,
24
+ error TEXT,
25
+ fallback_source TEXT,
26
+ fallback_text TEXT,
27
+ UNIQUE(event_key, device_id)
28
+ );
29
+ CREATE INDEX IF NOT EXISTS idx_push_outbox_due ON push_outbox(state, next_attempt_at);
30
+ CREATE INDEX IF NOT EXISTS idx_push_outbox_device ON push_outbox(device_id, sent_at);
31
+ `);
32
+ },
33
+ } satisfies Migration;