imprint-mcp 0.7.0 → 0.7.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 (58) hide show
  1. package/README.md +66 -2
  2. package/package.json +1 -1
  3. package/prompts/auth-compile-agent.md +173 -22
  4. package/prompts/compile-agent.md +45 -8
  5. package/prompts/component-recovery.md +40 -0
  6. package/prompts/master-teach-decision.md +28 -3
  7. package/prompts/master-teach-focused-planner.md +18 -5
  8. package/src/cli.ts +26 -18
  9. package/src/imprint/agent-tool.ts +15 -0
  10. package/src/imprint/agent.ts +2 -11
  11. package/src/imprint/audit.ts +35 -13
  12. package/src/imprint/auth-browser-discovery.ts +276 -0
  13. package/src/imprint/auth-compile-agent.ts +513 -224
  14. package/src/imprint/auth-compile-tools.ts +213 -25
  15. package/src/imprint/auth-discovery-evidence.ts +81 -0
  16. package/src/imprint/auth-plan.ts +28 -0
  17. package/src/imprint/auth-verifier.ts +234 -20
  18. package/src/imprint/authentication-interaction.ts +137 -0
  19. package/src/imprint/backend-ladder.ts +271 -43
  20. package/src/imprint/build-plan.ts +2 -22
  21. package/src/imprint/cdp-browser-fetch.ts +242 -60
  22. package/src/imprint/chromium.ts +93 -29
  23. package/src/imprint/claude-cli-compile.ts +2 -0
  24. package/src/imprint/codex-cli-compile.ts +99 -46
  25. package/src/imprint/compile-agent-types.ts +9 -5
  26. package/src/imprint/compile-agent.ts +33 -0
  27. package/src/imprint/compile-tool-executor.ts +783 -0
  28. package/src/imprint/compile-tools.ts +42 -9
  29. package/src/imprint/compile-verification.ts +2 -0
  30. package/src/imprint/compile.ts +3 -0
  31. package/src/imprint/compiler-log.ts +16 -0
  32. package/src/imprint/component-recovery.ts +120 -0
  33. package/src/imprint/cookie-jar.ts +117 -7
  34. package/src/imprint/credential-extract.ts +9 -2
  35. package/src/imprint/credential-lock.ts +147 -0
  36. package/src/imprint/credential-observation.ts +261 -0
  37. package/src/imprint/credential-store.ts +284 -44
  38. package/src/imprint/live-component-recovery.ts +73 -0
  39. package/src/imprint/llm.ts +4 -3
  40. package/src/imprint/log.ts +16 -1
  41. package/src/imprint/login.ts +9 -0
  42. package/src/imprint/master-teach-agent-contracts.ts +40 -3
  43. package/src/imprint/master-teach-agents.ts +229 -14
  44. package/src/imprint/master-teach-checks.ts +8 -1
  45. package/src/imprint/master-teach-controller.ts +681 -138
  46. package/src/imprint/master-teach-prompt-projections.ts +2 -0
  47. package/src/imprint/mcp-cdp-sessions.ts +7 -2
  48. package/src/imprint/mcp-compile-server.ts +62 -850
  49. package/src/imprint/mcp-server.ts +667 -91
  50. package/src/imprint/mcp-session-recovery.ts +167 -0
  51. package/src/imprint/native-teach-agents.ts +232 -108
  52. package/src/imprint/runtime.ts +416 -47
  53. package/src/imprint/stealth-chromium.ts +67 -7
  54. package/src/imprint/teach-browser-resources.ts +88 -0
  55. package/src/imprint/teach-stage-progress.ts +38 -0
  56. package/src/imprint/terminal-user-prompt.ts +42 -0
  57. package/src/imprint/types.ts +51 -18
  58. package/src/imprint/user-interaction.ts +836 -0
@@ -4,9 +4,20 @@
4
4
  * the requested actions.
5
5
  */
6
6
 
7
+ import {
8
+ AUTH_DISCOVERY_POOL_KEY,
9
+ type AuthBrowserAction,
10
+ AuthBrowserActionSchema,
11
+ type AuthBrowserObservation,
12
+ } from './auth-browser-discovery.ts';
7
13
  import { runWorkflowWithLadder } from './backend-ladder.ts';
8
- import type { CdpBrowserFetch, CdpPageSnapshot } from './cdp-browser-fetch.ts';
14
+ import {
15
+ type CdpBrowserFetch,
16
+ type CdpPageSnapshot,
17
+ createCdpBrowserFetch,
18
+ } from './cdp-browser-fetch.ts';
9
19
  import { abortSignalError, withAbortSignal } from './concurrency.ts';
20
+ import { redactFreeformText } from './freeform-redact.ts';
10
21
  import { createLog } from './log.ts';
11
22
  import type { CredentialStore } from './runtime.ts';
12
23
 
@@ -30,6 +41,9 @@ export interface AuthActionResult {
30
41
  status?: number;
31
42
  responseBodyPreview?: string;
32
43
  durationMs: number;
44
+ requestStageFacts?: Extract<import('./types.ts').ToolResult, { ok: false }>['requestStageFacts'];
45
+ authExecution?: Extract<import('./types.ts').ToolResult, { ok: false }>['authExecution'];
46
+ responseObservations?: Awaited<ReturnType<LadderRunner>>['responseObservations'];
33
47
  }
34
48
 
35
49
  interface AuthPageInspection {
@@ -43,38 +57,107 @@ interface AuthPageInspection {
43
57
  }
44
58
 
45
59
  export class AuthVerifier {
46
- private readonly cdpPool = new Map<string, CdpBrowserFetch>();
60
+ private cdpPool = new Map<string, CdpBrowserFetch>();
61
+ private readonly pendingPools = new Set<Map<string, CdpBrowserFetch>>();
62
+ private readonly retiredPools = new Set<Map<string, CdpBrowserFetch>>();
63
+ private readonly closedBrowsers = new WeakSet<CdpBrowserFetch>();
64
+ private poolRelease: Promise<void> = Promise.resolve();
47
65
  private continuation: Record<string, unknown> | undefined;
48
66
  private readonly sensitiveValues = new Set<string>();
67
+ private readonly abort = new AbortController();
68
+ private lastAction?: Pick<AuthActionResult, 'action' | 'ok' | 'error' | 'nextAction' | 'status'>;
69
+
70
+ /** Facts for a recovery turn; never expose carried state or browser values. */
71
+ get state(): {
72
+ browserActive: boolean;
73
+ hasContinuation: boolean;
74
+ lastAction?: Pick<AuthActionResult, 'action' | 'ok' | 'error' | 'nextAction' | 'status'>;
75
+ } {
76
+ return {
77
+ browserActive: this.cdpPool.size > 0,
78
+ hasContinuation: this.continuation !== undefined,
79
+ lastAction: this.lastAction,
80
+ };
81
+ }
49
82
 
50
83
  constructor(
51
84
  private readonly workflowPath: string,
52
- private readonly credentials: CredentialStore,
85
+ private credentials: CredentialStore,
86
+ private readonly discoveryUrl?: string,
53
87
  ) {
54
88
  this.rememberSensitiveValues(credentials.values);
55
89
  }
56
90
 
91
+ /** Refresh future request inputs without discarding the live page or challenge. */
92
+ updateCredentials(credentials: CredentialStore): void {
93
+ if (this.abort.signal.aborted) throw abortSignalError(this.abort.signal);
94
+ this.credentials = credentials;
95
+ this.rememberSensitiveValues(credentials.values);
96
+ }
97
+
57
98
  async runAction(
58
99
  action: string,
59
100
  parameters: Record<string, string | number | boolean> = {},
60
101
  options: { freshSession?: boolean; cleanSession?: boolean; signal?: AbortSignal } = {},
61
102
  ): Promise<AuthActionResult> {
62
- if (options.signal?.aborted) throw abortSignalError(options.signal);
103
+ const signal = options.signal
104
+ ? AbortSignal.any([options.signal, this.abort.signal])
105
+ : this.abort.signal;
106
+ if (signal.aborted) throw abortSignalError(signal);
107
+ await withAbortSignal(() => this.poolRelease, signal);
63
108
  if (options.freshSession || options.cleanSession) await this.reset();
64
109
  this.rememberSensitiveValues(parameters);
65
110
  const previousContinuation = this.continuation;
111
+ const pool = this.cdpPool;
66
112
  const startedAt = Date.now();
67
- const ladder = await ladderRunner({
68
- workflowPath: this.workflowPath,
69
- params: { ...parameters, action },
70
- credentials: options.cleanSession
71
- ? { ...this.credentials, cookies: [], storage: [] }
72
- : this.credentials,
73
- cdpPool: this.cdpPool,
74
- forceBackend: 'cdp-replay',
75
- initialState: this.continuation,
76
- signal: options.signal,
77
- });
113
+ // A cancelled ladder can still be unwinding an uninterruptible CDP await.
114
+ // Detach its map before returning to the caller: no later action may borrow
115
+ // a browser that the older ladder can still close or evict.
116
+ const abort = (): void => {
117
+ if (this.cdpPool === pool) {
118
+ this.lastAction = { action, ok: false, error: 'CANCELLED' };
119
+ }
120
+ void this.retirePool(pool);
121
+ };
122
+ signal.addEventListener('abort', abort, { once: true });
123
+ if (signal.aborted) {
124
+ abort();
125
+ signal.removeEventListener('abort', abort);
126
+ throw abortSignalError(signal);
127
+ }
128
+ this.pendingPools.add(pool);
129
+ let ladder: Awaited<ReturnType<LadderRunner>>;
130
+ try {
131
+ ladder = await withAbortSignal(
132
+ () =>
133
+ Promise.resolve()
134
+ .then(() => {
135
+ if (signal.aborted) throw abortSignalError(signal);
136
+ return ladderRunner({
137
+ workflowPath: this.workflowPath,
138
+ params: { ...parameters, action },
139
+ credentials: options.cleanSession
140
+ ? { ...this.credentials, cookies: [], storage: [] }
141
+ : this.credentials,
142
+ cdpPool: pool,
143
+ forceBackend: 'cdp-replay',
144
+ initialState: previousContinuation,
145
+ signal,
146
+ });
147
+ })
148
+ .finally(async () => {
149
+ if (signal.aborted || pool !== this.cdpPool) await this.closePool(pool);
150
+ this.pendingPools.delete(pool);
151
+ this.retiredPools.delete(pool);
152
+ }),
153
+ signal,
154
+ );
155
+ } finally {
156
+ signal.removeEventListener('abort', abort);
157
+ }
158
+ if (signal.aborted) throw abortSignalError(signal);
159
+ if (pool !== this.cdpPool)
160
+ throw new DOMException('Authentication action context was replaced', 'AbortError');
78
161
  const durationMs = Date.now() - startedAt;
79
162
  const result = ladder.result;
80
163
 
@@ -99,6 +182,37 @@ export class AuthVerifier {
99
182
  ? undefined
100
183
  : this.sanitizeOptional(result.responseBodyPreview),
101
184
  durationMs,
185
+ requestStageFacts: result.ok ? undefined : result.requestStageFacts,
186
+ authExecution:
187
+ result.ok || !result.authExecution
188
+ ? undefined
189
+ : {
190
+ ...result.authExecution,
191
+ ...(result.authExecution.lastFailure
192
+ ? {
193
+ lastFailure: {
194
+ error: this.sanitize(result.authExecution.lastFailure.error),
195
+ message: this.sanitize(result.authExecution.lastFailure.message),
196
+ },
197
+ }
198
+ : {}),
199
+ },
200
+ responseObservations: ladder.responseObservations?.map(
201
+ ({ redactedBodyPreview: _preview, ...facts }) => ({
202
+ ...facts,
203
+ ...(facts.contentType ? { contentType: this.sanitize(facts.contentType) } : {}),
204
+ ...(facts.topLevelKeys
205
+ ? { topLevelKeys: facts.topLevelKeys.map((key) => this.sanitize(key)) }
206
+ : {}),
207
+ }),
208
+ ),
209
+ };
210
+ this.lastAction = {
211
+ action,
212
+ ok: actionResult.ok,
213
+ error: actionResult.error,
214
+ nextAction: actionResult.nextAction,
215
+ status: actionResult.status,
102
216
  };
103
217
  log(
104
218
  `action=${JSON.stringify(action)} backend=${ladder.usedBackend} ok=${result.ok}${
@@ -111,6 +225,10 @@ export class AuthVerifier {
111
225
  async inspectPage(
112
226
  options: { maxChars?: number; includeCookies?: boolean; signal?: AbortSignal } = {},
113
227
  ): Promise<AuthPageInspection> {
228
+ const signal = options.signal
229
+ ? AbortSignal.any([options.signal, this.abort.signal])
230
+ : this.abort.signal;
231
+ if (signal.aborted) throw abortSignalError(signal);
114
232
  const browser = Array.from(this.cdpPool.values()).at(-1);
115
233
  if (!browser) {
116
234
  return { ok: false, message: 'No active verification browser session exists.' };
@@ -121,7 +239,7 @@ export class AuthVerifier {
121
239
  }
122
240
 
123
241
  const maxChars = Math.max(256, Math.min(20_000, Math.floor(options.maxChars ?? 8_000)));
124
- const snapshot = await withAbortSignal(() => inspectPage.call(browser), options.signal);
242
+ const snapshot = await withAbortSignal(() => inspectPage.call(browser), signal);
125
243
  const bodyText = this.sanitize(snapshot.bodyText);
126
244
  return {
127
245
  ok: true,
@@ -146,16 +264,112 @@ export class AuthVerifier {
146
264
  };
147
265
  }
148
266
 
267
+ async interactBrowser(
268
+ input: AuthBrowserAction,
269
+ callerSignal?: AbortSignal,
270
+ ): Promise<AuthBrowserObservation> {
271
+ const signal = callerSignal
272
+ ? AbortSignal.any([callerSignal, this.abort.signal])
273
+ : this.abort.signal;
274
+ if (signal.aborted) throw abortSignalError(signal);
275
+ await withAbortSignal(() => this.poolRelease, signal);
276
+ const action = AuthBrowserActionSchema.parse(input);
277
+ if ('value' in action) {
278
+ const credential = /^\$\{credential\.([^}]+)\}$/.exec(action.value);
279
+ if (credential) {
280
+ const value = this.credentials.values[credential[1] ?? ''];
281
+ if (value === undefined)
282
+ throw new Error('The requested credential is unavailable; ask the human for it.');
283
+ action.value = value;
284
+ }
285
+ if (action.action === 'fill' || credential) this.rememberSensitiveValues(action.value);
286
+ }
287
+ const pool = this.cdpPool;
288
+ let browser = Array.from(pool.values()).at(-1);
289
+ if (!browser) {
290
+ const url = action.action === 'navigate' ? action.url : this.discoveryUrl;
291
+ if (!url)
292
+ throw new Error('No verification browser exists; supply a discovery navigation URL.');
293
+ browser = createCdpBrowserFetch({
294
+ baseUrl: url,
295
+ bootstrapUrl: url,
296
+ headed: true,
297
+ seedCookies: this.credentials.cookies,
298
+ seedStorage: this.credentials.storage,
299
+ });
300
+ pool.set(AUTH_DISCOVERY_POOL_KEY, browser);
301
+ }
302
+ const interactBrowser = browser.interactBrowser?.bind(browser);
303
+ if (!interactBrowser)
304
+ throw new Error('The active verification browser cannot perform discovery.');
305
+ const abort = (): void => {
306
+ void this.retirePool(pool);
307
+ };
308
+ signal.addEventListener('abort', abort, { once: true });
309
+ try {
310
+ if (signal.aborted) throw abortSignalError(signal);
311
+ const observation = await withAbortSignal(() => interactBrowser(action), signal);
312
+ if (pool !== this.cdpPool || signal.aborted) throw abortSignalError(signal);
313
+ const replacements = new Map(
314
+ Object.entries(this.credentials.values).map(([name, value]) => [
315
+ value,
316
+ `\${credential.${name}}`,
317
+ ]),
318
+ );
319
+ const protect = (value: unknown): unknown => {
320
+ if (typeof value === 'string')
321
+ return this.sanitize(redactFreeformText(value, replacements).redacted);
322
+ if (Array.isArray(value)) return value.map(protect);
323
+ if (value && typeof value === 'object')
324
+ return Object.fromEntries(
325
+ Object.entries(value).map(([key, item]) => [key, protect(item)]),
326
+ );
327
+ return value;
328
+ };
329
+ return protect(observation) as AuthBrowserObservation;
330
+ } catch (error) {
331
+ if (signal.aborted) throw abortSignalError(signal);
332
+ throw new Error(this.sanitize(error instanceof Error ? error.message : String(error)));
333
+ } finally {
334
+ signal.removeEventListener('abort', abort);
335
+ }
336
+ }
337
+
149
338
  async drain(): Promise<void> {
339
+ this.abort.abort(new DOMException('Authentication verifier closed', 'AbortError'));
150
340
  await this.reset();
341
+ await Promise.allSettled(Array.from(this.retiredPools, (pool) => this.closePool(pool)));
342
+ this.sensitiveValues.clear();
343
+ this.lastAction = undefined;
151
344
  }
152
345
 
153
346
  private async reset(): Promise<void> {
154
- for (const browser of this.cdpPool.values()) {
155
- await browser.close().catch(() => {});
347
+ await this.retirePool(this.cdpPool);
348
+ await this.poolRelease;
349
+ }
350
+
351
+ private retirePool(pool: Map<string, CdpBrowserFetch>): Promise<void> {
352
+ if (this.cdpPool === pool) {
353
+ this.cdpPool = new Map();
354
+ this.continuation = undefined;
156
355
  }
157
- this.cdpPool.clear();
158
- this.continuation = undefined;
356
+ this.retiredPools.add(pool);
357
+ const release = this.closePool(pool);
358
+ this.poolRelease = Promise.allSettled([this.poolRelease, release]).then(() => {});
359
+ if (!this.pendingPools.has(pool)) void release.then(() => this.retiredPools.delete(pool));
360
+ return release;
361
+ }
362
+
363
+ private async closePool(pool: Map<string, CdpBrowserFetch>): Promise<void> {
364
+ const browsers = new Set(pool.values());
365
+ pool.clear();
366
+ await Promise.allSettled(
367
+ Array.from(browsers, async (browser) => {
368
+ if (this.closedBrowsers.has(browser)) return;
369
+ this.closedBrowsers.add(browser);
370
+ await browser.close();
371
+ }),
372
+ );
159
373
  }
160
374
 
161
375
  private rememberSensitiveValues(value: unknown, seen = new Set<object>()): void {
@@ -0,0 +1,137 @@
1
+ /** Connect agent-selected auth work to the existing live auth compiler. */
2
+ import { randomUUID } from 'node:crypto';
3
+ import { readFileSync, statSync } from 'node:fs';
4
+ import { join } from 'node:path';
5
+ import { AuthCompileContext, type compileAuthAgent } from './auth-compile-agent.ts';
6
+ import { isCredentialReference } from './credential-extract.ts';
7
+ import { credentialObservation } from './credential-observation.ts';
8
+ import { loadSiteCredentials } from './credential-store.ts';
9
+ import type { LLMOptions } from './llm.ts';
10
+ import type { RunDeadlineRef } from './provider-retry.ts';
11
+ import type { Session } from './types.ts';
12
+ import { currentUserInteraction } from './user-interaction.ts';
13
+
14
+ export function attachRecordedAuthentication(options: {
15
+ session: Session;
16
+ sessionPath: string;
17
+ root: string;
18
+ values: Record<string, string>;
19
+ llmConfig?: LLMOptions;
20
+ runDeadline?: RunDeadlineRef;
21
+ compile?: typeof compileAuthAgent;
22
+ report?: (message: string) => void;
23
+ }): void {
24
+ const interaction = currentUserInteraction();
25
+ if (!interaction) return;
26
+ const contexts = new Map<string, { context: AuthCompileContext; outDir: string }>();
27
+ const workspaces = new Set<string>();
28
+ const recordingRevision = randomUUID();
29
+ const refreshMetadata = (contextRef: string, outDir: string): void => {
30
+ const metadata: Parameters<typeof interaction.updateAuthenticationContext>[1] = {
31
+ recordingRef: options.sessionPath,
32
+ recordingRevision,
33
+ workflowPath: join(outDir, 'workflow.json'),
34
+ evidenceRefs: [
35
+ join(outDir, '.auth-evidence-session.json'),
36
+ join(outDir, '.auth-live-discovery.json'),
37
+ ],
38
+ };
39
+ try {
40
+ const stat = statSync(join(outDir, 'workflow.json'));
41
+ metadata.workflowRevision = `${stat.mtimeMs}:${stat.size}`;
42
+ } catch {
43
+ /* No artifact has been written yet. */
44
+ }
45
+ try {
46
+ const attempt: unknown = JSON.parse(
47
+ readFileSync(join(outDir, '.auth-verification-attempt.json'), 'utf8'),
48
+ );
49
+ if (attempt && typeof attempt === 'object') {
50
+ const value = attempt as Record<string, unknown>;
51
+ if (typeof value.action === 'string') metadata.latestAction = value.action;
52
+ if (typeof value.ok === 'boolean') metadata.latestOk = value.ok;
53
+ metadata.latestStatus = typeof value.status === 'number' ? value.status : undefined;
54
+ metadata.latestError = typeof value.error === 'string' ? value.error : undefined;
55
+ }
56
+ } catch {
57
+ /* Discovery need not have a compiled action attempt. */
58
+ }
59
+ interaction.updateAuthenticationContext(contextRef, metadata);
60
+ };
61
+ interaction.addCleanup(async () => {
62
+ await Promise.allSettled(Array.from(contexts.values(), ({ context }) => context.dispose()));
63
+ contexts.clear();
64
+ });
65
+ interaction.setAuthenticate(async (plan, signal) => {
66
+ const knownSeqs = new Set(options.session.requests.map(({ seq }) => seq));
67
+ if (
68
+ [...plan.credentialRequestSeqs, ...plan.authRequestSeqs].some((seq) => !knownSeqs.has(seq))
69
+ ) {
70
+ return {
71
+ status: 'failed',
72
+ message: 'Authentication plan cites a request outside the selected recording.',
73
+ };
74
+ }
75
+ options.report?.(
76
+ `authenticating with ${plan.toolName}; questions appear in the terminal when needed`,
77
+ );
78
+ const compile = options.compile ?? (await import('./auth-compile-agent.ts')).compileAuthAgent;
79
+ // UserInteraction resolves/validates identity before queuing the operation.
80
+ // Its legacy label mapping remains compatible, while explicit refs can cross labels.
81
+ const contextRef = plan.contextRef;
82
+ if (!contextRef) throw new Error('Authentication host requires a resolved contextRef.');
83
+ let retained = contexts.get(contextRef);
84
+ if (!retained) {
85
+ const legacyDir = join(options.root, plan.toolName);
86
+ const outDir = workspaces.has(legacyDir)
87
+ ? join(options.root, `${plan.toolName}-${contextRef}`)
88
+ : legacyDir;
89
+ retained = { context: new AuthCompileContext(), outDir };
90
+ contexts.set(contextRef, retained);
91
+ workspaces.add(outDir);
92
+ }
93
+ const { context, outDir } = retained;
94
+ refreshMetadata(contextRef, outDir);
95
+ if (process.env.IMPRINT_CREDENTIAL_DIAGNOSTICS_PATH) {
96
+ try {
97
+ const stored = await loadSiteCredentials(options.session.site);
98
+ const values = Object.fromEntries(
99
+ Object.entries(options.values).filter(([, value]) => !isCredentialReference(value)),
100
+ );
101
+ const observation = credentialObservation(
102
+ { ...stored, values: { ...stored.values, ...values } },
103
+ 'authentication-context-boundary',
104
+ );
105
+ if (observation)
106
+ interaction.updateAuthenticationContext(contextRef, {
107
+ credentialRevision: observation.revision,
108
+ });
109
+ } catch {
110
+ /* Optional context metadata cannot affect authentication. */
111
+ }
112
+ }
113
+ const result = await compile({
114
+ context,
115
+ site: options.session.site,
116
+ session: options.session,
117
+ sessionPath: options.sessionPath,
118
+ outDir,
119
+ authToolPlan: plan,
120
+ teachCredentials: { site: options.session.site, values: options.values },
121
+ llmConfig: options.llmConfig,
122
+ runDeadline: options.runDeadline,
123
+ signal,
124
+ onProgress: (progress) => {
125
+ refreshMetadata(contextRef, outDir);
126
+ options.report?.(`${plan.toolName}: ${progress.toolName ?? progress.phase}`);
127
+ },
128
+ });
129
+ refreshMetadata(contextRef, outDir);
130
+ return {
131
+ contextRef,
132
+ status: result.success ? 'authenticated' : 'failed',
133
+ message: result.message,
134
+ ...(result.workflowPath ? { workflowPath: result.workflowPath } : {}),
135
+ };
136
+ });
137
+ }