@cat-factory/app 0.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +88 -0
- package/app/app.config.ts +8 -0
- package/app/app.vue +11 -0
- package/app/assets/css/main.css +100 -0
- package/app/components/auth/AuthGate.vue +24 -0
- package/app/components/auth/LoginScreen.vue +143 -0
- package/app/components/auth/UserMenu.vue +39 -0
- package/app/components/board/AddTaskModal.vue +444 -0
- package/app/components/board/AgentFailureCard.vue +97 -0
- package/app/components/board/AgentStopButton.vue +61 -0
- package/app/components/board/BoardCanvas.vue +183 -0
- package/app/components/board/ContextPicker.vue +367 -0
- package/app/components/board/RecurringPipelineModal.vue +219 -0
- package/app/components/board/TaskDependencyEdges.vue +132 -0
- package/app/components/board/nodes/AgentChip.vue +59 -0
- package/app/components/board/nodes/BlockNode.vue +433 -0
- package/app/components/board/nodes/DecisionBadge.vue +27 -0
- package/app/components/board/nodes/DraggableTask.vue +48 -0
- package/app/components/board/nodes/ModuleFrame.vue +97 -0
- package/app/components/board/nodes/TaskCard.vue +359 -0
- package/app/components/board/nodes/TaskPipelineMini.vue +159 -0
- package/app/components/bootstrap/BootstrapModal.vue +665 -0
- package/app/components/clarity/ClarityReviewWindow.vue +611 -0
- package/app/components/consensus/ConsensusSessionWindow.vue +210 -0
- package/app/components/documents/DocumentImportModal.vue +161 -0
- package/app/components/documents/DocumentSourceConnectModal.vue +127 -0
- package/app/components/documents/SpawnPreviewModal.vue +161 -0
- package/app/components/documents/TaskContextDocs.vue +83 -0
- package/app/components/focus/BlockFocusView.vue +171 -0
- package/app/components/fragments/FragmentLibraryPanel.vue +340 -0
- package/app/components/gates/GateResultView.vue +282 -0
- package/app/components/github/AddServiceFromRepoModal.vue +354 -0
- package/app/components/github/GitHubConnect.vue +183 -0
- package/app/components/github/GitHubOnboarding.vue +45 -0
- package/app/components/github/GitHubPanel.vue +584 -0
- package/app/components/github/RepoTreeBrowser.vue +171 -0
- package/app/components/layout/AccountTeamSettings.vue +237 -0
- package/app/components/layout/BoardSwitcher.vue +280 -0
- package/app/components/layout/BoardToolbar.vue +156 -0
- package/app/components/layout/CommandBar.vue +336 -0
- package/app/components/layout/GitHubPatBanner.vue +73 -0
- package/app/components/layout/NotificationsInbox.vue +175 -0
- package/app/components/layout/SideBar.vue +314 -0
- package/app/components/layout/SpendWarningBanner.vue +107 -0
- package/app/components/observability/StepMetricsBar.vue +102 -0
- package/app/components/palettes/AgentPalette.vue +86 -0
- package/app/components/panels/AgentStepDetail.vue +737 -0
- package/app/components/panels/DecisionModal.vue +71 -0
- package/app/components/panels/InspectorPanel.vue +465 -0
- package/app/components/panels/ObservabilityPanel.vue +351 -0
- package/app/components/panels/StepMetadataCard.vue +253 -0
- package/app/components/panels/StepRestartControl.vue +90 -0
- package/app/components/panels/StepResultViewHost.vue +40 -0
- package/app/components/panels/StepTestReport.vue +84 -0
- package/app/components/panels/inspector/ContainerSummary.vue +74 -0
- package/app/components/panels/inspector/RecurringScheduleSettings.vue +178 -0
- package/app/components/panels/inspector/ServiceFragments.vue +82 -0
- package/app/components/panels/inspector/ServiceTestConfig.vue +198 -0
- package/app/components/panels/inspector/TaskAgentConfig.vue +81 -0
- package/app/components/panels/inspector/TaskDependencies.vue +70 -0
- package/app/components/panels/inspector/TaskEstimateBadge.vue +56 -0
- package/app/components/panels/inspector/TaskExecution.vue +364 -0
- package/app/components/panels/inspector/TaskRunSettings.vue +187 -0
- package/app/components/panels/inspector/TaskStructure.vue +96 -0
- package/app/components/pipeline/AgentKindIcon.vue +30 -0
- package/app/components/pipeline/IterationCapPrompt.vue +70 -0
- package/app/components/pipeline/PipelineBuilder.vue +817 -0
- package/app/components/pipeline/PipelineProgress.vue +484 -0
- package/app/components/providers/ApiKeysSection.vue +273 -0
- package/app/components/providers/PersonalCredentialModal.vue +128 -0
- package/app/components/providers/PersonalSubscriptionSection.vue +225 -0
- package/app/components/providers/VendorCredentialsModal.vue +197 -0
- package/app/components/recurring/RecurrenceEditor.vue +124 -0
- package/app/components/requirements/RequirementsReviewWindow.vue +620 -0
- package/app/components/settings/DatadogPanel.vue +213 -0
- package/app/components/settings/LocalModelEndpointsPanel.vue +286 -0
- package/app/components/settings/MergeThresholdsPanel.vue +378 -0
- package/app/components/settings/ModelDefaultsPanel.vue +250 -0
- package/app/components/settings/ServiceFragmentDefaultsPanel.vue +124 -0
- package/app/components/settings/WorkspaceSettingsPanel.vue +142 -0
- package/app/components/slack/SlackPanel.vue +299 -0
- package/app/components/tasks/TaskContextIssues.vue +88 -0
- package/app/components/tasks/TaskImportModal.vue +207 -0
- package/app/components/tasks/TaskSourceConnectModal.vue +133 -0
- package/app/components/testing/TestReportWindow.vue +404 -0
- package/app/composables/api/accounts.ts +81 -0
- package/app/composables/api/auth.ts +45 -0
- package/app/composables/api/board.ts +101 -0
- package/app/composables/api/bootstrap.ts +62 -0
- package/app/composables/api/context.ts +25 -0
- package/app/composables/api/documents.ts +74 -0
- package/app/composables/api/execution.ts +127 -0
- package/app/composables/api/fragments.ts +71 -0
- package/app/composables/api/github.ts +131 -0
- package/app/composables/api/models.ts +127 -0
- package/app/composables/api/notifications.ts +23 -0
- package/app/composables/api/presets.ts +29 -0
- package/app/composables/api/recurring.ts +68 -0
- package/app/composables/api/releaseHealth.ts +43 -0
- package/app/composables/api/reviews.ts +146 -0
- package/app/composables/api/slack.ts +54 -0
- package/app/composables/api/tasks.ts +72 -0
- package/app/composables/api/workspaces.ts +36 -0
- package/app/composables/useApi.ts +89 -0
- package/app/composables/useBlockDrag.ts +90 -0
- package/app/composables/useBlockQueries.ts +154 -0
- package/app/composables/useBoardFlow.ts +11 -0
- package/app/composables/useContextLinking.ts +65 -0
- package/app/composables/useDepLabels.ts +26 -0
- package/app/composables/useFrameResize.ts +54 -0
- package/app/composables/useResultView.ts +48 -0
- package/app/composables/useReviewStage.ts +40 -0
- package/app/composables/useSemanticZoom.ts +31 -0
- package/app/composables/useStepApproval.ts +233 -0
- package/app/composables/useStepProse.ts +78 -0
- package/app/composables/useStepTimer.ts +63 -0
- package/app/composables/useTaskExpansion.ts +92 -0
- package/app/composables/useWorkspaceStream.ts +155 -0
- package/app/docs/architecture.md +31 -0
- package/app/pages/index.vue +141 -0
- package/app/stores/accounts.ts +152 -0
- package/app/stores/agentConfig.ts +35 -0
- package/app/stores/agentRuns.ts +122 -0
- package/app/stores/agents.ts +40 -0
- package/app/stores/apiKeys.ts +108 -0
- package/app/stores/auth.ts +166 -0
- package/app/stores/board.spec.ts +205 -0
- package/app/stores/board.ts +286 -0
- package/app/stores/bootstrap.ts +97 -0
- package/app/stores/clarity.ts +196 -0
- package/app/stores/consensus.ts +60 -0
- package/app/stores/documents.ts +176 -0
- package/app/stores/execution.ts +273 -0
- package/app/stores/fragmentLibrary.ts +147 -0
- package/app/stores/fragments.ts +40 -0
- package/app/stores/github.ts +305 -0
- package/app/stores/localModels.ts +51 -0
- package/app/stores/mergePresets.ts +58 -0
- package/app/stores/modelDefaults.ts +76 -0
- package/app/stores/models.ts +134 -0
- package/app/stores/notifications.ts +70 -0
- package/app/stores/observability.ts +144 -0
- package/app/stores/personalSubscriptions.ts +215 -0
- package/app/stores/pipelines.ts +327 -0
- package/app/stores/recurringPipelines.ts +112 -0
- package/app/stores/releaseHealth.ts +75 -0
- package/app/stores/requirements.spec.ts +94 -0
- package/app/stores/requirements.ts +208 -0
- package/app/stores/serviceFragmentDefaults.ts +29 -0
- package/app/stores/services.ts +87 -0
- package/app/stores/slack.ts +142 -0
- package/app/stores/taskExpansion.ts +36 -0
- package/app/stores/tasks.spec.ts +71 -0
- package/app/stores/tasks.ts +176 -0
- package/app/stores/tracker.ts +27 -0
- package/app/stores/ui.ts +434 -0
- package/app/stores/vendorCredentials.ts +54 -0
- package/app/stores/workspace.ts +215 -0
- package/app/stores/workspaceSettings.ts +36 -0
- package/app/types/accounts.ts +77 -0
- package/app/types/bootstrap.ts +83 -0
- package/app/types/clarity.ts +59 -0
- package/app/types/consensus.ts +91 -0
- package/app/types/documents.ts +104 -0
- package/app/types/domain.ts +495 -0
- package/app/types/execution.ts +383 -0
- package/app/types/fragments.ts +72 -0
- package/app/types/github.ts +173 -0
- package/app/types/localModels.ts +73 -0
- package/app/types/merge.ts +71 -0
- package/app/types/models.ts +157 -0
- package/app/types/notifications.ts +74 -0
- package/app/types/recurring.ts +69 -0
- package/app/types/releaseHealth.ts +31 -0
- package/app/types/requirements.ts +61 -0
- package/app/types/services.ts +27 -0
- package/app/types/slack.ts +57 -0
- package/app/types/tasks.ts +82 -0
- package/app/types/tracker.ts +18 -0
- package/app/utils/agentOutput.spec.ts +128 -0
- package/app/utils/agentOutput.ts +173 -0
- package/app/utils/catalog.spec.ts +112 -0
- package/app/utils/catalog.ts +455 -0
- package/app/utils/dnd.ts +29 -0
- package/app/utils/observability.ts +52 -0
- package/app/utils/pipelineRender.ts +151 -0
- package/nuxt.config.ts +55 -0
- package/package.json +45 -0
package/app/stores/ui.ts
ADDED
|
@@ -0,0 +1,434 @@
|
|
|
1
|
+
import { defineStore } from 'pinia'
|
|
2
|
+
import { ref, computed } from 'vue'
|
|
3
|
+
import type { DocumentSourceKind, TaskSourceKind, LodLevel } from '~/types/domain'
|
|
4
|
+
import { zoomToLod, lodAtLeast } from '~/composables/useSemanticZoom'
|
|
5
|
+
import { useExecutionStore } from '~/stores/execution'
|
|
6
|
+
import { agentKindMeta } from '~/utils/catalog'
|
|
7
|
+
|
|
8
|
+
/** Transient UI state: selection, panels, zoom level. */
|
|
9
|
+
export const useUiStore = defineStore('ui', () => {
|
|
10
|
+
const selectedBlockId = ref<string | null>(null)
|
|
11
|
+
const focusBlockId = ref<string | null>(null)
|
|
12
|
+
const builderOpen = ref(false)
|
|
13
|
+
const decisionContext = ref<{ instanceId: string; decisionId: string } | null>(null)
|
|
14
|
+
|
|
15
|
+
// Document-source integration modals, keyed by source. `documentImport` and
|
|
16
|
+
// `spawnPreview` carry an optional target frame, so structure spawned from a
|
|
17
|
+
// frame's inspector lands inside that frame rather than creating new top-level
|
|
18
|
+
// frames. `documentConnect` carries the source whose connect form to show;
|
|
19
|
+
// `documentImport`'s source may be null to let the modal pick a connected one.
|
|
20
|
+
const documentConnect = ref<{ source: DocumentSourceKind } | null>(null)
|
|
21
|
+
const documentImport = ref<{
|
|
22
|
+
source: DocumentSourceKind | null
|
|
23
|
+
targetFrameId: string | null
|
|
24
|
+
} | null>(null)
|
|
25
|
+
const spawnPreview = ref<{
|
|
26
|
+
source: DocumentSourceKind
|
|
27
|
+
externalId: string
|
|
28
|
+
targetFrameId: string | null
|
|
29
|
+
} | null>(null)
|
|
30
|
+
|
|
31
|
+
// Task-source integration modals, keyed by source. `taskConnect` carries the
|
|
32
|
+
// source whose connect form to show; `taskImport`'s source may be null to let
|
|
33
|
+
// the modal pick a connected one (there is no spawn target — issues are linked
|
|
34
|
+
// to a block for context, not expanded into structure).
|
|
35
|
+
const taskConnect = ref<{ source: TaskSourceKind } | null>(null)
|
|
36
|
+
const taskImport = ref<{ source: TaskSourceKind | null } | null>(null)
|
|
37
|
+
|
|
38
|
+
// Add-task modal: the container (service frame or module) a new task is being
|
|
39
|
+
// added to, or null when closed. The user types the title + description; nothing
|
|
40
|
+
// is launched until they explicitly start the created task.
|
|
41
|
+
const addTaskContainerId = ref<string | null>(null)
|
|
42
|
+
|
|
43
|
+
// Add-recurring-pipeline modal: the service frame a new recurring pipeline is
|
|
44
|
+
// being added to, or null when closed (mirrors the add-task flow — a button on
|
|
45
|
+
// the frame opens it, scoped to that frame).
|
|
46
|
+
const addRecurringFrameId = ref<string | null>(null)
|
|
47
|
+
|
|
48
|
+
// Repo-bootstrap modal (manage reference architectures + launch a bootstrap).
|
|
49
|
+
const bootstrapOpen = ref(false)
|
|
50
|
+
|
|
51
|
+
// "Add a service from an existing GitHub repo" modal (no bootstrap run).
|
|
52
|
+
const addServiceOpen = ref(false)
|
|
53
|
+
|
|
54
|
+
// GitHub integration panel (connection management + repo/PR/issue browsing).
|
|
55
|
+
const githubOpen = ref(false)
|
|
56
|
+
|
|
57
|
+
// Slack integration panel (connect the account's Slack + per-workspace routing).
|
|
58
|
+
const slackOpen = ref(false)
|
|
59
|
+
|
|
60
|
+
// Prompt-fragment library panel (manage the board's best-practice catalog +
|
|
61
|
+
// linked guideline repos; ADR 0006).
|
|
62
|
+
const fragmentLibraryOpen = ref(false)
|
|
63
|
+
|
|
64
|
+
// Command bar (⌘K) — searchable launcher for every navbar action.
|
|
65
|
+
const commandBarOpen = ref(false)
|
|
66
|
+
|
|
67
|
+
// Workspace-settings panels: merge-threshold preset library + per-agent-kind
|
|
68
|
+
// default model overrides.
|
|
69
|
+
const mergeThresholdsOpen = ref(false)
|
|
70
|
+
// Workspace-settings panel: the run-timing escalation threshold + per-service task limit.
|
|
71
|
+
const workspaceSettingsOpen = ref(false)
|
|
72
|
+
const datadogOpen = ref(false)
|
|
73
|
+
const modelDefaultsOpen = ref(false)
|
|
74
|
+
// Workspace-settings panel: the default service-fragment selection new services inherit.
|
|
75
|
+
const serviceFragmentDefaultsOpen = ref(false)
|
|
76
|
+
// LLM-vendor subscription credentials (the token pool powering the Claude Code
|
|
77
|
+
// / Codex harnesses).
|
|
78
|
+
const vendorCredentialsOpen = ref(false)
|
|
79
|
+
// Per-user settings panel: the signed-in user's own-machine local model runners.
|
|
80
|
+
const localModelsOpen = ref(false)
|
|
81
|
+
|
|
82
|
+
// Dedicated result-view overlay: a step whose agent kind declares a bespoke
|
|
83
|
+
// visualization (via the archetype's `resultView`) opens here instead of the generic
|
|
84
|
+
// prose step-detail panel. `view` is the registry id (e.g. 'requirements-review');
|
|
85
|
+
// `blockId` is always set; `instanceId`/`stepIndex` are present on the pipeline path and
|
|
86
|
+
// null for an off-path open (e.g. the inspector's pre-start requirements review).
|
|
87
|
+
const resultView = ref<{
|
|
88
|
+
view: string
|
|
89
|
+
blockId: string
|
|
90
|
+
instanceId: string | null
|
|
91
|
+
stepIndex: number | null
|
|
92
|
+
} | null>(null)
|
|
93
|
+
|
|
94
|
+
// Agent step-detail overlay: which pipeline step (a run instance + step index)
|
|
95
|
+
// a human is inspecting, or null when closed. The overlay resolves the step
|
|
96
|
+
// from the execution store so it stays live; it shows the step's metadata
|
|
97
|
+
// (model, state, progress, subtasks, …) and — when the agent produced prose —
|
|
98
|
+
// a reader for it (ToC + collapsible sections).
|
|
99
|
+
const stepDetail = ref<{ instanceId: string; stepIndex: number } | null>(null)
|
|
100
|
+
|
|
101
|
+
// LLM observability panel: which run (execution instance) a human is inspecting
|
|
102
|
+
// the per-call model activity for, or null when closed. The panel loads the full
|
|
103
|
+
// per-call detail from the observability store on open.
|
|
104
|
+
const observabilityInstanceId = ref<string | null>(null)
|
|
105
|
+
|
|
106
|
+
/** Current canvas zoom (driven by Vue Flow viewport). */
|
|
107
|
+
const zoom = ref(1)
|
|
108
|
+
|
|
109
|
+
const lod = computed<LodLevel>(() => zoomToLod(zoom.value))
|
|
110
|
+
|
|
111
|
+
/** Frames the user has manually expanded to reveal their tasks. */
|
|
112
|
+
const expandedFrames = ref<Set<string>>(new Set())
|
|
113
|
+
|
|
114
|
+
function toggleFrame(id: string) {
|
|
115
|
+
const next = new Set(expandedFrames.value)
|
|
116
|
+
if (next.has(id)) next.delete(id)
|
|
117
|
+
else next.add(id)
|
|
118
|
+
expandedFrames.value = next
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
function expandFrame(id: string) {
|
|
122
|
+
if (expandedFrames.value.has(id)) return
|
|
123
|
+
expandedFrames.value = new Set(expandedFrames.value).add(id)
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/** A frame shows its tasks when manually expanded OR once zoomed in to `close`
|
|
127
|
+
* or any deeper band (`steps`/`subtasks` drill further into those tasks). */
|
|
128
|
+
function isFrameExpanded(id: string) {
|
|
129
|
+
return expandedFrames.value.has(id) || lodAtLeast(lod.value, 'close')
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
function select(id: string | null) {
|
|
133
|
+
selectedBlockId.value = id
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
function focus(id: string | null) {
|
|
137
|
+
focusBlockId.value = id
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
function openBuilder() {
|
|
141
|
+
builderOpen.value = true
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
function openDecision(instanceId: string, decisionId: string) {
|
|
145
|
+
decisionContext.value = { instanceId, decisionId }
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
function closeDecision() {
|
|
149
|
+
decisionContext.value = null
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* Open a pending approval gate in the conclusions reader (approval mode). Resolves
|
|
154
|
+
* the step index from the gate id so every board/inspector entry point can keep
|
|
155
|
+
* passing the approval id it already has.
|
|
156
|
+
*/
|
|
157
|
+
function openApprovalDetail(instanceId: string, approvalId: string) {
|
|
158
|
+
const execution = useExecutionStore()
|
|
159
|
+
const instance = execution.getInstance(instanceId)
|
|
160
|
+
const idx = instance?.steps.findIndex((s) => s.approval?.id === approvalId) ?? -1
|
|
161
|
+
if (idx >= 0) dispatchStepView(instanceId, idx)
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
/**
|
|
165
|
+
* Open a pipeline step: route it to its agent kind's DEDICATED result window when the
|
|
166
|
+
* archetype declares one (the universal `resultView` seam), else the generic prose
|
|
167
|
+
* step-detail panel. This is the single dispatch every board/inspector entry point uses,
|
|
168
|
+
* so adding a bespoke window for a new agent is just declaring `resultView` + registering
|
|
169
|
+
* a component — no caller changes.
|
|
170
|
+
*/
|
|
171
|
+
function dispatchStepView(instanceId: string, stepIndex: number) {
|
|
172
|
+
const execution = useExecutionStore()
|
|
173
|
+
const instance = execution.getInstance(instanceId)
|
|
174
|
+
const step = instance?.steps[stepIndex]
|
|
175
|
+
// A step that actually ran the consensus mechanism opens the dedicated Consensus
|
|
176
|
+
// Session window, regardless of its kind's normal result view — consensus is an
|
|
177
|
+
// execution MODE on a kind, not a kind, so it can't be a static archetype `resultView`.
|
|
178
|
+
const view = step?.consensus?.enabled
|
|
179
|
+
? 'consensus-session'
|
|
180
|
+
: step
|
|
181
|
+
? agentKindMeta(step.agentKind).resultView
|
|
182
|
+
: undefined
|
|
183
|
+
if (view && instance) {
|
|
184
|
+
resultView.value = { view, blockId: instance.blockId, instanceId, stepIndex }
|
|
185
|
+
return
|
|
186
|
+
}
|
|
187
|
+
stepDetail.value = { instanceId, stepIndex }
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
function openDocumentConnect(source: DocumentSourceKind) {
|
|
191
|
+
documentConnect.value = { source }
|
|
192
|
+
}
|
|
193
|
+
function closeDocumentConnect() {
|
|
194
|
+
documentConnect.value = null
|
|
195
|
+
}
|
|
196
|
+
function openDocumentImport(
|
|
197
|
+
targetFrameId: string | null = null,
|
|
198
|
+
source: DocumentSourceKind | null = null,
|
|
199
|
+
) {
|
|
200
|
+
documentImport.value = { source, targetFrameId }
|
|
201
|
+
}
|
|
202
|
+
function closeDocumentImport() {
|
|
203
|
+
documentImport.value = null
|
|
204
|
+
}
|
|
205
|
+
function openSpawnPreview(
|
|
206
|
+
source: DocumentSourceKind,
|
|
207
|
+
externalId: string,
|
|
208
|
+
targetFrameId: string | null = null,
|
|
209
|
+
) {
|
|
210
|
+
spawnPreview.value = { source, externalId, targetFrameId }
|
|
211
|
+
}
|
|
212
|
+
function closeSpawnPreview() {
|
|
213
|
+
spawnPreview.value = null
|
|
214
|
+
}
|
|
215
|
+
function openTaskConnect(source: TaskSourceKind) {
|
|
216
|
+
taskConnect.value = { source }
|
|
217
|
+
}
|
|
218
|
+
function closeTaskConnect() {
|
|
219
|
+
taskConnect.value = null
|
|
220
|
+
}
|
|
221
|
+
function openTaskImport(source: TaskSourceKind | null = null) {
|
|
222
|
+
taskImport.value = { source }
|
|
223
|
+
}
|
|
224
|
+
function closeTaskImport() {
|
|
225
|
+
taskImport.value = null
|
|
226
|
+
}
|
|
227
|
+
function openAddTask(containerId: string) {
|
|
228
|
+
addTaskContainerId.value = containerId
|
|
229
|
+
}
|
|
230
|
+
function closeAddTask() {
|
|
231
|
+
addTaskContainerId.value = null
|
|
232
|
+
}
|
|
233
|
+
function openAddRecurring(frameId: string) {
|
|
234
|
+
addRecurringFrameId.value = frameId
|
|
235
|
+
}
|
|
236
|
+
function closeAddRecurring() {
|
|
237
|
+
addRecurringFrameId.value = null
|
|
238
|
+
}
|
|
239
|
+
function openBootstrap() {
|
|
240
|
+
bootstrapOpen.value = true
|
|
241
|
+
}
|
|
242
|
+
function closeBootstrap() {
|
|
243
|
+
bootstrapOpen.value = false
|
|
244
|
+
}
|
|
245
|
+
function openAddService() {
|
|
246
|
+
addServiceOpen.value = true
|
|
247
|
+
}
|
|
248
|
+
function closeAddService() {
|
|
249
|
+
addServiceOpen.value = false
|
|
250
|
+
}
|
|
251
|
+
function openGitHub() {
|
|
252
|
+
githubOpen.value = true
|
|
253
|
+
}
|
|
254
|
+
function closeGitHub() {
|
|
255
|
+
githubOpen.value = false
|
|
256
|
+
}
|
|
257
|
+
function openSlack() {
|
|
258
|
+
slackOpen.value = true
|
|
259
|
+
}
|
|
260
|
+
function closeSlack() {
|
|
261
|
+
slackOpen.value = false
|
|
262
|
+
}
|
|
263
|
+
function openFragmentLibrary() {
|
|
264
|
+
fragmentLibraryOpen.value = true
|
|
265
|
+
}
|
|
266
|
+
function closeFragmentLibrary() {
|
|
267
|
+
fragmentLibraryOpen.value = false
|
|
268
|
+
}
|
|
269
|
+
function openCommandBar() {
|
|
270
|
+
commandBarOpen.value = true
|
|
271
|
+
}
|
|
272
|
+
function closeCommandBar() {
|
|
273
|
+
commandBarOpen.value = false
|
|
274
|
+
}
|
|
275
|
+
function toggleCommandBar() {
|
|
276
|
+
commandBarOpen.value = !commandBarOpen.value
|
|
277
|
+
}
|
|
278
|
+
function openMergeThresholds() {
|
|
279
|
+
mergeThresholdsOpen.value = true
|
|
280
|
+
}
|
|
281
|
+
function closeMergeThresholds() {
|
|
282
|
+
mergeThresholdsOpen.value = false
|
|
283
|
+
}
|
|
284
|
+
function openWorkspaceSettings() {
|
|
285
|
+
workspaceSettingsOpen.value = true
|
|
286
|
+
}
|
|
287
|
+
function closeWorkspaceSettings() {
|
|
288
|
+
workspaceSettingsOpen.value = false
|
|
289
|
+
}
|
|
290
|
+
function openDatadog() {
|
|
291
|
+
datadogOpen.value = true
|
|
292
|
+
}
|
|
293
|
+
function closeDatadog() {
|
|
294
|
+
datadogOpen.value = false
|
|
295
|
+
}
|
|
296
|
+
function openModelDefaults() {
|
|
297
|
+
modelDefaultsOpen.value = true
|
|
298
|
+
}
|
|
299
|
+
function closeModelDefaults() {
|
|
300
|
+
modelDefaultsOpen.value = false
|
|
301
|
+
}
|
|
302
|
+
function openServiceFragmentDefaults() {
|
|
303
|
+
serviceFragmentDefaultsOpen.value = true
|
|
304
|
+
}
|
|
305
|
+
function closeServiceFragmentDefaults() {
|
|
306
|
+
serviceFragmentDefaultsOpen.value = false
|
|
307
|
+
}
|
|
308
|
+
function openVendorCredentials() {
|
|
309
|
+
vendorCredentialsOpen.value = true
|
|
310
|
+
}
|
|
311
|
+
function closeVendorCredentials() {
|
|
312
|
+
vendorCredentialsOpen.value = false
|
|
313
|
+
}
|
|
314
|
+
function openLocalModels() {
|
|
315
|
+
localModelsOpen.value = true
|
|
316
|
+
}
|
|
317
|
+
function closeLocalModels() {
|
|
318
|
+
localModelsOpen.value = false
|
|
319
|
+
}
|
|
320
|
+
function openRequirementReview(blockId: string) {
|
|
321
|
+
resultView.value = { view: 'requirements-review', blockId, instanceId: null, stepIndex: null }
|
|
322
|
+
}
|
|
323
|
+
function openClarityReview(blockId: string) {
|
|
324
|
+
resultView.value = { view: 'clarity-review', blockId, instanceId: null, stepIndex: null }
|
|
325
|
+
}
|
|
326
|
+
function closeResultView() {
|
|
327
|
+
resultView.value = null
|
|
328
|
+
}
|
|
329
|
+
// Kept name for the requirements window's close handler.
|
|
330
|
+
const closeRequirementReview = closeResultView
|
|
331
|
+
function openStepDetail(instanceId: string, stepIndex: number) {
|
|
332
|
+
dispatchStepView(instanceId, stepIndex)
|
|
333
|
+
}
|
|
334
|
+
function closeStepDetail() {
|
|
335
|
+
stepDetail.value = null
|
|
336
|
+
}
|
|
337
|
+
function openObservability(instanceId: string) {
|
|
338
|
+
observabilityInstanceId.value = instanceId
|
|
339
|
+
}
|
|
340
|
+
function closeObservability() {
|
|
341
|
+
observabilityInstanceId.value = null
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
return {
|
|
345
|
+
selectedBlockId,
|
|
346
|
+
focusBlockId,
|
|
347
|
+
builderOpen,
|
|
348
|
+
decisionContext,
|
|
349
|
+
documentConnect,
|
|
350
|
+
documentImport,
|
|
351
|
+
spawnPreview,
|
|
352
|
+
taskConnect,
|
|
353
|
+
taskImport,
|
|
354
|
+
addTaskContainerId,
|
|
355
|
+
addRecurringFrameId,
|
|
356
|
+
bootstrapOpen,
|
|
357
|
+
addServiceOpen,
|
|
358
|
+
githubOpen,
|
|
359
|
+
slackOpen,
|
|
360
|
+
fragmentLibraryOpen,
|
|
361
|
+
commandBarOpen,
|
|
362
|
+
mergeThresholdsOpen,
|
|
363
|
+
workspaceSettingsOpen,
|
|
364
|
+
datadogOpen,
|
|
365
|
+
modelDefaultsOpen,
|
|
366
|
+
serviceFragmentDefaultsOpen,
|
|
367
|
+
vendorCredentialsOpen,
|
|
368
|
+
localModelsOpen,
|
|
369
|
+
resultView,
|
|
370
|
+
closeResultView,
|
|
371
|
+
stepDetail,
|
|
372
|
+
observabilityInstanceId,
|
|
373
|
+
zoom,
|
|
374
|
+
lod,
|
|
375
|
+
expandedFrames,
|
|
376
|
+
toggleFrame,
|
|
377
|
+
expandFrame,
|
|
378
|
+
isFrameExpanded,
|
|
379
|
+
select,
|
|
380
|
+
focus,
|
|
381
|
+
openBuilder,
|
|
382
|
+
openDecision,
|
|
383
|
+
closeDecision,
|
|
384
|
+
openApprovalDetail,
|
|
385
|
+
openDocumentConnect,
|
|
386
|
+
closeDocumentConnect,
|
|
387
|
+
openDocumentImport,
|
|
388
|
+
closeDocumentImport,
|
|
389
|
+
openSpawnPreview,
|
|
390
|
+
closeSpawnPreview,
|
|
391
|
+
openTaskConnect,
|
|
392
|
+
closeTaskConnect,
|
|
393
|
+
openTaskImport,
|
|
394
|
+
closeTaskImport,
|
|
395
|
+
openAddTask,
|
|
396
|
+
closeAddTask,
|
|
397
|
+
openAddRecurring,
|
|
398
|
+
closeAddRecurring,
|
|
399
|
+
openBootstrap,
|
|
400
|
+
closeBootstrap,
|
|
401
|
+
openAddService,
|
|
402
|
+
closeAddService,
|
|
403
|
+
openGitHub,
|
|
404
|
+
closeGitHub,
|
|
405
|
+
openSlack,
|
|
406
|
+
closeSlack,
|
|
407
|
+
openFragmentLibrary,
|
|
408
|
+
closeFragmentLibrary,
|
|
409
|
+
openCommandBar,
|
|
410
|
+
closeCommandBar,
|
|
411
|
+
toggleCommandBar,
|
|
412
|
+
openMergeThresholds,
|
|
413
|
+
closeMergeThresholds,
|
|
414
|
+
openWorkspaceSettings,
|
|
415
|
+
closeWorkspaceSettings,
|
|
416
|
+
openDatadog,
|
|
417
|
+
closeDatadog,
|
|
418
|
+
openModelDefaults,
|
|
419
|
+
closeModelDefaults,
|
|
420
|
+
openServiceFragmentDefaults,
|
|
421
|
+
closeServiceFragmentDefaults,
|
|
422
|
+
openVendorCredentials,
|
|
423
|
+
closeVendorCredentials,
|
|
424
|
+
openLocalModels,
|
|
425
|
+
closeLocalModels,
|
|
426
|
+
openRequirementReview,
|
|
427
|
+
openClarityReview,
|
|
428
|
+
closeRequirementReview,
|
|
429
|
+
openStepDetail,
|
|
430
|
+
closeStepDetail,
|
|
431
|
+
openObservability,
|
|
432
|
+
closeObservability,
|
|
433
|
+
}
|
|
434
|
+
})
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { defineStore } from 'pinia'
|
|
2
|
+
import { computed, ref } from 'vue'
|
|
3
|
+
import type { SubscriptionVendor, VendorCredential } from '~/types/domain'
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* The workspace's connected LLM-vendor subscription credentials (the token pool
|
|
7
|
+
* powering the Claude Code / Codex harnesses). Loaded from
|
|
8
|
+
* `GET /workspaces/:ws/vendor-credentials`; tokens are write-only so only
|
|
9
|
+
* metadata + rolling-window usage is ever returned. `configuredVendors` drives
|
|
10
|
+
* the model picker: a dual-mode model (GLM/Kimi) collapses to its subscription
|
|
11
|
+
* flavour, and a subscription-only model is enabled, once its vendor is here.
|
|
12
|
+
*/
|
|
13
|
+
export const useVendorCredentialsStore = defineStore('vendorCredentials', () => {
|
|
14
|
+
const api = useApi()
|
|
15
|
+
const credentials = ref<VendorCredential[]>([])
|
|
16
|
+
const workspaceId = ref<string | null>(null)
|
|
17
|
+
const loading = ref(false)
|
|
18
|
+
|
|
19
|
+
async function load(ws: string) {
|
|
20
|
+
workspaceId.value = ws
|
|
21
|
+
loading.value = true
|
|
22
|
+
try {
|
|
23
|
+
const { credentials: list } = await api.listVendorCredentials(ws)
|
|
24
|
+
credentials.value = list
|
|
25
|
+
} finally {
|
|
26
|
+
loading.value = false
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
async function add(input: { vendor: SubscriptionVendor; label: string; token: string }) {
|
|
31
|
+
if (!workspaceId.value) return
|
|
32
|
+
const created = await api.addVendorCredential(workspaceId.value, input)
|
|
33
|
+
credentials.value = [...credentials.value, created]
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
async function remove(id: string) {
|
|
37
|
+
if (!workspaceId.value) return
|
|
38
|
+
await api.removeVendorCredential(workspaceId.value, id)
|
|
39
|
+
credentials.value = credentials.value.filter((c) => c.id !== id)
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/** The set of vendors with at least one connected token. */
|
|
43
|
+
const configuredVendors = computed(() => new Set(credentials.value.map((c) => c.vendor)))
|
|
44
|
+
|
|
45
|
+
function hasVendor(vendor: SubscriptionVendor | undefined): boolean {
|
|
46
|
+
return vendor ? configuredVendors.value.has(vendor) : false
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function forVendor(vendor: SubscriptionVendor) {
|
|
50
|
+
return credentials.value.filter((c) => c.vendor === vendor)
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
return { credentials, loading, load, add, remove, configuredVendors, hasVendor, forVendor }
|
|
54
|
+
})
|
|
@@ -0,0 +1,215 @@
|
|
|
1
|
+
import { defineStore } from 'pinia'
|
|
2
|
+
import { computed, ref } from 'vue'
|
|
3
|
+
import type { SpendStatus, Workspace, WorkspaceSnapshot } from '~/types/domain'
|
|
4
|
+
import { useAccountsStore } from '~/stores/accounts'
|
|
5
|
+
import { useBoardStore } from '~/stores/board'
|
|
6
|
+
import { usePipelinesStore } from '~/stores/pipelines'
|
|
7
|
+
import { useExecutionStore } from '~/stores/execution'
|
|
8
|
+
import { useAgentRunsStore } from '~/stores/agentRuns'
|
|
9
|
+
import { useNotificationsStore } from '~/stores/notifications'
|
|
10
|
+
import { useMergePresetsStore } from '~/stores/mergePresets'
|
|
11
|
+
import { useWorkspaceSettingsStore } from '~/stores/workspaceSettings'
|
|
12
|
+
import { useAgentConfigStore } from '~/stores/agentConfig'
|
|
13
|
+
import { useModelDefaultsStore } from '~/stores/modelDefaults'
|
|
14
|
+
import { useServiceFragmentDefaultsStore } from '~/stores/serviceFragmentDefaults'
|
|
15
|
+
import { useRecurringPipelinesStore } from '~/stores/recurringPipelines'
|
|
16
|
+
import { useServicesStore } from '~/stores/services'
|
|
17
|
+
import { useTrackerStore } from '~/stores/tracker'
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Owns the active workspace and bootstraps the app against the backend. On load
|
|
21
|
+
* it resolves the user's accounts, lists the boards in the active account, opens
|
|
22
|
+
* the persisted one (or the first, or a fresh seeded board), and hydrates the
|
|
23
|
+
* board / pipelines / execution stores from its snapshot.
|
|
24
|
+
*
|
|
25
|
+
* Boards are scoped to an account: switching account re-scopes the board list,
|
|
26
|
+
* and new boards are stamped with the active account so a team can keep org
|
|
27
|
+
* boards separate from personal ones. Only the active workspace id is persisted —
|
|
28
|
+
* all board data lives on the server.
|
|
29
|
+
*/
|
|
30
|
+
export const useWorkspaceStore = defineStore(
|
|
31
|
+
'workspace',
|
|
32
|
+
() => {
|
|
33
|
+
const api = useApi()
|
|
34
|
+
|
|
35
|
+
/** Active workspace id (persisted so a reload reopens the same board). */
|
|
36
|
+
const workspaceId = ref<string | null>(null)
|
|
37
|
+
/** Every board visible to the user, across the accounts they belong to. */
|
|
38
|
+
const workspaces = ref<Workspace[]>([])
|
|
39
|
+
/** True once the initial snapshot has been loaded and stores hydrated. */
|
|
40
|
+
const ready = ref(false)
|
|
41
|
+
/** Set when bootstrap fails so the UI can show a retry. */
|
|
42
|
+
const error = ref<string | null>(null)
|
|
43
|
+
/** Latest spend-safeguard status from the server (null until first load). */
|
|
44
|
+
const spend = ref<SpendStatus | null>(null)
|
|
45
|
+
|
|
46
|
+
/** The boards belonging to the active account (all boards when auth is off). */
|
|
47
|
+
const accountWorkspaces = computed(() => {
|
|
48
|
+
const accounts = useAccountsStore()
|
|
49
|
+
if (!accounts.enabled || !accounts.activeAccountId) return workspaces.value
|
|
50
|
+
return workspaces.value.filter((w) => w.accountId === accounts.activeAccountId)
|
|
51
|
+
})
|
|
52
|
+
|
|
53
|
+
/** The active board's row (for the switcher label). */
|
|
54
|
+
const activeWorkspace = computed(
|
|
55
|
+
() => workspaces.value.find((w) => w.id === workspaceId.value) ?? null,
|
|
56
|
+
)
|
|
57
|
+
|
|
58
|
+
/** Push a snapshot into the data stores. */
|
|
59
|
+
function hydrate(snapshot: WorkspaceSnapshot) {
|
|
60
|
+
workspaceId.value = snapshot.workspace.id
|
|
61
|
+
spend.value = snapshot.spend ?? null
|
|
62
|
+
// Keep the board list in step (e.g. a freshly created board, or a rename).
|
|
63
|
+
const i = workspaces.value.findIndex((w) => w.id === snapshot.workspace.id)
|
|
64
|
+
if (i >= 0) workspaces.value[i] = snapshot.workspace
|
|
65
|
+
else workspaces.value.unshift(snapshot.workspace)
|
|
66
|
+
useBoardStore().hydrate(snapshot.blocks)
|
|
67
|
+
usePipelinesStore().hydrate(snapshot.pipelines)
|
|
68
|
+
useExecutionStore().hydrate(snapshot.executions)
|
|
69
|
+
useAgentRunsStore().hydrate(snapshot.bootstrapJobs ?? [])
|
|
70
|
+
useNotificationsStore().hydrate(snapshot.notifications ?? [])
|
|
71
|
+
useMergePresetsStore().hydrate(snapshot.mergePresets ?? [])
|
|
72
|
+
useWorkspaceSettingsStore().hydrate(snapshot.settings)
|
|
73
|
+
useAgentConfigStore().hydrate(snapshot.agentConfigCatalog ?? [])
|
|
74
|
+
useModelDefaultsStore().hydrate(snapshot.modelDefaults?.defaults)
|
|
75
|
+
useModelDefaultsStore().hydrateDeployment(snapshot.deploymentModelDefaults)
|
|
76
|
+
useServiceFragmentDefaultsStore().hydrate(snapshot.serviceFragmentDefaults?.fragmentIds)
|
|
77
|
+
useRecurringPipelinesStore().hydrate(snapshot.recurringPipelines ?? [])
|
|
78
|
+
useTrackerStore().hydrate(snapshot.trackerSettings)
|
|
79
|
+
useServicesStore().hydrate(snapshot.mounts ?? [], snapshot.serviceCatalog ?? [])
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/** Resolve accounts + boards, then open the right board for the active account. */
|
|
83
|
+
async function init() {
|
|
84
|
+
ready.value = false
|
|
85
|
+
error.value = null
|
|
86
|
+
try {
|
|
87
|
+
// Accounts are an auth concept — empty in dev, which leaves boards unscoped.
|
|
88
|
+
await useAccountsStore()
|
|
89
|
+
.load()
|
|
90
|
+
.catch(() => {})
|
|
91
|
+
workspaces.value = await api.listWorkspaces()
|
|
92
|
+
await resolveActiveBoard()
|
|
93
|
+
ready.value = true
|
|
94
|
+
} catch (e) {
|
|
95
|
+
error.value = e instanceof Error ? e.message : 'Failed to reach the backend.'
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/** Open the persisted board (aligning the active account to it), else pick/create one. */
|
|
100
|
+
async function resolveActiveBoard() {
|
|
101
|
+
const accounts = useAccountsStore()
|
|
102
|
+
if (workspaceId.value) {
|
|
103
|
+
const existing = workspaces.value.find((w) => w.id === workspaceId.value)
|
|
104
|
+
if (existing) {
|
|
105
|
+
if (accounts.enabled && existing.accountId) accounts.activeAccountId = existing.accountId
|
|
106
|
+
hydrate(await api.getWorkspace(existing.id))
|
|
107
|
+
return
|
|
108
|
+
}
|
|
109
|
+
// Persisted board is gone (deleted, or now another tenant's) — fall through.
|
|
110
|
+
workspaceId.value = null
|
|
111
|
+
}
|
|
112
|
+
const first = accountWorkspaces.value[0]
|
|
113
|
+
if (first) {
|
|
114
|
+
hydrate(await api.getWorkspace(first.id))
|
|
115
|
+
} else {
|
|
116
|
+
hydrate(
|
|
117
|
+
await api.createWorkspace({
|
|
118
|
+
seed: false,
|
|
119
|
+
accountId: accounts.activeAccountId ?? undefined,
|
|
120
|
+
}),
|
|
121
|
+
)
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/** Switch to another board (within reach of the active account). */
|
|
126
|
+
async function switchTo(id: string) {
|
|
127
|
+
if (id === workspaceId.value) return
|
|
128
|
+
hydrate(await api.getWorkspace(id))
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/** Switch the active account, then open one of its boards (creating one if needed). */
|
|
132
|
+
async function selectAccount(id: string) {
|
|
133
|
+
const accounts = useAccountsStore()
|
|
134
|
+
if (id === accounts.activeAccountId) return
|
|
135
|
+
accounts.switchTo(id)
|
|
136
|
+
workspaceId.value = null
|
|
137
|
+
await resolveActiveBoard()
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/** Create a new board in the active account and open it. */
|
|
141
|
+
async function create(name?: string, description?: string) {
|
|
142
|
+
const accounts = useAccountsStore()
|
|
143
|
+
const snapshot = await api.createWorkspace({
|
|
144
|
+
seed: false,
|
|
145
|
+
name,
|
|
146
|
+
description,
|
|
147
|
+
accountId: accounts.activeAccountId ?? undefined,
|
|
148
|
+
})
|
|
149
|
+
hydrate(snapshot)
|
|
150
|
+
return snapshot.workspace
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
/** Rename a board and/or update its description. */
|
|
154
|
+
async function update(id: string, patch: { name?: string; description?: string | null }) {
|
|
155
|
+
const updated = await api.updateWorkspace(id, patch)
|
|
156
|
+
const i = workspaces.value.findIndex((w) => w.id === id)
|
|
157
|
+
if (i >= 0) workspaces.value[i] = updated
|
|
158
|
+
return updated
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
/** Rename a board (kept for the existing rename callers). */
|
|
162
|
+
async function rename(id: string, name: string) {
|
|
163
|
+
return update(id, { name })
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
/** Delete a board; if it was active, fall back to another in the account. */
|
|
167
|
+
async function remove(id: string) {
|
|
168
|
+
await api.deleteWorkspace(id)
|
|
169
|
+
workspaces.value = workspaces.value.filter((w) => w.id !== id)
|
|
170
|
+
if (workspaceId.value === id) {
|
|
171
|
+
workspaceId.value = null
|
|
172
|
+
await resolveActiveBoard()
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
/** Re-fetch the snapshot and re-hydrate (after mutations and on stream (re)connect). */
|
|
177
|
+
async function refresh() {
|
|
178
|
+
if (!workspaceId.value) return
|
|
179
|
+
hydrate(await api.getWorkspace(workspaceId.value))
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
/** The active workspace id, or throw if the app isn't bootstrapped yet. */
|
|
183
|
+
function requireId(): string {
|
|
184
|
+
if (!workspaceId.value) throw new Error('No active workspace')
|
|
185
|
+
return workspaceId.value
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
/** Resume runs paused by the spend safeguard, then refresh the snapshot. */
|
|
189
|
+
async function resumeSpend() {
|
|
190
|
+
await api.resumeSpend(requireId())
|
|
191
|
+
await refresh()
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
return {
|
|
195
|
+
workspaceId,
|
|
196
|
+
workspaces,
|
|
197
|
+
accountWorkspaces,
|
|
198
|
+
activeWorkspace,
|
|
199
|
+
ready,
|
|
200
|
+
error,
|
|
201
|
+
spend,
|
|
202
|
+
init,
|
|
203
|
+
switchTo,
|
|
204
|
+
selectAccount,
|
|
205
|
+
create,
|
|
206
|
+
update,
|
|
207
|
+
rename,
|
|
208
|
+
remove,
|
|
209
|
+
refresh,
|
|
210
|
+
requireId,
|
|
211
|
+
resumeSpend,
|
|
212
|
+
}
|
|
213
|
+
},
|
|
214
|
+
{ persist: { pick: ['workspaceId'] } },
|
|
215
|
+
)
|