@volter-ai-dev/supercode-ui 0.1.71 → 0.1.73

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 (38) hide show
  1. package/README.md +24 -0
  2. package/activity.mjs +2 -2
  3. package/chunks/{chunk-O7Q2PELK.mjs → chunk-2FG7VKIW.mjs} +1 -1
  4. package/chunks/{chunk-XD2WJYSL.mjs → chunk-4XUMCWPC.mjs} +1 -1
  5. package/chunks/{chunk-LQMYNJPU.mjs → chunk-E65I2I2L.mjs} +1 -1
  6. package/chunks/{chunk-OW42DS6P.mjs → chunk-IL5T2AST.mjs} +4 -4
  7. package/chunks/{chunk-ATCOWFRV.mjs → chunk-TKTOVDXB.mjs} +40 -6
  8. package/chunks/{chunk-SSYNT434.mjs → chunk-WRK5TBX3.mjs} +1 -1
  9. package/components.mjs +6 -6
  10. package/embed.mjs +5 -5
  11. package/logo.mjs +1 -1
  12. package/messenger.mjs +5 -5
  13. package/package.json +21 -3
  14. package/react/activity.mjs +2 -2
  15. package/react/chunks/{chunk-ZM5X5LOF.mjs → chunk-LYRIQE5M.mjs} +1 -1
  16. package/react/chunks/{chunk-ZXD7NZXI.mjs → chunk-NJC6S2AW.mjs} +1 -1
  17. package/react/chunks/{chunk-2RIHDJT6.mjs → chunk-QL6QEYPK.mjs} +4 -4
  18. package/react/chunks/{chunk-TQHX2DQG.mjs → chunk-UGL6AB2K.mjs} +1 -1
  19. package/react/chunks/{chunk-GV5KV5UY.mjs → chunk-VFOVJ4S3.mjs} +40 -6
  20. package/react/chunks/{chunk-GYQOTTZ5.mjs → chunk-YK7SJ6EH.mjs} +1 -1
  21. package/react/components.mjs +6 -6
  22. package/react/logo.mjs +1 -1
  23. package/react/messenger.mjs +5 -5
  24. package/react/sessions.mjs +2 -2
  25. package/react/settings.mjs +2 -2
  26. package/react/subagents.mjs +2 -2
  27. package/react/supervision-components.mjs +1354 -0
  28. package/react/supervision.d.ts +36 -0
  29. package/sessions.mjs +2 -2
  30. package/settings.mjs +2 -2
  31. package/source-inventory.d.ts +240 -0
  32. package/source-inventory.mjs +367 -0
  33. package/styles.css +92 -0
  34. package/subagents.mjs +2 -2
  35. package/supervision-components.d.ts +36 -0
  36. package/supervision-components.mjs +1354 -0
  37. package/supervision.d.ts +242 -0
  38. package/supervision.mjs +349 -0
@@ -0,0 +1,242 @@
1
+ export type ReadStatus = 'ready' | 'loading' | 'error';
2
+ export * from './source-inventory.js';
3
+ export interface TaskRelation {
4
+ key: string;
5
+ label: string;
6
+ available: boolean;
7
+ }
8
+ export interface TaskAttempt {
9
+ key: string;
10
+ status: string;
11
+ profile: string;
12
+ startedAt: string;
13
+ endedAt: string;
14
+ error: string;
15
+ summary: string;
16
+ evidence: string;
17
+ sessionKey: string | null;
18
+ }
19
+ export interface TaskReview {
20
+ verdict: string;
21
+ by: string;
22
+ reason: string;
23
+ at: string;
24
+ }
25
+ export interface TaskComment {
26
+ author: string;
27
+ body: string;
28
+ at: string;
29
+ }
30
+ export interface WorkflowTask {
31
+ key: string;
32
+ id: string;
33
+ title: string;
34
+ lane: string;
35
+ assignee: string;
36
+ body: string;
37
+ note: string;
38
+ dependencies: TaskRelation[];
39
+ dependents: TaskRelation[];
40
+ attemptCount?: number;
41
+ reviewCount?: number;
42
+ commentCount?: number;
43
+ attempts: TaskAttempt[];
44
+ reviews: TaskReview[];
45
+ comments: TaskComment[];
46
+ }
47
+ export interface WorkflowBoardModel {
48
+ key: string;
49
+ title: string;
50
+ source: string;
51
+ status: ReadStatus;
52
+ stale: boolean;
53
+ total: number;
54
+ offset: number;
55
+ hasMore: boolean;
56
+ tasks: WorkflowTask[];
57
+ }
58
+ export interface JobModel {
59
+ key: string;
60
+ id: string;
61
+ title: string;
62
+ source: string;
63
+ harness: string;
64
+ profile: string;
65
+ schedule: string;
66
+ state: string;
67
+ enabled: boolean | null;
68
+ nextRunAt: string | null;
69
+ destination: string | null;
70
+ channelConnection: string;
71
+ canPause: boolean;
72
+ canResume?: boolean;
73
+ canRun?: boolean;
74
+ canDelete?: boolean;
75
+ }
76
+ export interface DeliveryModel {
77
+ state: string;
78
+ target: string;
79
+ attempts: number | null;
80
+ error: string;
81
+ deliveredAt: string;
82
+ }
83
+ export interface RunModel {
84
+ key: string;
85
+ id: string;
86
+ jobKey: string;
87
+ startedAt: string;
88
+ finishedAt: string;
89
+ execution: string;
90
+ error: string;
91
+ sessionKey: string | null;
92
+ delivery: DeliveryModel | null;
93
+ }
94
+ export interface JobControlResult {
95
+ key: string;
96
+ action: 'resume' | 'run' | 'delete';
97
+ confirmed: boolean;
98
+ deleted: boolean;
99
+ requested: boolean;
100
+ }
101
+ export interface JobControlsProps {
102
+ job: JobModel;
103
+ stale?: boolean;
104
+ onControl?(action: 'resume' | 'run' | 'delete', key: string): Promise<JobControlResult>;
105
+ }
106
+ export interface PauseResult {
107
+ job: JobModel;
108
+ confirmed: boolean;
109
+ }
110
+ export interface WorkflowBoardProps {
111
+ board: WorkflowBoardModel;
112
+ selectedKey?: string | null;
113
+ onSelect?(key: string | null): void;
114
+ onOpenSession?(key: string): void;
115
+ onLoadMore?(): void;
116
+ initialLayout?: 'board' | 'list';
117
+ }
118
+ export interface TaskCardProps {
119
+ task: WorkflowTask;
120
+ onOpen?(key: string, element: HTMLButtonElement): void;
121
+ }
122
+ export interface WorkflowListProps {
123
+ tasks?: WorkflowTask[];
124
+ onOpen?: TaskCardProps['onOpen'];
125
+ }
126
+ export interface TaskDetailsProps {
127
+ task: WorkflowTask;
128
+ onOpenTask?(key: string): void;
129
+ onOpenSession?(key: string): void;
130
+ onClose?(): void;
131
+ }
132
+ export interface DependencyListProps {
133
+ items?: TaskRelation[];
134
+ title?: string;
135
+ onOpen?(key: string): void;
136
+ }
137
+ export interface AttemptTimelineProps {
138
+ attempts?: TaskAttempt[];
139
+ onOpenSession?(key: string): void;
140
+ }
141
+ export interface HandoffCardProps {
142
+ attempt: TaskAttempt;
143
+ }
144
+ export interface ReviewCardProps {
145
+ review: TaskReview;
146
+ }
147
+ export interface TaskThreadProps {
148
+ comments?: TaskComment[];
149
+ }
150
+ export interface JobListProps {
151
+ jobs?: JobModel[];
152
+ onOpen?(key: string): void;
153
+ }
154
+ export interface JobDetailsProps {
155
+ job: JobModel;
156
+ }
157
+ export interface JobActionsProps {
158
+ job: JobModel;
159
+ stale?: boolean;
160
+ onPause?(key: string): Promise<PauseResult>;
161
+ }
162
+ export interface RunListProps {
163
+ runs?: RunModel[];
164
+ onOpen?(key: string, element: HTMLButtonElement): void;
165
+ }
166
+ export interface RunDetailsProps {
167
+ run: RunModel;
168
+ onClose?(): void;
169
+ onOpenSession?(key: string): void;
170
+ }
171
+ export interface DeliveryStatusProps {
172
+ delivery: DeliveryModel | null;
173
+ }
174
+ export interface OrchestrationJobsProps {
175
+ job: JobModel;
176
+ runs?: RunModel[];
177
+ status?: ReadStatus;
178
+ stale?: boolean;
179
+ selectedKey?: string | null;
180
+ onSelect?(key: string | null): void;
181
+ onPause?(key: string): Promise<PauseResult>;
182
+ onOpenSession?(key: string): void;
183
+ }
184
+ export function projectWorkflow(
185
+ read: unknown,
186
+ options?: {
187
+ sourceKey?: string;
188
+ title?: string;
189
+ limit?: number;
190
+ offset?: number;
191
+ sessionForAttempt?(attempt: any, task: any, board: any): string | null;
192
+ },
193
+ ): WorkflowBoardModel[];
194
+ export function projectJobs(
195
+ listing: unknown,
196
+ options?: { sourceKey?: string; limit?: number; canPause?: boolean },
197
+ ): { status: ReadStatus; stale: boolean; total: number; jobs: JobModel[] };
198
+ export function projectRuns(
199
+ listing: unknown,
200
+ options?: {
201
+ sourceKey?: string;
202
+ limit?: number;
203
+ sessionForRun?(run: any): string | null;
204
+ jobKey?: string;
205
+ },
206
+ ): { status: ReadStatus; runs: RunModel[] };
207
+ export interface SupervisionSource {
208
+ key: string;
209
+ harness: 'hermes' | 'openclaw' | 'claude-code' | 'orchestrator';
210
+ home?: string;
211
+ homes?: Record<string, string>;
212
+ profile?: string;
213
+ allowPause?: boolean;
214
+ allowManage?: boolean;
215
+ }
216
+ export interface SupervisionHost {
217
+ workflow(
218
+ sourceKey: string,
219
+ page?: { offset?: number },
220
+ ): Promise<WorkflowBoardModel[]>;
221
+ jobs(sourceKey: string): Promise<ReturnType<typeof projectJobs>>;
222
+ runs(jobKey: string): Promise<ReturnType<typeof projectRuns>>;
223
+ pause(jobKey: string): Promise<PauseResult>;
224
+ control(action: 'resume' | 'run' | 'delete', jobKey: string): Promise<JobControlResult>;
225
+ }
226
+ /** Trusted-host use only. The client must be the native SDK or an equivalent typed adapter. */
227
+ export function createSupervisionHost(options: {
228
+ client: {
229
+ workflowLoad(params: any): Promise<any>;
230
+ listJobs(params: any): Promise<any>;
231
+ listRuns(params: any): Promise<any>;
232
+ pauseJob(params: any): Promise<any>;
233
+ resumeJob?(params: any): Promise<any>;
234
+ runJob?(params: any): Promise<any>;
235
+ deleteJob?(params: any): Promise<any>;
236
+ };
237
+ sources: SupervisionSource[];
238
+ allowPause?: boolean;
239
+ allowManage?: boolean;
240
+ sessionForRun?(run: any): string | null;
241
+ sessionForAttempt?(attempt: any, task: any, board: any): string | null;
242
+ }): SupervisionHost;
@@ -0,0 +1,349 @@
1
+ /** Bounded, renderer-free projections over the native SDK's workflow/jobs/runs readers. */
2
+ export { SOURCE_SECTIONS, projectSourceInventory, projectMemoryMatches, createSourceInventoryHost, projectApprovals, createApprovalHost, projectRuntimeState } from './source-inventory.mjs';
3
+ const text = (value, max = 4000) =>
4
+ typeof value === 'string'
5
+ ? value.length > max
6
+ ? `${value.slice(0, max - 1)}…`
7
+ : value
8
+ : '';
9
+ const array = (value) => (Array.isArray(value) ? value : []);
10
+ const values = (value) =>
11
+ value && typeof value === 'object' ? Object.values(value) : [];
12
+ const key = (...parts) =>
13
+ parts.map((part) => encodeURIComponent(String(part))).join(':');
14
+ export function projectWorkflow(
15
+ read,
16
+ {
17
+ sourceKey = 'source',
18
+ title = 'Workflow',
19
+ limit = 100,
20
+ offset = 0,
21
+ sessionForAttempt,
22
+ } = {},
23
+ ) {
24
+ limit = Number.isFinite(limit)
25
+ ? Math.max(1, Math.min(500, Math.floor(limit)))
26
+ : 100;
27
+ offset = Number.isSafeInteger(offset) && offset >= 0 ? offset : 0;
28
+ const boards = values(read?.workflow?.boards).slice(0, 100);
29
+ return boards.map((board) => {
30
+ const all = values(board.tasks),
31
+ page = all.slice(offset, offset + limit);
32
+ const taskKey = (id) => key(sourceKey, board.slug, id);
33
+ return {
34
+ key: key(sourceKey, board.slug),
35
+ title: text(board.name || board.slug || title, 200),
36
+ source: sourceKey,
37
+ status: 'ready',
38
+ stale: false,
39
+ total: all.length,
40
+ offset,
41
+ hasMore: offset + page.length < all.length,
42
+ tasks: page.map((task) => ({
43
+ key: taskKey(task.id),
44
+ id: text(task.id, 200),
45
+ title: text(task.title, 500),
46
+ lane:
47
+ text(
48
+ task.lane === 'unknown'
49
+ ? task.residue?.status || task.lane
50
+ : task.lane,
51
+ 100,
52
+ ) || 'unknown',
53
+ assignee: text(task.assignee, 200) || 'Unassigned',
54
+ body: text(task.body),
55
+ note: text(task.result, 500),
56
+ dependencies: array(board.dependencies)
57
+ .filter((edge) => edge.child === task.id)
58
+ .slice(0, 100)
59
+ .map((edge) => ({
60
+ key: taskKey(edge.parent),
61
+ label: text(board.tasks?.[edge.parent]?.title || edge.parent, 500),
62
+ available: all.some((t) => t.id === edge.parent),
63
+ })),
64
+ dependents: array(board.dependencies)
65
+ .filter((edge) => edge.parent === task.id)
66
+ .slice(0, 100)
67
+ .map((edge) => ({
68
+ key: taskKey(edge.child),
69
+ label: text(board.tasks?.[edge.child]?.title || edge.child, 500),
70
+ available: all.some((t) => t.id === edge.child),
71
+ })),
72
+ attemptCount: array(task.attempts).length,
73
+ reviewCount: array(task.reviews).length,
74
+ commentCount: array(task.comments).length,
75
+ attempts: array(task.attempts)
76
+ .slice(-50)
77
+ .reverse()
78
+ .map((attempt) => ({
79
+ key: text(attempt.id, 200),
80
+ status: text(attempt.status, 100),
81
+ profile: text(attempt.profile, 200),
82
+ startedAt: text(attempt.started_at, 100),
83
+ endedAt: text(attempt.ended_at, 100),
84
+ error: text(attempt.error),
85
+ summary: text(attempt.handoff?.summary),
86
+ evidence:
87
+ attempt.handoff?.metadata == null
88
+ ? ''
89
+ : text(JSON.stringify(attempt.handoff.metadata, null, 2)),
90
+ sessionKey: sessionForAttempt?.(attempt, task, board) || null,
91
+ })),
92
+ reviews: array(task.reviews)
93
+ .slice(-50)
94
+ .reverse()
95
+ .map((review) => ({
96
+ verdict: text(review.verdict, 100),
97
+ by: text(review.by, 200),
98
+ reason: text(review.reason),
99
+ at: text(review.at, 100),
100
+ })),
101
+ comments: array(task.comments)
102
+ .slice(-50)
103
+ .map((comment) => ({
104
+ author: text(comment.author, 200),
105
+ body: text(comment.body),
106
+ at: text(comment.at, 100),
107
+ })),
108
+ })),
109
+ };
110
+ });
111
+ }
112
+ export function projectJobs(
113
+ listing,
114
+ { sourceKey = 'source', limit = 100, canPause = false } = {},
115
+ ) {
116
+ limit = Number.isFinite(limit)
117
+ ? Math.max(1, Math.min(500, Math.floor(limit)))
118
+ : 100;
119
+ const unreadable = array(listing?.sources).some(
120
+ (source) => source.state === 'unreadable',
121
+ );
122
+ return {
123
+ status: unreadable ? 'error' : 'ready',
124
+ stale: unreadable,
125
+ total: array(listing?.jobs).length,
126
+ jobs: array(listing?.jobs)
127
+ .slice(0, limit)
128
+ .map((job) => ({
129
+ key: key(
130
+ sourceKey,
131
+ job.harness,
132
+ job.profile || '',
133
+ job.session_id || '',
134
+ job.id,
135
+ ),
136
+ id: text(job.id, 200),
137
+ title: text(job.name || job.title || text(job.payload?.text, 120).split('\n')[0], 120) || 'Scheduled task',
138
+ source: sourceKey,
139
+ harness: text(job.harness, 100),
140
+ profile: text(job.profile, 200),
141
+ schedule: text(job.schedule?.display, 500) || 'Unknown schedule',
142
+ state: text(job.state, 100) || 'unknown',
143
+ enabled: typeof job.enabled === 'boolean' ? job.enabled : null,
144
+ nextRunAt: text(job.next_run_at, 100) || null,
145
+ destination: text(job.deliver?.target, 500) || null,
146
+ channelConnection: 'Unknown',
147
+ canPause:
148
+ !unreadable &&
149
+ canPause &&
150
+ ['hermes', 'openclaw', 'orchestrator'].includes(job.harness) &&
151
+ job.enabled === true,
152
+ })),
153
+ };
154
+ }
155
+ export function projectRuns(
156
+ listing,
157
+ { sourceKey = 'source', limit = 100, sessionForRun, jobKey } = {},
158
+ ) {
159
+ limit = Number.isFinite(limit)
160
+ ? Math.max(1, Math.min(500, Math.floor(limit)))
161
+ : 100;
162
+ return {
163
+ status: array(listing?.sources).some(
164
+ (source) => source.state === 'unreadable',
165
+ )
166
+ ? 'error'
167
+ : 'ready',
168
+ runs: array(listing?.runs)
169
+ .slice(0, limit)
170
+ .map((run) => ({
171
+ key: key(sourceKey, run.harness, run.id),
172
+ id: text(run.id, 200),
173
+ jobKey: jobKey || key(sourceKey, run.harness, '', '', run.job_id),
174
+ startedAt: text(run.started_at || run.claimed_at, 100),
175
+ finishedAt: text(run.finished_at, 100),
176
+ execution: text(run.status, 100) || 'unknown',
177
+ error: text(run.error),
178
+ sessionKey: sessionForRun?.(run) || null,
179
+ delivery:
180
+ run.delivery == null
181
+ ? null
182
+ : {
183
+ state: text(run.delivery.state, 100) || 'unknown',
184
+ target: text(run.delivery.target, 500),
185
+ attempts:
186
+ Number.isSafeInteger(run.delivery.attempts) &&
187
+ run.delivery.attempts >= 0
188
+ ? run.delivery.attempts
189
+ : null,
190
+ error: text(run.delivery.last_error),
191
+ deliveredAt: text(run.delivery.delivered_at, 100),
192
+ },
193
+ })),
194
+ };
195
+ }
196
+
197
+ /** Trusted-host binding: sources, native ids, homes and write policy never come from browser intents. */
198
+ export function createSupervisionHost({
199
+ client,
200
+ sources,
201
+ allowPause = false,
202
+ allowManage = false,
203
+ sessionForRun,
204
+ sessionForAttempt,
205
+ }) {
206
+ const sourceMap = new Map(sources.map((source) => [source.key, source]));
207
+ if (sourceMap.size !== sources.length)
208
+ throw new Error('Source keys must be unique');
209
+ const jobs = new Map();
210
+ const reads = new Map();
211
+ const sourceFor = (sourceKey) => {
212
+ const source = sourceMap.get(sourceKey);
213
+ if (!source) throw new Error('Unknown source');
214
+ return source;
215
+ };
216
+ return {
217
+ async workflow(sourceKey, page = {}) {
218
+ const source = sourceFor(sourceKey);
219
+ if (source.harness !== 'hermes' || !source.home)
220
+ throw new Error('Workflow reading is unavailable for this source');
221
+ const offset =
222
+ Number.isSafeInteger(page.offset) && page.offset >= 0 ? page.offset : 0;
223
+ return projectWorkflow(
224
+ await client.workflowLoad({ from: 'hermes', home: source.home }),
225
+ { sourceKey, offset, sessionForAttempt },
226
+ );
227
+ },
228
+ async jobs(sourceKey) {
229
+ const source = sourceFor(sourceKey);
230
+ const revision = (reads.get(sourceKey) || 0) + 1;
231
+ reads.set(sourceKey, revision);
232
+ // Revoke the previous observation before reading: a failed or superseded
233
+ // refresh must not leave an old mutation capability usable.
234
+ for (const [id, value] of jobs)
235
+ if (value.source.key === sourceKey) jobs.delete(id);
236
+ const listing = await client.listJobs({
237
+ harness: source.harness,
238
+ homes: source.homes,
239
+ profile: source.profile,
240
+ });
241
+ if (reads.get(sourceKey) !== revision)
242
+ throw new Error('Job read was superseded; refresh its source');
243
+ const projection = projectJobs(listing, {
244
+ sourceKey,
245
+ canPause: allowPause && source.allowPause === true,
246
+ });
247
+ projection.jobs.forEach((job, index) => {
248
+ const native = listing.jobs[index];
249
+ const store = listing.sources?.find(
250
+ (store) =>
251
+ store.harness === native.harness &&
252
+ (store.profile || null) === (native.profile || null),
253
+ );
254
+ const nativeHome =
255
+ ['hermes', 'orchestrator'].includes(native.harness) &&
256
+ /[\\/]cron[\\/]jobs\.json$/.test(store?.path || '')
257
+ ? store.path.replace(/[\\/]cron[\\/]jobs\.json$/, '')
258
+ : null;
259
+ const homes = nativeHome
260
+ ? { ...source.homes, ...(native.harness === 'orchestrator' ? { orchestrator: nativeHome } : { hermes: `${nativeHome}/state.db` }) }
261
+ : source.homes;
262
+ const ambiguous = listing.jobs.some(
263
+ (other) =>
264
+ other !== native &&
265
+ other.harness === native.harness &&
266
+ other.id === native.id &&
267
+ (!native.profile || !other.profile || other.profile === native.profile),
268
+ );
269
+ job.canPause = job.canPause && !ambiguous && typeof client.pauseJob === 'function';
270
+ const controlled = allowManage && source.allowManage === true && !projection.stale && !ambiguous && ['hermes', 'openclaw', 'orchestrator'].includes(native.harness);
271
+ job.canResume = !!(controlled && native.enabled === false && typeof client.resumeJob === 'function');
272
+ job.canRun = !!(controlled && native.enabled === true && typeof client.runJob === 'function');
273
+ job.canDelete = !!(controlled && typeof client.deleteJob === 'function');
274
+ jobs.set(job.key, { source, native, current: job, homes, ambiguous });
275
+ });
276
+ return projection;
277
+ },
278
+ async runs(jobKey) {
279
+ const job = jobs.get(jobKey);
280
+ if (!job) throw new Error('Unknown job; refresh its source');
281
+ if (job.ambiguous)
282
+ throw new Error('Run history is ambiguous across native profiles');
283
+ if (!['hermes', 'openclaw', 'orchestrator'].includes(job.native.harness))
284
+ throw new Error('Run history is unavailable for this source');
285
+ return projectRuns(
286
+ await client.listRuns({
287
+ harness: job.native.harness,
288
+ job: job.native.id,
289
+ homes: job.homes,
290
+ limit: 100,
291
+ }),
292
+ { sourceKey: job.source.key, sessionForRun, jobKey },
293
+ );
294
+ },
295
+ async control(action, jobKey) {
296
+ const job = jobs.get(jobKey);
297
+ const capability = { resume: 'canResume', run: 'canRun', delete: 'canDelete' }[action];
298
+ if (!capability || !job?.current[capability] || !allowManage || job.source.allowManage !== true)
299
+ throw new Error('This control is unavailable for the selected job');
300
+ const result = await client[`${action}Job`]({ harness: job.native.harness, id: job.native.id,
301
+ homes: job.source.homes, profile: job.native.profile || job.source.profile });
302
+ if (result.verb !== action || result.id !== job.native.id ||
303
+ (action === 'delete' ? result.deleted !== true || result.job != null :
304
+ result.job?.id !== job.native.id || result.job?.harness !== job.native.harness ||
305
+ (result.job?.profile || null) !== (job.native.profile || null) ||
306
+ (action === 'resume' && result.job?.enabled !== true)))
307
+ throw new Error(`The source did not confirm ${action}`);
308
+ if (action === 'delete') {
309
+ // The same native job can appear in both an aggregate and a profile
310
+ // source. Revoke every cached alias of the confirmed deletion.
311
+ for (const [key, cached] of jobs)
312
+ if (cached.native.harness === job.native.harness && cached.native.id === job.native.id &&
313
+ (cached.native.profile || null) === (job.native.profile || null)) jobs.delete(key);
314
+ }
315
+ // A run receipt means queued/requested, never completed execution.
316
+ return { key: jobKey, action, confirmed: true, deleted: action === 'delete', requested: action === 'run' };
317
+ },
318
+ async pause(jobKey) {
319
+ const job = jobs.get(jobKey);
320
+ if (
321
+ !job?.current.canPause ||
322
+ !allowPause ||
323
+ job.source.allowPause !== true
324
+ )
325
+ throw new Error('Pause is unavailable for this source');
326
+ const result = await client.pauseJob({
327
+ harness: job.native.harness,
328
+ id: job.native.id,
329
+ homes: job.source.homes,
330
+ profile: job.native.profile || job.source.profile,
331
+ });
332
+ if (
333
+ result.verb !== 'pause' ||
334
+ result.id !== job.native.id ||
335
+ result.job?.id !== job.native.id ||
336
+ result.job?.harness !== job.native.harness ||
337
+ result.job?.enabled !== false ||
338
+ (result.job?.profile || null) !== (job.native.profile || null)
339
+ )
340
+ throw new Error('The source did not confirm the paused job');
341
+ const updated = projectJobs(
342
+ { jobs: [result.job], sources: [] },
343
+ { sourceKey: job.source.key },
344
+ ).jobs[0];
345
+ jobs.set(jobKey, { ...job, native: result.job, current: updated });
346
+ return { job: updated, confirmed: true };
347
+ },
348
+ };
349
+ }