@morscherlab/mint-sdk 1.0.63 → 1.1.0-beta.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (165) hide show
  1. package/README.md +101 -2
  2. package/dist/EmptyState-D1c4RdC7.js +638 -0
  3. package/dist/EmptyState-D1c4RdC7.js.map +1 -0
  4. package/dist/{ExperimentPopover-BbPkIFsI.js → ExperimentPopover-BFw3QX8L.js} +10 -9
  5. package/dist/{ExperimentPopover-BbPkIFsI.js.map → ExperimentPopover-BFw3QX8L.js.map} +1 -1
  6. package/dist/{ExperimentSelectorModal-DZeSd-yr.js → ExperimentSelectorModal-B7w6k3Y8.js} +12 -216
  7. package/dist/ExperimentSelectorModal-B7w6k3Y8.js.map +1 -0
  8. package/dist/{SettingsModal-CJgLadsN.js → SettingsModal-DPDwCbXK.js} +12 -9
  9. package/dist/{SettingsModal-CJgLadsN.js.map → SettingsModal-DPDwCbXK.js.map} +1 -1
  10. package/dist/__tests__/components/GeneratedPlotlyResult.test.d.ts +1 -0
  11. package/dist/__tests__/components/GeneratedPluginView.test.d.ts +1 -0
  12. package/dist/__tests__/components/GeneratedResultRenderer.test.d.ts +1 -0
  13. package/dist/__tests__/components/InstrumentAlertLog.test.d.ts +1 -0
  14. package/dist/__tests__/composables/useGeneratedAnalysis.test.d.ts +1 -0
  15. package/dist/__tests__/generatedUi.test.d.ts +1 -0
  16. package/dist/{analysisArtifactTypes-Cu40dzSG.js → analysisArtifactTypes-ejqR8MX3.js} +1 -1
  17. package/dist/analysisArtifactTypes-ejqR8MX3.js.map +1 -0
  18. package/dist/{auth-DZAwEeis.js → auth-B87xM--s.js} +1 -1
  19. package/dist/{auth-DZAwEeis.js.map → auth-B87xM--s.js.map} +1 -1
  20. package/dist/chunk-BoAXSpZd.js +33 -0
  21. package/dist/components/AppSidebar.vue.d.ts +1 -1
  22. package/dist/components/AutoGroupModal.vue.d.ts +2 -2
  23. package/dist/components/BioTemplateExperimentWorkspaceView.vue.d.ts +2 -2
  24. package/dist/components/CollapsibleCard.vue.d.ts +1 -1
  25. package/dist/components/ComponentBindingRenderer.vue.d.ts +1 -1
  26. package/dist/components/ConfirmDialog.vue.d.ts +1 -1
  27. package/dist/components/ExperimentDataViewer.vue.d.ts +1 -1
  28. package/dist/components/ExperimentTimeline.vue.d.ts +1 -1
  29. package/dist/components/FileUploader.vue.d.ts +4 -2
  30. package/dist/components/FitPanel.vue.d.ts +1 -1
  31. package/dist/components/FormActions.vue.d.ts +1 -1
  32. package/dist/components/FormBuilder.vue.d.ts +1 -1
  33. package/dist/components/GeneratedPathInput.vue.d.ts +12 -0
  34. package/dist/components/GeneratedPlotlyResult.vue.d.ts +11 -0
  35. package/dist/components/GeneratedPluginView.vue.d.ts +10 -0
  36. package/dist/components/GeneratedResultRenderer.vue.d.ts +6 -0
  37. package/dist/components/JobsStatusTray.vue.d.ts +5 -1
  38. package/dist/components/LcmsSequenceTable.vue.d.ts +1 -1
  39. package/dist/components/SampleLegend.vue.d.ts +1 -1
  40. package/dist/components/SampleSelector.vue.d.ts +2 -2
  41. package/dist/components/SettingsModal.vue.d.ts +1 -1
  42. package/dist/components/SmartGroupFieldRecipe.vue.d.ts +2 -2
  43. package/dist/components/index.d.ts +4 -0
  44. package/dist/components/index.js +8 -9
  45. package/dist/components/internal/PlateMapEditorToolbarInternal.vue.d.ts +2 -2
  46. package/dist/{components-DdzHh3a_.js → components-UfkPN1bK.js} +2255 -2303
  47. package/dist/components-UfkPN1bK.js.map +1 -0
  48. package/dist/composables/index.d.ts +2 -3
  49. package/dist/composables/index.js +10 -11
  50. package/dist/composables/platformContextHelpers.d.ts +0 -2
  51. package/dist/composables/useAnalysisArtifacts.d.ts +1 -3
  52. package/dist/composables/useGeneratedAnalysis.d.ts +62 -0
  53. package/dist/composables/useJobsStatusTray.d.ts +48 -4
  54. package/dist/composables/usePlatformContext.d.ts +3 -0
  55. package/dist/composables/usePluginClient.d.ts +6 -3
  56. package/dist/{composables-BUUphBqg.js → composables-B512bWJP.js} +155 -74
  57. package/dist/composables-B512bWJP.js.map +1 -0
  58. package/dist/experiment-utils-CxkruZ3G.js +392 -0
  59. package/dist/experiment-utils-CxkruZ3G.js.map +1 -0
  60. package/dist/generated/schema.d.ts +7 -0
  61. package/dist/generated-ui/main.d.ts +0 -0
  62. package/dist/index.d.ts +1 -0
  63. package/dist/index.js +15 -18
  64. package/dist/index.js.map +1 -1
  65. package/dist/install.js +7 -8
  66. package/dist/install.js.map +1 -1
  67. package/dist/plotly.min-BR53hja4.js +180634 -0
  68. package/dist/plotly.min-BR53hja4.js.map +1 -0
  69. package/dist/stores/auth.d.ts +2 -2
  70. package/dist/stores/index.js +1 -1
  71. package/dist/styles.css +10543 -10404
  72. package/dist/templates/index.js +4 -3
  73. package/dist/templates/packs.d.ts +0 -1
  74. package/dist/{templates-CNbPQNID.js → templates-Cttbjbyc.js} +2 -4
  75. package/dist/templates-Cttbjbyc.js.map +1 -0
  76. package/dist/types/analysisArtifactTypes.d.ts +0 -8
  77. package/dist/types/form-builder.d.ts +1 -1
  78. package/dist/types/generated-ui.d.ts +170 -0
  79. package/dist/types/index.d.ts +2 -1
  80. package/dist/types/index.js +1 -1
  81. package/dist/types/instrument.d.ts +1 -0
  82. package/dist/types/jobs.d.ts +7 -1
  83. package/dist/types/platform.d.ts +2 -0
  84. package/dist/{useControlSchema-BZNdalmL.js → useControlSchema-Dallso-L.js} +1852 -171
  85. package/dist/useControlSchema-Dallso-L.js.map +1 -0
  86. package/dist/{useDropdownState-Bb-c_PeE.js → useDropdownState-gVaMXtwW.js} +2 -2
  87. package/dist/{useDropdownState-Bb-c_PeE.js.map → useDropdownState-gVaMXtwW.js.map} +1 -1
  88. package/dist/{useExperimentSelector-DRbj6vse.js → useExperimentSelector-BMjzNcf2.js} +4 -255
  89. package/dist/useExperimentSelector-BMjzNcf2.js.map +1 -0
  90. package/dist/{useFormBuilder-DKekvXRs.js → useFormBuilder-BXaaDI41.js} +2 -2
  91. package/dist/{useFormBuilder-DKekvXRs.js.map → useFormBuilder-BXaaDI41.js.map} +1 -1
  92. package/dist/{useAnalysisArtifacts-C3l0a0vu.js → useProtocolTemplates-BP7_4RGQ.js} +307 -475
  93. package/dist/useProtocolTemplates-BP7_4RGQ.js.map +1 -0
  94. package/package.json +7 -3
  95. package/src/__tests__/components/GeneratedPathInput.test.ts +132 -0
  96. package/src/__tests__/components/GeneratedPlotlyResult.test.ts +117 -0
  97. package/src/__tests__/components/GeneratedPluginView.test.ts +230 -0
  98. package/src/__tests__/components/GeneratedResultRenderer.test.ts +93 -0
  99. package/src/__tests__/components/InstrumentAlertLog.test.ts +59 -0
  100. package/src/__tests__/composables/formBuilderRegistry.test.ts +5 -1
  101. package/src/__tests__/composables/useAnalysisArtifacts.test.ts +1 -74
  102. package/src/__tests__/composables/useApi.test.ts +8 -0
  103. package/src/__tests__/composables/useGeneratedAnalysis.test.ts +1740 -0
  104. package/src/__tests__/composables/useJobsStatusTray.test.ts +179 -1
  105. package/src/__tests__/composables/usePlatformContext.test.ts +28 -0
  106. package/src/__tests__/composables/usePluginClient.test.ts +366 -20
  107. package/src/__tests__/composables/usePluginConfig.test.ts +323 -7
  108. package/src/__tests__/docs/extractDocsTemplates.test.ts +0 -1
  109. package/src/__tests__/generatedUi.test.ts +119 -0
  110. package/src/__tests__/templates/templates.test.ts +1 -1
  111. package/src/components/FileUploader.vue +49 -11
  112. package/src/components/GeneratedPathInput.vue +218 -0
  113. package/src/components/GeneratedPlotlyResult.vue +87 -0
  114. package/src/components/GeneratedPluginView.vue +279 -0
  115. package/src/components/GeneratedResultRenderer.vue +137 -0
  116. package/src/components/InstrumentAlertLog.vue +2 -2
  117. package/src/components/JobsStatusTray.vue +14 -8
  118. package/src/components/index.ts +4 -0
  119. package/src/composables/formBuilderRegistry.ts +2 -0
  120. package/src/composables/index.ts +18 -5
  121. package/src/composables/platformContextHelpers.ts +3 -10
  122. package/src/composables/useAnalysisArtifacts.ts +0 -48
  123. package/src/composables/useApi.ts +1 -0
  124. package/src/composables/useGeneratedAnalysis.ts +1252 -0
  125. package/src/composables/useJobsStatusTray.ts +234 -45
  126. package/src/composables/usePluginClient.ts +152 -25
  127. package/src/composables/usePluginConfig.ts +156 -20
  128. package/src/generated/schema.ts +130 -0
  129. package/src/generated-ui/main.ts +21 -0
  130. package/src/index.ts +3 -0
  131. package/src/jobs.ts +5 -0
  132. package/src/plotly-dist.d.ts +3 -0
  133. package/src/templates/packs.ts +0 -3
  134. package/src/types/analysisArtifactTypes.ts +0 -9
  135. package/src/types/form-builder.ts +1 -0
  136. package/src/types/generated-ui.ts +176 -0
  137. package/src/types/index.ts +2 -1
  138. package/src/types/instrument.ts +1 -0
  139. package/src/types/jobs.ts +10 -1
  140. package/src/types/platform.ts +2 -0
  141. package/dist/BaseButton-Dgqrze41.js +0 -74
  142. package/dist/BaseButton-Dgqrze41.js.map +0 -1
  143. package/dist/BaseModal-B9UA8Y_I.js +0 -165
  144. package/dist/BaseModal-B9UA8Y_I.js.map +0 -1
  145. package/dist/BaseSelect-ekgr9fDo.js +0 -179
  146. package/dist/BaseSelect-ekgr9fDo.js.map +0 -1
  147. package/dist/ExperimentPopover-Ryusjrhv.js +0 -4
  148. package/dist/ExperimentSelectorModal-Cg4-LXBD.js +0 -5
  149. package/dist/ExperimentSelectorModal-DZeSd-yr.js.map +0 -1
  150. package/dist/SettingsModal-Bl_9w2Y3.js +0 -5
  151. package/dist/analysisArtifactTypes-Cu40dzSG.js.map +0 -1
  152. package/dist/components-DdzHh3a_.js.map +0 -1
  153. package/dist/composables/analysisArtifactTarget.d.ts +0 -5
  154. package/dist/composables-BUUphBqg.js.map +0 -1
  155. package/dist/experiment-utils-Bfa7CwPU.js +0 -108
  156. package/dist/experiment-utils-Bfa7CwPU.js.map +0 -1
  157. package/dist/templates-CNbPQNID.js.map +0 -1
  158. package/dist/useAnalysisArtifacts-C3l0a0vu.js.map +0 -1
  159. package/dist/useControlSchema-BZNdalmL.js.map +0 -1
  160. package/dist/useEventListener-CfVkP9Xz.js +0 -57
  161. package/dist/useEventListener-CfVkP9Xz.js.map +0 -1
  162. package/dist/useExperimentSelector-DRbj6vse.js.map +0 -1
  163. package/src/__tests__/composables/analysisArtifactTarget.test.ts +0 -77
  164. package/src/composables/analysisArtifactTarget.ts +0 -53
  165. /package/dist/__tests__/{composables/analysisArtifactTarget.test.d.ts → components/GeneratedPathInput.test.d.ts} +0 -0
@@ -26,6 +26,127 @@ import type {
26
26
 
27
27
  export type JobsStatusTrayTab = 'active' | 'history'
28
28
 
29
+ /**
30
+ * The canonical owner of a collection of plugin jobs.
31
+ *
32
+ * Sources, rather than job-center UI, own polling, event streams, and all job
33
+ * mutations. This keeps components and consumers on one reactive job graph.
34
+ */
35
+ export interface PluginJobCenterSource {
36
+ jobs: MaybeRefOrGetter<readonly JobStateInput[]>
37
+ refreshJobs?: () => Promise<JobListPayload | readonly JobStateInput[] | void>
38
+ cancelJob?: (jobId: string) => Promise<JobStateInput | void>
39
+ deleteJob?: (jobId: string) => Promise<boolean | void>
40
+ clearFinishedJobs?: () => Promise<number | void>
41
+ dispose?: () => void
42
+ }
43
+
44
+ export interface CombinedPluginJobCenterSource extends PluginJobCenterSource {
45
+ jobs: ComputedRef<readonly JobStateInput[]>
46
+ }
47
+
48
+ /**
49
+ * Combines independent job runtimes without copying their reactive state.
50
+ *
51
+ * Job ids must be globally unique across the sources. A duplicate id throws
52
+ * when jobs are read or an id-routed action is requested, rather than silently
53
+ * dispatching a mutation to the wrong owner.
54
+ */
55
+ export function combinePluginJobSources(
56
+ sources: readonly PluginJobCenterSource[],
57
+ ): CombinedPluginJobCenterSource {
58
+ const jobs = computed<readonly JobStateInput[]>(() => {
59
+ const combined: JobStateInput[] = []
60
+ const owners = new Set<string>()
61
+ for (const source of sources) {
62
+ for (const job of toValue(source.jobs)) {
63
+ if (owners.has(job.job_id)) throw duplicatePluginJobId(job.job_id)
64
+ owners.add(job.job_id)
65
+ combined.push(job)
66
+ }
67
+ }
68
+ return combined
69
+ })
70
+
71
+ function ownerFor(jobId: string): PluginJobCenterSource {
72
+ let owner: PluginJobCenterSource | undefined
73
+ for (const source of sources) {
74
+ if (!toValue(source.jobs).some(job => job.job_id === jobId)) continue
75
+ if (owner) throw duplicatePluginJobId(jobId)
76
+ owner = source
77
+ }
78
+ if (!owner) throw new Error(`Unknown plugin job id: ${jobId}`)
79
+ return owner
80
+ }
81
+
82
+ const combined: CombinedPluginJobCenterSource = { jobs }
83
+ if (sources.some(source => source.refreshJobs)) {
84
+ combined.refreshJobs = async () => {
85
+ await Promise.all(sources.map(source => source.refreshJobs?.()))
86
+ return jobs.value
87
+ }
88
+ }
89
+ if (sources.some(source => source.cancelJob)) {
90
+ combined.cancelJob = async (jobId) => {
91
+ const owner = ownerFor(jobId)
92
+ if (!owner.cancelJob) throw new Error(`Job ${jobId} cannot be cancelled by its source`)
93
+ return owner.cancelJob(jobId)
94
+ }
95
+ }
96
+ if (sources.length > 0 && sources.every(source => source.deleteJob)) {
97
+ combined.deleteJob = async (jobId) => {
98
+ const owner = ownerFor(jobId)
99
+ if (!owner.deleteJob) throw new Error(`Job ${jobId} cannot be deleted by its source`)
100
+ return owner.deleteJob(jobId)
101
+ }
102
+ }
103
+ // Never report a successful combined clear after mutating only a subset.
104
+ // Without bulk support from every source, the job center can still fall
105
+ // back to this source's id-routed deleteJob method when all owners expose it.
106
+ if (sources.length > 0 && sources.every(source => source.clearFinishedJobs)) {
107
+ combined.clearFinishedJobs = async () => {
108
+ const results = await Promise.all(
109
+ sources.map(source => source.clearFinishedJobs!()),
110
+ )
111
+ return results.every((result): result is number => typeof result === 'number')
112
+ ? results.reduce((total, result) => total + result, 0)
113
+ : undefined
114
+ }
115
+ }
116
+ if (sources.some(source => source.dispose)) {
117
+ combined.dispose = () => {
118
+ const errors: unknown[] = []
119
+ for (const source of sources) {
120
+ try {
121
+ source.dispose?.()
122
+ } catch (cause) {
123
+ errors.push(cause)
124
+ }
125
+ }
126
+ if (errors.length === 1) throw errors[0]
127
+ if (errors.length > 1) throw new PluginJobSourceDisposeError(errors)
128
+ }
129
+ }
130
+ return combined
131
+ }
132
+
133
+ class PluginJobSourceDisposeError extends Error {
134
+ readonly cause: unknown
135
+ readonly errors: readonly unknown[]
136
+
137
+ constructor(errors: readonly unknown[]) {
138
+ super('Failed to dispose plugin job sources')
139
+ this.name = 'PluginJobSourceDisposeError'
140
+ this.cause = errors[0]
141
+ this.errors = Object.freeze([...errors])
142
+ }
143
+ }
144
+
145
+ function duplicatePluginJobId(jobId: string): Error {
146
+ return new Error(`Duplicate plugin job id across combined sources: ${jobId}`)
147
+ }
148
+
149
+ /** @deprecated Prefer a {@link PluginJobCenterSource}. */
29
150
  export interface JobsStatusTrayAdapter {
30
151
  listJobs?: () => Promise<JobListPayload | readonly JobStateInput[]>
31
152
  cancelJob?: (job: JobState) => Promise<JobStateInput | void>
@@ -33,24 +154,34 @@ export interface JobsStatusTrayAdapter {
33
154
  clearFinishedJobs?: (jobs: readonly JobState[]) => Promise<number | void>
34
155
  }
35
156
 
36
- export interface UseJobsStatusTrayOptions {
157
+ export interface UsePluginJobCenterOptions {
158
+ source?: MaybeRefOrGetter<PluginJobCenterSource | undefined>
159
+ /** @deprecated Pass a source with its canonical jobs ref. */
37
160
  jobs?: MaybeRefOrGetter<readonly JobStateInput[] | undefined>
161
+ /** @deprecated Pass source actions instead. */
38
162
  adapter?: MaybeRefOrGetter<JobsStatusTrayAdapter | undefined>
163
+ /** @deprecated Event handling belongs to the source runtime. */
39
164
  eventStream?: MaybeRefOrGetter<UsePluginEventStreamReturn<JobStreamData> | undefined>
40
165
  loadOnMount?: boolean
41
166
  }
42
167
 
168
+ export interface UseJobsStatusTrayOptions extends UsePluginJobCenterOptions {}
169
+
43
170
  export interface JobStreamMessage {
44
171
  event: string
45
172
  data: JobStreamData
46
173
  }
47
174
 
48
- export interface UseJobsStatusTrayReturn {
49
- jobs: ShallowRef<JobState[]>
175
+ export interface UsePluginJobCenterReturn {
176
+ jobs: ComputedRef<JobState[]>
50
177
  activeJobs: ComputedRef<JobState[]>
51
178
  historyJobs: ComputedRef<JobState[]>
52
179
  deletableJobs: ComputedRef<JobState[]>
53
180
  featuredJob: ComputedRef<JobState | null>
181
+ canRefresh: ComputedRef<boolean>
182
+ canCancel: ComputedRef<boolean>
183
+ canDelete: ComputedRef<boolean>
184
+ canClearFinished: ComputedRef<boolean>
54
185
  isOpen: Ref<boolean>
55
186
  activeTab: Ref<JobsStatusTrayTab>
56
187
  isLoading: Ref<boolean>
@@ -67,6 +198,13 @@ export interface UseJobsStatusTrayReturn {
67
198
  isPending: (job: JobState) => boolean
68
199
  }
69
200
 
201
+ export type UseJobsStatusTrayReturn = UsePluginJobCenterReturn
202
+
203
+ interface LegacyJobsOverride {
204
+ readonly input: readonly JobStateInput[] | undefined
205
+ readonly jobs: JobState[]
206
+ }
207
+
70
208
  function responseJobs(
71
209
  response: JobListPayload | readonly JobStateInput[],
72
210
  ): readonly JobStateInput[] {
@@ -81,11 +219,16 @@ function errorMessage(error: unknown, fallback: string): string {
81
219
  return error instanceof Error ? error.message : fallback
82
220
  }
83
221
 
84
- /** Headless state and action controller for the standard SDK jobs tray. */
85
- export function useJobsStatusTray(
86
- options: UseJobsStatusTrayOptions = {},
87
- ): UseJobsStatusTrayReturn {
88
- const jobs = shallowRef<JobState[]>([])
222
+ /**
223
+ * Headless controller for plugin job presentation and actions.
224
+ *
225
+ * When a source is supplied, every job list is derived directly from that
226
+ * source. The controller never mirrors source jobs into another ref.
227
+ */
228
+ export function usePluginJobCenter(
229
+ options: UsePluginJobCenterOptions = {},
230
+ ): UsePluginJobCenterReturn {
231
+ const legacyOverride = shallowRef<LegacyJobsOverride | null>(null)
89
232
  const isOpen = ref(false)
90
233
  const activeTab = ref<JobsStatusTrayTab>('active')
91
234
  const isLoading = ref(false)
@@ -95,44 +238,70 @@ export function useJobsStatusTray(
95
238
  let stateRevision = 0
96
239
  let requestGeneration = 0
97
240
  let active = true
241
+ const source = computed(() => toValue(options.source))
98
242
  const adapter = computed(() => toValue(options.adapter))
99
243
 
244
+ const jobs = computed<JobState[]>(() => {
245
+ const currentSource = source.value
246
+ if (currentSource) {
247
+ return [...toValue(currentSource.jobs)].map(normalizeJobState)
248
+ }
249
+ const input = toValue(options.jobs)
250
+ const override = legacyOverride.value
251
+ if (override && override.input === input) return override.jobs
252
+ return input ? [...input].map(normalizeJobState) : []
253
+ })
100
254
  const activeJobs = computed(() => jobs.value.filter(job => isActiveJobStatus(job.status)))
101
255
  const historyJobs = computed(() => jobs.value.filter(job => !isActiveJobStatus(job.status)))
102
256
  const deletableJobs = computed(() => historyJobs.value.filter(job => job.can_delete))
103
257
  const featuredJob = computed(() => activeJobs.value[0] ?? historyJobs.value[0] ?? null)
258
+ const canRefresh = computed(() => Boolean(source.value?.refreshJobs || adapter.value?.listJobs))
259
+ const canCancel = computed(() => Boolean(source.value?.cancelJob || adapter.value?.cancelJob))
260
+ const canDelete = computed(() => Boolean(source.value?.deleteJob || adapter.value?.deleteJob))
261
+ const canClearFinished = computed(() => Boolean(
262
+ source.value?.clearFinishedJobs
263
+ || source.value?.deleteJob
264
+ || adapter.value?.clearFinishedJobs
265
+ || adapter.value?.deleteJob,
266
+ ))
104
267
 
105
268
  function bumpJobRevision(jobId: string): void {
106
269
  jobRevisions.set(jobId, (jobRevisions.get(jobId) ?? 0) + 1)
107
270
  }
108
271
 
109
- function replaceJobs(nextJobs: readonly JobStateInput[]): void {
272
+ function replaceLegacyJobs(nextJobs: readonly JobStateInput[]): void {
110
273
  const normalized = nextJobs.map(normalizeJobState)
111
274
  const touchedJobIds = new Set([
112
275
  ...jobs.value.map(job => job.job_id),
113
276
  ...normalized.map(job => job.job_id),
114
277
  ])
115
278
  touchedJobIds.forEach(bumpJobRevision)
116
- jobs.value = normalized
279
+ legacyOverride.value = {
280
+ input: toValue(options.jobs),
281
+ jobs: normalized,
282
+ }
117
283
  stateRevision += 1
118
284
  }
119
285
 
120
- function upsertJob(nextJob: JobStateInput): void {
286
+ function upsertLegacyJob(nextJob: JobStateInput): void {
121
287
  const normalized = normalizeJobState(nextJob)
122
288
  const existingIndex = jobs.value.findIndex(job => job.job_id === normalized.job_id)
123
- if (existingIndex === -1) {
124
- jobs.value = [normalized, ...jobs.value]
125
- } else {
126
- const nextJobs = [...jobs.value]
127
- nextJobs[existingIndex] = normalized
128
- jobs.value = nextJobs
289
+ const nextJobs = [...jobs.value]
290
+ if (existingIndex === -1) nextJobs.unshift(normalized)
291
+ else nextJobs[existingIndex] = normalized
292
+ legacyOverride.value = {
293
+ input: toValue(options.jobs),
294
+ jobs: nextJobs,
129
295
  }
130
296
  bumpJobRevision(normalized.job_id)
131
297
  stateRevision += 1
132
298
  }
133
299
 
134
- function removeJobs(jobIds: Set<string>): void {
135
- jobs.value = jobs.value.filter(job => !jobIds.has(job.job_id))
300
+ function removeLegacyJobs(jobIds: Set<string>): void {
301
+ legacyOverride.value = {
302
+ input: toValue(options.jobs),
303
+ jobs: jobs.value.filter(job => !jobIds.has(job.job_id)),
304
+ }
136
305
  jobIds.forEach(bumpJobRevision)
137
306
  stateRevision += 1
138
307
  }
@@ -145,21 +314,22 @@ export function useJobsStatusTray(
145
314
  }
146
315
 
147
316
  function applyStreamMessage(message: JobStreamMessage): void {
148
- if (!active) return
317
+ if (!active || source.value) return
149
318
  if (message.event === 'snapshot' && isSnapshotPayload(message.data)) {
150
- replaceJobs(message.data.jobs)
319
+ replaceLegacyJobs(message.data.jobs)
151
320
  return
152
321
  }
153
322
  if (
154
323
  (message.event === 'job' || message.event === 'terminal')
155
324
  && !isSnapshotPayload(message.data)
156
325
  ) {
157
- upsertJob(message.data)
326
+ upsertLegacyJob(message.data)
158
327
  }
159
328
  }
160
329
 
161
330
  async function refresh(): Promise<boolean> {
162
- const listJobs = adapter.value?.listJobs
331
+ const currentSource = source.value
332
+ const listJobs = currentSource?.refreshJobs ?? adapter.value?.listJobs
163
333
  if (!listJobs) return false
164
334
  const generation = ++requestGeneration
165
335
  const revision = stateRevision
@@ -170,7 +340,7 @@ export function useJobsStatusTray(
170
340
  if (!active || generation !== requestGeneration || revision !== stateRevision) {
171
341
  return false
172
342
  }
173
- replaceJobs(responseJobs(response))
343
+ if (!currentSource && response) replaceLegacyJobs(responseJobs(response))
174
344
  return true
175
345
  } catch (cause) {
176
346
  if (active && generation === requestGeneration) {
@@ -183,18 +353,21 @@ export function useJobsStatusTray(
183
353
  }
184
354
 
185
355
  async function cancelJob(job: JobState): Promise<boolean> {
186
- const cancel = adapter.value?.cancelJob
187
- if (!job.can_cancel || !cancel || pendingJobIds.value.has(job.job_id)) {
356
+ const sourceCancel = source.value?.cancelJob
357
+ const adapterCancel = adapter.value?.cancelJob
358
+ if (!job.can_cancel || (!sourceCancel && !adapterCancel) || pendingJobIds.value.has(job.job_id)) {
188
359
  return false
189
360
  }
190
361
  const revision = jobRevisions.get(job.job_id) ?? 0
191
362
  setPending(job.job_id, true)
192
363
  error.value = null
193
364
  try {
194
- const updated = await cancel(job)
365
+ const updated = sourceCancel
366
+ ? await sourceCancel(job.job_id)
367
+ : await adapterCancel!(job)
195
368
  if (!active) return false
196
- if (revision === (jobRevisions.get(job.job_id) ?? 0)) {
197
- if (updated) upsertJob(updated)
369
+ if (!sourceCancel && revision === (jobRevisions.get(job.job_id) ?? 0)) {
370
+ if (updated) upsertLegacyJob(updated)
198
371
  else await refresh()
199
372
  }
200
373
  return true
@@ -207,17 +380,22 @@ export function useJobsStatusTray(
207
380
  }
208
381
 
209
382
  async function deleteJob(job: JobState): Promise<boolean> {
210
- const deleteJobWithAdapter = adapter.value?.deleteJob
211
- if (!job.can_delete || !deleteJobWithAdapter || pendingJobIds.value.has(job.job_id)) {
383
+ const sourceDelete = source.value?.deleteJob
384
+ const adapterDelete = adapter.value?.deleteJob
385
+ if (!job.can_delete || (!sourceDelete && !adapterDelete) || pendingJobIds.value.has(job.job_id)) {
212
386
  return false
213
387
  }
214
388
  setPending(job.job_id, true)
215
389
  error.value = null
216
390
  try {
217
- const deleted = await deleteJobWithAdapter(job)
391
+ const deleted = sourceDelete
392
+ ? await sourceDelete(job.job_id)
393
+ : await adapterDelete!(job)
218
394
  if (!active) return false
219
- if (deleted === false) await refresh()
220
- else removeJobs(new Set([job.job_id]))
395
+ if (!sourceDelete) {
396
+ if (deleted === false) await refresh()
397
+ else removeLegacyJobs(new Set([job.job_id]))
398
+ }
221
399
  return deleted !== false
222
400
  } catch (cause) {
223
401
  if (active) error.value = errorMessage(cause, 'Failed to delete job')
@@ -232,6 +410,16 @@ export function useJobsStatusTray(
232
410
  if (targets.length === 0) return false
233
411
  error.value = null
234
412
  try {
413
+ const currentSource = source.value
414
+ if (currentSource?.clearFinishedJobs) {
415
+ const deleted = await currentSource.clearFinishedJobs()
416
+ if (!active) return false
417
+ return typeof deleted !== 'number' || deleted === targets.length
418
+ }
419
+ if (currentSource?.deleteJob) {
420
+ const results = await Promise.all(targets.map(job => deleteJob(job)))
421
+ return results.every(Boolean)
422
+ }
235
423
  const currentAdapter = adapter.value
236
424
  if (currentAdapter?.clearFinishedJobs) {
237
425
  const deleted = await currentAdapter.clearFinishedJobs(targets)
@@ -240,7 +428,7 @@ export function useJobsStatusTray(
240
428
  await refresh()
241
429
  return false
242
430
  }
243
- removeJobs(new Set(targets.map(job => job.job_id)))
431
+ removeLegacyJobs(new Set(targets.map(job => job.job_id)))
244
432
  return true
245
433
  }
246
434
  if (!currentAdapter?.deleteJob) return false
@@ -252,16 +440,6 @@ export function useJobsStatusTray(
252
440
  }
253
441
  }
254
442
 
255
- if (options.jobs !== undefined) {
256
- watch(
257
- () => toValue(options.jobs),
258
- value => {
259
- if (value) replaceJobs(value)
260
- },
261
- { immediate: true, deep: true },
262
- )
263
- }
264
-
265
443
  if (options.eventStream !== undefined) {
266
444
  watch(
267
445
  () => toValue(options.eventStream)?.lastMessage.value ?? null,
@@ -286,6 +464,10 @@ export function useJobsStatusTray(
286
464
  historyJobs,
287
465
  deletableJobs,
288
466
  featuredJob,
467
+ canRefresh,
468
+ canCancel,
469
+ canDelete,
470
+ canClearFinished,
289
471
  isOpen,
290
472
  activeTab,
291
473
  isLoading,
@@ -302,3 +484,10 @@ export function useJobsStatusTray(
302
484
  isPending: job => pendingJobIds.value.has(job.job_id),
303
485
  }
304
486
  }
487
+
488
+ /** Backward-compatible name for the plugin job-center controller. */
489
+ export function useJobsStatusTray(
490
+ options: UseJobsStatusTrayOptions = {},
491
+ ): UseJobsStatusTrayReturn {
492
+ return usePluginJobCenter(options)
493
+ }