@cat-factory/app 0.153.2 → 0.153.4

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.
@@ -0,0 +1,121 @@
1
+ import type { Ref } from 'vue'
2
+ import { ref } from 'vue'
3
+ import type { Initiative } from '~/types/domain'
4
+ import { useWorkspaceStore } from '~/stores/workspace'
5
+
6
+ /**
7
+ * Shared reactive state + injected dependencies the initiatives-store action factories close
8
+ * over. Created once in the `initiatives` store setup and threaded into
9
+ * {@link createInitiativePlanningActions} / {@link createInitiativeCurationActions} so the split
10
+ * operations stay behaviourally identical to the original single-closure store — a size-only
11
+ * extraction mirroring `stores/board/` and `stores/pipelines/`, not a new seam.
12
+ */
13
+ export interface InitiativeActionContext {
14
+ api: ReturnType<typeof useApi>
15
+ workspace: ReturnType<typeof useWorkspaceStore>
16
+ /** Patch an entity a call returned into the by-block cache (newest rev wins). */
17
+ upsert: (initiative: Initiative) => void
18
+ }
19
+
20
+ /**
21
+ * The planning-window actions: answering / dismissing the interviewer's questions, asking it to
22
+ * recommend an answer, and the two loop-resuming controls (continue / proceed). Owns the
23
+ * in-flight flags the window renders its spinners from.
24
+ */
25
+ export function createInitiativePlanningActions(ctx: InitiativeActionContext) {
26
+ const { api, workspace, upsert } = ctx
27
+
28
+ /** True while a planning-window action (continue/proceed) is resuming the run. */
29
+ const resuming = ref(false)
30
+ /** Question ids the interviewer is currently drafting a recommendation for (window spinner). */
31
+ const recommending: Ref<Set<string>> = ref(new Set())
32
+
33
+ /** Record the human's answer to one pending interview question (no run resume). */
34
+ async function answerQuestion(blockId: string, questionId: string, answer: string) {
35
+ if (!workspace.workspaceId) throw new Error('No active workspace')
36
+ const updated = await api.answerInitiativeQuestion(
37
+ workspace.workspaceId,
38
+ blockId,
39
+ questionId,
40
+ answer,
41
+ )
42
+ upsert(updated)
43
+ return updated
44
+ }
45
+
46
+ /** Mark a planning question not-relevant (`dismissed`) or reopen it (no run resume). */
47
+ async function setQuestionStatus(
48
+ blockId: string,
49
+ questionId: string,
50
+ status: 'open' | 'dismissed',
51
+ ) {
52
+ if (!workspace.workspaceId) throw new Error('No active workspace')
53
+ const updated = await api.setInitiativeQuestionStatus(
54
+ workspace.workspaceId,
55
+ blockId,
56
+ questionId,
57
+ status,
58
+ )
59
+ upsert(updated)
60
+ return updated
61
+ }
62
+
63
+ /**
64
+ * Ask the interviewer to recommend a suggested answer for one pending question. Runs the
65
+ * interviewer LLM inline server-side; the returned entity carries the suggestion on the question.
66
+ * Tracks the in-flight id so the window can show a per-question spinner.
67
+ */
68
+ async function recommendAnswer(blockId: string, questionId: string) {
69
+ if (!workspace.workspaceId) throw new Error('No active workspace')
70
+ recommending.value = new Set(recommending.value).add(questionId)
71
+ try {
72
+ const updated = await api.recommendInitiativeAnswer(
73
+ workspace.workspaceId,
74
+ blockId,
75
+ questionId,
76
+ )
77
+ upsert(updated)
78
+ return updated
79
+ } finally {
80
+ const next = new Set(recommending.value)
81
+ next.delete(questionId)
82
+ recommending.value = next
83
+ }
84
+ }
85
+
86
+ /** Submit the answers and resume the interview (the interviewer re-runs, may ask more). */
87
+ async function continuePlanning(blockId: string) {
88
+ if (!workspace.workspaceId) throw new Error('No active workspace')
89
+ resuming.value = true
90
+ try {
91
+ const updated = await api.continueInitiativePlanning(workspace.workspaceId, blockId)
92
+ upsert(updated)
93
+ return updated
94
+ } finally {
95
+ resuming.value = false
96
+ }
97
+ }
98
+
99
+ /** Skip remaining questions: the interviewer converges and the run advances. */
100
+ async function proceedPlanning(blockId: string) {
101
+ if (!workspace.workspaceId) throw new Error('No active workspace')
102
+ resuming.value = true
103
+ try {
104
+ const updated = await api.proceedInitiativePlanning(workspace.workspaceId, blockId)
105
+ upsert(updated)
106
+ return updated
107
+ } finally {
108
+ resuming.value = false
109
+ }
110
+ }
111
+
112
+ return {
113
+ resuming,
114
+ recommending,
115
+ answerQuestion,
116
+ setQuestionStatus,
117
+ recommendAnswer,
118
+ continuePlanning,
119
+ proceedPlanning,
120
+ }
121
+ }
@@ -1,16 +1,14 @@
1
1
  import { defineStore } from 'pinia'
2
2
  import { computed, ref } from 'vue'
3
- import type {
4
- Initiative,
5
- InitiativeExecutionPolicy,
6
- InitiativePresetDescriptor,
7
- InitiativePresetInputs,
8
- PromoteInitiativeFollowUpInput,
9
- UpdateInitiativeItemInput,
10
- } from '~/types/domain'
3
+ import type { Initiative, InitiativePresetDescriptor, InitiativePresetInputs } from '~/types/domain'
11
4
 
12
5
  import { useWorkspaceStore } from '~/stores/workspace'
13
6
  import { useBoardStore } from '~/stores/board'
7
+ import {
8
+ createInitiativePlanningActions,
9
+ type InitiativeActionContext,
10
+ } from '~/stores/initiative/planning'
11
+ import { createInitiativeCurationActions } from '~/stores/initiative/curation'
14
12
 
15
13
  /** The built-in generic preset id (mirrors kernel's `GENERIC_INITIATIVE_PRESET_ID`). */
16
14
  export const GENERIC_PRESET_ID = 'preset_generic'
@@ -165,91 +163,6 @@ export const useInitiativesStore = defineStore('initiatives', () => {
165
163
  }
166
164
  }
167
165
 
168
- /** True while a planning-window action (continue/proceed) is resuming the run. */
169
- const resuming = ref(false)
170
-
171
- /** Record the human's answer to one pending interview question (no run resume). */
172
- async function answerQuestion(blockId: string, questionId: string, answer: string) {
173
- if (!workspace.workspaceId) throw new Error('No active workspace')
174
- const updated = await api.answerInitiativeQuestion(
175
- workspace.workspaceId,
176
- blockId,
177
- questionId,
178
- answer,
179
- )
180
- upsert(updated)
181
- return updated
182
- }
183
-
184
- /** Question ids the interviewer is currently drafting a recommendation for (window spinner). */
185
- const recommending = ref<Set<string>>(new Set())
186
-
187
- /** Mark a planning question not-relevant (`dismissed`) or reopen it (no run resume). */
188
- async function setQuestionStatus(
189
- blockId: string,
190
- questionId: string,
191
- status: 'open' | 'dismissed',
192
- ) {
193
- if (!workspace.workspaceId) throw new Error('No active workspace')
194
- const updated = await api.setInitiativeQuestionStatus(
195
- workspace.workspaceId,
196
- blockId,
197
- questionId,
198
- status,
199
- )
200
- upsert(updated)
201
- return updated
202
- }
203
-
204
- /**
205
- * Ask the interviewer to recommend a suggested answer for one pending question. Runs the
206
- * interviewer LLM inline server-side; the returned entity carries the suggestion on the question.
207
- * Tracks the in-flight id so the window can show a per-question spinner.
208
- */
209
- async function recommendAnswer(blockId: string, questionId: string) {
210
- if (!workspace.workspaceId) throw new Error('No active workspace')
211
- recommending.value = new Set(recommending.value).add(questionId)
212
- try {
213
- const updated = await api.recommendInitiativeAnswer(
214
- workspace.workspaceId,
215
- blockId,
216
- questionId,
217
- )
218
- upsert(updated)
219
- return updated
220
- } finally {
221
- const next = new Set(recommending.value)
222
- next.delete(questionId)
223
- recommending.value = next
224
- }
225
- }
226
-
227
- /** Submit the answers and resume the interview (the interviewer re-runs, may ask more). */
228
- async function continuePlanning(blockId: string) {
229
- if (!workspace.workspaceId) throw new Error('No active workspace')
230
- resuming.value = true
231
- try {
232
- const updated = await api.continueInitiativePlanning(workspace.workspaceId, blockId)
233
- upsert(updated)
234
- return updated
235
- } finally {
236
- resuming.value = false
237
- }
238
- }
239
-
240
- /** Skip remaining questions: the interviewer converges and the run advances. */
241
- async function proceedPlanning(blockId: string) {
242
- if (!workspace.workspaceId) throw new Error('No active workspace')
243
- resuming.value = true
244
- try {
245
- const updated = await api.proceedInitiativePlanning(workspace.workspaceId, blockId)
246
- upsert(updated)
247
- return updated
248
- } finally {
249
- resuming.value = false
250
- }
251
- }
252
-
253
166
  /** True while a loop control (pause/resume/cancel) is in flight. */
254
167
  const controlling = ref(false)
255
168
 
@@ -272,76 +185,12 @@ export const useInitiativesStore = defineStore('initiatives', () => {
272
185
  }
273
186
  }
274
187
 
275
- /** True while a curation action (promote/dismiss/edit item/edit policy) is in flight. */
276
- const curating = ref(false)
277
-
278
- async function curate<T>(fn: () => Promise<T>): Promise<T> {
279
- if (!workspace.workspaceId) throw new Error('No active workspace')
280
- curating.value = true
281
- try {
282
- return await fn()
283
- } finally {
284
- curating.value = false
285
- }
286
- }
287
-
288
- /** Promote an `open` harvested follow-up into a new pending tracker item. */
289
- async function promoteFollowUp(
290
- initiativeId: string,
291
- followUpId: string,
292
- input: PromoteInitiativeFollowUpInput,
293
- ) {
294
- return curate(async () => {
295
- const updated = await api.promoteInitiativeFollowUp(
296
- workspace.workspaceId!,
297
- initiativeId,
298
- followUpId,
299
- input,
300
- )
301
- upsert(updated)
302
- return updated
303
- })
304
- }
305
-
306
- /** Dismiss a harvested follow-up. */
307
- async function dismissFollowUp(initiativeId: string, followUpId: string) {
308
- return curate(async () => {
309
- const updated = await api.dismissInitiativeFollowUp(
310
- workspace.workspaceId!,
311
- initiativeId,
312
- followUpId,
313
- )
314
- upsert(updated)
315
- return updated
316
- })
317
- }
318
-
319
- /** Edit one tracker item and/or drive its status (retry a blocked item / skip it). */
320
- async function updateItem(
321
- initiativeId: string,
322
- itemId: string,
323
- input: UpdateInitiativeItemInput,
324
- ) {
325
- return curate(async () => {
326
- const updated = await api.updateInitiativeItem(
327
- workspace.workspaceId!,
328
- initiativeId,
329
- itemId,
330
- input,
331
- )
332
- upsert(updated)
333
- return updated
334
- })
335
- }
336
-
337
- /** Replace the execution policy (concurrency + pipeline rules). */
338
- async function updatePolicy(initiativeId: string, policy: InitiativeExecutionPolicy) {
339
- return curate(async () => {
340
- const updated = await api.updateInitiativePolicy(workspace.workspaceId!, initiativeId, policy)
341
- upsert(updated)
342
- return updated
343
- })
344
- }
188
+ // The planning-window + tracker-curation actions, split into cohesive factories sharing the
189
+ // state above (a size-only extraction mirroring `stores/board/` — behaviour is identical to the
190
+ // former in-closure functions). Each owns the in-flight flags its window renders.
191
+ const context: InitiativeActionContext = { api, workspace, upsert }
192
+ const planning = createInitiativePlanningActions(context)
193
+ const curation = createInitiativeCurationActions(context)
345
194
 
346
195
  function reset() {
347
196
  byBlock.value = {}
@@ -353,10 +202,7 @@ export const useInitiativesStore = defineStore('initiatives', () => {
353
202
  presets,
354
203
  all,
355
204
  creating,
356
- resuming,
357
205
  controlling,
358
- curating,
359
- recommending,
360
206
  forBlock,
361
207
  presetById,
362
208
  planningPipelineIdFor,
@@ -366,16 +212,9 @@ export const useInitiativesStore = defineStore('initiatives', () => {
366
212
  create,
367
213
  probePreset,
368
214
  load,
369
- answerQuestion,
370
- setQuestionStatus,
371
- recommendAnswer,
372
- continuePlanning,
373
- proceedPlanning,
374
215
  control,
375
- promoteFollowUp,
376
- dismissFollowUp,
377
- updateItem,
378
- updatePolicy,
216
+ ...planning,
217
+ ...curation,
379
218
  reset,
380
219
  }
381
220
  })
@@ -0,0 +1,105 @@
1
+ import type { WorkspaceSnapshot } from '~/types/domain'
2
+ import { useAgentConfigStore } from '~/stores/agentConfig'
3
+ import { useAgentRunsStore } from '~/stores/agentRuns'
4
+ import { useAgentsStore } from '~/stores/agents'
5
+ import { useBoardStore } from '~/stores/board'
6
+ import { useBrainstormStore } from '~/stores/brainstorm'
7
+ import { useClarityStore } from '~/stores/clarity'
8
+ import { useConsensusStore } from '~/stores/consensus'
9
+ import { useEnvironmentTestStore } from '~/stores/environmentTest'
10
+ import { useExecutionStore } from '~/stores/execution'
11
+ import { useFragmentsStore } from '~/stores/fragments'
12
+ import { useGitHubStore } from '~/stores/github'
13
+ import { useInitiativesStore } from '~/stores/initiative'
14
+ import { useModelPresetsStore } from '~/stores/modelPresets'
15
+ import { useNotificationsStore } from '~/stores/notifications'
16
+ import { usePipelinesStore } from '~/stores/pipelines'
17
+ import { useProviderConnectionsStore } from '~/stores/providerConnections'
18
+ import { useRecurringPipelinesStore } from '~/stores/recurringPipelines'
19
+ import { useRequirementsStore } from '~/stores/requirements'
20
+ import { useRiskPoliciesStore } from '~/stores/riskPolicies'
21
+ import { useServiceFragmentDefaultsStore } from '~/stores/serviceFragmentDefaults'
22
+ import { useServicesStore } from '~/stores/services'
23
+ import { useSharedStacksStore } from '~/stores/sharedStacks'
24
+ import { useSkillsStore } from '~/stores/skills'
25
+ import { useTaskTypesStore } from '~/stores/taskTypes'
26
+ import { useTrackerStore } from '~/stores/tracker'
27
+ import { useWorkspaceSettingsStore } from '~/stores/workspaceSettings'
28
+ import { buildWorkspaceCapabilitiesManifest } from '~/modular/capabilities'
29
+
30
+ /**
31
+ * Drop the per-block caches that are NOT part of the snapshot (reviews, brainstorm/consensus
32
+ * sessions, the GitHub projection) so a switched-to board never shows the previous one's stale
33
+ * state. These are lazily reloaded/re-probed per board, so clearing on a same-board refresh
34
+ * would needlessly wipe an open review window — hence only on an actual id change.
35
+ */
36
+ export function resetPerBoardCaches() {
37
+ useRequirementsStore().reset()
38
+ useClarityStore().reset()
39
+ useBrainstormStore().reset()
40
+ useConsensusStore().reset()
41
+ useGitHubStore().reset()
42
+ useInitiativesStore().reset()
43
+ useDocInterviewStore().reset()
44
+ // The fragment picker catalog is per-board (the merged tenant catalog), so drop
45
+ // it too — the next inspector open re-fetches it for the switched-to board rather
46
+ // than showing the previous board's (or a raw-id placeholder for) fragments.
47
+ useFragmentsStore().invalidate()
48
+ }
49
+
50
+ /**
51
+ * Fan a workspace snapshot out into the per-feature data stores. Extracted verbatim from the
52
+ * `workspace` store's `hydrate` (which keeps the workspace-scoped state it owns + the
53
+ * board-switch cache reset) so the ordering of the hydrate calls is preserved exactly — a
54
+ * size-only split, not a new seam.
55
+ *
56
+ * `boardSince` (captured BEFORE this snapshot's fetch) lets the board store preserve any block
57
+ * live-`upsert`ed while the fetch was in flight, so a slower refresh can't clobber a newer live
58
+ * status (see `useBoardStore().hydrate`).
59
+ */
60
+ export function applySnapshotToStores(snapshot: WorkspaceSnapshot, boardSince?: number) {
61
+ useUserSettingsStore().hydrate(snapshot.userSettings ?? null)
62
+ useBoardStore().hydrate(snapshot.blocks, boardSince)
63
+ useBoardStore().hydrateArchived(snapshot.archivedServices ?? [])
64
+ usePipelinesStore().hydrate(snapshot.pipelines, snapshot.pipelineCatalogVersions)
65
+ useExecutionStore().hydrate(snapshot.executions, snapshot.workspace.id)
66
+ useAgentRunsStore().hydrate(snapshot.bootstrapJobs ?? [], snapshot.workspace.id)
67
+ useAgentRunsStore().hydrateEnvConfigRepair(snapshot.envConfigRepairJobs ?? [])
68
+ useEnvironmentTestStore().hydrate(snapshot.environmentTestRuns ?? [], snapshot.workspace.id)
69
+ useNotificationsStore().hydrate(snapshot.notifications ?? [])
70
+ useRiskPoliciesStore().hydrate(snapshot.riskPolicies ?? [], snapshot.riskPolicyCatalogVersions)
71
+ useSharedStacksStore().hydrate(snapshot.sharedStacks ?? [])
72
+ useWorkspaceSettingsStore().hydrate(snapshot.settings)
73
+ useAgentConfigStore().hydrate(snapshot.agentConfigCatalog ?? [])
74
+ useModelPresetsStore().hydrate(snapshot.modelPresets ?? [], snapshot.modelPresetCatalogVersions)
75
+ useServiceFragmentDefaultsStore().hydrate(snapshot.serviceFragmentDefaults?.fragmentIds)
76
+ useRecurringPipelinesStore().hydrate(snapshot.recurringPipelines ?? [])
77
+ useInitiativesStore().hydrate(snapshot.initiatives)
78
+ // Registered initiative presets (built-in generic + any a deployment mixed in): drive the
79
+ // create picker and which planning pipeline "Run planning" starts. Workspace-independent.
80
+ useInitiativesStore().hydratePresets(snapshot.initiativePresets)
81
+ useTrackerStore().hydrate(snapshot.trackerSettings)
82
+ useServicesStore().hydrate(snapshot.mounts ?? [], snapshot.serviceCatalog ?? [])
83
+ // Hydrate the deployment's backend-registered capabilities from ONE shared per-workspace
84
+ // remote capability manifest carrying BOTH custom agent kinds AND custom task types (its
85
+ // version covers both, so an unchanged snapshot no-ops both stores). Each store reads its
86
+ // own slot off it, so a proprietary agent kind renders as a first-class palette block +
87
+ // result view and a proprietary task type as a first-class create-task choice + card badge.
88
+ // Swapped wholesale per workspace (no global-catalog mutation).
89
+ const capabilities = buildWorkspaceCapabilitiesManifest(
90
+ snapshot.customAgentKinds ?? [],
91
+ snapshot.customTaskTypes ?? [],
92
+ )
93
+ useAgentsStore().hydrateCapabilities(capabilities)
94
+ useTaskTypesStore().hydrateCapabilities(capabilities)
95
+ // The account's repo-sourced Claude Skills catalog (shared across its workspaces), so the
96
+ // pipeline builder's per-step skill picker has its options. A straight replace.
97
+ useSkillsStore().hydrate(snapshot.skills ?? [])
98
+ // Seed the connect form's backend-kind selectors (built-in + any custom backend a
99
+ // deployment registered), so a programmatically-registered env/runner backend is a
100
+ // first-class connect option instead of a hardcoded manifest/kubernetes list.
101
+ useProviderConnectionsStore().registerBackendKinds({
102
+ environment: snapshot.environmentBackendKinds,
103
+ 'runner-pool': snapshot.runnerBackendKinds,
104
+ })
105
+ }
@@ -10,32 +10,7 @@ import type {
10
10
  } from '~/types/domain'
11
11
  import { useAccountsStore } from '~/stores/accounts'
12
12
  import { useBoardStore } from '~/stores/board'
13
- import { usePipelinesStore } from '~/stores/pipelines'
14
- import { useExecutionStore } from '~/stores/execution'
15
- import { useAgentRunsStore } from '~/stores/agentRuns'
16
- import { useEnvironmentTestStore } from '~/stores/environmentTest'
17
- import { useNotificationsStore } from '~/stores/notifications'
18
- import { useRiskPoliciesStore } from '~/stores/riskPolicies'
19
- import { useSharedStacksStore } from '~/stores/sharedStacks'
20
- import { useWorkspaceSettingsStore } from '~/stores/workspaceSettings'
21
- import { useAgentConfigStore } from '~/stores/agentConfig'
22
- import { useModelPresetsStore } from '~/stores/modelPresets'
23
- import { useServiceFragmentDefaultsStore } from '~/stores/serviceFragmentDefaults'
24
- import { useRecurringPipelinesStore } from '~/stores/recurringPipelines'
25
- import { useInitiativesStore } from '~/stores/initiative'
26
- import { useServicesStore } from '~/stores/services'
27
- import { useAgentsStore } from '~/stores/agents'
28
- import { useTaskTypesStore } from '~/stores/taskTypes'
29
- import { buildWorkspaceCapabilitiesManifest } from '~/modular/capabilities'
30
- import { useSkillsStore } from '~/stores/skills'
31
- import { useTrackerStore } from '~/stores/tracker'
32
- import { useRequirementsStore } from '~/stores/requirements'
33
- import { useClarityStore } from '~/stores/clarity'
34
- import { useBrainstormStore } from '~/stores/brainstorm'
35
- import { useConsensusStore } from '~/stores/consensus'
36
- import { useGitHubStore } from '~/stores/github'
37
- import { useFragmentsStore } from '~/stores/fragments'
38
- import { useProviderConnectionsStore } from '~/stores/providerConnections'
13
+ import { applySnapshotToStores, resetPerBoardCaches } from '~/stores/workspace/hydrate'
39
14
  import { markBoot } from '~/utils/bootMarks'
40
15
  import { retryWhileBackendUnreachable } from '~/utils/backendReady'
41
16
 
@@ -109,30 +84,14 @@ export const useWorkspaceStore = defineStore(
109
84
  * fresh loads (init/switch/create), where there is no in-flight-upsert race to guard.
110
85
  */
111
86
  function hydrate(snapshot: WorkspaceSnapshot, boardSince?: number) {
112
- // A change of active board (or the first load) drop the per-block caches that are
113
- // NOT part of the snapshot (reviews, brainstorm/consensus sessions, the GitHub
114
- // projection) so a switched-to board never shows the previous one's stale state.
115
- // These are lazily reloaded/re-probed per board, so clearing on a same-board refresh
116
- // would needlessly wipe an open review window — hence only on an actual id change.
117
- if (workspaceId.value !== snapshot.workspace.id) {
118
- useRequirementsStore().reset()
119
- useClarityStore().reset()
120
- useBrainstormStore().reset()
121
- useConsensusStore().reset()
122
- useGitHubStore().reset()
123
- useInitiativesStore().reset()
124
- useDocInterviewStore().reset()
125
- // The fragment picker catalog is per-board (the merged tenant catalog), so drop
126
- // it too — the next inspector open re-fetches it for the switched-to board rather
127
- // than showing the previous board's (or a raw-id placeholder for) fragments.
128
- useFragmentsStore().invalidate()
129
- }
87
+ // A change of active board (or the first load) drops the per-block caches that are NOT
88
+ // part of the snapshot; a same-board refresh keeps them (see `resetPerBoardCaches`).
89
+ if (workspaceId.value !== snapshot.workspace.id) resetPerBoardCaches()
130
90
  workspaceId.value = snapshot.workspace.id
131
91
  spend.value = snapshot.spend ?? null
132
92
  accountSpend.value = snapshot.accountSpend ?? null
133
93
  userSpend.value = snapshot.userSpend ?? null
134
94
  budgetCaps.value = snapshot.budgetCaps ?? null
135
- useUserSettingsStore().hydrate(snapshot.userSettings ?? null)
136
95
  infraSetup.value = snapshot.infraSetup ?? null
137
96
  access.value = snapshot.access ?? null
138
97
  // Keep the board list in step (e.g. a freshly created board, or a rename). The
@@ -144,55 +103,8 @@ export const useWorkspaceStore = defineStore(
144
103
  } else {
145
104
  workspaces.value.unshift(snapshot.workspace)
146
105
  }
147
- useBoardStore().hydrate(snapshot.blocks, boardSince)
148
- useBoardStore().hydrateArchived(snapshot.archivedServices ?? [])
149
- usePipelinesStore().hydrate(snapshot.pipelines, snapshot.pipelineCatalogVersions)
150
- useExecutionStore().hydrate(snapshot.executions, snapshot.workspace.id)
151
- useAgentRunsStore().hydrate(snapshot.bootstrapJobs ?? [], snapshot.workspace.id)
152
- useAgentRunsStore().hydrateEnvConfigRepair(snapshot.envConfigRepairJobs ?? [])
153
- useEnvironmentTestStore().hydrate(snapshot.environmentTestRuns ?? [], snapshot.workspace.id)
154
- useNotificationsStore().hydrate(snapshot.notifications ?? [])
155
- useRiskPoliciesStore().hydrate(
156
- snapshot.riskPolicies ?? [],
157
- snapshot.riskPolicyCatalogVersions,
158
- )
159
- useSharedStacksStore().hydrate(snapshot.sharedStacks ?? [])
160
- useWorkspaceSettingsStore().hydrate(snapshot.settings)
161
- useAgentConfigStore().hydrate(snapshot.agentConfigCatalog ?? [])
162
- useModelPresetsStore().hydrate(
163
- snapshot.modelPresets ?? [],
164
- snapshot.modelPresetCatalogVersions,
165
- )
166
- useServiceFragmentDefaultsStore().hydrate(snapshot.serviceFragmentDefaults?.fragmentIds)
167
- useRecurringPipelinesStore().hydrate(snapshot.recurringPipelines ?? [])
168
- useInitiativesStore().hydrate(snapshot.initiatives)
169
- // Registered initiative presets (built-in generic + any a deployment mixed in): drive the
170
- // create picker and which planning pipeline "Run planning" starts. Workspace-independent.
171
- useInitiativesStore().hydratePresets(snapshot.initiativePresets)
172
- useTrackerStore().hydrate(snapshot.trackerSettings)
173
- useServicesStore().hydrate(snapshot.mounts ?? [], snapshot.serviceCatalog ?? [])
174
- // Hydrate the deployment's backend-registered capabilities from ONE shared per-workspace
175
- // remote capability manifest carrying BOTH custom agent kinds AND custom task types (its
176
- // version covers both, so an unchanged snapshot no-ops both stores). Each store reads its
177
- // own slot off it, so a proprietary agent kind renders as a first-class palette block +
178
- // result view and a proprietary task type as a first-class create-task choice + card badge.
179
- // Swapped wholesale per workspace (no global-catalog mutation).
180
- const capabilities = buildWorkspaceCapabilitiesManifest(
181
- snapshot.customAgentKinds ?? [],
182
- snapshot.customTaskTypes ?? [],
183
- )
184
- useAgentsStore().hydrateCapabilities(capabilities)
185
- useTaskTypesStore().hydrateCapabilities(capabilities)
186
- // The account's repo-sourced Claude Skills catalog (shared across its workspaces), so the
187
- // pipeline builder's per-step skill picker has its options. A straight replace.
188
- useSkillsStore().hydrate(snapshot.skills ?? [])
189
- // Seed the connect form's backend-kind selectors (built-in + any custom backend a
190
- // deployment registered), so a programmatically-registered env/runner backend is a
191
- // first-class connect option instead of a hardcoded manifest/kubernetes list.
192
- useProviderConnectionsStore().registerBackendKinds({
193
- environment: snapshot.environmentBackendKinds,
194
- 'runner-pool': snapshot.runnerBackendKinds,
195
- })
106
+ // Fan the rest of the snapshot out into the per-feature data stores.
107
+ applySnapshotToStores(snapshot, boardSince)
196
108
  }
197
109
 
198
110
  /** Resolve accounts + boards, then open the right board for the active account. */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cat-factory/app",
3
- "version": "0.153.2",
3
+ "version": "0.153.4",
4
4
  "description": "Reusable Nuxt layer for the Agent Architecture Board SPA (components, stores, composables, pages). Consume it from a thin deployment app via `extends: ['@cat-factory/app']` and point it at your backend with NUXT_PUBLIC_API_BASE. See deploy/frontend for an example.",
5
5
  "repository": {
6
6
  "type": "git",
@@ -40,7 +40,7 @@
40
40
  "valibot": "^1.4.2",
41
41
  "vue": "3.5.40",
42
42
  "wretch": "^3.0.9",
43
- "@cat-factory/contracts": "0.160.1"
43
+ "@cat-factory/contracts": "0.161.0"
44
44
  },
45
45
  "devDependencies": {
46
46
  "@toad-contracts/testing": "0.3.2",