@proletariat/cli 0.3.81 → 0.3.83
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/README.md +2 -46
- package/dist/commands/action/run.js +2 -2
- package/dist/commands/action/run.js.map +1 -1
- package/dist/commands/agent/staff/add.d.ts +17 -1
- package/dist/commands/agent/staff/add.js +274 -1
- package/dist/commands/agent/staff/add.js.map +1 -1
- package/dist/commands/agent/staff/list.d.ts +10 -1
- package/dist/commands/agent/staff/list.js +103 -1
- package/dist/commands/agent/staff/list.js.map +1 -1
- package/dist/commands/agent/staff/remove.d.ts +18 -1
- package/dist/commands/agent/staff/remove.js +143 -1
- package/dist/commands/agent/staff/remove.js.map +1 -1
- package/dist/commands/{epic/activate.d.ts → pr/close.d.ts} +8 -2
- package/dist/commands/pr/close.js +157 -0
- package/dist/commands/pr/close.js.map +1 -0
- package/dist/commands/pr/index.js +6 -2
- package/dist/commands/pr/index.js.map +1 -1
- package/dist/commands/pr/list.d.ts +1 -0
- package/dist/commands/pr/list.js +37 -3
- package/dist/commands/pr/list.js.map +1 -1
- package/dist/commands/pr/merge.js +1 -1
- package/dist/commands/pr/merge.js.map +1 -1
- package/dist/commands/pr/status.js +33 -1
- package/dist/commands/pr/status.js.map +1 -1
- package/dist/commands/self-update.d.ts +7 -14
- package/dist/commands/self-update.js +11 -179
- package/dist/commands/self-update.js.map +1 -1
- package/dist/commands/{workflow/switch.d.ts → ticket/cancel.d.ts} +4 -2
- package/dist/commands/ticket/cancel.js +204 -0
- package/dist/commands/ticket/cancel.js.map +1 -0
- package/dist/commands/ticket/link/index.js +0 -12
- package/dist/commands/ticket/link/index.js.map +1 -1
- package/dist/commands/ticket/move.d.ts +1 -0
- package/dist/commands/ticket/move.js +10 -3
- package/dist/commands/ticket/move.js.map +1 -1
- package/dist/commands/update.d.ts +18 -0
- package/dist/commands/update.js +184 -0
- package/dist/commands/update.js.map +1 -0
- package/dist/lib/database/drizzle-schema.d.ts +301 -0
- package/dist/lib/database/drizzle-schema.js +22 -0
- package/dist/lib/database/drizzle-schema.js.map +1 -1
- package/dist/lib/database/index.d.ts +3 -0
- package/dist/lib/database/index.js +556 -479
- package/dist/lib/database/index.js.map +1 -1
- package/dist/lib/database/migrations/0006_drop_theme_names_used.d.ts +8 -0
- package/dist/lib/database/migrations/0006_drop_theme_names_used.js +30 -0
- package/dist/lib/database/migrations/0006_drop_theme_names_used.js.map +1 -0
- package/dist/lib/database/migrations/0007_add_worktree_columns.d.ts +8 -0
- package/dist/lib/database/migrations/0007_add_worktree_columns.js +24 -0
- package/dist/lib/database/migrations/0007_add_worktree_columns.js.map +1 -0
- package/dist/lib/database/migrations/0008_add_agent_mount_mode.d.ts +8 -0
- package/dist/lib/database/migrations/0008_add_agent_mount_mode.js +18 -0
- package/dist/lib/database/migrations/0008_add_agent_mount_mode.js.map +1 -0
- package/dist/lib/database/migrations/0009_create_media_items.d.ts +7 -0
- package/dist/lib/database/migrations/0009_create_media_items.js +29 -0
- package/dist/lib/database/migrations/0009_create_media_items.js.map +1 -0
- package/dist/lib/database/migrations/0010_add_ticket_position.d.ts +9 -0
- package/dist/lib/database/migrations/0010_add_ticket_position.js +44 -0
- package/dist/lib/database/migrations/0010_add_ticket_position.js.map +1 -0
- package/dist/lib/database/migrations/index.js +10 -0
- package/dist/lib/database/migrations/index.js.map +1 -1
- package/dist/lib/events/events.d.ts +2 -1
- package/dist/lib/pmo/storage/dependencies.js +45 -21
- package/dist/lib/pmo/storage/dependencies.js.map +1 -1
- package/dist/lib/pmo/storage/statuses.d.ts +2 -0
- package/dist/lib/pmo/storage/statuses.js +209 -150
- package/dist/lib/pmo/storage/statuses.js.map +1 -1
- package/dist/lib/pr/index.d.ts +14 -0
- package/dist/lib/pr/index.js +48 -0
- package/dist/lib/pr/index.js.map +1 -1
- package/dist/lib/telemetry/analytics.d.ts +5 -5
- package/dist/lib/telemetry/analytics.js +87 -21
- package/dist/lib/telemetry/analytics.js.map +1 -1
- package/dist/lib/update-check.js +1 -1
- package/dist/lib/update-check.js.map +1 -1
- package/dist/lib/update-prompt.js +1 -1
- package/dist/lib/work-lifecycle/events.d.ts +11 -0
- package/oclif.manifest.json +4823 -11486
- package/package.json +2 -1
- package/dist/commands/agents/index.d.ts +0 -11
- package/dist/commands/agents/index.js +0 -105
- package/dist/commands/agents/index.js.map +0 -1
- package/dist/commands/agents/themes/add-names.d.ts +0 -1
- package/dist/commands/agents/themes/add-names.js +0 -2
- package/dist/commands/agents/themes/add-names.js.map +0 -1
- package/dist/commands/agents/themes/create.d.ts +0 -1
- package/dist/commands/agents/themes/create.js +0 -2
- package/dist/commands/agents/themes/create.js.map +0 -1
- package/dist/commands/agents/themes/list.d.ts +0 -1
- package/dist/commands/agents/themes/list.js +0 -2
- package/dist/commands/agents/themes/list.js.map +0 -1
- package/dist/commands/category/create.d.ts +0 -19
- package/dist/commands/category/create.js +0 -130
- package/dist/commands/category/create.js.map +0 -1
- package/dist/commands/category/delete.d.ts +0 -18
- package/dist/commands/category/delete.js +0 -124
- package/dist/commands/category/delete.js.map +0 -1
- package/dist/commands/category/index.d.ts +0 -16
- package/dist/commands/category/index.js +0 -88
- package/dist/commands/category/index.js.map +0 -1
- package/dist/commands/category/list.d.ts +0 -19
- package/dist/commands/category/list.js +0 -111
- package/dist/commands/category/list.js.map +0 -1
- package/dist/commands/category/rename.d.ts +0 -19
- package/dist/commands/category/rename.js +0 -160
- package/dist/commands/category/rename.js.map +0 -1
- package/dist/commands/epic/activate.js +0 -107
- package/dist/commands/epic/activate.js.map +0 -1
- package/dist/commands/epic/archive.d.ts +0 -15
- package/dist/commands/epic/archive.js +0 -118
- package/dist/commands/epic/archive.js.map +0 -1
- package/dist/commands/epic/create.d.ts +0 -17
- package/dist/commands/epic/create.js +0 -179
- package/dist/commands/epic/create.js.map +0 -1
- package/dist/commands/epic/delete.d.ts +0 -15
- package/dist/commands/epic/delete.js +0 -129
- package/dist/commands/epic/delete.js.map +0 -1
- package/dist/commands/epic/index.d.ts +0 -14
- package/dist/commands/epic/index.js +0 -88
- package/dist/commands/epic/index.js.map +0 -1
- package/dist/commands/epic/list.d.ts +0 -13
- package/dist/commands/epic/list.js +0 -103
- package/dist/commands/epic/list.js.map +0 -1
- package/dist/commands/epic/move.d.ts +0 -16
- package/dist/commands/epic/move.js +0 -162
- package/dist/commands/epic/move.js.map +0 -1
- package/dist/commands/epic/progress.d.ts +0 -17
- package/dist/commands/epic/progress.js +0 -170
- package/dist/commands/epic/progress.js.map +0 -1
- package/dist/commands/epic/project.d.ts +0 -16
- package/dist/commands/epic/project.js +0 -199
- package/dist/commands/epic/project.js.map +0 -1
- package/dist/commands/epic/reorder.d.ts +0 -22
- package/dist/commands/epic/reorder.js +0 -152
- package/dist/commands/epic/reorder.js.map +0 -1
- package/dist/commands/epic/show.d.ts +0 -13
- package/dist/commands/epic/show.js +0 -17
- package/dist/commands/epic/show.js.map +0 -1
- package/dist/commands/epic/spec.d.ts +0 -17
- package/dist/commands/epic/spec.js +0 -189
- package/dist/commands/epic/spec.js.map +0 -1
- package/dist/commands/epic/ticket.d.ts +0 -21
- package/dist/commands/epic/ticket.js +0 -300
- package/dist/commands/epic/ticket.js.map +0 -1
- package/dist/commands/epic/view.d.ts +0 -14
- package/dist/commands/epic/view.js +0 -141
- package/dist/commands/epic/view.js.map +0 -1
- package/dist/commands/execution/kill.d.ts +0 -12
- package/dist/commands/execution/kill.js +0 -18
- package/dist/commands/execution/kill.js.map +0 -1
- package/dist/commands/label/create.d.ts +0 -20
- package/dist/commands/label/create.js +0 -58
- package/dist/commands/label/create.js.map +0 -1
- package/dist/commands/label/delete.d.ts +0 -17
- package/dist/commands/label/delete.js +0 -33
- package/dist/commands/label/delete.js.map +0 -1
- package/dist/commands/label/group/create.d.ts +0 -20
- package/dist/commands/label/group/create.js +0 -56
- package/dist/commands/label/group/create.js.map +0 -1
- package/dist/commands/label/group/list.d.ts +0 -14
- package/dist/commands/label/group/list.js +0 -53
- package/dist/commands/label/group/list.js.map +0 -1
- package/dist/commands/label/index.d.ts +0 -15
- package/dist/commands/label/index.js +0 -59
- package/dist/commands/label/index.js.map +0 -1
- package/dist/commands/label/list.d.ts +0 -16
- package/dist/commands/label/list.js +0 -84
- package/dist/commands/label/list.js.map +0 -1
- package/dist/commands/link/create.d.ts +0 -16
- package/dist/commands/link/create.js +0 -136
- package/dist/commands/link/create.js.map +0 -1
- package/dist/commands/link/index.d.ts +0 -14
- package/dist/commands/link/index.js +0 -82
- package/dist/commands/link/index.js.map +0 -1
- package/dist/commands/link/list.d.ts +0 -18
- package/dist/commands/link/list.js +0 -178
- package/dist/commands/link/list.js.map +0 -1
- package/dist/commands/link/remove.d.ts +0 -16
- package/dist/commands/link/remove.js +0 -115
- package/dist/commands/link/remove.js.map +0 -1
- package/dist/commands/media/add.d.ts +0 -19
- package/dist/commands/media/add.js +0 -98
- package/dist/commands/media/add.js.map +0 -1
- package/dist/commands/media/index.d.ts +0 -14
- package/dist/commands/media/index.js +0 -93
- package/dist/commands/media/index.js.map +0 -1
- package/dist/commands/media/list.d.ts +0 -15
- package/dist/commands/media/list.js +0 -94
- package/dist/commands/media/list.js.map +0 -1
- package/dist/commands/media/preprocess.d.ts +0 -19
- package/dist/commands/media/preprocess.js +0 -91
- package/dist/commands/media/preprocess.js.map +0 -1
- package/dist/commands/media/remove.d.ts +0 -18
- package/dist/commands/media/remove.js +0 -105
- package/dist/commands/media/remove.js.map +0 -1
- package/dist/commands/media/show.d.ts +0 -17
- package/dist/commands/media/show.js +0 -122
- package/dist/commands/media/show.js.map +0 -1
- package/dist/commands/phase/create.d.ts +0 -22
- package/dist/commands/phase/create.js +0 -165
- package/dist/commands/phase/create.js.map +0 -1
- package/dist/commands/phase/delete.d.ts +0 -18
- package/dist/commands/phase/delete.js +0 -75
- package/dist/commands/phase/delete.js.map +0 -1
- package/dist/commands/phase/list.d.ts +0 -13
- package/dist/commands/phase/list.js +0 -75
- package/dist/commands/phase/list.js.map +0 -1
- package/dist/commands/phase/move.d.ts +0 -18
- package/dist/commands/phase/move.js +0 -124
- package/dist/commands/phase/move.js.map +0 -1
- package/dist/commands/phase/template/apply.d.ts +0 -26
- package/dist/commands/phase/template/apply.js +0 -15
- package/dist/commands/phase/template/apply.js.map +0 -1
- package/dist/commands/phase/template/create.d.ts +0 -23
- package/dist/commands/phase/template/create.js +0 -15
- package/dist/commands/phase/template/create.js.map +0 -1
- package/dist/commands/phase/template/delete.d.ts +0 -18
- package/dist/commands/phase/template/delete.js +0 -63
- package/dist/commands/phase/template/delete.js.map +0 -1
- package/dist/commands/phase/template/list.d.ts +0 -17
- package/dist/commands/phase/template/list.js +0 -90
- package/dist/commands/phase/template/list.js.map +0 -1
- package/dist/commands/phase/template/update.d.ts +0 -1
- package/dist/commands/phase/template/update.js +0 -2
- package/dist/commands/phase/template/update.js.map +0 -1
- package/dist/commands/phase/update.d.ts +0 -23
- package/dist/commands/phase/update.js +0 -209
- package/dist/commands/phase/update.js.map +0 -1
- package/dist/commands/priority/add.d.ts +0 -16
- package/dist/commands/priority/add.js +0 -71
- package/dist/commands/priority/add.js.map +0 -1
- package/dist/commands/priority/list.d.ts +0 -11
- package/dist/commands/priority/list.js +0 -35
- package/dist/commands/priority/list.js.map +0 -1
- package/dist/commands/priority/remove.d.ts +0 -14
- package/dist/commands/priority/remove.js +0 -55
- package/dist/commands/priority/remove.js.map +0 -1
- package/dist/commands/priority/set.d.ts +0 -15
- package/dist/commands/priority/set.js +0 -61
- package/dist/commands/priority/set.js.map +0 -1
- package/dist/commands/roadmap/add-project.d.ts +0 -19
- package/dist/commands/roadmap/add-project.js +0 -119
- package/dist/commands/roadmap/add-project.js.map +0 -1
- package/dist/commands/roadmap/create.d.ts +0 -22
- package/dist/commands/roadmap/create.js +0 -168
- package/dist/commands/roadmap/create.js.map +0 -1
- package/dist/commands/roadmap/delete.d.ts +0 -18
- package/dist/commands/roadmap/delete.js +0 -96
- package/dist/commands/roadmap/delete.js.map +0 -1
- package/dist/commands/roadmap/generate.d.ts +0 -24
- package/dist/commands/roadmap/generate.js +0 -201
- package/dist/commands/roadmap/generate.js.map +0 -1
- package/dist/commands/roadmap/index.d.ts +0 -14
- package/dist/commands/roadmap/index.js +0 -57
- package/dist/commands/roadmap/index.js.map +0 -1
- package/dist/commands/roadmap/list.d.ts +0 -14
- package/dist/commands/roadmap/list.js +0 -58
- package/dist/commands/roadmap/list.js.map +0 -1
- package/dist/commands/roadmap/remove-project.d.ts +0 -19
- package/dist/commands/roadmap/remove-project.js +0 -124
- package/dist/commands/roadmap/remove-project.js.map +0 -1
- package/dist/commands/roadmap/reorder.d.ts +0 -18
- package/dist/commands/roadmap/reorder.js +0 -147
- package/dist/commands/roadmap/reorder.js.map +0 -1
- package/dist/commands/roadmap/update.d.ts +0 -20
- package/dist/commands/roadmap/update.js +0 -134
- package/dist/commands/roadmap/update.js.map +0 -1
- package/dist/commands/roadmap/view.d.ts +0 -17
- package/dist/commands/roadmap/view.js +0 -93
- package/dist/commands/roadmap/view.js.map +0 -1
- package/dist/commands/staff/add.d.ts +0 -17
- package/dist/commands/staff/add.js +0 -275
- package/dist/commands/staff/add.js.map +0 -1
- package/dist/commands/staff/index.d.ts +0 -15
- package/dist/commands/staff/index.js +0 -88
- package/dist/commands/staff/index.js.map +0 -1
- package/dist/commands/staff/list.d.ts +0 -10
- package/dist/commands/staff/list.js +0 -104
- package/dist/commands/staff/list.js.map +0 -1
- package/dist/commands/staff/remove.d.ts +0 -18
- package/dist/commands/staff/remove.js +0 -144
- package/dist/commands/staff/remove.js.map +0 -1
- package/dist/commands/status/category.d.ts +0 -15
- package/dist/commands/status/category.js +0 -64
- package/dist/commands/status/category.js.map +0 -1
- package/dist/commands/status/create.d.ts +0 -21
- package/dist/commands/status/create.js +0 -161
- package/dist/commands/status/create.js.map +0 -1
- package/dist/commands/status/delete.d.ts +0 -14
- package/dist/commands/status/delete.js +0 -86
- package/dist/commands/status/delete.js.map +0 -1
- package/dist/commands/status/index.d.ts +0 -15
- package/dist/commands/status/index.js +0 -98
- package/dist/commands/status/index.js.map +0 -1
- package/dist/commands/status/list.d.ts +0 -13
- package/dist/commands/status/list.js +0 -97
- package/dist/commands/status/list.js.map +0 -1
- package/dist/commands/status/move.d.ts +0 -15
- package/dist/commands/status/move.js +0 -126
- package/dist/commands/status/move.js.map +0 -1
- package/dist/commands/status/update.d.ts +0 -20
- package/dist/commands/status/update.js +0 -214
- package/dist/commands/status/update.js.map +0 -1
- package/dist/commands/template/apply.d.ts +0 -28
- package/dist/commands/template/apply.js +0 -258
- package/dist/commands/template/apply.js.map +0 -1
- package/dist/commands/template/create.d.ts +0 -28
- package/dist/commands/template/create.js +0 -238
- package/dist/commands/template/create.js.map +0 -1
- package/dist/commands/template/delete.d.ts +0 -16
- package/dist/commands/template/delete.js +0 -141
- package/dist/commands/template/delete.js.map +0 -1
- package/dist/commands/template/index.d.ts +0 -11
- package/dist/commands/template/index.js +0 -65
- package/dist/commands/template/index.js.map +0 -1
- package/dist/commands/template/list.d.ts +0 -19
- package/dist/commands/template/list.js +0 -155
- package/dist/commands/template/list.js.map +0 -1
- package/dist/commands/template/save.d.ts +0 -17
- package/dist/commands/template/save.js +0 -101
- package/dist/commands/template/save.js.map +0 -1
- package/dist/commands/template/update.d.ts +0 -19
- package/dist/commands/template/update.js +0 -94
- package/dist/commands/template/update.js.map +0 -1
- package/dist/commands/ticket/template/apply.d.ts +0 -26
- package/dist/commands/ticket/template/apply.js +0 -15
- package/dist/commands/ticket/template/apply.js.map +0 -1
- package/dist/commands/ticket/template/delete.d.ts +0 -18
- package/dist/commands/ticket/template/delete.js +0 -63
- package/dist/commands/ticket/template/delete.js.map +0 -1
- package/dist/commands/ticket/template/list.d.ts +0 -17
- package/dist/commands/ticket/template/list.js +0 -79
- package/dist/commands/ticket/template/list.js.map +0 -1
- package/dist/commands/ticket/template/save.d.ts +0 -17
- package/dist/commands/ticket/template/save.js +0 -99
- package/dist/commands/ticket/template/save.js.map +0 -1
- package/dist/commands/work/spawn-all.d.ts +0 -19
- package/dist/commands/work/spawn-all.js +0 -64
- package/dist/commands/work/spawn-all.js.map +0 -1
- package/dist/commands/workflow/create.d.ts +0 -19
- package/dist/commands/workflow/create.js +0 -118
- package/dist/commands/workflow/create.js.map +0 -1
- package/dist/commands/workflow/delete.d.ts +0 -18
- package/dist/commands/workflow/delete.js +0 -108
- package/dist/commands/workflow/delete.js.map +0 -1
- package/dist/commands/workflow/index.d.ts +0 -16
- package/dist/commands/workflow/index.js +0 -73
- package/dist/commands/workflow/index.js.map +0 -1
- package/dist/commands/workflow/list.d.ts +0 -16
- package/dist/commands/workflow/list.js +0 -73
- package/dist/commands/workflow/list.js.map +0 -1
- package/dist/commands/workflow/setup.d.ts +0 -24
- package/dist/commands/workflow/setup.js +0 -351
- package/dist/commands/workflow/setup.js.map +0 -1
- package/dist/commands/workflow/show.d.ts +0 -13
- package/dist/commands/workflow/show.js +0 -17
- package/dist/commands/workflow/show.js.map +0 -1
- package/dist/commands/workflow/switch.js +0 -144
- package/dist/commands/workflow/switch.js.map +0 -1
- package/dist/commands/workflow/view.d.ts +0 -17
- package/dist/commands/workflow/view.js +0 -106
- package/dist/commands/workflow/view.js.map +0 -1
- package/dist/commands/workflow-rule/create.d.ts +0 -19
- package/dist/commands/workflow-rule/create.js +0 -141
- package/dist/commands/workflow-rule/create.js.map +0 -1
- package/dist/commands/workflow-rule/delete.d.ts +0 -18
- package/dist/commands/workflow-rule/delete.js +0 -72
- package/dist/commands/workflow-rule/delete.js.map +0 -1
- package/dist/commands/workflow-rule/index.d.ts +0 -16
- package/dist/commands/workflow-rule/index.js +0 -83
- package/dist/commands/workflow-rule/index.js.map +0 -1
- package/dist/commands/workflow-rule/list.d.ts +0 -17
- package/dist/commands/workflow-rule/list.js +0 -72
- package/dist/commands/workflow-rule/list.js.map +0 -1
- package/dist/commands/workflow-rule/update.d.ts +0 -22
- package/dist/commands/workflow-rule/update.js +0 -84
- package/dist/commands/workflow-rule/update.js.map +0 -1
|
@@ -1,33 +1,81 @@
|
|
|
1
1
|
import Database from 'better-sqlite3';
|
|
2
|
+
import { eq, and, or, isNull, sql, asc, desc, like } from 'drizzle-orm';
|
|
2
3
|
import * as fs from 'node:fs';
|
|
3
4
|
import * as path from 'node:path';
|
|
4
5
|
import { getThemePersistentDir, isEphemeralAgentName } from '../themes.js';
|
|
5
6
|
import { throwIfNativeBindingError } from './native-validation.js';
|
|
6
7
|
import { runDrizzleMigrations } from './migrator.js';
|
|
7
8
|
import { ALL_MIGRATIONS } from './migrations/index.js';
|
|
9
|
+
import { createDrizzleConnection } from './drizzle.js';
|
|
10
|
+
import { workspace as workspaceTable, repositories as repositoriesTable, agents as agentsTable, agentThemes as agentThemesTable, agentThemeNames as agentThemeNamesTable, agentWorktrees as agentWorktreesTable, workspaceSettings as workspaceSettingsTable, mediaItems as mediaItemsTable, } from './drizzle-schema.js';
|
|
8
11
|
// Re-export CREATE_TABLES_SQL from its canonical location
|
|
9
12
|
export { CREATE_TABLES_SQL } from './workspace-schema.js';
|
|
10
|
-
//
|
|
13
|
+
// =============================================================================
|
|
14
|
+
// Internal helpers
|
|
15
|
+
// =============================================================================
|
|
16
|
+
/**
|
|
17
|
+
* Open the workspace database, wrap it with Drizzle, run a function,
|
|
18
|
+
* and close the connection. Handles the open/close lifecycle.
|
|
19
|
+
*/
|
|
20
|
+
function withDrizzle(workspacePath, fn) {
|
|
21
|
+
const sqliteDb = openWorkspaceDatabase(workspacePath);
|
|
22
|
+
const ddb = createDrizzleConnection(sqliteDb);
|
|
23
|
+
try {
|
|
24
|
+
return fn(ddb, sqliteDb);
|
|
25
|
+
}
|
|
26
|
+
finally {
|
|
27
|
+
sqliteDb.close();
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Map a Drizzle agent row to the Agent interface.
|
|
32
|
+
* Handles default values for backwards compatibility with old databases.
|
|
33
|
+
*/
|
|
34
|
+
function toAgent(row) {
|
|
35
|
+
return {
|
|
36
|
+
name: row.name,
|
|
37
|
+
type: (row.type || 'persistent'),
|
|
38
|
+
status: (row.status || 'active'),
|
|
39
|
+
base_name: row.baseName,
|
|
40
|
+
theme_id: row.themeId,
|
|
41
|
+
worktree_path: row.worktreePath,
|
|
42
|
+
mount_mode: (row.mountMode || 'worktree'),
|
|
43
|
+
created_at: row.createdAt,
|
|
44
|
+
cleaned_at: row.cleanedAt,
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Map a Drizzle theme row to the AgentTheme interface.
|
|
49
|
+
*/
|
|
50
|
+
function toAgentTheme(row) {
|
|
51
|
+
return {
|
|
52
|
+
id: row.id,
|
|
53
|
+
name: row.name,
|
|
54
|
+
display_name: row.displayName,
|
|
55
|
+
description: row.description,
|
|
56
|
+
builtin: Boolean(row.builtin),
|
|
57
|
+
created_at: row.createdAt,
|
|
58
|
+
};
|
|
59
|
+
}
|
|
11
60
|
/**
|
|
12
|
-
* Ensure ephemeral agents are correctly typed based on their worktree path or naming pattern
|
|
61
|
+
* Ensure ephemeral agents are correctly typed based on their worktree path or naming pattern.
|
|
62
|
+
* Uses raw SQL because it relies on SQLite-specific GLOB operator and sqlite_master introspection.
|
|
13
63
|
*/
|
|
14
64
|
function ensureEphemeralAgentTypes(db) {
|
|
15
|
-
// Check if agents table exists
|
|
65
|
+
// Check if agents table exists (sqlite_master introspection — no Drizzle equivalent)
|
|
16
66
|
const tableExists = db.prepare("SELECT name FROM sqlite_master WHERE type='table' AND name='agents'").get();
|
|
17
67
|
if (!tableExists) {
|
|
18
68
|
return;
|
|
19
69
|
}
|
|
20
70
|
// Agents in temp directory should be ephemeral
|
|
21
71
|
db.exec("UPDATE agents SET type = 'ephemeral' WHERE worktree_path LIKE 'agents/temp/%' AND type != 'ephemeral'");
|
|
22
|
-
// Detect ephemeral agents by naming pattern
|
|
23
|
-
// Staff agents are single names like 'lecun', 'musk', 'gates'
|
|
72
|
+
// Detect ephemeral agents by naming pattern using SQLite GLOB (no Drizzle equivalent)
|
|
24
73
|
db.exec(`
|
|
25
74
|
UPDATE agents SET type = 'ephemeral'
|
|
26
75
|
WHERE type != 'ephemeral'
|
|
27
76
|
AND name GLOB '*-*-[0-9]*'
|
|
28
77
|
`);
|
|
29
78
|
// Also detect numberless ephemeral names (e.g., bold-bezos) using isEphemeralAgentName()
|
|
30
|
-
// This catches agents that match the adjective-name pattern but don't have a number suffix
|
|
31
79
|
const potentialEphemeral = db.prepare(`
|
|
32
80
|
SELECT name FROM agents
|
|
33
81
|
WHERE type != 'ephemeral'
|
|
@@ -73,142 +121,8 @@ export function openWorkspaceDatabase(workspacePath) {
|
|
|
73
121
|
db.pragma('busy_timeout = 5000'); // Wait up to 5 seconds if database is locked
|
|
74
122
|
// Run Drizzle migrations (creates tracking table, applies pending migrations)
|
|
75
123
|
runDrizzleMigrations(db, ALL_MIGRATIONS);
|
|
76
|
-
// Ensure ephemeral agents are correctly typed
|
|
124
|
+
// Ensure ephemeral agents are correctly typed (raw SQL — uses SQLite GLOB)
|
|
77
125
|
ensureEphemeralAgentTypes(db);
|
|
78
|
-
// Ensure theme tables exist
|
|
79
|
-
db.exec(`
|
|
80
|
-
CREATE TABLE IF NOT EXISTS agent_themes (
|
|
81
|
-
id TEXT PRIMARY KEY,
|
|
82
|
-
name TEXT NOT NULL UNIQUE,
|
|
83
|
-
display_name TEXT NOT NULL,
|
|
84
|
-
description TEXT,
|
|
85
|
-
builtin BOOLEAN DEFAULT FALSE,
|
|
86
|
-
created_at TEXT NOT NULL
|
|
87
|
-
);
|
|
88
|
-
CREATE TABLE IF NOT EXISTS agent_theme_names (
|
|
89
|
-
theme_id TEXT NOT NULL,
|
|
90
|
-
name TEXT NOT NULL,
|
|
91
|
-
PRIMARY KEY (theme_id, name),
|
|
92
|
-
FOREIGN KEY (theme_id) REFERENCES agent_themes(id) ON DELETE CASCADE
|
|
93
|
-
);
|
|
94
|
-
CREATE INDEX IF NOT EXISTS idx_theme_names_theme ON agent_theme_names(theme_id);
|
|
95
|
-
CREATE INDEX IF NOT EXISTS idx_agents_theme ON agents(theme_id);
|
|
96
|
-
`);
|
|
97
|
-
// Migration: drop 'used' column if it exists (no longer needed)
|
|
98
|
-
try {
|
|
99
|
-
const tableInfo = db.prepare("PRAGMA table_info(agent_theme_names)").all();
|
|
100
|
-
if (tableInfo.some(col => col.name === 'used')) {
|
|
101
|
-
// SQLite doesn't support DROP COLUMN directly, so recreate the table
|
|
102
|
-
db.exec(`
|
|
103
|
-
CREATE TABLE IF NOT EXISTS agent_theme_names_new (
|
|
104
|
-
theme_id TEXT NOT NULL,
|
|
105
|
-
name TEXT NOT NULL,
|
|
106
|
-
PRIMARY KEY (theme_id, name),
|
|
107
|
-
FOREIGN KEY (theme_id) REFERENCES agent_themes(id) ON DELETE CASCADE
|
|
108
|
-
);
|
|
109
|
-
INSERT OR IGNORE INTO agent_theme_names_new (theme_id, name)
|
|
110
|
-
SELECT theme_id, name FROM agent_theme_names;
|
|
111
|
-
DROP TABLE agent_theme_names;
|
|
112
|
-
ALTER TABLE agent_theme_names_new RENAME TO agent_theme_names;
|
|
113
|
-
CREATE INDEX IF NOT EXISTS idx_theme_names_theme ON agent_theme_names(theme_id);
|
|
114
|
-
`);
|
|
115
|
-
}
|
|
116
|
-
}
|
|
117
|
-
catch {
|
|
118
|
-
// Ignore migration errors - table might not exist yet
|
|
119
|
-
}
|
|
120
|
-
// Migration: add missing columns to agent_worktrees table (TKT-1014)
|
|
121
|
-
try {
|
|
122
|
-
const worktreesTableExists = db.prepare("SELECT name FROM sqlite_master WHERE type='table' AND name='agent_worktrees'").get();
|
|
123
|
-
if (worktreesTableExists) {
|
|
124
|
-
const worktreeTableInfo = db.prepare("PRAGMA table_info(agent_worktrees)").all();
|
|
125
|
-
if (!worktreeTableInfo.some(col => col.name === 'commits_ahead')) {
|
|
126
|
-
db.exec("ALTER TABLE agent_worktrees ADD COLUMN last_commit_hash TEXT");
|
|
127
|
-
db.exec("ALTER TABLE agent_worktrees ADD COLUMN commits_ahead INTEGER NOT NULL DEFAULT 0");
|
|
128
|
-
db.exec("ALTER TABLE agent_worktrees ADD COLUMN is_clean INTEGER NOT NULL DEFAULT 1");
|
|
129
|
-
db.exec("ALTER TABLE agent_worktrees ADD COLUMN last_checked TEXT");
|
|
130
|
-
}
|
|
131
|
-
}
|
|
132
|
-
}
|
|
133
|
-
catch {
|
|
134
|
-
// Ignore migration errors - table might not exist yet or columns already exist
|
|
135
|
-
}
|
|
136
|
-
// Migration: add mount_mode column to agents table (TKT-686)
|
|
137
|
-
try {
|
|
138
|
-
const agentsTableInfo = db.prepare("PRAGMA table_info(agents)").all();
|
|
139
|
-
if (!agentsTableInfo.some(col => col.name === 'mount_mode')) {
|
|
140
|
-
// Add mount_mode column with default 'worktree' for existing agents (backward compat)
|
|
141
|
-
db.exec("ALTER TABLE agents ADD COLUMN mount_mode TEXT NOT NULL DEFAULT 'worktree' CHECK (mount_mode IN ('worktree', 'clone'))");
|
|
142
|
-
}
|
|
143
|
-
}
|
|
144
|
-
catch {
|
|
145
|
-
// Ignore migration errors - table might not exist yet or column already exists
|
|
146
|
-
}
|
|
147
|
-
// Migration: create media_items table (TKT-077)
|
|
148
|
-
try {
|
|
149
|
-
const mediaTableExists = db.prepare("SELECT name FROM sqlite_master WHERE type='table' AND name='media_items'").get();
|
|
150
|
-
if (!mediaTableExists) {
|
|
151
|
-
db.exec(`
|
|
152
|
-
CREATE TABLE IF NOT EXISTS media_items (
|
|
153
|
-
name TEXT PRIMARY KEY,
|
|
154
|
-
path TEXT NOT NULL,
|
|
155
|
-
source_path TEXT,
|
|
156
|
-
media_type TEXT NOT NULL DEFAULT 'video' CHECK (media_type IN ('video', 'audio')),
|
|
157
|
-
duration_seconds REAL,
|
|
158
|
-
resolution TEXT,
|
|
159
|
-
frame_count INTEGER NOT NULL DEFAULT 0,
|
|
160
|
-
has_transcript INTEGER NOT NULL DEFAULT 0,
|
|
161
|
-
frame_interval INTEGER NOT NULL DEFAULT 30,
|
|
162
|
-
status TEXT NOT NULL DEFAULT 'pending' CHECK (status IN ('pending', 'processing', 'ready', 'error')),
|
|
163
|
-
error_message TEXT,
|
|
164
|
-
added_at TEXT NOT NULL,
|
|
165
|
-
processed_at TEXT
|
|
166
|
-
)
|
|
167
|
-
`);
|
|
168
|
-
}
|
|
169
|
-
}
|
|
170
|
-
catch {
|
|
171
|
-
// Ignore migration errors
|
|
172
|
-
}
|
|
173
|
-
// Migration: add position column to pmo_tickets table (TKT-965)
|
|
174
|
-
try {
|
|
175
|
-
const ticketsTableExists = db.prepare("SELECT name FROM sqlite_master WHERE type='table' AND name='pmo_tickets'").get();
|
|
176
|
-
if (ticketsTableExists) {
|
|
177
|
-
const ticketsTableInfo = db.prepare("PRAGMA table_info(pmo_tickets)").all();
|
|
178
|
-
if (!ticketsTableInfo.some(col => col.name === 'position')) {
|
|
179
|
-
db.exec("ALTER TABLE pmo_tickets ADD COLUMN position INTEGER NOT NULL DEFAULT 0");
|
|
180
|
-
db.exec("CREATE INDEX IF NOT EXISTS idx_pmo_tickets_status_position ON pmo_tickets(status_id, position)");
|
|
181
|
-
// Backfill existing tickets with gapped positions (1000, 2000, ...) per status,
|
|
182
|
-
// ordered by priority then created_at
|
|
183
|
-
const statuses = db.prepare("SELECT DISTINCT status_id FROM pmo_tickets WHERE status_id IS NOT NULL").all();
|
|
184
|
-
const getTicketsForStatus = db.prepare(`
|
|
185
|
-
SELECT id FROM pmo_tickets WHERE status_id = ?
|
|
186
|
-
ORDER BY
|
|
187
|
-
CASE priority
|
|
188
|
-
WHEN 'P0' THEN 0
|
|
189
|
-
WHEN 'P1' THEN 1
|
|
190
|
-
WHEN 'P2' THEN 2
|
|
191
|
-
WHEN 'P3' THEN 3
|
|
192
|
-
ELSE 4
|
|
193
|
-
END,
|
|
194
|
-
created_at ASC
|
|
195
|
-
`);
|
|
196
|
-
const updatePosition = db.prepare("UPDATE pmo_tickets SET position = ? WHERE id = ?");
|
|
197
|
-
const backfill = db.transaction(() => {
|
|
198
|
-
for (const { status_id } of statuses) {
|
|
199
|
-
const tickets = getTicketsForStatus.all(status_id);
|
|
200
|
-
tickets.forEach((ticket, idx) => {
|
|
201
|
-
updatePosition.run((idx + 1) * 1000, ticket.id);
|
|
202
|
-
});
|
|
203
|
-
}
|
|
204
|
-
});
|
|
205
|
-
backfill();
|
|
206
|
-
}
|
|
207
|
-
}
|
|
208
|
-
}
|
|
209
|
-
catch {
|
|
210
|
-
// Ignore migration errors - table might not exist yet
|
|
211
|
-
}
|
|
212
126
|
return db;
|
|
213
127
|
}
|
|
214
128
|
/**
|
|
@@ -241,11 +155,15 @@ export function createWorkspaceDatabase(workspacePath, type, workspaceName, hasP
|
|
|
241
155
|
db.pragma('foreign_keys = ON');
|
|
242
156
|
// Run all migrations (baseline creates core workspace + PMO tables)
|
|
243
157
|
runDrizzleMigrations(db, ALL_MIGRATIONS);
|
|
244
|
-
// Insert workspace data
|
|
245
|
-
db
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
158
|
+
// Insert workspace data using Drizzle
|
|
159
|
+
const ddb = createDrizzleConnection(db);
|
|
160
|
+
ddb.insert(workspaceTable).values({
|
|
161
|
+
id: 1,
|
|
162
|
+
type,
|
|
163
|
+
workspaceName,
|
|
164
|
+
hasPmo: hasPMO,
|
|
165
|
+
createdAt: new Date().toISOString(),
|
|
166
|
+
}).run();
|
|
249
167
|
return db;
|
|
250
168
|
}
|
|
251
169
|
/**
|
|
@@ -253,10 +171,19 @@ export function createWorkspaceDatabase(workspacePath, type, workspaceName, hasP
|
|
|
253
171
|
*/
|
|
254
172
|
export function getWorkspaceConfig(workspacePath) {
|
|
255
173
|
try {
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
174
|
+
return withDrizzle(workspacePath, (ddb) => {
|
|
175
|
+
const row = ddb.select().from(workspaceTable).limit(1).get();
|
|
176
|
+
if (!row)
|
|
177
|
+
return null;
|
|
178
|
+
return {
|
|
179
|
+
id: row.id ?? 1,
|
|
180
|
+
type: row.type,
|
|
181
|
+
workspace_name: row.workspaceName,
|
|
182
|
+
has_pmo: Boolean(row.hasPmo),
|
|
183
|
+
active_theme_id: row.activeThemeId,
|
|
184
|
+
created_at: row.createdAt,
|
|
185
|
+
};
|
|
186
|
+
});
|
|
260
187
|
}
|
|
261
188
|
catch {
|
|
262
189
|
return null;
|
|
@@ -273,12 +200,12 @@ export function getActiveTheme(workspacePath) {
|
|
|
273
200
|
return getTheme(workspacePath, config.active_theme_id);
|
|
274
201
|
}
|
|
275
202
|
// Auto-detect from existing agents
|
|
276
|
-
const
|
|
277
|
-
if (
|
|
203
|
+
const agentList = getWorkspaceAgents(workspacePath);
|
|
204
|
+
if (agentList.length === 0) {
|
|
278
205
|
return null;
|
|
279
206
|
}
|
|
280
207
|
// Check if any agent has a theme_id set
|
|
281
|
-
const themedAgent =
|
|
208
|
+
const themedAgent = agentList.find(a => a.theme_id);
|
|
282
209
|
if (themedAgent?.theme_id) {
|
|
283
210
|
const theme = getTheme(workspacePath, themedAgent.theme_id);
|
|
284
211
|
if (theme) {
|
|
@@ -293,7 +220,7 @@ export function getActiveTheme(workspacePath) {
|
|
|
293
220
|
const themeNames = getThemeNames(workspacePath, theme.id);
|
|
294
221
|
const themeNameSet = new Set(themeNames.map(n => n.name.toLowerCase()));
|
|
295
222
|
// If any existing agent matches this theme's names
|
|
296
|
-
const matchingAgent =
|
|
223
|
+
const matchingAgent = agentList.find(a => themeNameSet.has(a.name.toLowerCase()));
|
|
297
224
|
if (matchingAgent) {
|
|
298
225
|
// Auto-set it for future use
|
|
299
226
|
setActiveTheme(workspacePath, theme.id);
|
|
@@ -306,82 +233,125 @@ export function getActiveTheme(workspacePath) {
|
|
|
306
233
|
* Set the active theme for a workspace
|
|
307
234
|
*/
|
|
308
235
|
export function setActiveTheme(workspacePath, themeId) {
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
236
|
+
withDrizzle(workspacePath, (ddb) => {
|
|
237
|
+
if (themeId) {
|
|
238
|
+
// Validate theme exists
|
|
239
|
+
const theme = ddb.select({ id: agentThemesTable.id })
|
|
240
|
+
.from(agentThemesTable)
|
|
241
|
+
.where(eq(agentThemesTable.id, themeId))
|
|
242
|
+
.get();
|
|
243
|
+
if (!theme) {
|
|
244
|
+
throw new Error(`Theme "${themeId}" not found`);
|
|
245
|
+
}
|
|
316
246
|
}
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
247
|
+
ddb.update(workspaceTable)
|
|
248
|
+
.set({ activeThemeId: themeId })
|
|
249
|
+
.where(eq(workspaceTable.id, 1))
|
|
250
|
+
.run();
|
|
251
|
+
});
|
|
320
252
|
}
|
|
321
253
|
/**
|
|
322
254
|
* Add repositories to database
|
|
323
255
|
*/
|
|
324
256
|
export function addRepositoriesToDatabase(workspacePath, repos) {
|
|
325
|
-
|
|
326
|
-
const insertRepo = db.prepare(`
|
|
327
|
-
INSERT OR REPLACE INTO repositories (name, path, type, source_url, action, added_at)
|
|
328
|
-
VALUES (?, ?, ?, ?, ?, ?)
|
|
329
|
-
`);
|
|
330
|
-
const transaction = db.transaction(() => {
|
|
257
|
+
withDrizzle(workspacePath, (ddb) => {
|
|
331
258
|
for (const repo of repos) {
|
|
332
|
-
|
|
259
|
+
ddb.insert(repositoriesTable)
|
|
260
|
+
.values({
|
|
261
|
+
name: repo.name,
|
|
262
|
+
path: repo.path,
|
|
263
|
+
type: 'main',
|
|
264
|
+
sourceUrl: repo.source_url || null,
|
|
265
|
+
action: repo.action || null,
|
|
266
|
+
addedAt: new Date().toISOString(),
|
|
267
|
+
})
|
|
268
|
+
.onConflictDoUpdate({
|
|
269
|
+
target: repositoriesTable.name,
|
|
270
|
+
set: {
|
|
271
|
+
path: repo.path,
|
|
272
|
+
type: 'main',
|
|
273
|
+
sourceUrl: repo.source_url || null,
|
|
274
|
+
action: repo.action || null,
|
|
275
|
+
addedAt: new Date().toISOString(),
|
|
276
|
+
},
|
|
277
|
+
})
|
|
278
|
+
.run();
|
|
333
279
|
}
|
|
334
280
|
});
|
|
335
|
-
transaction();
|
|
336
|
-
db.close();
|
|
337
281
|
}
|
|
338
282
|
/**
|
|
339
283
|
* Add agents to database (case-insensitive uniqueness)
|
|
340
284
|
*/
|
|
341
285
|
export function addAgentsToDatabase(workspacePath, agentNames, themeId, mountMode = 'worktree') {
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
:
|
|
379
|
-
|
|
286
|
+
withDrizzle(workspacePath, (ddb, sqliteDb) => {
|
|
287
|
+
// Get workspace config to determine paths
|
|
288
|
+
const wsRow = ddb.select().from(workspaceTable).get();
|
|
289
|
+
if (!wsRow)
|
|
290
|
+
throw new Error('No workspace config found');
|
|
291
|
+
// Get all repos for this workspace
|
|
292
|
+
const repos = ddb.select({ name: repositoriesTable.name }).from(repositoriesTable).all();
|
|
293
|
+
// Determine the effective theme ID (provided or active theme)
|
|
294
|
+
const effectiveThemeId = themeId || wsRow.activeThemeId || undefined;
|
|
295
|
+
const persistentDir = getThemePersistentDir(effectiveThemeId);
|
|
296
|
+
const transaction = sqliteDb.transaction(() => {
|
|
297
|
+
for (const agentName of agentNames) {
|
|
298
|
+
// Check for existing agents (case-insensitive) via Drizzle sql
|
|
299
|
+
const existing = ddb.select({ name: agentsTable.name })
|
|
300
|
+
.from(agentsTable)
|
|
301
|
+
.where(sql `LOWER(${agentsTable.name}) = LOWER(${agentName})`)
|
|
302
|
+
.get();
|
|
303
|
+
if (existing) {
|
|
304
|
+
continue; // Agent already exists with same name (different case)
|
|
305
|
+
}
|
|
306
|
+
const now = new Date().toISOString();
|
|
307
|
+
// Determine worktree path for the agent
|
|
308
|
+
const agentWorktreePath = wsRow.type === 'hq'
|
|
309
|
+
? `agents/${persistentDir}/${agentName}`
|
|
310
|
+
: agentName;
|
|
311
|
+
// Add agent (persistent type for manually added agents)
|
|
312
|
+
ddb.insert(agentsTable).values({
|
|
313
|
+
name: agentName,
|
|
314
|
+
type: 'persistent',
|
|
315
|
+
baseName: null,
|
|
316
|
+
themeId: effectiveThemeId || null,
|
|
317
|
+
worktreePath: agentWorktreePath,
|
|
318
|
+
mountMode,
|
|
319
|
+
createdAt: now,
|
|
320
|
+
}).onConflictDoUpdate({
|
|
321
|
+
target: agentsTable.name,
|
|
322
|
+
set: {
|
|
323
|
+
type: 'persistent',
|
|
324
|
+
baseName: null,
|
|
325
|
+
themeId: effectiveThemeId || null,
|
|
326
|
+
worktreePath: agentWorktreePath,
|
|
327
|
+
mountMode,
|
|
328
|
+
createdAt: now,
|
|
329
|
+
},
|
|
330
|
+
}).run();
|
|
331
|
+
// Add worktrees for all repos
|
|
332
|
+
for (const repo of repos) {
|
|
333
|
+
const worktreePath = wsRow.type === 'hq'
|
|
334
|
+
? `agents/${persistentDir}/${agentName}/${repo.name}`
|
|
335
|
+
: `${agentName}/${repo.name}`;
|
|
336
|
+
ddb.insert(agentWorktreesTable).values({
|
|
337
|
+
agentName,
|
|
338
|
+
repoName: repo.name,
|
|
339
|
+
worktreePath,
|
|
340
|
+
branch: `agent-${agentName}`,
|
|
341
|
+
createdAt: now,
|
|
342
|
+
}).onConflictDoUpdate({
|
|
343
|
+
target: [agentWorktreesTable.agentName, agentWorktreesTable.repoName],
|
|
344
|
+
set: {
|
|
345
|
+
worktreePath,
|
|
346
|
+
branch: `agent-${agentName}`,
|
|
347
|
+
createdAt: now,
|
|
348
|
+
},
|
|
349
|
+
}).run();
|
|
350
|
+
}
|
|
380
351
|
}
|
|
381
|
-
}
|
|
352
|
+
});
|
|
353
|
+
transaction();
|
|
382
354
|
});
|
|
383
|
-
transaction();
|
|
384
|
-
db.close();
|
|
385
355
|
}
|
|
386
356
|
/**
|
|
387
357
|
* Add an ephemeral agent to the database.
|
|
@@ -403,26 +373,27 @@ export function addEphemeralAgentToDatabase(workspacePath, agentName, baseName,
|
|
|
403
373
|
* simply gets null and can retry with a different name.
|
|
404
374
|
*/
|
|
405
375
|
export function tryAddEphemeralAgentToDatabase(workspacePath, agentName, baseName, themeId, mountMode = 'worktree') {
|
|
406
|
-
const
|
|
376
|
+
const sqliteDb = openWorkspaceDatabase(workspacePath);
|
|
377
|
+
const ddb = createDrizzleConnection(sqliteDb);
|
|
407
378
|
try {
|
|
408
379
|
const now = new Date().toISOString();
|
|
409
380
|
const worktreePath = `agents/temp/${agentName}`;
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
381
|
+
ddb.insert(agentsTable).values({
|
|
382
|
+
name: agentName,
|
|
383
|
+
type: 'ephemeral',
|
|
384
|
+
status: 'active',
|
|
385
|
+
baseName,
|
|
386
|
+
themeId: themeId || null,
|
|
387
|
+
worktreePath,
|
|
388
|
+
mountMode,
|
|
389
|
+
createdAt: now,
|
|
390
|
+
}).run();
|
|
391
|
+
const agent = ddb.select().from(agentsTable)
|
|
392
|
+
.where(eq(agentsTable.name, agentName))
|
|
393
|
+
.get();
|
|
394
|
+
if (!agent)
|
|
395
|
+
return null;
|
|
396
|
+
return toAgent(agent);
|
|
426
397
|
}
|
|
427
398
|
catch (err) {
|
|
428
399
|
const sqliteErr = err;
|
|
@@ -432,48 +403,50 @@ export function tryAddEphemeralAgentToDatabase(workspacePath, agentName, baseNam
|
|
|
432
403
|
throw err;
|
|
433
404
|
}
|
|
434
405
|
finally {
|
|
435
|
-
|
|
406
|
+
sqliteDb.close();
|
|
436
407
|
}
|
|
437
408
|
}
|
|
438
409
|
/**
|
|
439
410
|
* Get all ephemeral agent names from the database
|
|
440
411
|
*/
|
|
441
412
|
export function getEphemeralAgentNames(workspacePath) {
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
413
|
+
return withDrizzle(workspacePath, (ddb) => {
|
|
414
|
+
const rows = ddb.select({ name: agentsTable.name })
|
|
415
|
+
.from(agentsTable)
|
|
416
|
+
.where(eq(agentsTable.type, 'ephemeral'))
|
|
417
|
+
.all();
|
|
418
|
+
return new Set(rows.map(a => a.name.toLowerCase()));
|
|
419
|
+
});
|
|
446
420
|
}
|
|
447
421
|
/**
|
|
448
422
|
* Remove an ephemeral agent from the database
|
|
449
423
|
*/
|
|
450
424
|
export function removeEphemeralAgent(workspacePath, agentName) {
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
425
|
+
withDrizzle(workspacePath, (ddb) => {
|
|
426
|
+
ddb.delete(agentsTable)
|
|
427
|
+
.where(and(eq(agentsTable.name, agentName), eq(agentsTable.type, 'ephemeral')))
|
|
428
|
+
.run();
|
|
429
|
+
});
|
|
454
430
|
}
|
|
455
431
|
/**
|
|
456
432
|
* Get all agents in workspace
|
|
457
433
|
*/
|
|
458
434
|
export function getWorkspaceAgents(workspacePath, includeCleanedUp = false) {
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
created_at: row.created_at,
|
|
475
|
-
cleaned_at: row.cleaned_at,
|
|
476
|
-
}));
|
|
435
|
+
return withDrizzle(workspacePath, (ddb) => {
|
|
436
|
+
let rows;
|
|
437
|
+
if (includeCleanedUp) {
|
|
438
|
+
rows = ddb.select().from(agentsTable)
|
|
439
|
+
.orderBy(asc(agentsTable.createdAt))
|
|
440
|
+
.all();
|
|
441
|
+
}
|
|
442
|
+
else {
|
|
443
|
+
rows = ddb.select().from(agentsTable)
|
|
444
|
+
.where(or(eq(agentsTable.status, 'active'), isNull(agentsTable.status)))
|
|
445
|
+
.orderBy(asc(agentsTable.createdAt))
|
|
446
|
+
.all();
|
|
447
|
+
}
|
|
448
|
+
return rows.map(toAgent);
|
|
449
|
+
});
|
|
477
450
|
}
|
|
478
451
|
/**
|
|
479
452
|
* Get an agent by directory path.
|
|
@@ -490,38 +463,31 @@ export function getAgentByPath(workspacePath, absolutePath) {
|
|
|
490
463
|
}
|
|
491
464
|
// Get relative path from workspace root
|
|
492
465
|
const relativePath = path.relative(normalizedWorkspace, normalizedPath);
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
type: (row.type || 'persistent'),
|
|
504
|
-
status: (row.status || 'active'),
|
|
505
|
-
base_name: row.base_name,
|
|
506
|
-
theme_id: row.theme_id,
|
|
507
|
-
worktree_path: row.worktree_path,
|
|
508
|
-
mount_mode: (row.mount_mode || 'worktree'),
|
|
509
|
-
created_at: row.created_at,
|
|
510
|
-
cleaned_at: row.cleaned_at,
|
|
511
|
-
};
|
|
466
|
+
return withDrizzle(workspacePath, (ddb) => {
|
|
467
|
+
const rows = ddb.select().from(agentsTable)
|
|
468
|
+
.where(or(eq(agentsTable.status, 'active'), isNull(agentsTable.status)))
|
|
469
|
+
.all();
|
|
470
|
+
// Find agent whose worktree_path matches or contains the relative path
|
|
471
|
+
for (const row of rows) {
|
|
472
|
+
if (row.worktreePath) {
|
|
473
|
+
if (relativePath === row.worktreePath || relativePath.startsWith(row.worktreePath + '/')) {
|
|
474
|
+
return toAgent(row);
|
|
475
|
+
}
|
|
512
476
|
}
|
|
513
477
|
}
|
|
514
|
-
|
|
515
|
-
|
|
478
|
+
return null;
|
|
479
|
+
});
|
|
516
480
|
}
|
|
517
481
|
/**
|
|
518
482
|
* Mark an agent as cleaned up (keeps the record for history)
|
|
519
483
|
*/
|
|
520
484
|
export function markAgentCleaned(workspacePath, agentName) {
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
485
|
+
withDrizzle(workspacePath, (ddb) => {
|
|
486
|
+
ddb.update(agentsTable)
|
|
487
|
+
.set({ status: 'cleaned', cleanedAt: new Date().toISOString() })
|
|
488
|
+
.where(eq(agentsTable.name, agentName))
|
|
489
|
+
.run();
|
|
490
|
+
});
|
|
525
491
|
}
|
|
526
492
|
/**
|
|
527
493
|
* Sync agents in database with what exists on disk.
|
|
@@ -529,9 +495,9 @@ export function markAgentCleaned(workspacePath, agentName) {
|
|
|
529
495
|
* Returns list of agents that were cleaned up.
|
|
530
496
|
*/
|
|
531
497
|
export function syncAgentsWithDisk(workspacePath) {
|
|
532
|
-
const
|
|
498
|
+
const agentList = getWorkspaceAgents(workspacePath, false); // Only active agents
|
|
533
499
|
const cleanedAgents = [];
|
|
534
|
-
for (const agent of
|
|
500
|
+
for (const agent of agentList) {
|
|
535
501
|
// Determine expected directory path
|
|
536
502
|
let agentDir;
|
|
537
503
|
if (agent.worktree_path) {
|
|
@@ -565,9 +531,8 @@ export function discoverAgentsOnDisk(workspacePath) {
|
|
|
565
531
|
const activeNames = new Set(activeAgents.map(a => a.name.toLowerCase()));
|
|
566
532
|
// Get ALL agents including cleaned (for reactivation)
|
|
567
533
|
const allAgents = getWorkspaceAgents(workspacePath, true);
|
|
568
|
-
const
|
|
569
|
-
|
|
570
|
-
try {
|
|
534
|
+
const cleanedAgentsMap = new Map(allAgents.filter(a => a.status === 'cleaned').map(a => [a.name.toLowerCase(), a]));
|
|
535
|
+
withDrizzle(workspacePath, (ddb) => {
|
|
571
536
|
// Scan staff directory
|
|
572
537
|
const staffDir = path.join(workspacePath, 'agents', 'staff');
|
|
573
538
|
if (fs.existsSync(staffDir)) {
|
|
@@ -578,21 +543,24 @@ export function discoverAgentsOnDisk(workspacePath) {
|
|
|
578
543
|
if (!activeNames.has(nameLower)) {
|
|
579
544
|
const worktreePath = `agents/staff/${entry.name}`;
|
|
580
545
|
const now = new Date().toISOString();
|
|
581
|
-
|
|
582
|
-
const cleanedAgent = cleanedAgents.get(nameLower);
|
|
546
|
+
const cleanedAgent = cleanedAgentsMap.get(nameLower);
|
|
583
547
|
if (cleanedAgent) {
|
|
584
548
|
// Reactivate the cleaned agent
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
549
|
+
ddb.update(agentsTable)
|
|
550
|
+
.set({ status: 'active', cleanedAt: null, worktreePath })
|
|
551
|
+
.where(sql `LOWER(${agentsTable.name}) = LOWER(${entry.name})`)
|
|
552
|
+
.run();
|
|
589
553
|
}
|
|
590
554
|
else {
|
|
591
|
-
// Register new agent
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
555
|
+
// Register new agent
|
|
556
|
+
ddb.insert(agentsTable).values({
|
|
557
|
+
name: entry.name,
|
|
558
|
+
type: 'persistent',
|
|
559
|
+
status: 'active',
|
|
560
|
+
worktreePath,
|
|
561
|
+
mountMode: 'worktree',
|
|
562
|
+
createdAt: now,
|
|
563
|
+
}).run();
|
|
596
564
|
}
|
|
597
565
|
result.discovered.push({ name: entry.name, type: 'persistent', path: worktreePath });
|
|
598
566
|
activeNames.add(nameLower);
|
|
@@ -610,21 +578,24 @@ export function discoverAgentsOnDisk(workspacePath) {
|
|
|
610
578
|
if (!activeNames.has(nameLower)) {
|
|
611
579
|
const worktreePath = `agents/temp/${entry.name}`;
|
|
612
580
|
const now = new Date().toISOString();
|
|
613
|
-
|
|
614
|
-
const cleanedAgent = cleanedAgents.get(nameLower);
|
|
581
|
+
const cleanedAgent = cleanedAgentsMap.get(nameLower);
|
|
615
582
|
if (cleanedAgent) {
|
|
616
583
|
// Reactivate the cleaned agent
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
584
|
+
ddb.update(agentsTable)
|
|
585
|
+
.set({ status: 'active', cleanedAt: null, worktreePath })
|
|
586
|
+
.where(sql `LOWER(${agentsTable.name}) = LOWER(${entry.name})`)
|
|
587
|
+
.run();
|
|
621
588
|
}
|
|
622
589
|
else {
|
|
623
|
-
// Register new agent
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
590
|
+
// Register new agent
|
|
591
|
+
ddb.insert(agentsTable).values({
|
|
592
|
+
name: entry.name,
|
|
593
|
+
type: 'ephemeral',
|
|
594
|
+
status: 'active',
|
|
595
|
+
worktreePath,
|
|
596
|
+
mountMode: 'worktree',
|
|
597
|
+
createdAt: now,
|
|
598
|
+
}).run();
|
|
628
599
|
}
|
|
629
600
|
result.discovered.push({ name: entry.name, type: 'ephemeral', path: worktreePath });
|
|
630
601
|
activeNames.add(nameLower);
|
|
@@ -632,79 +603,123 @@ export function discoverAgentsOnDisk(workspacePath) {
|
|
|
632
603
|
}
|
|
633
604
|
}
|
|
634
605
|
}
|
|
635
|
-
}
|
|
636
|
-
finally {
|
|
637
|
-
db.close();
|
|
638
|
-
}
|
|
606
|
+
});
|
|
639
607
|
return result;
|
|
640
608
|
}
|
|
641
609
|
/**
|
|
642
610
|
* Get all repositories in workspace
|
|
643
611
|
*/
|
|
644
612
|
export function getWorkspaceRepositories(workspacePath) {
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
613
|
+
return withDrizzle(workspacePath, (ddb) => {
|
|
614
|
+
const rows = ddb.select().from(repositoriesTable)
|
|
615
|
+
.orderBy(asc(repositoriesTable.addedAt))
|
|
616
|
+
.all();
|
|
617
|
+
return rows.map(row => ({
|
|
618
|
+
name: row.name,
|
|
619
|
+
path: row.path,
|
|
620
|
+
type: (row.type || 'main'),
|
|
621
|
+
source_url: row.sourceUrl ?? undefined,
|
|
622
|
+
action: (row.action ?? undefined),
|
|
623
|
+
added_at: row.addedAt,
|
|
624
|
+
}));
|
|
625
|
+
});
|
|
649
626
|
}
|
|
650
627
|
/**
|
|
651
628
|
* Get worktrees for a specific agent
|
|
652
629
|
*/
|
|
653
630
|
export function getAgentWorktrees(workspacePath, agentName) {
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
631
|
+
return withDrizzle(workspacePath, (ddb) => {
|
|
632
|
+
const rows = ddb.select().from(agentWorktreesTable)
|
|
633
|
+
.where(eq(agentWorktreesTable.agentName, agentName))
|
|
634
|
+
.all();
|
|
635
|
+
return rows.map(row => ({
|
|
636
|
+
agent_name: row.agentName,
|
|
637
|
+
repo_name: row.repoName,
|
|
638
|
+
worktree_path: row.worktreePath,
|
|
639
|
+
branch: row.branch,
|
|
640
|
+
created_at: row.createdAt,
|
|
641
|
+
last_commit_hash: row.lastCommitHash ?? undefined,
|
|
642
|
+
commits_ahead: row.commitsAhead,
|
|
643
|
+
is_clean: Boolean(row.isClean),
|
|
644
|
+
last_checked: row.lastChecked ?? undefined,
|
|
645
|
+
}));
|
|
646
|
+
});
|
|
658
647
|
}
|
|
659
648
|
/**
|
|
660
649
|
* Find agent worktrees matching a branch pattern (case-insensitive LIKE).
|
|
661
650
|
*/
|
|
662
651
|
export function findWorktreesByBranch(workspacePath, branchPattern) {
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
652
|
+
return withDrizzle(workspacePath, (ddb) => {
|
|
653
|
+
const rows = ddb.select().from(agentWorktreesTable)
|
|
654
|
+
.where(like(sql `LOWER(${agentWorktreesTable.branch})`, branchPattern))
|
|
655
|
+
.all();
|
|
656
|
+
return rows.map(row => ({
|
|
657
|
+
agent_name: row.agentName,
|
|
658
|
+
repo_name: row.repoName,
|
|
659
|
+
worktree_path: row.worktreePath,
|
|
660
|
+
branch: row.branch,
|
|
661
|
+
created_at: row.createdAt,
|
|
662
|
+
last_commit_hash: row.lastCommitHash ?? undefined,
|
|
663
|
+
commits_ahead: row.commitsAhead,
|
|
664
|
+
is_clean: Boolean(row.isClean),
|
|
665
|
+
last_checked: row.lastChecked ?? undefined,
|
|
666
|
+
}));
|
|
667
|
+
});
|
|
667
668
|
}
|
|
668
669
|
/**
|
|
669
670
|
* Get agent worktrees for a specific repository.
|
|
670
671
|
*/
|
|
671
672
|
export function getWorktreesForRepo(workspacePath, repoName) {
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
673
|
+
return withDrizzle(workspacePath, (ddb) => {
|
|
674
|
+
const rows = ddb.select({
|
|
675
|
+
agent_name: agentWorktreesTable.agentName,
|
|
676
|
+
is_clean: sql `${agentWorktreesTable.isClean}`,
|
|
677
|
+
commits_ahead: agentWorktreesTable.commitsAhead,
|
|
678
|
+
branch: agentWorktreesTable.branch,
|
|
679
|
+
}).from(agentWorktreesTable)
|
|
680
|
+
.where(eq(agentWorktreesTable.repoName, repoName))
|
|
681
|
+
.all();
|
|
682
|
+
return rows;
|
|
683
|
+
});
|
|
676
684
|
}
|
|
677
685
|
/**
|
|
678
686
|
* Upsert a workspace setting (key-value pair).
|
|
679
687
|
*/
|
|
680
688
|
export function upsertWorkspaceSetting(db, key, value) {
|
|
681
|
-
db
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
689
|
+
const ddb = createDrizzleConnection(db);
|
|
690
|
+
ddb.insert(workspaceSettingsTable)
|
|
691
|
+
.values({ key, value })
|
|
692
|
+
.onConflictDoUpdate({
|
|
693
|
+
target: workspaceSettingsTable.key,
|
|
694
|
+
set: { value },
|
|
695
|
+
})
|
|
696
|
+
.run();
|
|
686
697
|
}
|
|
687
698
|
/**
|
|
688
699
|
* Remove agents from database
|
|
689
700
|
*/
|
|
690
701
|
export function removeAgentsFromDatabase(workspacePath, agentNames) {
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
702
|
+
withDrizzle(workspacePath, (ddb, sqliteDb) => {
|
|
703
|
+
// Note: agent_worktrees will be deleted automatically due to CASCADE
|
|
704
|
+
const transaction = sqliteDb.transaction(() => {
|
|
705
|
+
for (const agentName of agentNames) {
|
|
706
|
+
ddb.delete(agentsTable)
|
|
707
|
+
.where(eq(agentsTable.name, agentName))
|
|
708
|
+
.run();
|
|
709
|
+
}
|
|
710
|
+
});
|
|
711
|
+
transaction();
|
|
698
712
|
});
|
|
699
|
-
transaction();
|
|
700
|
-
db.close();
|
|
701
713
|
}
|
|
702
714
|
// =============================================================================
|
|
703
715
|
// PMO Bootstrapping Operations
|
|
716
|
+
// Raw SQL is required here because these operate before migrations run
|
|
717
|
+
// or perform DDL operations that Drizzle doesn't support.
|
|
704
718
|
// =============================================================================
|
|
705
719
|
/**
|
|
706
720
|
* Check if PMO tables exist and get basic stats.
|
|
707
721
|
* Used by pmo init to detect existing PMO before storage layer is available.
|
|
722
|
+
* Raw SQL: uses sqlite_master introspection (pre-migration bootstrap).
|
|
708
723
|
*/
|
|
709
724
|
export function checkPMOExists(dbPath) {
|
|
710
725
|
let db;
|
|
@@ -735,6 +750,7 @@ export function checkPMOExists(dbPath) {
|
|
|
735
750
|
/**
|
|
736
751
|
* Get a PMO setting from the pmo_settings table.
|
|
737
752
|
* Used for bootstrapping queries before storage layer is available.
|
|
753
|
+
* Raw SQL: pre-migration bootstrap query.
|
|
738
754
|
*/
|
|
739
755
|
export function getPMOSetting(dbPath, key) {
|
|
740
756
|
let db;
|
|
@@ -759,6 +775,7 @@ export function getPMOSetting(dbPath, key) {
|
|
|
759
775
|
/**
|
|
760
776
|
* Drop PMO tables from the database.
|
|
761
777
|
* Used during PMO reinitialization.
|
|
778
|
+
* Raw SQL: DDL operations (DROP TABLE) are not supported by Drizzle.
|
|
762
779
|
*/
|
|
763
780
|
export function dropPMOTables(dbPath, tables) {
|
|
764
781
|
let db;
|
|
@@ -790,61 +807,79 @@ export function dropPMOTables(dbPath, tables) {
|
|
|
790
807
|
* Get all themes
|
|
791
808
|
*/
|
|
792
809
|
export function getThemes(workspacePath) {
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
810
|
+
return withDrizzle(workspacePath, (ddb) => {
|
|
811
|
+
const rows = ddb.select().from(agentThemesTable)
|
|
812
|
+
.orderBy(desc(agentThemesTable.builtin), asc(agentThemesTable.name))
|
|
813
|
+
.all();
|
|
814
|
+
return rows.map(toAgentTheme);
|
|
815
|
+
});
|
|
797
816
|
}
|
|
798
817
|
/**
|
|
799
818
|
* Get a theme by ID
|
|
800
819
|
*/
|
|
801
820
|
export function getTheme(workspacePath, themeId) {
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
821
|
+
return withDrizzle(workspacePath, (ddb) => {
|
|
822
|
+
const row = ddb.select().from(agentThemesTable)
|
|
823
|
+
.where(eq(agentThemesTable.id, themeId))
|
|
824
|
+
.get();
|
|
825
|
+
return row ? toAgentTheme(row) : null;
|
|
826
|
+
});
|
|
806
827
|
}
|
|
807
828
|
/**
|
|
808
829
|
* Create a new theme
|
|
809
830
|
*/
|
|
810
831
|
export function createTheme(workspacePath, theme) {
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
832
|
+
return withDrizzle(workspacePath, (ddb) => {
|
|
833
|
+
const now = new Date().toISOString();
|
|
834
|
+
ddb.insert(agentThemesTable).values({
|
|
835
|
+
id: theme.id,
|
|
836
|
+
name: theme.name,
|
|
837
|
+
displayName: theme.displayName,
|
|
838
|
+
description: theme.description || null,
|
|
839
|
+
builtin: theme.builtin || false,
|
|
840
|
+
createdAt: now,
|
|
841
|
+
}).run();
|
|
842
|
+
const created = ddb.select().from(agentThemesTable)
|
|
843
|
+
.where(eq(agentThemesTable.id, theme.id))
|
|
844
|
+
.get();
|
|
845
|
+
return toAgentTheme(created);
|
|
846
|
+
});
|
|
820
847
|
}
|
|
821
848
|
/**
|
|
822
849
|
* Delete a theme (cannot delete builtin themes)
|
|
823
850
|
*/
|
|
824
851
|
export function deleteTheme(workspacePath, themeId) {
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
852
|
+
return withDrizzle(workspacePath, (ddb) => {
|
|
853
|
+
const theme = ddb.select({ builtin: agentThemesTable.builtin })
|
|
854
|
+
.from(agentThemesTable)
|
|
855
|
+
.where(eq(agentThemesTable.id, themeId))
|
|
856
|
+
.get();
|
|
857
|
+
if (!theme) {
|
|
858
|
+
return false;
|
|
859
|
+
}
|
|
860
|
+
if (theme.builtin) {
|
|
861
|
+
throw new Error('Cannot delete built-in themes');
|
|
862
|
+
}
|
|
863
|
+
ddb.delete(agentThemesTable)
|
|
864
|
+
.where(eq(agentThemesTable.id, themeId))
|
|
865
|
+
.run();
|
|
866
|
+
return true;
|
|
867
|
+
});
|
|
839
868
|
}
|
|
840
869
|
/**
|
|
841
870
|
* Get names for a theme
|
|
842
871
|
*/
|
|
843
872
|
export function getThemeNames(workspacePath, themeId) {
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
873
|
+
return withDrizzle(workspacePath, (ddb) => {
|
|
874
|
+
const rows = ddb.select().from(agentThemeNamesTable)
|
|
875
|
+
.where(eq(agentThemeNamesTable.themeId, themeId))
|
|
876
|
+
.orderBy(asc(agentThemeNamesTable.name))
|
|
877
|
+
.all();
|
|
878
|
+
return rows.map(row => ({
|
|
879
|
+
theme_id: row.themeId,
|
|
880
|
+
name: row.name,
|
|
881
|
+
}));
|
|
882
|
+
});
|
|
848
883
|
}
|
|
849
884
|
/**
|
|
850
885
|
* Get available names for a theme.
|
|
@@ -853,141 +888,183 @@ export function getThemeNames(workspacePath, themeId) {
|
|
|
853
888
|
* 2. The agent exists but its worktree directory is missing (manually deleted)
|
|
854
889
|
*/
|
|
855
890
|
export function getAvailableThemeNames(workspacePath, themeId) {
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
891
|
+
return withDrizzle(workspacePath, (ddb) => {
|
|
892
|
+
// Get all theme names
|
|
893
|
+
const names = ddb.select({ name: agentThemeNamesTable.name })
|
|
894
|
+
.from(agentThemeNamesTable)
|
|
895
|
+
.where(eq(agentThemeNamesTable.themeId, themeId))
|
|
896
|
+
.orderBy(asc(agentThemeNamesTable.name))
|
|
897
|
+
.all();
|
|
898
|
+
// Get existing staff agents with their worktree paths (persistent type only)
|
|
899
|
+
const existingAgents = ddb.select({
|
|
900
|
+
name: sql `LOWER(${agentsTable.name})`,
|
|
901
|
+
worktreePath: agentsTable.worktreePath,
|
|
902
|
+
})
|
|
903
|
+
.from(agentsTable)
|
|
904
|
+
.where(and(eq(agentsTable.type, 'persistent'), or(eq(agentsTable.status, 'active'), isNull(agentsTable.status))))
|
|
905
|
+
.all();
|
|
906
|
+
// Build a set of names that are truly in use (agent exists AND worktree exists)
|
|
907
|
+
const inUseNames = new Set();
|
|
908
|
+
for (const agent of existingAgents) {
|
|
909
|
+
if (agent.worktreePath) {
|
|
910
|
+
const fullPath = path.join(workspacePath, agent.worktreePath);
|
|
911
|
+
if (fs.existsSync(fullPath)) {
|
|
912
|
+
inUseNames.add(agent.name);
|
|
913
|
+
}
|
|
914
|
+
}
|
|
915
|
+
else {
|
|
916
|
+
// No worktree path means we can't verify - treat as in use to be safe
|
|
872
917
|
inUseNames.add(agent.name);
|
|
873
918
|
}
|
|
874
919
|
}
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
}
|
|
880
|
-
// Filter out names that are truly in use
|
|
881
|
-
return names
|
|
882
|
-
.map(n => n.name)
|
|
883
|
-
.filter(name => !inUseNames.has(name.toLowerCase()));
|
|
920
|
+
// Filter out names that are truly in use
|
|
921
|
+
return names
|
|
922
|
+
.map(n => n.name)
|
|
923
|
+
.filter(name => !inUseNames.has(name.toLowerCase()));
|
|
924
|
+
});
|
|
884
925
|
}
|
|
885
926
|
/**
|
|
886
927
|
* Add names to a theme (case-insensitive uniqueness)
|
|
887
928
|
*/
|
|
888
929
|
export function addThemeNames(workspacePath, themeId, names) {
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
930
|
+
withDrizzle(workspacePath, (ddb, sqliteDb) => {
|
|
931
|
+
const transaction = sqliteDb.transaction(() => {
|
|
932
|
+
for (const name of names) {
|
|
933
|
+
// Check for existing name (case-insensitive)
|
|
934
|
+
const existing = ddb.select({ name: agentThemeNamesTable.name })
|
|
935
|
+
.from(agentThemeNamesTable)
|
|
936
|
+
.where(and(eq(agentThemeNamesTable.themeId, themeId), sql `LOWER(${agentThemeNamesTable.name}) = LOWER(${name})`))
|
|
937
|
+
.get();
|
|
938
|
+
if (existing) {
|
|
939
|
+
continue;
|
|
940
|
+
}
|
|
941
|
+
ddb.insert(agentThemeNamesTable).values({
|
|
942
|
+
themeId,
|
|
943
|
+
name,
|
|
944
|
+
}).run();
|
|
902
945
|
}
|
|
903
|
-
|
|
904
|
-
|
|
946
|
+
});
|
|
947
|
+
transaction();
|
|
905
948
|
});
|
|
906
|
-
transaction();
|
|
907
|
-
db.close();
|
|
908
949
|
}
|
|
909
950
|
/**
|
|
910
951
|
* Add a media item to the database
|
|
911
952
|
*/
|
|
912
953
|
export function addMediaItemToDatabase(workspacePath, item) {
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
954
|
+
withDrizzle(workspacePath, (ddb) => {
|
|
955
|
+
const now = new Date().toISOString();
|
|
956
|
+
ddb.insert(mediaItemsTable)
|
|
957
|
+
.values({
|
|
958
|
+
name: item.name,
|
|
959
|
+
path: item.path,
|
|
960
|
+
sourcePath: item.source_path || null,
|
|
961
|
+
mediaType: item.media_type,
|
|
962
|
+
frameInterval: item.frame_interval || 30,
|
|
963
|
+
addedAt: now,
|
|
964
|
+
})
|
|
965
|
+
.onConflictDoUpdate({
|
|
966
|
+
target: mediaItemsTable.name,
|
|
967
|
+
set: {
|
|
968
|
+
path: item.path,
|
|
969
|
+
sourcePath: item.source_path || null,
|
|
970
|
+
mediaType: item.media_type,
|
|
971
|
+
frameInterval: item.frame_interval || 30,
|
|
972
|
+
addedAt: now,
|
|
973
|
+
},
|
|
974
|
+
})
|
|
975
|
+
.run();
|
|
976
|
+
});
|
|
919
977
|
}
|
|
920
978
|
/**
|
|
921
979
|
* Update media item after preprocessing
|
|
922
980
|
*/
|
|
923
981
|
export function updateMediaItemStatus(workspacePath, name, updates) {
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
values.push(new Date().toISOString());
|
|
950
|
-
}
|
|
951
|
-
values.push(name);
|
|
952
|
-
db.prepare(`UPDATE media_items SET ${sets.join(', ')} WHERE name = ?`).run(...values);
|
|
953
|
-
db.close();
|
|
982
|
+
withDrizzle(workspacePath, (ddb) => {
|
|
983
|
+
const setValues = { status: updates.status };
|
|
984
|
+
if (updates.duration_seconds !== undefined) {
|
|
985
|
+
setValues.durationSeconds = updates.duration_seconds;
|
|
986
|
+
}
|
|
987
|
+
if (updates.resolution !== undefined) {
|
|
988
|
+
setValues.resolution = updates.resolution;
|
|
989
|
+
}
|
|
990
|
+
if (updates.frame_count !== undefined) {
|
|
991
|
+
setValues.frameCount = updates.frame_count;
|
|
992
|
+
}
|
|
993
|
+
if (updates.has_transcript !== undefined) {
|
|
994
|
+
setValues.hasTranscript = updates.has_transcript;
|
|
995
|
+
}
|
|
996
|
+
if (updates.error_message !== undefined) {
|
|
997
|
+
setValues.errorMessage = updates.error_message;
|
|
998
|
+
}
|
|
999
|
+
if (updates.status === 'ready' || updates.status === 'error') {
|
|
1000
|
+
setValues.processedAt = new Date().toISOString();
|
|
1001
|
+
}
|
|
1002
|
+
ddb.update(mediaItemsTable)
|
|
1003
|
+
.set(setValues)
|
|
1004
|
+
.where(eq(mediaItemsTable.name, name))
|
|
1005
|
+
.run();
|
|
1006
|
+
});
|
|
954
1007
|
}
|
|
955
1008
|
/**
|
|
956
1009
|
* Get all media items in workspace
|
|
957
1010
|
*/
|
|
958
1011
|
export function getWorkspaceMediaItems(workspacePath) {
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
1012
|
+
return withDrizzle(workspacePath, (ddb) => {
|
|
1013
|
+
const rows = ddb.select().from(mediaItemsTable)
|
|
1014
|
+
.orderBy(asc(mediaItemsTable.addedAt))
|
|
1015
|
+
.all();
|
|
1016
|
+
return rows.map(row => ({
|
|
1017
|
+
name: row.name,
|
|
1018
|
+
path: row.path,
|
|
1019
|
+
source_path: row.sourcePath,
|
|
1020
|
+
media_type: row.mediaType,
|
|
1021
|
+
duration_seconds: row.durationSeconds,
|
|
1022
|
+
resolution: row.resolution,
|
|
1023
|
+
frame_count: row.frameCount,
|
|
1024
|
+
has_transcript: Boolean(row.hasTranscript),
|
|
1025
|
+
frame_interval: row.frameInterval,
|
|
1026
|
+
status: row.status,
|
|
1027
|
+
error_message: row.errorMessage,
|
|
1028
|
+
added_at: row.addedAt,
|
|
1029
|
+
processed_at: row.processedAt,
|
|
1030
|
+
}));
|
|
1031
|
+
});
|
|
968
1032
|
}
|
|
969
1033
|
/**
|
|
970
1034
|
* Get a single media item by name
|
|
971
1035
|
*/
|
|
972
1036
|
export function getMediaItem(workspacePath, name) {
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
1037
|
+
return withDrizzle(workspacePath, (ddb) => {
|
|
1038
|
+
const row = ddb.select().from(mediaItemsTable)
|
|
1039
|
+
.where(eq(mediaItemsTable.name, name))
|
|
1040
|
+
.get();
|
|
1041
|
+
if (!row)
|
|
1042
|
+
return null;
|
|
1043
|
+
return {
|
|
1044
|
+
name: row.name,
|
|
1045
|
+
path: row.path,
|
|
1046
|
+
source_path: row.sourcePath,
|
|
1047
|
+
media_type: row.mediaType,
|
|
1048
|
+
duration_seconds: row.durationSeconds,
|
|
1049
|
+
resolution: row.resolution,
|
|
1050
|
+
frame_count: row.frameCount,
|
|
1051
|
+
has_transcript: Boolean(row.hasTranscript),
|
|
1052
|
+
frame_interval: row.frameInterval,
|
|
1053
|
+
status: row.status,
|
|
1054
|
+
error_message: row.errorMessage,
|
|
1055
|
+
added_at: row.addedAt,
|
|
1056
|
+
processed_at: row.processedAt,
|
|
1057
|
+
};
|
|
1058
|
+
});
|
|
984
1059
|
}
|
|
985
1060
|
/**
|
|
986
1061
|
* Remove a media item from the database
|
|
987
1062
|
*/
|
|
988
1063
|
export function removeMediaItemFromDatabase(workspacePath, name) {
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
1064
|
+
withDrizzle(workspacePath, (ddb) => {
|
|
1065
|
+
ddb.delete(mediaItemsTable)
|
|
1066
|
+
.where(eq(mediaItemsTable.name, name))
|
|
1067
|
+
.run();
|
|
1068
|
+
});
|
|
992
1069
|
}
|
|
993
1070
|
//# sourceMappingURL=index.js.map
|