@kb-labs/workflow-entry 2.89.0 → 2.94.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.
Files changed (39) hide show
  1. package/dist/commands/health.js +1 -1
  2. package/dist/commands/health.js.map +1 -1
  3. package/dist/commands/list.js +1 -1
  4. package/dist/commands/list.js.map +1 -1
  5. package/dist/commands/logs.js +1 -1
  6. package/dist/commands/logs.js.map +1 -1
  7. package/dist/commands/metrics.js +1 -1
  8. package/dist/commands/metrics.js.map +1 -1
  9. package/dist/commands/run.js +1 -1
  10. package/dist/commands/run.js.map +1 -1
  11. package/dist/commands/status.js +2 -2
  12. package/dist/commands/status.js.map +1 -1
  13. package/dist/commands/workflow-run.js +1 -1
  14. package/dist/commands/workflow-run.js.map +1 -1
  15. package/dist/rest/cron-list-handler.js.map +1 -1
  16. package/dist/rest/index.js.map +1 -1
  17. package/dist/rest/job-cancel-handler.js.map +1 -1
  18. package/dist/rest/job-detail-handler.js.map +1 -1
  19. package/dist/rest/job-logs-handler.js.map +1 -1
  20. package/dist/rest/job-steps-handler.js.map +1 -1
  21. package/dist/rest/jobs-list-handler.js.map +1 -1
  22. package/dist/rest/pending-approvals-handler.js.map +1 -1
  23. package/dist/rest/resolve-approval-handler.js.map +1 -1
  24. package/dist/rest/run-detail-handler.js.map +1 -1
  25. package/dist/rest/runs-list-handler.js.map +1 -1
  26. package/dist/rest/stats-handler.js.map +1 -1
  27. package/dist/rest/workflow-detail-handler.js.map +1 -1
  28. package/dist/rest/workflow-run-cancel-handler.js.map +1 -1
  29. package/dist/rest/workflow-run-handler.js.map +1 -1
  30. package/dist/rest/workflow-runs-handler.js.map +1 -1
  31. package/dist/rest/workflows-list-handler.js.map +1 -1
  32. package/dist/widgets/__federation_expose_RunDetail.js.map +1 -1
  33. package/dist/widgets/mf-manifest.json +5 -5
  34. package/dist/widgets/mf-stats.json +5 -5
  35. package/dist/widgets/remoteEntry.js +2 -2
  36. package/dist/widgets/remoteEntry.js.map +1 -1
  37. package/dist/ws/logs-channel.js.map +1 -1
  38. package/dist/ws/progress-channel.js.map +1 -1
  39. package/package.json +6 -6
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/http-client.ts","../../src/commands/status.ts"],"names":[],"mappings":"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","file":"status.js","sourcesContent":["/**\n * HTTP client for interacting with Workflow Daemon\n */\nimport type { WorkflowRunRequest } from '@kb-labs/workflow-contracts';\nimport { useEnv } from '@kb-labs/sdk';\n\nconst DEFAULT_DAEMON_URL = 'http://localhost:7778';\n\nexport interface DaemonClientOptions {\n url?: string;\n}\n\n/**\n * Get workflow daemon URL from environment or default\n */\nexport function getWorkflowDaemonUrl(): string {\n return useEnv('WORKFLOW_DAEMON_URL') ?? DEFAULT_DAEMON_URL;\n}\n\nexport class WorkflowDaemonClient {\n private readonly baseUrl: string;\n\n constructor(options: DaemonClientOptions = {}) {\n this.baseUrl = options.url ?? useEnv('WORKFLOW_DAEMON_URL') ?? DEFAULT_DAEMON_URL;\n }\n\n /**\n * Validate response Content-Type and parse JSON safely\n */\n private async parseJsonResponse<T>(response: Response): Promise<T> {\n const contentType = response.headers.get('content-type');\n if (!contentType?.includes('application/json')) {\n throw new Error(`Invalid Content-Type: expected application/json, got ${contentType}`);\n }\n return response.json() as Promise<T>;\n }\n\n private unwrapData<T>(payload: unknown): T {\n if (\n payload\n && typeof payload === 'object'\n && 'ok' in payload\n && (payload as any).ok === true\n && 'data' in payload\n ) {\n return (payload as { data: T }).data;\n }\n return payload as T;\n }\n\n /**\n * Validate and encode job ID to prevent path traversal attacks\n */\n private validateAndEncodeJobId(jobId: string): string {\n // Validate job ID format (alphanumeric, hyphens, underscores only)\n if (!/^[a-zA-Z0-9_-]+$/.test(jobId)) {\n throw new Error(`Invalid job ID format: ${jobId}`);\n }\n // Encode for URL safety (defense in depth)\n return encodeURIComponent(jobId);\n }\n\n /**\n * Health check\n */\n async health(): Promise<{ ok: boolean; service: string }> {\n const response = await fetch(`${this.baseUrl}/health`);\n if (!response.ok) {\n throw new Error(`Health check failed: ${response.statusText}`);\n }\n return this.parseJsonResponse(response);\n }\n\n /**\n * Get workflow metrics\n */\n async getMetrics(): Promise<any> {\n const response = await fetch(`${this.baseUrl}/metrics`);\n if (!response.ok) {\n throw new Error(`Failed to get metrics: ${response.statusText}`);\n }\n const data = await this.parseJsonResponse<any>(response);\n return this.unwrapData(data);\n }\n\n /**\n * Get job status with full details (jobs, steps, outputs)\n */\n async getJobStatus(jobId: string): Promise<any> {\n const encodedJobId = this.validateAndEncodeJobId(jobId);\n const response = await fetch(`${this.baseUrl}/api/v1/jobs/${encodedJobId}`);\n if (response.status === 404) {\n throw new Error(`Job ${jobId} not found`);\n }\n if (!response.ok) {\n throw new Error(`Failed to get job status: ${response.statusText}`);\n }\n const data = await this.parseJsonResponse<any>(response);\n return this.unwrapData(data);\n }\n\n /**\n * Get job steps with outputs\n */\n async getJobSteps(jobId: string): Promise<any> {\n const encodedJobId = this.validateAndEncodeJobId(jobId);\n const response = await fetch(`${this.baseUrl}/api/v1/jobs/${encodedJobId}/steps`);\n if (response.status === 404) {\n throw new Error(`Job ${jobId} not found`);\n }\n if (!response.ok) {\n throw new Error(`Failed to get job steps: ${response.statusText}`);\n }\n const data = await this.parseJsonResponse<any>(response);\n return this.unwrapData(data);\n }\n\n /**\n * Get job logs\n */\n async getJobLogs(jobId: string): Promise<any[]> {\n const encodedJobId = this.validateAndEncodeJobId(jobId);\n const response = await fetch(`${this.baseUrl}/api/v1/jobs/${encodedJobId}/logs`);\n if (response.status === 404) {\n throw new Error(`Job ${jobId} not found`);\n }\n if (!response.ok) {\n throw new Error(`Failed to get job logs: ${response.statusText}`);\n }\n const data = await this.parseJsonResponse<any>(response);\n const unwrapped = this.unwrapData<{ logs: any[] }>(data);\n return unwrapped.logs ?? [];\n }\n\n /**\n * Get active executions\n */\n async getExecutions(): Promise<any[]> {\n const response = await fetch(`${this.baseUrl}/api/v1/jobs`);\n if (!response.ok) {\n throw new Error(`Failed to get executions: ${response.statusText}`);\n }\n const data = await this.parseJsonResponse<any>(response);\n const unwrapped = this.unwrapData<{ jobs?: any[] }>(data);\n const jobs = unwrapped.jobs ?? [];\n return jobs.filter((job: any) => job.status === 'running' || job.status === 'pending');\n }\n\n /**\n * Get cron jobs\n */\n async getCronJobs(): Promise<{\n crons: any[];\n }> {\n const response = await fetch(`${this.baseUrl}/api/v1/cron`);\n if (!response.ok) {\n throw new Error(`Failed to get cron jobs: ${response.statusText}`);\n }\n const data = await this.parseJsonResponse<any>(response);\n return this.unwrapData(data);\n }\n\n /**\n * Submit a job for execution\n */\n async submitJob(params: {\n handler: string;\n input?: unknown;\n priority?: number;\n }): Promise<{ id: string; status: string }> {\n const response = await fetch(`${this.baseUrl}/api/v1/jobs`, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n },\n body: JSON.stringify({\n type: params.handler,\n payload: params.input,\n priority: params.priority,\n }),\n });\n\n if (!response.ok) {\n const error = (await response.json().catch(() => ({ error: response.statusText }))) as {\n error?: string;\n };\n throw new Error(error.error || `Failed to submit job: ${response.statusText}`);\n }\n\n const payload = await this.parseJsonResponse<any>(response);\n const data = this.unwrapData<{ jobId: string }>(payload);\n return { id: data.jobId, status: 'pending' };\n }\n\n /**\n * Run workflow by ID\n */\n async runWorkflow(\n workflowId: string,\n request: WorkflowRunRequest = {}\n ): Promise<{ runId: string; status: string }> {\n const response = await fetch(\n `${this.baseUrl}/api/v1/workflows/${encodeURIComponent(workflowId)}/runs`,\n {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n },\n body: JSON.stringify(request),\n }\n );\n\n if (!response.ok) {\n const error = (await response.json().catch(() => ({ error: response.statusText }))) as {\n error?: string;\n };\n throw new Error(error.error || `Failed to run workflow: ${response.statusText}`);\n }\n\n const payload = await this.parseJsonResponse<{ ok: boolean; data?: { runId: string; status: string } }>(response);\n return this.unwrapData<{ runId: string; status: string }>(payload);\n }\n}\n","/**\n * workflow:status command - Get job status\n */\n\nimport { defineCommand, type PluginContextV3 } from '@kb-labs/sdk';\nimport { type StatusFlags } from '@kb-labs/workflow-contracts';\nimport { WorkflowDaemonClient } from '../http-client.js';\n\ntype StatusInput = StatusFlags & { argv?: string[] };\n\nexport default defineCommand<unknown, StatusInput, { exitCode: number }>({\n id: 'workflow:status',\n description: 'Get status of a workflow job',\n\n handler: {\n // eslint-disable-next-line sonarjs/cognitive-complexity -- Job status display with deep run/job/step traversal, multiple output formats (JSON/human), timing calculations, status color coding, and error aggregation\n async execute(ctx: PluginContextV3, input: StatusInput): Promise<{ exitCode: number }> {\n const flags = (input as any).flags ?? input;\n const outputJson = flags.json ?? false;\n const jobId = flags['job-id'];\n\n if (!jobId) {\n if (outputJson) {\n ctx.ui?.json?.({ ok: false, error: 'Missing required flag: --job-id' });\n } else {\n ctx.ui?.error?.('Missing required flag: --job-id');\n ctx.ui?.info?.('Usage: kb workflow status --job-id=<job-id>');\n }\n return { exitCode: 1 };\n }\n\n try {\n const client = new WorkflowDaemonClient();\n const status = await client.getJobStatus(jobId);\n\n if (outputJson) {\n ctx.ui?.json?.({ ok: true, data: status });\n } else {\n const statusItems = [\n `ID: ${status.id}`,\n `Status: ${status.status}`,\n `Type: ${status.type || 'N/A'}`,\n `Started: ${status.startedAt || 'N/A'}`,\n `Finished: ${status.finishedAt || 'N/A'}`,\n ];\n\n // Add error if present\n if (status.error) {\n statusItems.push(`Error: ${status.error}`);\n }\n\n // Add result summary if present\n if (status.result?.summary) {\n statusItems.push(`Summary: ${status.result.summary}`);\n }\n\n const sections: Array<{ header: string; items: string[] }> = [\n { header: 'Details', items: statusItems },\n ];\n\n // Add jobs and steps details\n if (status.jobs && status.jobs.length > 0) {\n for (const job of status.jobs) {\n const jobItems = [\n `Status: ${job.status}`,\n `Duration: ${job.durationMs ? `${job.durationMs}ms` : 'N/A'}`,\n ];\n\n if (job.error) {\n jobItems.push(`Error: ${job.error}`);\n }\n\n sections.push({ header: `Job: ${job.name}`, items: jobItems });\n\n // Add steps for this job\n if (job.steps && job.steps.length > 0) {\n const stepItems: string[] = [];\n for (const step of job.steps) {\n const statusIcon = step.status === 'success' ? '✓' : step.status === 'failed' ? '✗' : '○';\n const duration = step.durationMs ? ` (${step.durationMs}ms)` : '';\n stepItems.push(`${statusIcon} ${step.name}: ${step.status}${duration}`);\n\n // Show outputs if present\n if (step.outputs && Object.keys(step.outputs).length > 0) {\n const outputStr = JSON.stringify(step.outputs, null, 2);\n // Truncate long outputs\n const truncated = outputStr.length > 500 ? outputStr.slice(0, 500) + '...' : outputStr;\n stepItems.push(` └─ Output: ${truncated}`);\n }\n\n // Show error if present\n if (step.error) {\n const errorMsg = typeof step.error === 'object' ? (step.error as any).message : step.error;\n stepItems.push(` └─ Error: ${errorMsg}`);\n }\n }\n sections.push({ header: ` Steps (${job.steps.length})`, items: stepItems });\n }\n }\n }\n\n ctx.ui?.success?.('Job Status Retrieved', {\n title: 'Workflow Job',\n sections,\n });\n }\n\n return { exitCode: 0 };\n } catch (error) {\n const message = error instanceof Error ? error.message : String(error);\n\n if (outputJson) {\n ctx.ui?.json?.({ ok: false, error: message });\n } else {\n ctx.ui?.error?.(`Failed to get job status: ${message}`);\n }\n\n return { exitCode: 1 };\n }\n },\n },\n});\n\n"]}
1
+ {"version":3,"sources":["../../src/http-client.ts","../../src/commands/status.ts"],"names":[],"mappings":"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","file":"status.js","sourcesContent":["/**\n * HTTP client for interacting with Workflow Daemon\n */\nimport type {\n WorkflowRunRequest,\n JobStatusInfo,\n JobStepsResponse,\n JobLogsResponse,\n CronListResponse,\n} from '@kb-labs/workflow-contracts';\nimport { useEnv } from '@kb-labs/sdk';\n\n/** Metrics data returned by GET /metrics */\nexport interface WorkflowMetricsData {\n runs: {\n total: number;\n queued: number;\n running: number;\n completed: number;\n failed: number;\n cancelled: number;\n };\n jobs: {\n total: number;\n queued: number;\n running: number;\n completed: number;\n failed: number;\n };\n [key: string]: unknown;\n}\n\n/** Extended job status returned by GET /api/v1/jobs/:id — includes nested jobs/steps */\nexport interface JobStatusDetail extends Omit<JobStatusInfo, 'result'> {\n result?: {\n ok: boolean;\n summary?: string;\n error?: { code: string; message: string };\n };\n jobs?: Array<{\n id: string;\n name: string;\n status: string;\n startedAt?: string;\n finishedAt?: string;\n durationMs?: number;\n error?: string;\n steps?: Array<{\n id: string;\n name: string;\n status: string;\n handler?: string;\n startedAt?: string;\n finishedAt?: string;\n durationMs?: number;\n outputs?: Record<string, unknown>;\n error?: { message?: string } | string;\n }>;\n }>;\n}\n\nconst DEFAULT_DAEMON_URL = 'http://localhost:7778';\n\nexport interface DaemonClientOptions {\n url?: string;\n}\n\n/**\n * Get workflow daemon URL from environment or default\n */\nexport function getWorkflowDaemonUrl(): string {\n return useEnv('WORKFLOW_DAEMON_URL') ?? DEFAULT_DAEMON_URL;\n}\n\nexport class WorkflowDaemonClient {\n private readonly baseUrl: string;\n\n constructor(options: DaemonClientOptions = {}) {\n this.baseUrl = options.url ?? useEnv('WORKFLOW_DAEMON_URL') ?? DEFAULT_DAEMON_URL;\n }\n\n /**\n * Validate response Content-Type and parse JSON safely\n */\n private async parseJsonResponse<T>(response: Response): Promise<T> {\n const contentType = response.headers.get('content-type');\n if (!contentType?.includes('application/json')) {\n throw new Error(`Invalid Content-Type: expected application/json, got ${contentType}`);\n }\n return response.json() as Promise<T>;\n }\n\n private unwrapData<T>(payload: unknown): T {\n if (\n payload\n && typeof payload === 'object'\n && 'ok' in payload\n && (payload as { ok?: boolean }).ok === true\n && 'data' in payload\n ) {\n return (payload as { data: T }).data;\n }\n return payload as T;\n }\n\n /**\n * Validate and encode job ID to prevent path traversal attacks\n */\n private validateAndEncodeJobId(jobId: string): string {\n // Validate job ID format (alphanumeric, hyphens, underscores only)\n if (!/^[a-zA-Z0-9_-]+$/.test(jobId)) {\n throw new Error(`Invalid job ID format: ${jobId}`);\n }\n // Encode for URL safety (defense in depth)\n return encodeURIComponent(jobId);\n }\n\n /**\n * Health check\n */\n async health(): Promise<{ ok: boolean; service: string }> {\n const response = await fetch(`${this.baseUrl}/health`);\n if (!response.ok) {\n throw new Error(`Health check failed: ${response.statusText}`);\n }\n return this.parseJsonResponse(response);\n }\n\n /**\n * Get workflow metrics (raw metrics data from daemon)\n */\n async getMetrics(): Promise<WorkflowMetricsData> {\n const response = await fetch(`${this.baseUrl}/metrics`);\n if (!response.ok) {\n throw new Error(`Failed to get metrics: ${response.statusText}`);\n }\n const data = await this.parseJsonResponse<unknown>(response);\n return this.unwrapData<WorkflowMetricsData>(data);\n }\n\n /**\n * Get job status with full details (jobs, steps, outputs)\n */\n async getJobStatus(jobId: string): Promise<JobStatusDetail> {\n const encodedJobId = this.validateAndEncodeJobId(jobId);\n const response = await fetch(`${this.baseUrl}/api/v1/jobs/${encodedJobId}`);\n if (response.status === 404) {\n throw new Error(`Job ${jobId} not found`);\n }\n if (!response.ok) {\n throw new Error(`Failed to get job status: ${response.statusText}`);\n }\n const data = await this.parseJsonResponse<unknown>(response);\n return this.unwrapData<JobStatusDetail>(data);\n }\n\n /**\n * Get job steps with outputs\n */\n async getJobSteps(jobId: string): Promise<JobStepsResponse> {\n const encodedJobId = this.validateAndEncodeJobId(jobId);\n const response = await fetch(`${this.baseUrl}/api/v1/jobs/${encodedJobId}/steps`);\n if (response.status === 404) {\n throw new Error(`Job ${jobId} not found`);\n }\n if (!response.ok) {\n throw new Error(`Failed to get job steps: ${response.statusText}`);\n }\n const data = await this.parseJsonResponse<unknown>(response);\n return this.unwrapData<JobStepsResponse>(data);\n }\n\n /**\n * Get job logs\n */\n async getJobLogs(jobId: string): Promise<JobLogsResponse['logs']> {\n const encodedJobId = this.validateAndEncodeJobId(jobId);\n const response = await fetch(`${this.baseUrl}/api/v1/jobs/${encodedJobId}/logs`);\n if (response.status === 404) {\n throw new Error(`Job ${jobId} not found`);\n }\n if (!response.ok) {\n throw new Error(`Failed to get job logs: ${response.statusText}`);\n }\n const data = await this.parseJsonResponse<unknown>(response);\n const unwrapped = this.unwrapData<JobLogsResponse>(data);\n return unwrapped.logs ?? [];\n }\n\n /**\n * Get active executions\n */\n async getExecutions(): Promise<JobStatusInfo[]> {\n const response = await fetch(`${this.baseUrl}/api/v1/jobs`);\n if (!response.ok) {\n throw new Error(`Failed to get executions: ${response.statusText}`);\n }\n const data = await this.parseJsonResponse<unknown>(response);\n const unwrapped = this.unwrapData<{ jobs?: JobStatusInfo[] }>(data);\n const jobs = unwrapped.jobs ?? [];\n return jobs.filter((job) => job.status === 'running' || job.status === 'pending');\n }\n\n /**\n * Get cron jobs\n */\n async getCronJobs(): Promise<CronListResponse> {\n const response = await fetch(`${this.baseUrl}/api/v1/cron`);\n if (!response.ok) {\n throw new Error(`Failed to get cron jobs: ${response.statusText}`);\n }\n const data = await this.parseJsonResponse<unknown>(response);\n return this.unwrapData<CronListResponse>(data);\n }\n\n /**\n * Submit a job for execution\n */\n async submitJob(params: {\n handler: string;\n input?: unknown;\n priority?: number;\n }): Promise<{ id: string; status: string }> {\n const response = await fetch(`${this.baseUrl}/api/v1/jobs`, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n },\n body: JSON.stringify({\n type: params.handler,\n payload: params.input,\n priority: params.priority,\n }),\n });\n\n if (!response.ok) {\n const error = (await response.json().catch(() => ({ error: response.statusText }))) as {\n error?: string;\n };\n throw new Error(error.error || `Failed to submit job: ${response.statusText}`);\n }\n\n const payload = await this.parseJsonResponse<unknown>(response);\n const data = this.unwrapData<{ jobId: string }>(payload);\n return { id: data.jobId, status: 'pending' };\n }\n\n /**\n * Run workflow by ID\n */\n async runWorkflow(\n workflowId: string,\n request: WorkflowRunRequest = {}\n ): Promise<{ runId: string; status: string }> {\n const response = await fetch(\n `${this.baseUrl}/api/v1/workflows/${encodeURIComponent(workflowId)}/runs`,\n {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n },\n body: JSON.stringify(request),\n }\n );\n\n if (!response.ok) {\n const error = (await response.json().catch(() => ({ error: response.statusText }))) as {\n error?: string;\n };\n throw new Error(error.error || `Failed to run workflow: ${response.statusText}`);\n }\n\n const payload = await this.parseJsonResponse<{ ok: boolean; data?: { runId: string; status: string } }>(response);\n return this.unwrapData<{ runId: string; status: string }>(payload);\n }\n}\n","/**\n * workflow:status command - Get job status\n */\n\nimport { defineCommand, type PluginContextV3 } from '@kb-labs/sdk';\nimport { type StatusFlags } from '@kb-labs/workflow-contracts';\nimport { WorkflowDaemonClient } from '../http-client.js';\n\ntype StatusInput = StatusFlags & { argv?: string[] };\n\nexport default defineCommand<unknown, StatusInput, { exitCode: number }>({\n id: 'workflow:status',\n description: 'Get status of a workflow job',\n\n handler: {\n // eslint-disable-next-line sonarjs/cognitive-complexity -- Job status display with deep run/job/step traversal, multiple output formats (JSON/human), timing calculations, status color coding, and error aggregation\n async execute(ctx: PluginContextV3, input: StatusInput): Promise<{ exitCode: number }> {\n const flags = (input as { flags?: StatusFlags }).flags ?? input;\n const outputJson = flags.json ?? false;\n const jobId = flags['job-id'];\n\n if (!jobId) {\n if (outputJson) {\n ctx.ui?.json?.({ ok: false, error: 'Missing required flag: --job-id' });\n } else {\n ctx.ui?.error?.('Missing required flag: --job-id');\n ctx.ui?.info?.('Usage: kb workflow status --job-id=<job-id>');\n }\n return { exitCode: 1 };\n }\n\n try {\n const client = new WorkflowDaemonClient();\n const status = await client.getJobStatus(jobId);\n\n if (outputJson) {\n ctx.ui?.json?.({ ok: true, data: status });\n } else {\n const statusItems = [\n `ID: ${status.id}`,\n `Status: ${status.status}`,\n `Type: ${status.type || 'N/A'}`,\n `Started: ${status.startedAt || 'N/A'}`,\n `Finished: ${status.finishedAt || 'N/A'}`,\n ];\n\n // Add error if present\n if (status.error) {\n statusItems.push(`Error: ${status.error}`);\n }\n\n // Add result summary if present\n if (status.result?.summary) {\n statusItems.push(`Summary: ${status.result.summary}`);\n }\n\n const sections: Array<{ header: string; items: string[] }> = [\n { header: 'Details', items: statusItems },\n ];\n\n // Add jobs and steps details\n if (status.jobs && status.jobs.length > 0) {\n for (const job of status.jobs) {\n const jobItems = [\n `Status: ${job.status}`,\n `Duration: ${job.durationMs ? `${job.durationMs}ms` : 'N/A'}`,\n ];\n\n if (job.error) {\n jobItems.push(`Error: ${job.error}`);\n }\n\n sections.push({ header: `Job: ${job.name}`, items: jobItems });\n\n // Add steps for this job\n if (job.steps && job.steps.length > 0) {\n const stepItems: string[] = [];\n for (const step of job.steps) {\n const statusIcon = step.status === 'success' ? '✓' : step.status === 'failed' ? '✗' : '○';\n const duration = step.durationMs ? ` (${step.durationMs}ms)` : '';\n stepItems.push(`${statusIcon} ${step.name}: ${step.status}${duration}`);\n\n // Show outputs if present\n if (step.outputs && Object.keys(step.outputs).length > 0) {\n const outputStr = JSON.stringify(step.outputs, null, 2);\n // Truncate long outputs\n const truncated = outputStr.length > 500 ? outputStr.slice(0, 500) + '...' : outputStr;\n stepItems.push(` └─ Output: ${truncated}`);\n }\n\n // Show error if present\n if (step.error) {\n const errorMsg = typeof step.error === 'string' ? step.error : step.error?.message ?? String(step.error);\n stepItems.push(` └─ Error: ${errorMsg}`);\n }\n }\n sections.push({ header: ` Steps (${job.steps.length})`, items: stepItems });\n }\n }\n }\n\n ctx.ui?.success?.('Job Status Retrieved', {\n title: 'Workflow Job',\n sections,\n });\n }\n\n return { exitCode: 0 };\n } catch (error) {\n const message = error instanceof Error ? error.message : String(error);\n\n if (outputJson) {\n ctx.ui?.json?.({ ok: false, error: message });\n } else {\n ctx.ui?.error?.(`Failed to get job status: ${message}`);\n }\n\n return { exitCode: 1 };\n }\n },\n },\n});\n\n"]}
@@ -43,7 +43,7 @@ var WorkflowDaemonClient = class {
43
43
  return this.parseJsonResponse(response);
44
44
  }
45
45
  /**
46
- * Get workflow metrics
46
+ * Get workflow metrics (raw metrics data from daemon)
47
47
  */
48
48
  async getMetrics() {
49
49
  const response = await fetch(`${this.baseUrl}/metrics`);
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/http-client.ts","../../src/commands/workflow-run.ts"],"names":[],"mappings":"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","file":"workflow-run.js","sourcesContent":["/**\n * HTTP client for interacting with Workflow Daemon\n */\nimport type { WorkflowRunRequest } from '@kb-labs/workflow-contracts';\nimport { useEnv } from '@kb-labs/sdk';\n\nconst DEFAULT_DAEMON_URL = 'http://localhost:7778';\n\nexport interface DaemonClientOptions {\n url?: string;\n}\n\n/**\n * Get workflow daemon URL from environment or default\n */\nexport function getWorkflowDaemonUrl(): string {\n return useEnv('WORKFLOW_DAEMON_URL') ?? DEFAULT_DAEMON_URL;\n}\n\nexport class WorkflowDaemonClient {\n private readonly baseUrl: string;\n\n constructor(options: DaemonClientOptions = {}) {\n this.baseUrl = options.url ?? useEnv('WORKFLOW_DAEMON_URL') ?? DEFAULT_DAEMON_URL;\n }\n\n /**\n * Validate response Content-Type and parse JSON safely\n */\n private async parseJsonResponse<T>(response: Response): Promise<T> {\n const contentType = response.headers.get('content-type');\n if (!contentType?.includes('application/json')) {\n throw new Error(`Invalid Content-Type: expected application/json, got ${contentType}`);\n }\n return response.json() as Promise<T>;\n }\n\n private unwrapData<T>(payload: unknown): T {\n if (\n payload\n && typeof payload === 'object'\n && 'ok' in payload\n && (payload as any).ok === true\n && 'data' in payload\n ) {\n return (payload as { data: T }).data;\n }\n return payload as T;\n }\n\n /**\n * Validate and encode job ID to prevent path traversal attacks\n */\n private validateAndEncodeJobId(jobId: string): string {\n // Validate job ID format (alphanumeric, hyphens, underscores only)\n if (!/^[a-zA-Z0-9_-]+$/.test(jobId)) {\n throw new Error(`Invalid job ID format: ${jobId}`);\n }\n // Encode for URL safety (defense in depth)\n return encodeURIComponent(jobId);\n }\n\n /**\n * Health check\n */\n async health(): Promise<{ ok: boolean; service: string }> {\n const response = await fetch(`${this.baseUrl}/health`);\n if (!response.ok) {\n throw new Error(`Health check failed: ${response.statusText}`);\n }\n return this.parseJsonResponse(response);\n }\n\n /**\n * Get workflow metrics\n */\n async getMetrics(): Promise<any> {\n const response = await fetch(`${this.baseUrl}/metrics`);\n if (!response.ok) {\n throw new Error(`Failed to get metrics: ${response.statusText}`);\n }\n const data = await this.parseJsonResponse<any>(response);\n return this.unwrapData(data);\n }\n\n /**\n * Get job status with full details (jobs, steps, outputs)\n */\n async getJobStatus(jobId: string): Promise<any> {\n const encodedJobId = this.validateAndEncodeJobId(jobId);\n const response = await fetch(`${this.baseUrl}/api/v1/jobs/${encodedJobId}`);\n if (response.status === 404) {\n throw new Error(`Job ${jobId} not found`);\n }\n if (!response.ok) {\n throw new Error(`Failed to get job status: ${response.statusText}`);\n }\n const data = await this.parseJsonResponse<any>(response);\n return this.unwrapData(data);\n }\n\n /**\n * Get job steps with outputs\n */\n async getJobSteps(jobId: string): Promise<any> {\n const encodedJobId = this.validateAndEncodeJobId(jobId);\n const response = await fetch(`${this.baseUrl}/api/v1/jobs/${encodedJobId}/steps`);\n if (response.status === 404) {\n throw new Error(`Job ${jobId} not found`);\n }\n if (!response.ok) {\n throw new Error(`Failed to get job steps: ${response.statusText}`);\n }\n const data = await this.parseJsonResponse<any>(response);\n return this.unwrapData(data);\n }\n\n /**\n * Get job logs\n */\n async getJobLogs(jobId: string): Promise<any[]> {\n const encodedJobId = this.validateAndEncodeJobId(jobId);\n const response = await fetch(`${this.baseUrl}/api/v1/jobs/${encodedJobId}/logs`);\n if (response.status === 404) {\n throw new Error(`Job ${jobId} not found`);\n }\n if (!response.ok) {\n throw new Error(`Failed to get job logs: ${response.statusText}`);\n }\n const data = await this.parseJsonResponse<any>(response);\n const unwrapped = this.unwrapData<{ logs: any[] }>(data);\n return unwrapped.logs ?? [];\n }\n\n /**\n * Get active executions\n */\n async getExecutions(): Promise<any[]> {\n const response = await fetch(`${this.baseUrl}/api/v1/jobs`);\n if (!response.ok) {\n throw new Error(`Failed to get executions: ${response.statusText}`);\n }\n const data = await this.parseJsonResponse<any>(response);\n const unwrapped = this.unwrapData<{ jobs?: any[] }>(data);\n const jobs = unwrapped.jobs ?? [];\n return jobs.filter((job: any) => job.status === 'running' || job.status === 'pending');\n }\n\n /**\n * Get cron jobs\n */\n async getCronJobs(): Promise<{\n crons: any[];\n }> {\n const response = await fetch(`${this.baseUrl}/api/v1/cron`);\n if (!response.ok) {\n throw new Error(`Failed to get cron jobs: ${response.statusText}`);\n }\n const data = await this.parseJsonResponse<any>(response);\n return this.unwrapData(data);\n }\n\n /**\n * Submit a job for execution\n */\n async submitJob(params: {\n handler: string;\n input?: unknown;\n priority?: number;\n }): Promise<{ id: string; status: string }> {\n const response = await fetch(`${this.baseUrl}/api/v1/jobs`, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n },\n body: JSON.stringify({\n type: params.handler,\n payload: params.input,\n priority: params.priority,\n }),\n });\n\n if (!response.ok) {\n const error = (await response.json().catch(() => ({ error: response.statusText }))) as {\n error?: string;\n };\n throw new Error(error.error || `Failed to submit job: ${response.statusText}`);\n }\n\n const payload = await this.parseJsonResponse<any>(response);\n const data = this.unwrapData<{ jobId: string }>(payload);\n return { id: data.jobId, status: 'pending' };\n }\n\n /**\n * Run workflow by ID\n */\n async runWorkflow(\n workflowId: string,\n request: WorkflowRunRequest = {}\n ): Promise<{ runId: string; status: string }> {\n const response = await fetch(\n `${this.baseUrl}/api/v1/workflows/${encodeURIComponent(workflowId)}/runs`,\n {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n },\n body: JSON.stringify(request),\n }\n );\n\n if (!response.ok) {\n const error = (await response.json().catch(() => ({ error: response.statusText }))) as {\n error?: string;\n };\n throw new Error(error.error || `Failed to run workflow: ${response.statusText}`);\n }\n\n const payload = await this.parseJsonResponse<{ ok: boolean; data?: { runId: string; status: string } }>(response);\n return this.unwrapData<{ runId: string; status: string }>(payload);\n }\n}\n","/**\n * workflow:run command - Run workflow by workflow ID\n */\n\nimport { defineCommand, type PluginContextV3 } from '@kb-labs/sdk';\nimport type { WorkflowRunRequest } from '@kb-labs/workflow-contracts';\nimport { WorkflowDaemonClient } from '../http-client.js';\nimport { type WorkflowRunFlags } from '../flags.js';\n\ntype WorkflowRunInput = WorkflowRunFlags & { argv?: string[] };\n\nfunction parseJsonInput(value: string | undefined): unknown {\n if (!value) {\n return undefined;\n }\n return JSON.parse(value);\n}\n\nfunction parseIsolation(value: string | undefined): WorkflowRunRequest['isolation'] {\n if (!value) {\n return undefined;\n }\n if (value === 'strict' || value === 'balanced' || value === 'relaxed') {\n return value;\n }\n throw new Error(`Invalid isolation value: ${value}. Expected strict|balanced|relaxed`);\n}\n\nfunction parseTriggerType(value: string | undefined): WorkflowRunRequest['trigger'] {\n if (!value) {\n return undefined;\n }\n if (value === 'manual' || value === 'api' || value === 'cron') {\n return { type: value };\n }\n throw new Error(`Invalid trigger type: ${value}. Expected manual|api|cron`);\n}\n\nexport default defineCommand<unknown, WorkflowRunInput, { exitCode: number }>({\n id: 'workflow:run',\n description: 'Run workflow by ID with optional target/isolation overrides',\n\n handler: {\n async execute(ctx: PluginContextV3, input: WorkflowRunInput): Promise<{ exitCode: number }> {\n const flags = (input as any).flags ?? input;\n const outputJson = flags.json ?? false;\n const workflowId = flags['workflow-id'];\n\n if (!workflowId) {\n const message = 'Missing required flag: --workflow-id';\n if (outputJson) {\n ctx.ui?.json?.({ ok: false, error: message });\n } else {\n ctx.ui?.error?.(message);\n }\n return { exitCode: 1 };\n }\n\n try {\n const request: WorkflowRunRequest = {};\n\n const inputPayload = parseJsonInput(flags.input);\n if (inputPayload !== undefined) {\n request.input = inputPayload;\n }\n\n const isolation = parseIsolation(flags.isolation);\n if (isolation) {\n request.isolation = isolation;\n }\n\n const trigger = parseTriggerType(flags['trigger-type']);\n if (trigger) {\n request.trigger = {\n ...trigger,\n user: flags['trigger-user'],\n };\n } else if (flags['trigger-user']) {\n request.trigger = {\n type: 'manual',\n user: flags['trigger-user'],\n };\n }\n\n const target = {\n environmentId: flags['target-environment-id'],\n workspaceId: flags['target-workspace-id'],\n namespace: flags['target-namespace'],\n workdir: flags['target-workdir'],\n };\n if (target.environmentId || target.workspaceId || target.namespace || target.workdir) {\n request.target = target;\n }\n\n const client = new WorkflowDaemonClient();\n const result = await client.runWorkflow(workflowId, request);\n\n if (outputJson) {\n ctx.ui?.json?.({ ok: true, data: result });\n } else {\n ctx.ui?.success?.('Workflow run submitted', {\n title: 'Workflow Engine',\n sections: [{\n header: 'Details',\n items: [\n `Workflow ID: ${workflowId}`,\n `Run ID: ${result.runId}`,\n `Status: ${result.status}`,\n ...(request.isolation ? [`Isolation: ${request.isolation}`] : []),\n ...(request.target?.namespace ? [`Target Namespace: ${request.target.namespace}`] : []),\n ...(request.target?.environmentId ? [`Target Environment: ${request.target.environmentId}`] : []),\n ...(request.target?.workspaceId ? [`Target Workspace: ${request.target.workspaceId}`] : []),\n ],\n }],\n });\n }\n\n return { exitCode: 0 };\n } catch (error) {\n const message = error instanceof Error ? error.message : String(error);\n if (outputJson) {\n ctx.ui?.json?.({ ok: false, error: message });\n } else {\n ctx.ui?.error?.(`Failed to run workflow: ${message}`);\n }\n return { exitCode: 1 };\n }\n },\n },\n});\n"]}
1
+ {"version":3,"sources":["../../src/http-client.ts","../../src/commands/workflow-run.ts"],"names":[],"mappings":"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","file":"workflow-run.js","sourcesContent":["/**\n * HTTP client for interacting with Workflow Daemon\n */\nimport type {\n WorkflowRunRequest,\n JobStatusInfo,\n JobStepsResponse,\n JobLogsResponse,\n CronListResponse,\n} from '@kb-labs/workflow-contracts';\nimport { useEnv } from '@kb-labs/sdk';\n\n/** Metrics data returned by GET /metrics */\nexport interface WorkflowMetricsData {\n runs: {\n total: number;\n queued: number;\n running: number;\n completed: number;\n failed: number;\n cancelled: number;\n };\n jobs: {\n total: number;\n queued: number;\n running: number;\n completed: number;\n failed: number;\n };\n [key: string]: unknown;\n}\n\n/** Extended job status returned by GET /api/v1/jobs/:id — includes nested jobs/steps */\nexport interface JobStatusDetail extends Omit<JobStatusInfo, 'result'> {\n result?: {\n ok: boolean;\n summary?: string;\n error?: { code: string; message: string };\n };\n jobs?: Array<{\n id: string;\n name: string;\n status: string;\n startedAt?: string;\n finishedAt?: string;\n durationMs?: number;\n error?: string;\n steps?: Array<{\n id: string;\n name: string;\n status: string;\n handler?: string;\n startedAt?: string;\n finishedAt?: string;\n durationMs?: number;\n outputs?: Record<string, unknown>;\n error?: { message?: string } | string;\n }>;\n }>;\n}\n\nconst DEFAULT_DAEMON_URL = 'http://localhost:7778';\n\nexport interface DaemonClientOptions {\n url?: string;\n}\n\n/**\n * Get workflow daemon URL from environment or default\n */\nexport function getWorkflowDaemonUrl(): string {\n return useEnv('WORKFLOW_DAEMON_URL') ?? DEFAULT_DAEMON_URL;\n}\n\nexport class WorkflowDaemonClient {\n private readonly baseUrl: string;\n\n constructor(options: DaemonClientOptions = {}) {\n this.baseUrl = options.url ?? useEnv('WORKFLOW_DAEMON_URL') ?? DEFAULT_DAEMON_URL;\n }\n\n /**\n * Validate response Content-Type and parse JSON safely\n */\n private async parseJsonResponse<T>(response: Response): Promise<T> {\n const contentType = response.headers.get('content-type');\n if (!contentType?.includes('application/json')) {\n throw new Error(`Invalid Content-Type: expected application/json, got ${contentType}`);\n }\n return response.json() as Promise<T>;\n }\n\n private unwrapData<T>(payload: unknown): T {\n if (\n payload\n && typeof payload === 'object'\n && 'ok' in payload\n && (payload as { ok?: boolean }).ok === true\n && 'data' in payload\n ) {\n return (payload as { data: T }).data;\n }\n return payload as T;\n }\n\n /**\n * Validate and encode job ID to prevent path traversal attacks\n */\n private validateAndEncodeJobId(jobId: string): string {\n // Validate job ID format (alphanumeric, hyphens, underscores only)\n if (!/^[a-zA-Z0-9_-]+$/.test(jobId)) {\n throw new Error(`Invalid job ID format: ${jobId}`);\n }\n // Encode for URL safety (defense in depth)\n return encodeURIComponent(jobId);\n }\n\n /**\n * Health check\n */\n async health(): Promise<{ ok: boolean; service: string }> {\n const response = await fetch(`${this.baseUrl}/health`);\n if (!response.ok) {\n throw new Error(`Health check failed: ${response.statusText}`);\n }\n return this.parseJsonResponse(response);\n }\n\n /**\n * Get workflow metrics (raw metrics data from daemon)\n */\n async getMetrics(): Promise<WorkflowMetricsData> {\n const response = await fetch(`${this.baseUrl}/metrics`);\n if (!response.ok) {\n throw new Error(`Failed to get metrics: ${response.statusText}`);\n }\n const data = await this.parseJsonResponse<unknown>(response);\n return this.unwrapData<WorkflowMetricsData>(data);\n }\n\n /**\n * Get job status with full details (jobs, steps, outputs)\n */\n async getJobStatus(jobId: string): Promise<JobStatusDetail> {\n const encodedJobId = this.validateAndEncodeJobId(jobId);\n const response = await fetch(`${this.baseUrl}/api/v1/jobs/${encodedJobId}`);\n if (response.status === 404) {\n throw new Error(`Job ${jobId} not found`);\n }\n if (!response.ok) {\n throw new Error(`Failed to get job status: ${response.statusText}`);\n }\n const data = await this.parseJsonResponse<unknown>(response);\n return this.unwrapData<JobStatusDetail>(data);\n }\n\n /**\n * Get job steps with outputs\n */\n async getJobSteps(jobId: string): Promise<JobStepsResponse> {\n const encodedJobId = this.validateAndEncodeJobId(jobId);\n const response = await fetch(`${this.baseUrl}/api/v1/jobs/${encodedJobId}/steps`);\n if (response.status === 404) {\n throw new Error(`Job ${jobId} not found`);\n }\n if (!response.ok) {\n throw new Error(`Failed to get job steps: ${response.statusText}`);\n }\n const data = await this.parseJsonResponse<unknown>(response);\n return this.unwrapData<JobStepsResponse>(data);\n }\n\n /**\n * Get job logs\n */\n async getJobLogs(jobId: string): Promise<JobLogsResponse['logs']> {\n const encodedJobId = this.validateAndEncodeJobId(jobId);\n const response = await fetch(`${this.baseUrl}/api/v1/jobs/${encodedJobId}/logs`);\n if (response.status === 404) {\n throw new Error(`Job ${jobId} not found`);\n }\n if (!response.ok) {\n throw new Error(`Failed to get job logs: ${response.statusText}`);\n }\n const data = await this.parseJsonResponse<unknown>(response);\n const unwrapped = this.unwrapData<JobLogsResponse>(data);\n return unwrapped.logs ?? [];\n }\n\n /**\n * Get active executions\n */\n async getExecutions(): Promise<JobStatusInfo[]> {\n const response = await fetch(`${this.baseUrl}/api/v1/jobs`);\n if (!response.ok) {\n throw new Error(`Failed to get executions: ${response.statusText}`);\n }\n const data = await this.parseJsonResponse<unknown>(response);\n const unwrapped = this.unwrapData<{ jobs?: JobStatusInfo[] }>(data);\n const jobs = unwrapped.jobs ?? [];\n return jobs.filter((job) => job.status === 'running' || job.status === 'pending');\n }\n\n /**\n * Get cron jobs\n */\n async getCronJobs(): Promise<CronListResponse> {\n const response = await fetch(`${this.baseUrl}/api/v1/cron`);\n if (!response.ok) {\n throw new Error(`Failed to get cron jobs: ${response.statusText}`);\n }\n const data = await this.parseJsonResponse<unknown>(response);\n return this.unwrapData<CronListResponse>(data);\n }\n\n /**\n * Submit a job for execution\n */\n async submitJob(params: {\n handler: string;\n input?: unknown;\n priority?: number;\n }): Promise<{ id: string; status: string }> {\n const response = await fetch(`${this.baseUrl}/api/v1/jobs`, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n },\n body: JSON.stringify({\n type: params.handler,\n payload: params.input,\n priority: params.priority,\n }),\n });\n\n if (!response.ok) {\n const error = (await response.json().catch(() => ({ error: response.statusText }))) as {\n error?: string;\n };\n throw new Error(error.error || `Failed to submit job: ${response.statusText}`);\n }\n\n const payload = await this.parseJsonResponse<unknown>(response);\n const data = this.unwrapData<{ jobId: string }>(payload);\n return { id: data.jobId, status: 'pending' };\n }\n\n /**\n * Run workflow by ID\n */\n async runWorkflow(\n workflowId: string,\n request: WorkflowRunRequest = {}\n ): Promise<{ runId: string; status: string }> {\n const response = await fetch(\n `${this.baseUrl}/api/v1/workflows/${encodeURIComponent(workflowId)}/runs`,\n {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n },\n body: JSON.stringify(request),\n }\n );\n\n if (!response.ok) {\n const error = (await response.json().catch(() => ({ error: response.statusText }))) as {\n error?: string;\n };\n throw new Error(error.error || `Failed to run workflow: ${response.statusText}`);\n }\n\n const payload = await this.parseJsonResponse<{ ok: boolean; data?: { runId: string; status: string } }>(response);\n return this.unwrapData<{ runId: string; status: string }>(payload);\n }\n}\n","/**\n * workflow:run command - Run workflow by workflow ID\n */\n\nimport { defineCommand, type PluginContextV3 } from '@kb-labs/sdk';\nimport type { WorkflowRunRequest } from '@kb-labs/workflow-contracts';\nimport { WorkflowDaemonClient } from '../http-client.js';\nimport { type WorkflowRunFlags } from '../flags.js';\n\ntype WorkflowRunInput = WorkflowRunFlags & { argv?: string[] };\n\nfunction parseJsonInput(value: string | undefined): unknown {\n if (!value) {\n return undefined;\n }\n return JSON.parse(value);\n}\n\nfunction parseIsolation(value: string | undefined): WorkflowRunRequest['isolation'] {\n if (!value) {\n return undefined;\n }\n if (value === 'strict' || value === 'balanced' || value === 'relaxed') {\n return value;\n }\n throw new Error(`Invalid isolation value: ${value}. Expected strict|balanced|relaxed`);\n}\n\nfunction parseTriggerType(value: string | undefined): WorkflowRunRequest['trigger'] {\n if (!value) {\n return undefined;\n }\n if (value === 'manual' || value === 'api' || value === 'cron') {\n return { type: value };\n }\n throw new Error(`Invalid trigger type: ${value}. Expected manual|api|cron`);\n}\n\nexport default defineCommand<unknown, WorkflowRunInput, { exitCode: number }>({\n id: 'workflow:run',\n description: 'Run workflow by ID with optional target/isolation overrides',\n\n handler: {\n async execute(ctx: PluginContextV3, input: WorkflowRunInput): Promise<{ exitCode: number }> {\n const flags = ((input as { flags?: WorkflowRunInput } & WorkflowRunInput).flags ?? input) as unknown as {\n json?: boolean; 'workflow-id'?: string; input?: string; isolation?: string;\n 'trigger-type'?: string; 'trigger-user'?: string;\n 'target-environment-id'?: string; 'target-workspace-id'?: string;\n 'target-namespace'?: string; 'target-workdir'?: string;\n };\n const outputJson = flags.json ?? false;\n const workflowId = flags['workflow-id'];\n\n if (!workflowId) {\n const message = 'Missing required flag: --workflow-id';\n if (outputJson) {\n ctx.ui?.json?.({ ok: false, error: message });\n } else {\n ctx.ui?.error?.(message);\n }\n return { exitCode: 1 };\n }\n\n try {\n const request: WorkflowRunRequest = {};\n\n const inputPayload = parseJsonInput(flags.input);\n if (inputPayload !== undefined) {\n request.input = inputPayload;\n }\n\n const isolation = parseIsolation(flags.isolation);\n if (isolation) {\n request.isolation = isolation;\n }\n\n const trigger = parseTriggerType(flags['trigger-type']);\n if (trigger) {\n request.trigger = {\n ...trigger,\n user: flags['trigger-user'],\n };\n } else if (flags['trigger-user']) {\n request.trigger = {\n type: 'manual',\n user: flags['trigger-user'],\n };\n }\n\n const target = {\n environmentId: flags['target-environment-id'],\n workspaceId: flags['target-workspace-id'],\n namespace: flags['target-namespace'],\n workdir: flags['target-workdir'],\n };\n if (target.environmentId || target.workspaceId || target.namespace || target.workdir) {\n request.target = target;\n }\n\n const client = new WorkflowDaemonClient();\n const result = await client.runWorkflow(workflowId, request);\n\n if (outputJson) {\n ctx.ui?.json?.({ ok: true, data: result });\n } else {\n ctx.ui?.success?.('Workflow run submitted', {\n title: 'Workflow Engine',\n sections: [{\n header: 'Details',\n items: [\n `Workflow ID: ${workflowId}`,\n `Run ID: ${result.runId}`,\n `Status: ${result.status}`,\n ...(request.isolation ? [`Isolation: ${request.isolation}`] : []),\n ...(request.target?.namespace ? [`Target Namespace: ${request.target.namespace}`] : []),\n ...(request.target?.environmentId ? [`Target Environment: ${request.target.environmentId}`] : []),\n ...(request.target?.workspaceId ? [`Target Workspace: ${request.target.workspaceId}`] : []),\n ],\n }],\n });\n }\n\n return { exitCode: 0 };\n } catch (error) {\n const message = error instanceof Error ? error.message : String(error);\n if (outputJson) {\n ctx.ui?.json?.({ ok: false, error: message });\n } else {\n ctx.ui?.error?.(`Failed to run workflow: ${message}`);\n }\n return { exitCode: 1 };\n }\n },\n },\n});\n"]}
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/http-client.ts","../../src/rest/cron-list-handler.ts"],"names":[],"mappings":";;;AAMA,IAAM,kBAAA,GAAqB,uBAAA;AASpB,SAAS,oBAAA,GAA+B;AAC7C,EAAA,OAAO,MAAA,CAAO,qBAAqB,CAAA,IAAK,kBAAA;AAC1C;;;ACYA,IAAO,4BAAQ,aAAA,CAAc;AAAA,EAC3B,MAAM,OAAA,CACJ,GAAA,EACA,MAAA,EAC2B;AAC3B,IAAA,MAAM,YAAY,oBAAA,EAAqB;AACvC,IAAA,MAAM,GAAA,GAAM,GAAG,SAAS,CAAA,YAAA,CAAA;AAExB,IAAA,GAAA,CAAI,QAAA,CAAS,MAAA,CAAO,IAAA,CAAK,CAAA,4CAAA,EAA+C,GAAG,CAAA,CAAE,CAAA;AAE7E,IAAA,IAAI;AACF,MAAA,MAAM,QAAA,GAAW,MAAM,KAAA,CAAM,GAAA,EAAK;AAAA,QAChC,MAAA,EAAQ,KAAA;AAAA,QACR,OAAA,EAAS;AAAA,UACP,cAAA,EAAgB;AAAA;AAClB,OACD,CAAA;AAED,MAAA,IAAI,CAAC,SAAS,EAAA,EAAI;AAChB,QAAA,MAAM,SAAA,GAAY,MAAM,QAAA,CAAS,IAAA,EAAK,CAAE,MAAM,OAAO,EAAE,KAAA,EAAO,eAAA,EAAgB,CAAE,CAAA;AAChF,QAAA,GAAA,CAAI,QAAA,CAAS,OAAO,KAAA,CAAM,CAAA,+CAAA,EAAkD,KAAK,SAAA,CAAU,SAAS,CAAC,CAAA,CAAE,CAAA;AACvG,QAAA,MAAM,IAAI,KAAA,CAAM,SAAA,CAAU,KAAA,IAAS,2BAA2B,CAAA;AAAA,MAChE;AAEA,MAAA,MAAM,OAAA,GAAW,MAAM,QAAA,CAAS,IAAA,EAAK;AACrC,MAAA,IAAI,CAAC,OAAA,CAAQ,EAAA,IAAM,CAAC,QAAQ,IAAA,EAAM;AAChC,QAAA,MAAM,IAAI,KAAA,CAAM,OAAA,CAAQ,KAAA,IAAS,2BAA2B,CAAA;AAAA,MAC9D;AACA,MAAA,MAAM,OAAO,OAAA,CAAQ,IAAA;AACrB,MAAA,GAAA,CAAI,SAAS,MAAA,CAAO,IAAA,CAAK,+BAA+B,IAAA,CAAK,KAAA,CAAM,MAAM,CAAA,UAAA,CAAY,CAAA;AACrF,MAAA,OAAO,IAAA;AAAA,IACT,SAAS,KAAA,EAAO;AACd,MAAA,GAAA,CAAI,SAAS,MAAA,CAAO,KAAA;AAAA,QAClB,8CAAA;AAAA,QACA,KAAA,YAAiB,QAAQ,KAAA,GAAQ;AAAA,OACnC;AACA,MAAA,MAAM,KAAA;AAAA,IACR;AAAA,EACF;AACF,CAAC","file":"cron-list-handler.js","sourcesContent":["/**\n * HTTP client for interacting with Workflow Daemon\n */\nimport type { WorkflowRunRequest } from '@kb-labs/workflow-contracts';\nimport { useEnv } from '@kb-labs/sdk';\n\nconst DEFAULT_DAEMON_URL = 'http://localhost:7778';\n\nexport interface DaemonClientOptions {\n url?: string;\n}\n\n/**\n * Get workflow daemon URL from environment or default\n */\nexport function getWorkflowDaemonUrl(): string {\n return useEnv('WORKFLOW_DAEMON_URL') ?? DEFAULT_DAEMON_URL;\n}\n\nexport class WorkflowDaemonClient {\n private readonly baseUrl: string;\n\n constructor(options: DaemonClientOptions = {}) {\n this.baseUrl = options.url ?? useEnv('WORKFLOW_DAEMON_URL') ?? DEFAULT_DAEMON_URL;\n }\n\n /**\n * Validate response Content-Type and parse JSON safely\n */\n private async parseJsonResponse<T>(response: Response): Promise<T> {\n const contentType = response.headers.get('content-type');\n if (!contentType?.includes('application/json')) {\n throw new Error(`Invalid Content-Type: expected application/json, got ${contentType}`);\n }\n return response.json() as Promise<T>;\n }\n\n private unwrapData<T>(payload: unknown): T {\n if (\n payload\n && typeof payload === 'object'\n && 'ok' in payload\n && (payload as any).ok === true\n && 'data' in payload\n ) {\n return (payload as { data: T }).data;\n }\n return payload as T;\n }\n\n /**\n * Validate and encode job ID to prevent path traversal attacks\n */\n private validateAndEncodeJobId(jobId: string): string {\n // Validate job ID format (alphanumeric, hyphens, underscores only)\n if (!/^[a-zA-Z0-9_-]+$/.test(jobId)) {\n throw new Error(`Invalid job ID format: ${jobId}`);\n }\n // Encode for URL safety (defense in depth)\n return encodeURIComponent(jobId);\n }\n\n /**\n * Health check\n */\n async health(): Promise<{ ok: boolean; service: string }> {\n const response = await fetch(`${this.baseUrl}/health`);\n if (!response.ok) {\n throw new Error(`Health check failed: ${response.statusText}`);\n }\n return this.parseJsonResponse(response);\n }\n\n /**\n * Get workflow metrics\n */\n async getMetrics(): Promise<any> {\n const response = await fetch(`${this.baseUrl}/metrics`);\n if (!response.ok) {\n throw new Error(`Failed to get metrics: ${response.statusText}`);\n }\n const data = await this.parseJsonResponse<any>(response);\n return this.unwrapData(data);\n }\n\n /**\n * Get job status with full details (jobs, steps, outputs)\n */\n async getJobStatus(jobId: string): Promise<any> {\n const encodedJobId = this.validateAndEncodeJobId(jobId);\n const response = await fetch(`${this.baseUrl}/api/v1/jobs/${encodedJobId}`);\n if (response.status === 404) {\n throw new Error(`Job ${jobId} not found`);\n }\n if (!response.ok) {\n throw new Error(`Failed to get job status: ${response.statusText}`);\n }\n const data = await this.parseJsonResponse<any>(response);\n return this.unwrapData(data);\n }\n\n /**\n * Get job steps with outputs\n */\n async getJobSteps(jobId: string): Promise<any> {\n const encodedJobId = this.validateAndEncodeJobId(jobId);\n const response = await fetch(`${this.baseUrl}/api/v1/jobs/${encodedJobId}/steps`);\n if (response.status === 404) {\n throw new Error(`Job ${jobId} not found`);\n }\n if (!response.ok) {\n throw new Error(`Failed to get job steps: ${response.statusText}`);\n }\n const data = await this.parseJsonResponse<any>(response);\n return this.unwrapData(data);\n }\n\n /**\n * Get job logs\n */\n async getJobLogs(jobId: string): Promise<any[]> {\n const encodedJobId = this.validateAndEncodeJobId(jobId);\n const response = await fetch(`${this.baseUrl}/api/v1/jobs/${encodedJobId}/logs`);\n if (response.status === 404) {\n throw new Error(`Job ${jobId} not found`);\n }\n if (!response.ok) {\n throw new Error(`Failed to get job logs: ${response.statusText}`);\n }\n const data = await this.parseJsonResponse<any>(response);\n const unwrapped = this.unwrapData<{ logs: any[] }>(data);\n return unwrapped.logs ?? [];\n }\n\n /**\n * Get active executions\n */\n async getExecutions(): Promise<any[]> {\n const response = await fetch(`${this.baseUrl}/api/v1/jobs`);\n if (!response.ok) {\n throw new Error(`Failed to get executions: ${response.statusText}`);\n }\n const data = await this.parseJsonResponse<any>(response);\n const unwrapped = this.unwrapData<{ jobs?: any[] }>(data);\n const jobs = unwrapped.jobs ?? [];\n return jobs.filter((job: any) => job.status === 'running' || job.status === 'pending');\n }\n\n /**\n * Get cron jobs\n */\n async getCronJobs(): Promise<{\n crons: any[];\n }> {\n const response = await fetch(`${this.baseUrl}/api/v1/cron`);\n if (!response.ok) {\n throw new Error(`Failed to get cron jobs: ${response.statusText}`);\n }\n const data = await this.parseJsonResponse<any>(response);\n return this.unwrapData(data);\n }\n\n /**\n * Submit a job for execution\n */\n async submitJob(params: {\n handler: string;\n input?: unknown;\n priority?: number;\n }): Promise<{ id: string; status: string }> {\n const response = await fetch(`${this.baseUrl}/api/v1/jobs`, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n },\n body: JSON.stringify({\n type: params.handler,\n payload: params.input,\n priority: params.priority,\n }),\n });\n\n if (!response.ok) {\n const error = (await response.json().catch(() => ({ error: response.statusText }))) as {\n error?: string;\n };\n throw new Error(error.error || `Failed to submit job: ${response.statusText}`);\n }\n\n const payload = await this.parseJsonResponse<any>(response);\n const data = this.unwrapData<{ jobId: string }>(payload);\n return { id: data.jobId, status: 'pending' };\n }\n\n /**\n * Run workflow by ID\n */\n async runWorkflow(\n workflowId: string,\n request: WorkflowRunRequest = {}\n ): Promise<{ runId: string; status: string }> {\n const response = await fetch(\n `${this.baseUrl}/api/v1/workflows/${encodeURIComponent(workflowId)}/runs`,\n {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n },\n body: JSON.stringify(request),\n }\n );\n\n if (!response.ok) {\n const error = (await response.json().catch(() => ({ error: response.statusText }))) as {\n error?: string;\n };\n throw new Error(error.error || `Failed to run workflow: ${response.statusText}`);\n }\n\n const payload = await this.parseJsonResponse<{ ok: boolean; data?: { runId: string; status: string } }>(response);\n return this.unwrapData<{ runId: string; status: string }>(payload);\n }\n}\n","/**\n * REST handler: GET /workflows/cron\n * Proxies to workflow daemon GET /api/v1/cron\n */\n\nimport { defineHandler, type RestInput, type PluginContextV3 } from '@kb-labs/sdk';\nimport { getWorkflowDaemonUrl } from '../http-client';\n\ninterface CronInfo {\n id: string;\n schedule: string;\n jobType: string;\n timezone?: string;\n enabled: boolean;\n lastRun?: string;\n nextRun?: string;\n pluginId?: string;\n}\n\ninterface CronListResponse {\n crons: CronInfo[];\n}\n\ninterface ApiEnvelope<T> {\n ok: boolean;\n data?: T;\n error?: string;\n}\n\nexport default defineHandler({\n async execute(\n ctx: PluginContextV3,\n _input: RestInput\n ): Promise<CronListResponse> {\n const daemonUrl = getWorkflowDaemonUrl();\n const url = `${daemonUrl}/api/v1/cron`;\n\n ctx.platform.logger.info(`[cron-list-handler] Fetching cron jobs from ${url}`);\n\n try {\n const response = await fetch(url, {\n method: 'GET',\n headers: {\n 'Content-Type': 'application/json',\n },\n });\n\n if (!response.ok) {\n const errorData = await response.json().catch(() => ({ error: 'Unknown error' })) as { error?: string };\n ctx.platform.logger.error(`[cron-list-handler] Failed to fetch cron jobs: ${JSON.stringify(errorData)}`);\n throw new Error(errorData.error || 'Failed to fetch cron jobs');\n }\n\n const payload = (await response.json()) as ApiEnvelope<CronListResponse>;\n if (!payload.ok || !payload.data) {\n throw new Error(payload.error || 'Failed to fetch cron jobs');\n }\n const data = payload.data;\n ctx.platform.logger.info(`[cron-list-handler] Fetched ${data.crons.length} cron jobs`);\n return data;\n } catch (error) {\n ctx.platform.logger.error(\n '[cron-list-handler] Error fetching cron list',\n error instanceof Error ? error : undefined\n );\n throw error;\n }\n },\n});\n"]}
1
+ {"version":3,"sources":["../../src/http-client.ts","../../src/rest/cron-list-handler.ts"],"names":[],"mappings":";;;AA6DA,IAAM,kBAAA,GAAqB,uBAAA;AASpB,SAAS,oBAAA,GAA+B;AAC7C,EAAA,OAAO,MAAA,CAAO,qBAAqB,CAAA,IAAK,kBAAA;AAC1C;;;AC3CA,IAAO,4BAAQ,aAAA,CAAc;AAAA,EAC3B,MAAM,OAAA,CACJ,GAAA,EACA,MAAA,EAC2B;AAC3B,IAAA,MAAM,YAAY,oBAAA,EAAqB;AACvC,IAAA,MAAM,GAAA,GAAM,GAAG,SAAS,CAAA,YAAA,CAAA;AAExB,IAAA,GAAA,CAAI,QAAA,CAAS,MAAA,CAAO,IAAA,CAAK,CAAA,4CAAA,EAA+C,GAAG,CAAA,CAAE,CAAA;AAE7E,IAAA,IAAI;AACF,MAAA,MAAM,QAAA,GAAW,MAAM,KAAA,CAAM,GAAA,EAAK;AAAA,QAChC,MAAA,EAAQ,KAAA;AAAA,QACR,OAAA,EAAS;AAAA,UACP,cAAA,EAAgB;AAAA;AAClB,OACD,CAAA;AAED,MAAA,IAAI,CAAC,SAAS,EAAA,EAAI;AAChB,QAAA,MAAM,SAAA,GAAY,MAAM,QAAA,CAAS,IAAA,EAAK,CAAE,MAAM,OAAO,EAAE,KAAA,EAAO,eAAA,EAAgB,CAAE,CAAA;AAChF,QAAA,GAAA,CAAI,QAAA,CAAS,OAAO,KAAA,CAAM,CAAA,+CAAA,EAAkD,KAAK,SAAA,CAAU,SAAS,CAAC,CAAA,CAAE,CAAA;AACvG,QAAA,MAAM,IAAI,KAAA,CAAM,SAAA,CAAU,KAAA,IAAS,2BAA2B,CAAA;AAAA,MAChE;AAEA,MAAA,MAAM,OAAA,GAAW,MAAM,QAAA,CAAS,IAAA,EAAK;AACrC,MAAA,IAAI,CAAC,OAAA,CAAQ,EAAA,IAAM,CAAC,QAAQ,IAAA,EAAM;AAChC,QAAA,MAAM,IAAI,KAAA,CAAM,OAAA,CAAQ,KAAA,IAAS,2BAA2B,CAAA;AAAA,MAC9D;AACA,MAAA,MAAM,OAAO,OAAA,CAAQ,IAAA;AACrB,MAAA,GAAA,CAAI,SAAS,MAAA,CAAO,IAAA,CAAK,+BAA+B,IAAA,CAAK,KAAA,CAAM,MAAM,CAAA,UAAA,CAAY,CAAA;AACrF,MAAA,OAAO,IAAA;AAAA,IACT,SAAS,KAAA,EAAO;AACd,MAAA,GAAA,CAAI,SAAS,MAAA,CAAO,KAAA;AAAA,QAClB,8CAAA;AAAA,QACA,KAAA,YAAiB,QAAQ,KAAA,GAAQ;AAAA,OACnC;AACA,MAAA,MAAM,KAAA;AAAA,IACR;AAAA,EACF;AACF,CAAC","file":"cron-list-handler.js","sourcesContent":["/**\n * HTTP client for interacting with Workflow Daemon\n */\nimport type {\n WorkflowRunRequest,\n JobStatusInfo,\n JobStepsResponse,\n JobLogsResponse,\n CronListResponse,\n} from '@kb-labs/workflow-contracts';\nimport { useEnv } from '@kb-labs/sdk';\n\n/** Metrics data returned by GET /metrics */\nexport interface WorkflowMetricsData {\n runs: {\n total: number;\n queued: number;\n running: number;\n completed: number;\n failed: number;\n cancelled: number;\n };\n jobs: {\n total: number;\n queued: number;\n running: number;\n completed: number;\n failed: number;\n };\n [key: string]: unknown;\n}\n\n/** Extended job status returned by GET /api/v1/jobs/:id — includes nested jobs/steps */\nexport interface JobStatusDetail extends Omit<JobStatusInfo, 'result'> {\n result?: {\n ok: boolean;\n summary?: string;\n error?: { code: string; message: string };\n };\n jobs?: Array<{\n id: string;\n name: string;\n status: string;\n startedAt?: string;\n finishedAt?: string;\n durationMs?: number;\n error?: string;\n steps?: Array<{\n id: string;\n name: string;\n status: string;\n handler?: string;\n startedAt?: string;\n finishedAt?: string;\n durationMs?: number;\n outputs?: Record<string, unknown>;\n error?: { message?: string } | string;\n }>;\n }>;\n}\n\nconst DEFAULT_DAEMON_URL = 'http://localhost:7778';\n\nexport interface DaemonClientOptions {\n url?: string;\n}\n\n/**\n * Get workflow daemon URL from environment or default\n */\nexport function getWorkflowDaemonUrl(): string {\n return useEnv('WORKFLOW_DAEMON_URL') ?? DEFAULT_DAEMON_URL;\n}\n\nexport class WorkflowDaemonClient {\n private readonly baseUrl: string;\n\n constructor(options: DaemonClientOptions = {}) {\n this.baseUrl = options.url ?? useEnv('WORKFLOW_DAEMON_URL') ?? DEFAULT_DAEMON_URL;\n }\n\n /**\n * Validate response Content-Type and parse JSON safely\n */\n private async parseJsonResponse<T>(response: Response): Promise<T> {\n const contentType = response.headers.get('content-type');\n if (!contentType?.includes('application/json')) {\n throw new Error(`Invalid Content-Type: expected application/json, got ${contentType}`);\n }\n return response.json() as Promise<T>;\n }\n\n private unwrapData<T>(payload: unknown): T {\n if (\n payload\n && typeof payload === 'object'\n && 'ok' in payload\n && (payload as { ok?: boolean }).ok === true\n && 'data' in payload\n ) {\n return (payload as { data: T }).data;\n }\n return payload as T;\n }\n\n /**\n * Validate and encode job ID to prevent path traversal attacks\n */\n private validateAndEncodeJobId(jobId: string): string {\n // Validate job ID format (alphanumeric, hyphens, underscores only)\n if (!/^[a-zA-Z0-9_-]+$/.test(jobId)) {\n throw new Error(`Invalid job ID format: ${jobId}`);\n }\n // Encode for URL safety (defense in depth)\n return encodeURIComponent(jobId);\n }\n\n /**\n * Health check\n */\n async health(): Promise<{ ok: boolean; service: string }> {\n const response = await fetch(`${this.baseUrl}/health`);\n if (!response.ok) {\n throw new Error(`Health check failed: ${response.statusText}`);\n }\n return this.parseJsonResponse(response);\n }\n\n /**\n * Get workflow metrics (raw metrics data from daemon)\n */\n async getMetrics(): Promise<WorkflowMetricsData> {\n const response = await fetch(`${this.baseUrl}/metrics`);\n if (!response.ok) {\n throw new Error(`Failed to get metrics: ${response.statusText}`);\n }\n const data = await this.parseJsonResponse<unknown>(response);\n return this.unwrapData<WorkflowMetricsData>(data);\n }\n\n /**\n * Get job status with full details (jobs, steps, outputs)\n */\n async getJobStatus(jobId: string): Promise<JobStatusDetail> {\n const encodedJobId = this.validateAndEncodeJobId(jobId);\n const response = await fetch(`${this.baseUrl}/api/v1/jobs/${encodedJobId}`);\n if (response.status === 404) {\n throw new Error(`Job ${jobId} not found`);\n }\n if (!response.ok) {\n throw new Error(`Failed to get job status: ${response.statusText}`);\n }\n const data = await this.parseJsonResponse<unknown>(response);\n return this.unwrapData<JobStatusDetail>(data);\n }\n\n /**\n * Get job steps with outputs\n */\n async getJobSteps(jobId: string): Promise<JobStepsResponse> {\n const encodedJobId = this.validateAndEncodeJobId(jobId);\n const response = await fetch(`${this.baseUrl}/api/v1/jobs/${encodedJobId}/steps`);\n if (response.status === 404) {\n throw new Error(`Job ${jobId} not found`);\n }\n if (!response.ok) {\n throw new Error(`Failed to get job steps: ${response.statusText}`);\n }\n const data = await this.parseJsonResponse<unknown>(response);\n return this.unwrapData<JobStepsResponse>(data);\n }\n\n /**\n * Get job logs\n */\n async getJobLogs(jobId: string): Promise<JobLogsResponse['logs']> {\n const encodedJobId = this.validateAndEncodeJobId(jobId);\n const response = await fetch(`${this.baseUrl}/api/v1/jobs/${encodedJobId}/logs`);\n if (response.status === 404) {\n throw new Error(`Job ${jobId} not found`);\n }\n if (!response.ok) {\n throw new Error(`Failed to get job logs: ${response.statusText}`);\n }\n const data = await this.parseJsonResponse<unknown>(response);\n const unwrapped = this.unwrapData<JobLogsResponse>(data);\n return unwrapped.logs ?? [];\n }\n\n /**\n * Get active executions\n */\n async getExecutions(): Promise<JobStatusInfo[]> {\n const response = await fetch(`${this.baseUrl}/api/v1/jobs`);\n if (!response.ok) {\n throw new Error(`Failed to get executions: ${response.statusText}`);\n }\n const data = await this.parseJsonResponse<unknown>(response);\n const unwrapped = this.unwrapData<{ jobs?: JobStatusInfo[] }>(data);\n const jobs = unwrapped.jobs ?? [];\n return jobs.filter((job) => job.status === 'running' || job.status === 'pending');\n }\n\n /**\n * Get cron jobs\n */\n async getCronJobs(): Promise<CronListResponse> {\n const response = await fetch(`${this.baseUrl}/api/v1/cron`);\n if (!response.ok) {\n throw new Error(`Failed to get cron jobs: ${response.statusText}`);\n }\n const data = await this.parseJsonResponse<unknown>(response);\n return this.unwrapData<CronListResponse>(data);\n }\n\n /**\n * Submit a job for execution\n */\n async submitJob(params: {\n handler: string;\n input?: unknown;\n priority?: number;\n }): Promise<{ id: string; status: string }> {\n const response = await fetch(`${this.baseUrl}/api/v1/jobs`, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n },\n body: JSON.stringify({\n type: params.handler,\n payload: params.input,\n priority: params.priority,\n }),\n });\n\n if (!response.ok) {\n const error = (await response.json().catch(() => ({ error: response.statusText }))) as {\n error?: string;\n };\n throw new Error(error.error || `Failed to submit job: ${response.statusText}`);\n }\n\n const payload = await this.parseJsonResponse<unknown>(response);\n const data = this.unwrapData<{ jobId: string }>(payload);\n return { id: data.jobId, status: 'pending' };\n }\n\n /**\n * Run workflow by ID\n */\n async runWorkflow(\n workflowId: string,\n request: WorkflowRunRequest = {}\n ): Promise<{ runId: string; status: string }> {\n const response = await fetch(\n `${this.baseUrl}/api/v1/workflows/${encodeURIComponent(workflowId)}/runs`,\n {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n },\n body: JSON.stringify(request),\n }\n );\n\n if (!response.ok) {\n const error = (await response.json().catch(() => ({ error: response.statusText }))) as {\n error?: string;\n };\n throw new Error(error.error || `Failed to run workflow: ${response.statusText}`);\n }\n\n const payload = await this.parseJsonResponse<{ ok: boolean; data?: { runId: string; status: string } }>(response);\n return this.unwrapData<{ runId: string; status: string }>(payload);\n }\n}\n","/**\n * REST handler: GET /workflows/cron\n * Proxies to workflow daemon GET /api/v1/cron\n */\n\nimport { defineHandler, type RestInput, type PluginContextV3 } from '@kb-labs/sdk';\nimport { getWorkflowDaemonUrl } from '../http-client';\n\ninterface CronInfo {\n id: string;\n schedule: string;\n jobType: string;\n timezone?: string;\n enabled: boolean;\n lastRun?: string;\n nextRun?: string;\n pluginId?: string;\n}\n\ninterface CronListResponse {\n crons: CronInfo[];\n}\n\ninterface ApiEnvelope<T> {\n ok: boolean;\n data?: T;\n error?: string;\n}\n\nexport default defineHandler({\n async execute(\n ctx: PluginContextV3,\n _input: RestInput\n ): Promise<CronListResponse> {\n const daemonUrl = getWorkflowDaemonUrl();\n const url = `${daemonUrl}/api/v1/cron`;\n\n ctx.platform.logger.info(`[cron-list-handler] Fetching cron jobs from ${url}`);\n\n try {\n const response = await fetch(url, {\n method: 'GET',\n headers: {\n 'Content-Type': 'application/json',\n },\n });\n\n if (!response.ok) {\n const errorData = await response.json().catch(() => ({ error: 'Unknown error' })) as { error?: string };\n ctx.platform.logger.error(`[cron-list-handler] Failed to fetch cron jobs: ${JSON.stringify(errorData)}`);\n throw new Error(errorData.error || 'Failed to fetch cron jobs');\n }\n\n const payload = (await response.json()) as ApiEnvelope<CronListResponse>;\n if (!payload.ok || !payload.data) {\n throw new Error(payload.error || 'Failed to fetch cron jobs');\n }\n const data = payload.data;\n ctx.platform.logger.info(`[cron-list-handler] Fetched ${data.crons.length} cron jobs`);\n return data;\n } catch (error) {\n ctx.platform.logger.error(\n '[cron-list-handler] Error fetching cron list',\n error instanceof Error ? error : undefined\n );\n throw error;\n }\n },\n});\n"]}
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/http-client.ts","../../src/rest/jobs-list-handler.ts","../../src/rest/job-detail-handler.ts","../../src/rest/job-cancel-handler.ts","../../src/rest/cron-list-handler.ts"],"names":["defineHandler"],"mappings":"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","file":"index.js","sourcesContent":["/**\n * HTTP client for interacting with Workflow Daemon\n */\nimport type { WorkflowRunRequest } from '@kb-labs/workflow-contracts';\nimport { useEnv } from '@kb-labs/sdk';\n\nconst DEFAULT_DAEMON_URL = 'http://localhost:7778';\n\nexport interface DaemonClientOptions {\n url?: string;\n}\n\n/**\n * Get workflow daemon URL from environment or default\n */\nexport function getWorkflowDaemonUrl(): string {\n return useEnv('WORKFLOW_DAEMON_URL') ?? DEFAULT_DAEMON_URL;\n}\n\nexport class WorkflowDaemonClient {\n private readonly baseUrl: string;\n\n constructor(options: DaemonClientOptions = {}) {\n this.baseUrl = options.url ?? useEnv('WORKFLOW_DAEMON_URL') ?? DEFAULT_DAEMON_URL;\n }\n\n /**\n * Validate response Content-Type and parse JSON safely\n */\n private async parseJsonResponse<T>(response: Response): Promise<T> {\n const contentType = response.headers.get('content-type');\n if (!contentType?.includes('application/json')) {\n throw new Error(`Invalid Content-Type: expected application/json, got ${contentType}`);\n }\n return response.json() as Promise<T>;\n }\n\n private unwrapData<T>(payload: unknown): T {\n if (\n payload\n && typeof payload === 'object'\n && 'ok' in payload\n && (payload as any).ok === true\n && 'data' in payload\n ) {\n return (payload as { data: T }).data;\n }\n return payload as T;\n }\n\n /**\n * Validate and encode job ID to prevent path traversal attacks\n */\n private validateAndEncodeJobId(jobId: string): string {\n // Validate job ID format (alphanumeric, hyphens, underscores only)\n if (!/^[a-zA-Z0-9_-]+$/.test(jobId)) {\n throw new Error(`Invalid job ID format: ${jobId}`);\n }\n // Encode for URL safety (defense in depth)\n return encodeURIComponent(jobId);\n }\n\n /**\n * Health check\n */\n async health(): Promise<{ ok: boolean; service: string }> {\n const response = await fetch(`${this.baseUrl}/health`);\n if (!response.ok) {\n throw new Error(`Health check failed: ${response.statusText}`);\n }\n return this.parseJsonResponse(response);\n }\n\n /**\n * Get workflow metrics\n */\n async getMetrics(): Promise<any> {\n const response = await fetch(`${this.baseUrl}/metrics`);\n if (!response.ok) {\n throw new Error(`Failed to get metrics: ${response.statusText}`);\n }\n const data = await this.parseJsonResponse<any>(response);\n return this.unwrapData(data);\n }\n\n /**\n * Get job status with full details (jobs, steps, outputs)\n */\n async getJobStatus(jobId: string): Promise<any> {\n const encodedJobId = this.validateAndEncodeJobId(jobId);\n const response = await fetch(`${this.baseUrl}/api/v1/jobs/${encodedJobId}`);\n if (response.status === 404) {\n throw new Error(`Job ${jobId} not found`);\n }\n if (!response.ok) {\n throw new Error(`Failed to get job status: ${response.statusText}`);\n }\n const data = await this.parseJsonResponse<any>(response);\n return this.unwrapData(data);\n }\n\n /**\n * Get job steps with outputs\n */\n async getJobSteps(jobId: string): Promise<any> {\n const encodedJobId = this.validateAndEncodeJobId(jobId);\n const response = await fetch(`${this.baseUrl}/api/v1/jobs/${encodedJobId}/steps`);\n if (response.status === 404) {\n throw new Error(`Job ${jobId} not found`);\n }\n if (!response.ok) {\n throw new Error(`Failed to get job steps: ${response.statusText}`);\n }\n const data = await this.parseJsonResponse<any>(response);\n return this.unwrapData(data);\n }\n\n /**\n * Get job logs\n */\n async getJobLogs(jobId: string): Promise<any[]> {\n const encodedJobId = this.validateAndEncodeJobId(jobId);\n const response = await fetch(`${this.baseUrl}/api/v1/jobs/${encodedJobId}/logs`);\n if (response.status === 404) {\n throw new Error(`Job ${jobId} not found`);\n }\n if (!response.ok) {\n throw new Error(`Failed to get job logs: ${response.statusText}`);\n }\n const data = await this.parseJsonResponse<any>(response);\n const unwrapped = this.unwrapData<{ logs: any[] }>(data);\n return unwrapped.logs ?? [];\n }\n\n /**\n * Get active executions\n */\n async getExecutions(): Promise<any[]> {\n const response = await fetch(`${this.baseUrl}/api/v1/jobs`);\n if (!response.ok) {\n throw new Error(`Failed to get executions: ${response.statusText}`);\n }\n const data = await this.parseJsonResponse<any>(response);\n const unwrapped = this.unwrapData<{ jobs?: any[] }>(data);\n const jobs = unwrapped.jobs ?? [];\n return jobs.filter((job: any) => job.status === 'running' || job.status === 'pending');\n }\n\n /**\n * Get cron jobs\n */\n async getCronJobs(): Promise<{\n crons: any[];\n }> {\n const response = await fetch(`${this.baseUrl}/api/v1/cron`);\n if (!response.ok) {\n throw new Error(`Failed to get cron jobs: ${response.statusText}`);\n }\n const data = await this.parseJsonResponse<any>(response);\n return this.unwrapData(data);\n }\n\n /**\n * Submit a job for execution\n */\n async submitJob(params: {\n handler: string;\n input?: unknown;\n priority?: number;\n }): Promise<{ id: string; status: string }> {\n const response = await fetch(`${this.baseUrl}/api/v1/jobs`, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n },\n body: JSON.stringify({\n type: params.handler,\n payload: params.input,\n priority: params.priority,\n }),\n });\n\n if (!response.ok) {\n const error = (await response.json().catch(() => ({ error: response.statusText }))) as {\n error?: string;\n };\n throw new Error(error.error || `Failed to submit job: ${response.statusText}`);\n }\n\n const payload = await this.parseJsonResponse<any>(response);\n const data = this.unwrapData<{ jobId: string }>(payload);\n return { id: data.jobId, status: 'pending' };\n }\n\n /**\n * Run workflow by ID\n */\n async runWorkflow(\n workflowId: string,\n request: WorkflowRunRequest = {}\n ): Promise<{ runId: string; status: string }> {\n const response = await fetch(\n `${this.baseUrl}/api/v1/workflows/${encodeURIComponent(workflowId)}/runs`,\n {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n },\n body: JSON.stringify(request),\n }\n );\n\n if (!response.ok) {\n const error = (await response.json().catch(() => ({ error: response.statusText }))) as {\n error?: string;\n };\n throw new Error(error.error || `Failed to run workflow: ${response.statusText}`);\n }\n\n const payload = await this.parseJsonResponse<{ ok: boolean; data?: { runId: string; status: string } }>(response);\n return this.unwrapData<{ runId: string; status: string }>(payload);\n }\n}\n","/**\n * REST handler: GET /workflows/jobs\n * Proxies to workflow daemon GET /api/v1/jobs\n */\n\nimport { defineHandler, type RestInput, type PluginContextV3 } from '@kb-labs/sdk';\nimport { getWorkflowDaemonUrl } from '../http-client';\n\ninterface JobsListQuery {\n type?: string;\n status?: string;\n limit?: string;\n offset?: string;\n}\n\ninterface JobStatusInfo {\n id: string;\n type: string;\n status: 'pending' | 'running' | 'completed' | 'failed' | 'cancelled';\n tenantId?: string;\n createdAt?: string;\n startedAt?: string;\n finishedAt?: string;\n result?: unknown;\n error?: string;\n}\n\ninterface JobListResponse {\n jobs: JobStatusInfo[];\n}\n\ninterface ApiEnvelope<T> {\n ok: boolean;\n data?: T;\n error?: string;\n}\n\nexport default defineHandler({\n async execute(\n ctx: PluginContextV3,\n input: RestInput<JobsListQuery>\n ): Promise<JobListResponse> {\n const daemonUrl = getWorkflowDaemonUrl();\n const { type, status, limit, offset } = input.query || {};\n\n // Build query string\n const params = new URLSearchParams();\n if (type) {params.set('type', type);}\n if (status) {params.set('status', status);}\n if (limit) {params.set('limit', limit);}\n if (offset) {params.set('offset', offset);}\n\n const queryString = params.toString();\n const url = `${daemonUrl}/api/v1/jobs${queryString ? `?${queryString}` : ''}`;\n\n ctx.platform.logger.info(`[jobs-list-handler] Fetching jobs from ${url}`);\n\n try {\n const response = await fetch(url, {\n method: 'GET',\n headers: {\n 'Content-Type': 'application/json',\n },\n });\n\n if (!response.ok) {\n const errorData = await response.json().catch(() => ({ error: 'Unknown error' })) as { error?: string };\n ctx.platform.logger.error(`[jobs-list-handler] Failed to fetch jobs: ${JSON.stringify(errorData)}`);\n throw new Error(errorData.error || 'Failed to fetch jobs list');\n }\n\n const payload = (await response.json()) as ApiEnvelope<JobListResponse>;\n if (!payload.ok || !payload.data) {\n throw new Error(payload.error || 'Failed to fetch jobs list');\n }\n const data = payload.data;\n ctx.platform.logger.info(`[jobs-list-handler] Fetched ${data.jobs.length} jobs`);\n return data;\n } catch (error) {\n ctx.platform.logger.error(\n '[jobs-list-handler] Error fetching jobs list',\n error instanceof Error ? error : undefined\n );\n throw error;\n }\n },\n});\n","/**\n * REST handler: GET /workflows/jobs/:jobId\n * Proxies to workflow daemon GET /api/v1/jobs/:jobId\n */\n\nimport { defineHandler, type RestInput, type PluginContextV3 } from '@kb-labs/sdk';\nimport { getWorkflowDaemonUrl } from '../http-client';\n\ninterface JobDetailParams {\n jobId: string;\n}\n\ninterface JobStatusInfo {\n id: string;\n type: string;\n status: 'pending' | 'running' | 'completed' | 'failed' | 'cancelled';\n tenantId?: string;\n createdAt?: string;\n startedAt?: string;\n finishedAt?: string;\n result?: unknown;\n error?: string;\n progress?: number;\n progressMessage?: string;\n}\n\ninterface ApiEnvelope<T> {\n ok: boolean;\n data?: T;\n error?: string;\n}\n\nexport default defineHandler({\n async execute(\n ctx: PluginContextV3,\n input: RestInput<unknown, unknown, JobDetailParams>\n ): Promise<JobStatusInfo> {\n const daemonUrl = getWorkflowDaemonUrl();\n const { jobId } = input.params!;\n\n if (!jobId) {\n throw new Error('Missing jobId parameter');\n }\n\n const url = `${daemonUrl}/api/v1/jobs/${jobId}`;\n ctx.platform.logger.info(`[job-detail-handler] Fetching job ${jobId} from ${url}`);\n\n try {\n const response = await fetch(url, {\n method: 'GET',\n headers: {\n 'Content-Type': 'application/json',\n },\n });\n\n if (!response.ok) {\n const errorData = await response.json().catch(() => ({ error: 'Unknown error' })) as { error?: string };\n ctx.platform.logger.error(`[job-detail-handler] Failed to fetch job: ${JSON.stringify(errorData)}`);\n throw new Error(errorData.error || `Failed to fetch job ${jobId}`);\n }\n\n const payload = (await response.json()) as ApiEnvelope<JobStatusInfo>;\n if (!payload.ok || !payload.data) {\n throw new Error(payload.error || `Failed to fetch job ${jobId}`);\n }\n const data = payload.data;\n ctx.platform.logger.info(`[job-detail-handler] Fetched job ${jobId}, status: ${data.status}`);\n return data;\n } catch (error) {\n ctx.platform.logger.error(\n '[job-detail-handler] Error fetching job detail',\n error instanceof Error ? error : undefined\n );\n throw error;\n }\n },\n});\n","/**\n * REST handler: POST /workflows/jobs/:jobId/cancel\n * Proxies to workflow daemon POST /api/v1/jobs/:jobId/cancel\n */\n\nimport { defineHandler, type RestInput, type PluginContextV3 } from '@kb-labs/sdk';\nimport { getWorkflowDaemonUrl } from '../http-client';\n\ninterface JobCancelParams {\n jobId: string;\n}\n\ninterface JobCancelResponse {\n cancelled: boolean;\n}\n\ninterface ApiEnvelope<T> {\n ok: boolean;\n data?: T;\n error?: string;\n}\n\nexport default defineHandler({\n async execute(\n ctx: PluginContextV3,\n input: RestInput<unknown, unknown, JobCancelParams>\n ): Promise<JobCancelResponse> {\n const daemonUrl = getWorkflowDaemonUrl();\n const { jobId } = input.params!;\n\n if (!jobId) {\n throw new Error('Missing jobId parameter');\n }\n\n const url = `${daemonUrl}/api/v1/jobs/${jobId}/cancel`;\n ctx.platform.logger.info(`[job-cancel-handler] Cancelling job ${jobId} at ${url}`);\n\n try {\n const response = await fetch(url, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n },\n });\n\n if (!response.ok) {\n const errorData = await response.json().catch(() => ({ error: 'Unknown error' })) as { error?: string };\n ctx.platform.logger.error(`[job-cancel-handler] Failed to cancel job: ${JSON.stringify(errorData)}`);\n throw new Error(errorData.error || `Failed to cancel job ${jobId}`);\n }\n\n const payload = (await response.json()) as ApiEnvelope<JobCancelResponse>;\n if (!payload.ok || !payload.data) {\n throw new Error(payload.error || `Failed to cancel job ${jobId}`);\n }\n const data = payload.data;\n ctx.platform.logger.info(`[job-cancel-handler] Job ${jobId} cancelled: ${data.cancelled}`);\n return data;\n } catch (error) {\n ctx.platform.logger.error(\n '[job-cancel-handler] Error cancelling job',\n error instanceof Error ? error : undefined\n );\n throw error;\n }\n },\n});\n","/**\n * REST handler: GET /workflows/cron\n * Proxies to workflow daemon GET /api/v1/cron\n */\n\nimport { defineHandler, type RestInput, type PluginContextV3 } from '@kb-labs/sdk';\nimport { getWorkflowDaemonUrl } from '../http-client';\n\ninterface CronInfo {\n id: string;\n schedule: string;\n jobType: string;\n timezone?: string;\n enabled: boolean;\n lastRun?: string;\n nextRun?: string;\n pluginId?: string;\n}\n\ninterface CronListResponse {\n crons: CronInfo[];\n}\n\ninterface ApiEnvelope<T> {\n ok: boolean;\n data?: T;\n error?: string;\n}\n\nexport default defineHandler({\n async execute(\n ctx: PluginContextV3,\n _input: RestInput\n ): Promise<CronListResponse> {\n const daemonUrl = getWorkflowDaemonUrl();\n const url = `${daemonUrl}/api/v1/cron`;\n\n ctx.platform.logger.info(`[cron-list-handler] Fetching cron jobs from ${url}`);\n\n try {\n const response = await fetch(url, {\n method: 'GET',\n headers: {\n 'Content-Type': 'application/json',\n },\n });\n\n if (!response.ok) {\n const errorData = await response.json().catch(() => ({ error: 'Unknown error' })) as { error?: string };\n ctx.platform.logger.error(`[cron-list-handler] Failed to fetch cron jobs: ${JSON.stringify(errorData)}`);\n throw new Error(errorData.error || 'Failed to fetch cron jobs');\n }\n\n const payload = (await response.json()) as ApiEnvelope<CronListResponse>;\n if (!payload.ok || !payload.data) {\n throw new Error(payload.error || 'Failed to fetch cron jobs');\n }\n const data = payload.data;\n ctx.platform.logger.info(`[cron-list-handler] Fetched ${data.crons.length} cron jobs`);\n return data;\n } catch (error) {\n ctx.platform.logger.error(\n '[cron-list-handler] Error fetching cron list',\n error instanceof Error ? error : undefined\n );\n throw error;\n }\n },\n});\n"]}
1
+ {"version":3,"sources":["../../src/http-client.ts","../../src/rest/jobs-list-handler.ts","../../src/rest/job-detail-handler.ts","../../src/rest/job-cancel-handler.ts","../../src/rest/cron-list-handler.ts"],"names":["defineHandler"],"mappings":"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","file":"index.js","sourcesContent":["/**\n * HTTP client for interacting with Workflow Daemon\n */\nimport type {\n WorkflowRunRequest,\n JobStatusInfo,\n JobStepsResponse,\n JobLogsResponse,\n CronListResponse,\n} from '@kb-labs/workflow-contracts';\nimport { useEnv } from '@kb-labs/sdk';\n\n/** Metrics data returned by GET /metrics */\nexport interface WorkflowMetricsData {\n runs: {\n total: number;\n queued: number;\n running: number;\n completed: number;\n failed: number;\n cancelled: number;\n };\n jobs: {\n total: number;\n queued: number;\n running: number;\n completed: number;\n failed: number;\n };\n [key: string]: unknown;\n}\n\n/** Extended job status returned by GET /api/v1/jobs/:id — includes nested jobs/steps */\nexport interface JobStatusDetail extends Omit<JobStatusInfo, 'result'> {\n result?: {\n ok: boolean;\n summary?: string;\n error?: { code: string; message: string };\n };\n jobs?: Array<{\n id: string;\n name: string;\n status: string;\n startedAt?: string;\n finishedAt?: string;\n durationMs?: number;\n error?: string;\n steps?: Array<{\n id: string;\n name: string;\n status: string;\n handler?: string;\n startedAt?: string;\n finishedAt?: string;\n durationMs?: number;\n outputs?: Record<string, unknown>;\n error?: { message?: string } | string;\n }>;\n }>;\n}\n\nconst DEFAULT_DAEMON_URL = 'http://localhost:7778';\n\nexport interface DaemonClientOptions {\n url?: string;\n}\n\n/**\n * Get workflow daemon URL from environment or default\n */\nexport function getWorkflowDaemonUrl(): string {\n return useEnv('WORKFLOW_DAEMON_URL') ?? DEFAULT_DAEMON_URL;\n}\n\nexport class WorkflowDaemonClient {\n private readonly baseUrl: string;\n\n constructor(options: DaemonClientOptions = {}) {\n this.baseUrl = options.url ?? useEnv('WORKFLOW_DAEMON_URL') ?? DEFAULT_DAEMON_URL;\n }\n\n /**\n * Validate response Content-Type and parse JSON safely\n */\n private async parseJsonResponse<T>(response: Response): Promise<T> {\n const contentType = response.headers.get('content-type');\n if (!contentType?.includes('application/json')) {\n throw new Error(`Invalid Content-Type: expected application/json, got ${contentType}`);\n }\n return response.json() as Promise<T>;\n }\n\n private unwrapData<T>(payload: unknown): T {\n if (\n payload\n && typeof payload === 'object'\n && 'ok' in payload\n && (payload as { ok?: boolean }).ok === true\n && 'data' in payload\n ) {\n return (payload as { data: T }).data;\n }\n return payload as T;\n }\n\n /**\n * Validate and encode job ID to prevent path traversal attacks\n */\n private validateAndEncodeJobId(jobId: string): string {\n // Validate job ID format (alphanumeric, hyphens, underscores only)\n if (!/^[a-zA-Z0-9_-]+$/.test(jobId)) {\n throw new Error(`Invalid job ID format: ${jobId}`);\n }\n // Encode for URL safety (defense in depth)\n return encodeURIComponent(jobId);\n }\n\n /**\n * Health check\n */\n async health(): Promise<{ ok: boolean; service: string }> {\n const response = await fetch(`${this.baseUrl}/health`);\n if (!response.ok) {\n throw new Error(`Health check failed: ${response.statusText}`);\n }\n return this.parseJsonResponse(response);\n }\n\n /**\n * Get workflow metrics (raw metrics data from daemon)\n */\n async getMetrics(): Promise<WorkflowMetricsData> {\n const response = await fetch(`${this.baseUrl}/metrics`);\n if (!response.ok) {\n throw new Error(`Failed to get metrics: ${response.statusText}`);\n }\n const data = await this.parseJsonResponse<unknown>(response);\n return this.unwrapData<WorkflowMetricsData>(data);\n }\n\n /**\n * Get job status with full details (jobs, steps, outputs)\n */\n async getJobStatus(jobId: string): Promise<JobStatusDetail> {\n const encodedJobId = this.validateAndEncodeJobId(jobId);\n const response = await fetch(`${this.baseUrl}/api/v1/jobs/${encodedJobId}`);\n if (response.status === 404) {\n throw new Error(`Job ${jobId} not found`);\n }\n if (!response.ok) {\n throw new Error(`Failed to get job status: ${response.statusText}`);\n }\n const data = await this.parseJsonResponse<unknown>(response);\n return this.unwrapData<JobStatusDetail>(data);\n }\n\n /**\n * Get job steps with outputs\n */\n async getJobSteps(jobId: string): Promise<JobStepsResponse> {\n const encodedJobId = this.validateAndEncodeJobId(jobId);\n const response = await fetch(`${this.baseUrl}/api/v1/jobs/${encodedJobId}/steps`);\n if (response.status === 404) {\n throw new Error(`Job ${jobId} not found`);\n }\n if (!response.ok) {\n throw new Error(`Failed to get job steps: ${response.statusText}`);\n }\n const data = await this.parseJsonResponse<unknown>(response);\n return this.unwrapData<JobStepsResponse>(data);\n }\n\n /**\n * Get job logs\n */\n async getJobLogs(jobId: string): Promise<JobLogsResponse['logs']> {\n const encodedJobId = this.validateAndEncodeJobId(jobId);\n const response = await fetch(`${this.baseUrl}/api/v1/jobs/${encodedJobId}/logs`);\n if (response.status === 404) {\n throw new Error(`Job ${jobId} not found`);\n }\n if (!response.ok) {\n throw new Error(`Failed to get job logs: ${response.statusText}`);\n }\n const data = await this.parseJsonResponse<unknown>(response);\n const unwrapped = this.unwrapData<JobLogsResponse>(data);\n return unwrapped.logs ?? [];\n }\n\n /**\n * Get active executions\n */\n async getExecutions(): Promise<JobStatusInfo[]> {\n const response = await fetch(`${this.baseUrl}/api/v1/jobs`);\n if (!response.ok) {\n throw new Error(`Failed to get executions: ${response.statusText}`);\n }\n const data = await this.parseJsonResponse<unknown>(response);\n const unwrapped = this.unwrapData<{ jobs?: JobStatusInfo[] }>(data);\n const jobs = unwrapped.jobs ?? [];\n return jobs.filter((job) => job.status === 'running' || job.status === 'pending');\n }\n\n /**\n * Get cron jobs\n */\n async getCronJobs(): Promise<CronListResponse> {\n const response = await fetch(`${this.baseUrl}/api/v1/cron`);\n if (!response.ok) {\n throw new Error(`Failed to get cron jobs: ${response.statusText}`);\n }\n const data = await this.parseJsonResponse<unknown>(response);\n return this.unwrapData<CronListResponse>(data);\n }\n\n /**\n * Submit a job for execution\n */\n async submitJob(params: {\n handler: string;\n input?: unknown;\n priority?: number;\n }): Promise<{ id: string; status: string }> {\n const response = await fetch(`${this.baseUrl}/api/v1/jobs`, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n },\n body: JSON.stringify({\n type: params.handler,\n payload: params.input,\n priority: params.priority,\n }),\n });\n\n if (!response.ok) {\n const error = (await response.json().catch(() => ({ error: response.statusText }))) as {\n error?: string;\n };\n throw new Error(error.error || `Failed to submit job: ${response.statusText}`);\n }\n\n const payload = await this.parseJsonResponse<unknown>(response);\n const data = this.unwrapData<{ jobId: string }>(payload);\n return { id: data.jobId, status: 'pending' };\n }\n\n /**\n * Run workflow by ID\n */\n async runWorkflow(\n workflowId: string,\n request: WorkflowRunRequest = {}\n ): Promise<{ runId: string; status: string }> {\n const response = await fetch(\n `${this.baseUrl}/api/v1/workflows/${encodeURIComponent(workflowId)}/runs`,\n {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n },\n body: JSON.stringify(request),\n }\n );\n\n if (!response.ok) {\n const error = (await response.json().catch(() => ({ error: response.statusText }))) as {\n error?: string;\n };\n throw new Error(error.error || `Failed to run workflow: ${response.statusText}`);\n }\n\n const payload = await this.parseJsonResponse<{ ok: boolean; data?: { runId: string; status: string } }>(response);\n return this.unwrapData<{ runId: string; status: string }>(payload);\n }\n}\n","/**\n * REST handler: GET /workflows/jobs\n * Proxies to workflow daemon GET /api/v1/jobs\n */\n\nimport { defineHandler, type RestInput, type PluginContextV3 } from '@kb-labs/sdk';\nimport { getWorkflowDaemonUrl } from '../http-client';\n\ninterface JobsListQuery {\n type?: string;\n status?: string;\n limit?: string;\n offset?: string;\n}\n\ninterface JobStatusInfo {\n id: string;\n type: string;\n status: 'pending' | 'running' | 'completed' | 'failed' | 'cancelled';\n tenantId?: string;\n createdAt?: string;\n startedAt?: string;\n finishedAt?: string;\n result?: unknown;\n error?: string;\n}\n\ninterface JobListResponse {\n jobs: JobStatusInfo[];\n}\n\ninterface ApiEnvelope<T> {\n ok: boolean;\n data?: T;\n error?: string;\n}\n\nexport default defineHandler({\n async execute(\n ctx: PluginContextV3,\n input: RestInput<JobsListQuery>\n ): Promise<JobListResponse> {\n const daemonUrl = getWorkflowDaemonUrl();\n const { type, status, limit, offset } = input.query || {};\n\n // Build query string\n const params = new URLSearchParams();\n if (type) {params.set('type', type);}\n if (status) {params.set('status', status);}\n if (limit) {params.set('limit', limit);}\n if (offset) {params.set('offset', offset);}\n\n const queryString = params.toString();\n const url = `${daemonUrl}/api/v1/jobs${queryString ? `?${queryString}` : ''}`;\n\n ctx.platform.logger.info(`[jobs-list-handler] Fetching jobs from ${url}`);\n\n try {\n const response = await fetch(url, {\n method: 'GET',\n headers: {\n 'Content-Type': 'application/json',\n },\n });\n\n if (!response.ok) {\n const errorData = await response.json().catch(() => ({ error: 'Unknown error' })) as { error?: string };\n ctx.platform.logger.error(`[jobs-list-handler] Failed to fetch jobs: ${JSON.stringify(errorData)}`);\n throw new Error(errorData.error || 'Failed to fetch jobs list');\n }\n\n const payload = (await response.json()) as ApiEnvelope<JobListResponse>;\n if (!payload.ok || !payload.data) {\n throw new Error(payload.error || 'Failed to fetch jobs list');\n }\n const data = payload.data;\n ctx.platform.logger.info(`[jobs-list-handler] Fetched ${data.jobs.length} jobs`);\n return data;\n } catch (error) {\n ctx.platform.logger.error(\n '[jobs-list-handler] Error fetching jobs list',\n error instanceof Error ? error : undefined\n );\n throw error;\n }\n },\n});\n","/**\n * REST handler: GET /workflows/jobs/:jobId\n * Proxies to workflow daemon GET /api/v1/jobs/:jobId\n */\n\nimport { defineHandler, type RestInput, type PluginContextV3 } from '@kb-labs/sdk';\nimport { getWorkflowDaemonUrl } from '../http-client';\n\ninterface JobDetailParams {\n jobId: string;\n}\n\ninterface JobStatusInfo {\n id: string;\n type: string;\n status: 'pending' | 'running' | 'completed' | 'failed' | 'cancelled';\n tenantId?: string;\n createdAt?: string;\n startedAt?: string;\n finishedAt?: string;\n result?: unknown;\n error?: string;\n progress?: number;\n progressMessage?: string;\n}\n\ninterface ApiEnvelope<T> {\n ok: boolean;\n data?: T;\n error?: string;\n}\n\nexport default defineHandler({\n async execute(\n ctx: PluginContextV3,\n input: RestInput<unknown, unknown, JobDetailParams>\n ): Promise<JobStatusInfo> {\n const daemonUrl = getWorkflowDaemonUrl();\n const { jobId } = input.params!;\n\n if (!jobId) {\n throw new Error('Missing jobId parameter');\n }\n\n const url = `${daemonUrl}/api/v1/jobs/${jobId}`;\n ctx.platform.logger.info(`[job-detail-handler] Fetching job ${jobId} from ${url}`);\n\n try {\n const response = await fetch(url, {\n method: 'GET',\n headers: {\n 'Content-Type': 'application/json',\n },\n });\n\n if (!response.ok) {\n const errorData = await response.json().catch(() => ({ error: 'Unknown error' })) as { error?: string };\n ctx.platform.logger.error(`[job-detail-handler] Failed to fetch job: ${JSON.stringify(errorData)}`);\n throw new Error(errorData.error || `Failed to fetch job ${jobId}`);\n }\n\n const payload = (await response.json()) as ApiEnvelope<JobStatusInfo>;\n if (!payload.ok || !payload.data) {\n throw new Error(payload.error || `Failed to fetch job ${jobId}`);\n }\n const data = payload.data;\n ctx.platform.logger.info(`[job-detail-handler] Fetched job ${jobId}, status: ${data.status}`);\n return data;\n } catch (error) {\n ctx.platform.logger.error(\n '[job-detail-handler] Error fetching job detail',\n error instanceof Error ? error : undefined\n );\n throw error;\n }\n },\n});\n","/**\n * REST handler: POST /workflows/jobs/:jobId/cancel\n * Proxies to workflow daemon POST /api/v1/jobs/:jobId/cancel\n */\n\nimport { defineHandler, type RestInput, type PluginContextV3 } from '@kb-labs/sdk';\nimport { getWorkflowDaemonUrl } from '../http-client';\n\ninterface JobCancelParams {\n jobId: string;\n}\n\ninterface JobCancelResponse {\n cancelled: boolean;\n}\n\ninterface ApiEnvelope<T> {\n ok: boolean;\n data?: T;\n error?: string;\n}\n\nexport default defineHandler({\n async execute(\n ctx: PluginContextV3,\n input: RestInput<unknown, unknown, JobCancelParams>\n ): Promise<JobCancelResponse> {\n const daemonUrl = getWorkflowDaemonUrl();\n const { jobId } = input.params!;\n\n if (!jobId) {\n throw new Error('Missing jobId parameter');\n }\n\n const url = `${daemonUrl}/api/v1/jobs/${jobId}/cancel`;\n ctx.platform.logger.info(`[job-cancel-handler] Cancelling job ${jobId} at ${url}`);\n\n try {\n const response = await fetch(url, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n },\n });\n\n if (!response.ok) {\n const errorData = await response.json().catch(() => ({ error: 'Unknown error' })) as { error?: string };\n ctx.platform.logger.error(`[job-cancel-handler] Failed to cancel job: ${JSON.stringify(errorData)}`);\n throw new Error(errorData.error || `Failed to cancel job ${jobId}`);\n }\n\n const payload = (await response.json()) as ApiEnvelope<JobCancelResponse>;\n if (!payload.ok || !payload.data) {\n throw new Error(payload.error || `Failed to cancel job ${jobId}`);\n }\n const data = payload.data;\n ctx.platform.logger.info(`[job-cancel-handler] Job ${jobId} cancelled: ${data.cancelled}`);\n return data;\n } catch (error) {\n ctx.platform.logger.error(\n '[job-cancel-handler] Error cancelling job',\n error instanceof Error ? error : undefined\n );\n throw error;\n }\n },\n});\n","/**\n * REST handler: GET /workflows/cron\n * Proxies to workflow daemon GET /api/v1/cron\n */\n\nimport { defineHandler, type RestInput, type PluginContextV3 } from '@kb-labs/sdk';\nimport { getWorkflowDaemonUrl } from '../http-client';\n\ninterface CronInfo {\n id: string;\n schedule: string;\n jobType: string;\n timezone?: string;\n enabled: boolean;\n lastRun?: string;\n nextRun?: string;\n pluginId?: string;\n}\n\ninterface CronListResponse {\n crons: CronInfo[];\n}\n\ninterface ApiEnvelope<T> {\n ok: boolean;\n data?: T;\n error?: string;\n}\n\nexport default defineHandler({\n async execute(\n ctx: PluginContextV3,\n _input: RestInput\n ): Promise<CronListResponse> {\n const daemonUrl = getWorkflowDaemonUrl();\n const url = `${daemonUrl}/api/v1/cron`;\n\n ctx.platform.logger.info(`[cron-list-handler] Fetching cron jobs from ${url}`);\n\n try {\n const response = await fetch(url, {\n method: 'GET',\n headers: {\n 'Content-Type': 'application/json',\n },\n });\n\n if (!response.ok) {\n const errorData = await response.json().catch(() => ({ error: 'Unknown error' })) as { error?: string };\n ctx.platform.logger.error(`[cron-list-handler] Failed to fetch cron jobs: ${JSON.stringify(errorData)}`);\n throw new Error(errorData.error || 'Failed to fetch cron jobs');\n }\n\n const payload = (await response.json()) as ApiEnvelope<CronListResponse>;\n if (!payload.ok || !payload.data) {\n throw new Error(payload.error || 'Failed to fetch cron jobs');\n }\n const data = payload.data;\n ctx.platform.logger.info(`[cron-list-handler] Fetched ${data.crons.length} cron jobs`);\n return data;\n } catch (error) {\n ctx.platform.logger.error(\n '[cron-list-handler] Error fetching cron list',\n error instanceof Error ? error : undefined\n );\n throw error;\n }\n },\n});\n"]}
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/http-client.ts","../../src/rest/job-cancel-handler.ts"],"names":[],"mappings":";;;AAMA,IAAM,kBAAA,GAAqB,uBAAA;AASpB,SAAS,oBAAA,GAA+B;AAC7C,EAAA,OAAO,MAAA,CAAO,qBAAqB,CAAA,IAAK,kBAAA;AAC1C;;;ACKA,IAAO,6BAAQ,aAAA,CAAc;AAAA,EAC3B,MAAM,OAAA,CACJ,GAAA,EACA,KAAA,EAC4B;AAC5B,IAAA,MAAM,YAAY,oBAAA,EAAqB;AACvC,IAAA,MAAM,EAAE,KAAA,EAAM,GAAI,KAAA,CAAM,MAAA;AAExB,IAAA,IAAI,CAAC,KAAA,EAAO;AACV,MAAA,MAAM,IAAI,MAAM,yBAAyB,CAAA;AAAA,IAC3C;AAEA,IAAA,MAAM,GAAA,GAAM,CAAA,EAAG,SAAS,CAAA,aAAA,EAAgB,KAAK,CAAA,OAAA,CAAA;AAC7C,IAAA,GAAA,CAAI,SAAS,MAAA,CAAO,IAAA,CAAK,uCAAuC,KAAK,CAAA,IAAA,EAAO,GAAG,CAAA,CAAE,CAAA;AAEjF,IAAA,IAAI;AACF,MAAA,MAAM,QAAA,GAAW,MAAM,KAAA,CAAM,GAAA,EAAK;AAAA,QAChC,MAAA,EAAQ,MAAA;AAAA,QACR,OAAA,EAAS;AAAA,UACP,cAAA,EAAgB;AAAA;AAClB,OACD,CAAA;AAED,MAAA,IAAI,CAAC,SAAS,EAAA,EAAI;AAChB,QAAA,MAAM,SAAA,GAAY,MAAM,QAAA,CAAS,IAAA,EAAK,CAAE,MAAM,OAAO,EAAE,KAAA,EAAO,eAAA,EAAgB,CAAE,CAAA;AAChF,QAAA,GAAA,CAAI,QAAA,CAAS,OAAO,KAAA,CAAM,CAAA,2CAAA,EAA8C,KAAK,SAAA,CAAU,SAAS,CAAC,CAAA,CAAE,CAAA;AACnG,QAAA,MAAM,IAAI,KAAA,CAAM,SAAA,CAAU,KAAA,IAAS,CAAA,qBAAA,EAAwB,KAAK,CAAA,CAAE,CAAA;AAAA,MACpE;AAEA,MAAA,MAAM,OAAA,GAAW,MAAM,QAAA,CAAS,IAAA,EAAK;AACrC,MAAA,IAAI,CAAC,OAAA,CAAQ,EAAA,IAAM,CAAC,QAAQ,IAAA,EAAM;AAChC,QAAA,MAAM,IAAI,KAAA,CAAM,OAAA,CAAQ,KAAA,IAAS,CAAA,qBAAA,EAAwB,KAAK,CAAA,CAAE,CAAA;AAAA,MAClE;AACA,MAAA,MAAM,OAAO,OAAA,CAAQ,IAAA;AACrB,MAAA,GAAA,CAAI,QAAA,CAAS,OAAO,IAAA,CAAK,CAAA,yBAAA,EAA4B,KAAK,CAAA,YAAA,EAAe,IAAA,CAAK,SAAS,CAAA,CAAE,CAAA;AACzF,MAAA,OAAO,IAAA;AAAA,IACT,SAAS,KAAA,EAAO;AACd,MAAA,GAAA,CAAI,SAAS,MAAA,CAAO,KAAA;AAAA,QAClB,2CAAA;AAAA,QACA,KAAA,YAAiB,QAAQ,KAAA,GAAQ;AAAA,OACnC;AACA,MAAA,MAAM,KAAA;AAAA,IACR;AAAA,EACF;AACF,CAAC","file":"job-cancel-handler.js","sourcesContent":["/**\n * HTTP client for interacting with Workflow Daemon\n */\nimport type { WorkflowRunRequest } from '@kb-labs/workflow-contracts';\nimport { useEnv } from '@kb-labs/sdk';\n\nconst DEFAULT_DAEMON_URL = 'http://localhost:7778';\n\nexport interface DaemonClientOptions {\n url?: string;\n}\n\n/**\n * Get workflow daemon URL from environment or default\n */\nexport function getWorkflowDaemonUrl(): string {\n return useEnv('WORKFLOW_DAEMON_URL') ?? DEFAULT_DAEMON_URL;\n}\n\nexport class WorkflowDaemonClient {\n private readonly baseUrl: string;\n\n constructor(options: DaemonClientOptions = {}) {\n this.baseUrl = options.url ?? useEnv('WORKFLOW_DAEMON_URL') ?? DEFAULT_DAEMON_URL;\n }\n\n /**\n * Validate response Content-Type and parse JSON safely\n */\n private async parseJsonResponse<T>(response: Response): Promise<T> {\n const contentType = response.headers.get('content-type');\n if (!contentType?.includes('application/json')) {\n throw new Error(`Invalid Content-Type: expected application/json, got ${contentType}`);\n }\n return response.json() as Promise<T>;\n }\n\n private unwrapData<T>(payload: unknown): T {\n if (\n payload\n && typeof payload === 'object'\n && 'ok' in payload\n && (payload as any).ok === true\n && 'data' in payload\n ) {\n return (payload as { data: T }).data;\n }\n return payload as T;\n }\n\n /**\n * Validate and encode job ID to prevent path traversal attacks\n */\n private validateAndEncodeJobId(jobId: string): string {\n // Validate job ID format (alphanumeric, hyphens, underscores only)\n if (!/^[a-zA-Z0-9_-]+$/.test(jobId)) {\n throw new Error(`Invalid job ID format: ${jobId}`);\n }\n // Encode for URL safety (defense in depth)\n return encodeURIComponent(jobId);\n }\n\n /**\n * Health check\n */\n async health(): Promise<{ ok: boolean; service: string }> {\n const response = await fetch(`${this.baseUrl}/health`);\n if (!response.ok) {\n throw new Error(`Health check failed: ${response.statusText}`);\n }\n return this.parseJsonResponse(response);\n }\n\n /**\n * Get workflow metrics\n */\n async getMetrics(): Promise<any> {\n const response = await fetch(`${this.baseUrl}/metrics`);\n if (!response.ok) {\n throw new Error(`Failed to get metrics: ${response.statusText}`);\n }\n const data = await this.parseJsonResponse<any>(response);\n return this.unwrapData(data);\n }\n\n /**\n * Get job status with full details (jobs, steps, outputs)\n */\n async getJobStatus(jobId: string): Promise<any> {\n const encodedJobId = this.validateAndEncodeJobId(jobId);\n const response = await fetch(`${this.baseUrl}/api/v1/jobs/${encodedJobId}`);\n if (response.status === 404) {\n throw new Error(`Job ${jobId} not found`);\n }\n if (!response.ok) {\n throw new Error(`Failed to get job status: ${response.statusText}`);\n }\n const data = await this.parseJsonResponse<any>(response);\n return this.unwrapData(data);\n }\n\n /**\n * Get job steps with outputs\n */\n async getJobSteps(jobId: string): Promise<any> {\n const encodedJobId = this.validateAndEncodeJobId(jobId);\n const response = await fetch(`${this.baseUrl}/api/v1/jobs/${encodedJobId}/steps`);\n if (response.status === 404) {\n throw new Error(`Job ${jobId} not found`);\n }\n if (!response.ok) {\n throw new Error(`Failed to get job steps: ${response.statusText}`);\n }\n const data = await this.parseJsonResponse<any>(response);\n return this.unwrapData(data);\n }\n\n /**\n * Get job logs\n */\n async getJobLogs(jobId: string): Promise<any[]> {\n const encodedJobId = this.validateAndEncodeJobId(jobId);\n const response = await fetch(`${this.baseUrl}/api/v1/jobs/${encodedJobId}/logs`);\n if (response.status === 404) {\n throw new Error(`Job ${jobId} not found`);\n }\n if (!response.ok) {\n throw new Error(`Failed to get job logs: ${response.statusText}`);\n }\n const data = await this.parseJsonResponse<any>(response);\n const unwrapped = this.unwrapData<{ logs: any[] }>(data);\n return unwrapped.logs ?? [];\n }\n\n /**\n * Get active executions\n */\n async getExecutions(): Promise<any[]> {\n const response = await fetch(`${this.baseUrl}/api/v1/jobs`);\n if (!response.ok) {\n throw new Error(`Failed to get executions: ${response.statusText}`);\n }\n const data = await this.parseJsonResponse<any>(response);\n const unwrapped = this.unwrapData<{ jobs?: any[] }>(data);\n const jobs = unwrapped.jobs ?? [];\n return jobs.filter((job: any) => job.status === 'running' || job.status === 'pending');\n }\n\n /**\n * Get cron jobs\n */\n async getCronJobs(): Promise<{\n crons: any[];\n }> {\n const response = await fetch(`${this.baseUrl}/api/v1/cron`);\n if (!response.ok) {\n throw new Error(`Failed to get cron jobs: ${response.statusText}`);\n }\n const data = await this.parseJsonResponse<any>(response);\n return this.unwrapData(data);\n }\n\n /**\n * Submit a job for execution\n */\n async submitJob(params: {\n handler: string;\n input?: unknown;\n priority?: number;\n }): Promise<{ id: string; status: string }> {\n const response = await fetch(`${this.baseUrl}/api/v1/jobs`, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n },\n body: JSON.stringify({\n type: params.handler,\n payload: params.input,\n priority: params.priority,\n }),\n });\n\n if (!response.ok) {\n const error = (await response.json().catch(() => ({ error: response.statusText }))) as {\n error?: string;\n };\n throw new Error(error.error || `Failed to submit job: ${response.statusText}`);\n }\n\n const payload = await this.parseJsonResponse<any>(response);\n const data = this.unwrapData<{ jobId: string }>(payload);\n return { id: data.jobId, status: 'pending' };\n }\n\n /**\n * Run workflow by ID\n */\n async runWorkflow(\n workflowId: string,\n request: WorkflowRunRequest = {}\n ): Promise<{ runId: string; status: string }> {\n const response = await fetch(\n `${this.baseUrl}/api/v1/workflows/${encodeURIComponent(workflowId)}/runs`,\n {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n },\n body: JSON.stringify(request),\n }\n );\n\n if (!response.ok) {\n const error = (await response.json().catch(() => ({ error: response.statusText }))) as {\n error?: string;\n };\n throw new Error(error.error || `Failed to run workflow: ${response.statusText}`);\n }\n\n const payload = await this.parseJsonResponse<{ ok: boolean; data?: { runId: string; status: string } }>(response);\n return this.unwrapData<{ runId: string; status: string }>(payload);\n }\n}\n","/**\n * REST handler: POST /workflows/jobs/:jobId/cancel\n * Proxies to workflow daemon POST /api/v1/jobs/:jobId/cancel\n */\n\nimport { defineHandler, type RestInput, type PluginContextV3 } from '@kb-labs/sdk';\nimport { getWorkflowDaemonUrl } from '../http-client';\n\ninterface JobCancelParams {\n jobId: string;\n}\n\ninterface JobCancelResponse {\n cancelled: boolean;\n}\n\ninterface ApiEnvelope<T> {\n ok: boolean;\n data?: T;\n error?: string;\n}\n\nexport default defineHandler({\n async execute(\n ctx: PluginContextV3,\n input: RestInput<unknown, unknown, JobCancelParams>\n ): Promise<JobCancelResponse> {\n const daemonUrl = getWorkflowDaemonUrl();\n const { jobId } = input.params!;\n\n if (!jobId) {\n throw new Error('Missing jobId parameter');\n }\n\n const url = `${daemonUrl}/api/v1/jobs/${jobId}/cancel`;\n ctx.platform.logger.info(`[job-cancel-handler] Cancelling job ${jobId} at ${url}`);\n\n try {\n const response = await fetch(url, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n },\n });\n\n if (!response.ok) {\n const errorData = await response.json().catch(() => ({ error: 'Unknown error' })) as { error?: string };\n ctx.platform.logger.error(`[job-cancel-handler] Failed to cancel job: ${JSON.stringify(errorData)}`);\n throw new Error(errorData.error || `Failed to cancel job ${jobId}`);\n }\n\n const payload = (await response.json()) as ApiEnvelope<JobCancelResponse>;\n if (!payload.ok || !payload.data) {\n throw new Error(payload.error || `Failed to cancel job ${jobId}`);\n }\n const data = payload.data;\n ctx.platform.logger.info(`[job-cancel-handler] Job ${jobId} cancelled: ${data.cancelled}`);\n return data;\n } catch (error) {\n ctx.platform.logger.error(\n '[job-cancel-handler] Error cancelling job',\n error instanceof Error ? error : undefined\n );\n throw error;\n }\n },\n});\n"]}
1
+ {"version":3,"sources":["../../src/http-client.ts","../../src/rest/job-cancel-handler.ts"],"names":[],"mappings":";;;AA6DA,IAAM,kBAAA,GAAqB,uBAAA;AASpB,SAAS,oBAAA,GAA+B;AAC7C,EAAA,OAAO,MAAA,CAAO,qBAAqB,CAAA,IAAK,kBAAA;AAC1C;;;AClDA,IAAO,6BAAQ,aAAA,CAAc;AAAA,EAC3B,MAAM,OAAA,CACJ,GAAA,EACA,KAAA,EAC4B;AAC5B,IAAA,MAAM,YAAY,oBAAA,EAAqB;AACvC,IAAA,MAAM,EAAE,KAAA,EAAM,GAAI,KAAA,CAAM,MAAA;AAExB,IAAA,IAAI,CAAC,KAAA,EAAO;AACV,MAAA,MAAM,IAAI,MAAM,yBAAyB,CAAA;AAAA,IAC3C;AAEA,IAAA,MAAM,GAAA,GAAM,CAAA,EAAG,SAAS,CAAA,aAAA,EAAgB,KAAK,CAAA,OAAA,CAAA;AAC7C,IAAA,GAAA,CAAI,SAAS,MAAA,CAAO,IAAA,CAAK,uCAAuC,KAAK,CAAA,IAAA,EAAO,GAAG,CAAA,CAAE,CAAA;AAEjF,IAAA,IAAI;AACF,MAAA,MAAM,QAAA,GAAW,MAAM,KAAA,CAAM,GAAA,EAAK;AAAA,QAChC,MAAA,EAAQ,MAAA;AAAA,QACR,OAAA,EAAS;AAAA,UACP,cAAA,EAAgB;AAAA;AAClB,OACD,CAAA;AAED,MAAA,IAAI,CAAC,SAAS,EAAA,EAAI;AAChB,QAAA,MAAM,SAAA,GAAY,MAAM,QAAA,CAAS,IAAA,EAAK,CAAE,MAAM,OAAO,EAAE,KAAA,EAAO,eAAA,EAAgB,CAAE,CAAA;AAChF,QAAA,GAAA,CAAI,QAAA,CAAS,OAAO,KAAA,CAAM,CAAA,2CAAA,EAA8C,KAAK,SAAA,CAAU,SAAS,CAAC,CAAA,CAAE,CAAA;AACnG,QAAA,MAAM,IAAI,KAAA,CAAM,SAAA,CAAU,KAAA,IAAS,CAAA,qBAAA,EAAwB,KAAK,CAAA,CAAE,CAAA;AAAA,MACpE;AAEA,MAAA,MAAM,OAAA,GAAW,MAAM,QAAA,CAAS,IAAA,EAAK;AACrC,MAAA,IAAI,CAAC,OAAA,CAAQ,EAAA,IAAM,CAAC,QAAQ,IAAA,EAAM;AAChC,QAAA,MAAM,IAAI,KAAA,CAAM,OAAA,CAAQ,KAAA,IAAS,CAAA,qBAAA,EAAwB,KAAK,CAAA,CAAE,CAAA;AAAA,MAClE;AACA,MAAA,MAAM,OAAO,OAAA,CAAQ,IAAA;AACrB,MAAA,GAAA,CAAI,QAAA,CAAS,OAAO,IAAA,CAAK,CAAA,yBAAA,EAA4B,KAAK,CAAA,YAAA,EAAe,IAAA,CAAK,SAAS,CAAA,CAAE,CAAA;AACzF,MAAA,OAAO,IAAA;AAAA,IACT,SAAS,KAAA,EAAO;AACd,MAAA,GAAA,CAAI,SAAS,MAAA,CAAO,KAAA;AAAA,QAClB,2CAAA;AAAA,QACA,KAAA,YAAiB,QAAQ,KAAA,GAAQ;AAAA,OACnC;AACA,MAAA,MAAM,KAAA;AAAA,IACR;AAAA,EACF;AACF,CAAC","file":"job-cancel-handler.js","sourcesContent":["/**\n * HTTP client for interacting with Workflow Daemon\n */\nimport type {\n WorkflowRunRequest,\n JobStatusInfo,\n JobStepsResponse,\n JobLogsResponse,\n CronListResponse,\n} from '@kb-labs/workflow-contracts';\nimport { useEnv } from '@kb-labs/sdk';\n\n/** Metrics data returned by GET /metrics */\nexport interface WorkflowMetricsData {\n runs: {\n total: number;\n queued: number;\n running: number;\n completed: number;\n failed: number;\n cancelled: number;\n };\n jobs: {\n total: number;\n queued: number;\n running: number;\n completed: number;\n failed: number;\n };\n [key: string]: unknown;\n}\n\n/** Extended job status returned by GET /api/v1/jobs/:id — includes nested jobs/steps */\nexport interface JobStatusDetail extends Omit<JobStatusInfo, 'result'> {\n result?: {\n ok: boolean;\n summary?: string;\n error?: { code: string; message: string };\n };\n jobs?: Array<{\n id: string;\n name: string;\n status: string;\n startedAt?: string;\n finishedAt?: string;\n durationMs?: number;\n error?: string;\n steps?: Array<{\n id: string;\n name: string;\n status: string;\n handler?: string;\n startedAt?: string;\n finishedAt?: string;\n durationMs?: number;\n outputs?: Record<string, unknown>;\n error?: { message?: string } | string;\n }>;\n }>;\n}\n\nconst DEFAULT_DAEMON_URL = 'http://localhost:7778';\n\nexport interface DaemonClientOptions {\n url?: string;\n}\n\n/**\n * Get workflow daemon URL from environment or default\n */\nexport function getWorkflowDaemonUrl(): string {\n return useEnv('WORKFLOW_DAEMON_URL') ?? DEFAULT_DAEMON_URL;\n}\n\nexport class WorkflowDaemonClient {\n private readonly baseUrl: string;\n\n constructor(options: DaemonClientOptions = {}) {\n this.baseUrl = options.url ?? useEnv('WORKFLOW_DAEMON_URL') ?? DEFAULT_DAEMON_URL;\n }\n\n /**\n * Validate response Content-Type and parse JSON safely\n */\n private async parseJsonResponse<T>(response: Response): Promise<T> {\n const contentType = response.headers.get('content-type');\n if (!contentType?.includes('application/json')) {\n throw new Error(`Invalid Content-Type: expected application/json, got ${contentType}`);\n }\n return response.json() as Promise<T>;\n }\n\n private unwrapData<T>(payload: unknown): T {\n if (\n payload\n && typeof payload === 'object'\n && 'ok' in payload\n && (payload as { ok?: boolean }).ok === true\n && 'data' in payload\n ) {\n return (payload as { data: T }).data;\n }\n return payload as T;\n }\n\n /**\n * Validate and encode job ID to prevent path traversal attacks\n */\n private validateAndEncodeJobId(jobId: string): string {\n // Validate job ID format (alphanumeric, hyphens, underscores only)\n if (!/^[a-zA-Z0-9_-]+$/.test(jobId)) {\n throw new Error(`Invalid job ID format: ${jobId}`);\n }\n // Encode for URL safety (defense in depth)\n return encodeURIComponent(jobId);\n }\n\n /**\n * Health check\n */\n async health(): Promise<{ ok: boolean; service: string }> {\n const response = await fetch(`${this.baseUrl}/health`);\n if (!response.ok) {\n throw new Error(`Health check failed: ${response.statusText}`);\n }\n return this.parseJsonResponse(response);\n }\n\n /**\n * Get workflow metrics (raw metrics data from daemon)\n */\n async getMetrics(): Promise<WorkflowMetricsData> {\n const response = await fetch(`${this.baseUrl}/metrics`);\n if (!response.ok) {\n throw new Error(`Failed to get metrics: ${response.statusText}`);\n }\n const data = await this.parseJsonResponse<unknown>(response);\n return this.unwrapData<WorkflowMetricsData>(data);\n }\n\n /**\n * Get job status with full details (jobs, steps, outputs)\n */\n async getJobStatus(jobId: string): Promise<JobStatusDetail> {\n const encodedJobId = this.validateAndEncodeJobId(jobId);\n const response = await fetch(`${this.baseUrl}/api/v1/jobs/${encodedJobId}`);\n if (response.status === 404) {\n throw new Error(`Job ${jobId} not found`);\n }\n if (!response.ok) {\n throw new Error(`Failed to get job status: ${response.statusText}`);\n }\n const data = await this.parseJsonResponse<unknown>(response);\n return this.unwrapData<JobStatusDetail>(data);\n }\n\n /**\n * Get job steps with outputs\n */\n async getJobSteps(jobId: string): Promise<JobStepsResponse> {\n const encodedJobId = this.validateAndEncodeJobId(jobId);\n const response = await fetch(`${this.baseUrl}/api/v1/jobs/${encodedJobId}/steps`);\n if (response.status === 404) {\n throw new Error(`Job ${jobId} not found`);\n }\n if (!response.ok) {\n throw new Error(`Failed to get job steps: ${response.statusText}`);\n }\n const data = await this.parseJsonResponse<unknown>(response);\n return this.unwrapData<JobStepsResponse>(data);\n }\n\n /**\n * Get job logs\n */\n async getJobLogs(jobId: string): Promise<JobLogsResponse['logs']> {\n const encodedJobId = this.validateAndEncodeJobId(jobId);\n const response = await fetch(`${this.baseUrl}/api/v1/jobs/${encodedJobId}/logs`);\n if (response.status === 404) {\n throw new Error(`Job ${jobId} not found`);\n }\n if (!response.ok) {\n throw new Error(`Failed to get job logs: ${response.statusText}`);\n }\n const data = await this.parseJsonResponse<unknown>(response);\n const unwrapped = this.unwrapData<JobLogsResponse>(data);\n return unwrapped.logs ?? [];\n }\n\n /**\n * Get active executions\n */\n async getExecutions(): Promise<JobStatusInfo[]> {\n const response = await fetch(`${this.baseUrl}/api/v1/jobs`);\n if (!response.ok) {\n throw new Error(`Failed to get executions: ${response.statusText}`);\n }\n const data = await this.parseJsonResponse<unknown>(response);\n const unwrapped = this.unwrapData<{ jobs?: JobStatusInfo[] }>(data);\n const jobs = unwrapped.jobs ?? [];\n return jobs.filter((job) => job.status === 'running' || job.status === 'pending');\n }\n\n /**\n * Get cron jobs\n */\n async getCronJobs(): Promise<CronListResponse> {\n const response = await fetch(`${this.baseUrl}/api/v1/cron`);\n if (!response.ok) {\n throw new Error(`Failed to get cron jobs: ${response.statusText}`);\n }\n const data = await this.parseJsonResponse<unknown>(response);\n return this.unwrapData<CronListResponse>(data);\n }\n\n /**\n * Submit a job for execution\n */\n async submitJob(params: {\n handler: string;\n input?: unknown;\n priority?: number;\n }): Promise<{ id: string; status: string }> {\n const response = await fetch(`${this.baseUrl}/api/v1/jobs`, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n },\n body: JSON.stringify({\n type: params.handler,\n payload: params.input,\n priority: params.priority,\n }),\n });\n\n if (!response.ok) {\n const error = (await response.json().catch(() => ({ error: response.statusText }))) as {\n error?: string;\n };\n throw new Error(error.error || `Failed to submit job: ${response.statusText}`);\n }\n\n const payload = await this.parseJsonResponse<unknown>(response);\n const data = this.unwrapData<{ jobId: string }>(payload);\n return { id: data.jobId, status: 'pending' };\n }\n\n /**\n * Run workflow by ID\n */\n async runWorkflow(\n workflowId: string,\n request: WorkflowRunRequest = {}\n ): Promise<{ runId: string; status: string }> {\n const response = await fetch(\n `${this.baseUrl}/api/v1/workflows/${encodeURIComponent(workflowId)}/runs`,\n {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n },\n body: JSON.stringify(request),\n }\n );\n\n if (!response.ok) {\n const error = (await response.json().catch(() => ({ error: response.statusText }))) as {\n error?: string;\n };\n throw new Error(error.error || `Failed to run workflow: ${response.statusText}`);\n }\n\n const payload = await this.parseJsonResponse<{ ok: boolean; data?: { runId: string; status: string } }>(response);\n return this.unwrapData<{ runId: string; status: string }>(payload);\n }\n}\n","/**\n * REST handler: POST /workflows/jobs/:jobId/cancel\n * Proxies to workflow daemon POST /api/v1/jobs/:jobId/cancel\n */\n\nimport { defineHandler, type RestInput, type PluginContextV3 } from '@kb-labs/sdk';\nimport { getWorkflowDaemonUrl } from '../http-client';\n\ninterface JobCancelParams {\n jobId: string;\n}\n\ninterface JobCancelResponse {\n cancelled: boolean;\n}\n\ninterface ApiEnvelope<T> {\n ok: boolean;\n data?: T;\n error?: string;\n}\n\nexport default defineHandler({\n async execute(\n ctx: PluginContextV3,\n input: RestInput<unknown, unknown, JobCancelParams>\n ): Promise<JobCancelResponse> {\n const daemonUrl = getWorkflowDaemonUrl();\n const { jobId } = input.params!;\n\n if (!jobId) {\n throw new Error('Missing jobId parameter');\n }\n\n const url = `${daemonUrl}/api/v1/jobs/${jobId}/cancel`;\n ctx.platform.logger.info(`[job-cancel-handler] Cancelling job ${jobId} at ${url}`);\n\n try {\n const response = await fetch(url, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n },\n });\n\n if (!response.ok) {\n const errorData = await response.json().catch(() => ({ error: 'Unknown error' })) as { error?: string };\n ctx.platform.logger.error(`[job-cancel-handler] Failed to cancel job: ${JSON.stringify(errorData)}`);\n throw new Error(errorData.error || `Failed to cancel job ${jobId}`);\n }\n\n const payload = (await response.json()) as ApiEnvelope<JobCancelResponse>;\n if (!payload.ok || !payload.data) {\n throw new Error(payload.error || `Failed to cancel job ${jobId}`);\n }\n const data = payload.data;\n ctx.platform.logger.info(`[job-cancel-handler] Job ${jobId} cancelled: ${data.cancelled}`);\n return data;\n } catch (error) {\n ctx.platform.logger.error(\n '[job-cancel-handler] Error cancelling job',\n error instanceof Error ? error : undefined\n );\n throw error;\n }\n },\n});\n"]}
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/http-client.ts","../../src/rest/job-detail-handler.ts"],"names":[],"mappings":";;;AAMA,IAAM,kBAAA,GAAqB,uBAAA;AASpB,SAAS,oBAAA,GAA+B;AAC7C,EAAA,OAAO,MAAA,CAAO,qBAAqB,CAAA,IAAK,kBAAA;AAC1C;;;ACeA,IAAO,6BAAQ,aAAA,CAAc;AAAA,EAC3B,MAAM,OAAA,CACJ,GAAA,EACA,KAAA,EACwB;AACxB,IAAA,MAAM,YAAY,oBAAA,EAAqB;AACvC,IAAA,MAAM,EAAE,KAAA,EAAM,GAAI,KAAA,CAAM,MAAA;AAExB,IAAA,IAAI,CAAC,KAAA,EAAO;AACV,MAAA,MAAM,IAAI,MAAM,yBAAyB,CAAA;AAAA,IAC3C;AAEA,IAAA,MAAM,GAAA,GAAM,CAAA,EAAG,SAAS,CAAA,aAAA,EAAgB,KAAK,CAAA,CAAA;AAC7C,IAAA,GAAA,CAAI,SAAS,MAAA,CAAO,IAAA,CAAK,qCAAqC,KAAK,CAAA,MAAA,EAAS,GAAG,CAAA,CAAE,CAAA;AAEjF,IAAA,IAAI;AACF,MAAA,MAAM,QAAA,GAAW,MAAM,KAAA,CAAM,GAAA,EAAK;AAAA,QAChC,MAAA,EAAQ,KAAA;AAAA,QACR,OAAA,EAAS;AAAA,UACP,cAAA,EAAgB;AAAA;AAClB,OACD,CAAA;AAED,MAAA,IAAI,CAAC,SAAS,EAAA,EAAI;AAChB,QAAA,MAAM,SAAA,GAAY,MAAM,QAAA,CAAS,IAAA,EAAK,CAAE,MAAM,OAAO,EAAE,KAAA,EAAO,eAAA,EAAgB,CAAE,CAAA;AAChF,QAAA,GAAA,CAAI,QAAA,CAAS,OAAO,KAAA,CAAM,CAAA,0CAAA,EAA6C,KAAK,SAAA,CAAU,SAAS,CAAC,CAAA,CAAE,CAAA;AAClG,QAAA,MAAM,IAAI,KAAA,CAAM,SAAA,CAAU,KAAA,IAAS,CAAA,oBAAA,EAAuB,KAAK,CAAA,CAAE,CAAA;AAAA,MACnE;AAEA,MAAA,MAAM,OAAA,GAAW,MAAM,QAAA,CAAS,IAAA,EAAK;AACrC,MAAA,IAAI,CAAC,OAAA,CAAQ,EAAA,IAAM,CAAC,QAAQ,IAAA,EAAM;AAChC,QAAA,MAAM,IAAI,KAAA,CAAM,OAAA,CAAQ,KAAA,IAAS,CAAA,oBAAA,EAAuB,KAAK,CAAA,CAAE,CAAA;AAAA,MACjE;AACA,MAAA,MAAM,OAAO,OAAA,CAAQ,IAAA;AACrB,MAAA,GAAA,CAAI,QAAA,CAAS,OAAO,IAAA,CAAK,CAAA,iCAAA,EAAoC,KAAK,CAAA,UAAA,EAAa,IAAA,CAAK,MAAM,CAAA,CAAE,CAAA;AAC5F,MAAA,OAAO,IAAA;AAAA,IACT,SAAS,KAAA,EAAO;AACd,MAAA,GAAA,CAAI,SAAS,MAAA,CAAO,KAAA;AAAA,QAClB,gDAAA;AAAA,QACA,KAAA,YAAiB,QAAQ,KAAA,GAAQ;AAAA,OACnC;AACA,MAAA,MAAM,KAAA;AAAA,IACR;AAAA,EACF;AACF,CAAC","file":"job-detail-handler.js","sourcesContent":["/**\n * HTTP client for interacting with Workflow Daemon\n */\nimport type { WorkflowRunRequest } from '@kb-labs/workflow-contracts';\nimport { useEnv } from '@kb-labs/sdk';\n\nconst DEFAULT_DAEMON_URL = 'http://localhost:7778';\n\nexport interface DaemonClientOptions {\n url?: string;\n}\n\n/**\n * Get workflow daemon URL from environment or default\n */\nexport function getWorkflowDaemonUrl(): string {\n return useEnv('WORKFLOW_DAEMON_URL') ?? DEFAULT_DAEMON_URL;\n}\n\nexport class WorkflowDaemonClient {\n private readonly baseUrl: string;\n\n constructor(options: DaemonClientOptions = {}) {\n this.baseUrl = options.url ?? useEnv('WORKFLOW_DAEMON_URL') ?? DEFAULT_DAEMON_URL;\n }\n\n /**\n * Validate response Content-Type and parse JSON safely\n */\n private async parseJsonResponse<T>(response: Response): Promise<T> {\n const contentType = response.headers.get('content-type');\n if (!contentType?.includes('application/json')) {\n throw new Error(`Invalid Content-Type: expected application/json, got ${contentType}`);\n }\n return response.json() as Promise<T>;\n }\n\n private unwrapData<T>(payload: unknown): T {\n if (\n payload\n && typeof payload === 'object'\n && 'ok' in payload\n && (payload as any).ok === true\n && 'data' in payload\n ) {\n return (payload as { data: T }).data;\n }\n return payload as T;\n }\n\n /**\n * Validate and encode job ID to prevent path traversal attacks\n */\n private validateAndEncodeJobId(jobId: string): string {\n // Validate job ID format (alphanumeric, hyphens, underscores only)\n if (!/^[a-zA-Z0-9_-]+$/.test(jobId)) {\n throw new Error(`Invalid job ID format: ${jobId}`);\n }\n // Encode for URL safety (defense in depth)\n return encodeURIComponent(jobId);\n }\n\n /**\n * Health check\n */\n async health(): Promise<{ ok: boolean; service: string }> {\n const response = await fetch(`${this.baseUrl}/health`);\n if (!response.ok) {\n throw new Error(`Health check failed: ${response.statusText}`);\n }\n return this.parseJsonResponse(response);\n }\n\n /**\n * Get workflow metrics\n */\n async getMetrics(): Promise<any> {\n const response = await fetch(`${this.baseUrl}/metrics`);\n if (!response.ok) {\n throw new Error(`Failed to get metrics: ${response.statusText}`);\n }\n const data = await this.parseJsonResponse<any>(response);\n return this.unwrapData(data);\n }\n\n /**\n * Get job status with full details (jobs, steps, outputs)\n */\n async getJobStatus(jobId: string): Promise<any> {\n const encodedJobId = this.validateAndEncodeJobId(jobId);\n const response = await fetch(`${this.baseUrl}/api/v1/jobs/${encodedJobId}`);\n if (response.status === 404) {\n throw new Error(`Job ${jobId} not found`);\n }\n if (!response.ok) {\n throw new Error(`Failed to get job status: ${response.statusText}`);\n }\n const data = await this.parseJsonResponse<any>(response);\n return this.unwrapData(data);\n }\n\n /**\n * Get job steps with outputs\n */\n async getJobSteps(jobId: string): Promise<any> {\n const encodedJobId = this.validateAndEncodeJobId(jobId);\n const response = await fetch(`${this.baseUrl}/api/v1/jobs/${encodedJobId}/steps`);\n if (response.status === 404) {\n throw new Error(`Job ${jobId} not found`);\n }\n if (!response.ok) {\n throw new Error(`Failed to get job steps: ${response.statusText}`);\n }\n const data = await this.parseJsonResponse<any>(response);\n return this.unwrapData(data);\n }\n\n /**\n * Get job logs\n */\n async getJobLogs(jobId: string): Promise<any[]> {\n const encodedJobId = this.validateAndEncodeJobId(jobId);\n const response = await fetch(`${this.baseUrl}/api/v1/jobs/${encodedJobId}/logs`);\n if (response.status === 404) {\n throw new Error(`Job ${jobId} not found`);\n }\n if (!response.ok) {\n throw new Error(`Failed to get job logs: ${response.statusText}`);\n }\n const data = await this.parseJsonResponse<any>(response);\n const unwrapped = this.unwrapData<{ logs: any[] }>(data);\n return unwrapped.logs ?? [];\n }\n\n /**\n * Get active executions\n */\n async getExecutions(): Promise<any[]> {\n const response = await fetch(`${this.baseUrl}/api/v1/jobs`);\n if (!response.ok) {\n throw new Error(`Failed to get executions: ${response.statusText}`);\n }\n const data = await this.parseJsonResponse<any>(response);\n const unwrapped = this.unwrapData<{ jobs?: any[] }>(data);\n const jobs = unwrapped.jobs ?? [];\n return jobs.filter((job: any) => job.status === 'running' || job.status === 'pending');\n }\n\n /**\n * Get cron jobs\n */\n async getCronJobs(): Promise<{\n crons: any[];\n }> {\n const response = await fetch(`${this.baseUrl}/api/v1/cron`);\n if (!response.ok) {\n throw new Error(`Failed to get cron jobs: ${response.statusText}`);\n }\n const data = await this.parseJsonResponse<any>(response);\n return this.unwrapData(data);\n }\n\n /**\n * Submit a job for execution\n */\n async submitJob(params: {\n handler: string;\n input?: unknown;\n priority?: number;\n }): Promise<{ id: string; status: string }> {\n const response = await fetch(`${this.baseUrl}/api/v1/jobs`, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n },\n body: JSON.stringify({\n type: params.handler,\n payload: params.input,\n priority: params.priority,\n }),\n });\n\n if (!response.ok) {\n const error = (await response.json().catch(() => ({ error: response.statusText }))) as {\n error?: string;\n };\n throw new Error(error.error || `Failed to submit job: ${response.statusText}`);\n }\n\n const payload = await this.parseJsonResponse<any>(response);\n const data = this.unwrapData<{ jobId: string }>(payload);\n return { id: data.jobId, status: 'pending' };\n }\n\n /**\n * Run workflow by ID\n */\n async runWorkflow(\n workflowId: string,\n request: WorkflowRunRequest = {}\n ): Promise<{ runId: string; status: string }> {\n const response = await fetch(\n `${this.baseUrl}/api/v1/workflows/${encodeURIComponent(workflowId)}/runs`,\n {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n },\n body: JSON.stringify(request),\n }\n );\n\n if (!response.ok) {\n const error = (await response.json().catch(() => ({ error: response.statusText }))) as {\n error?: string;\n };\n throw new Error(error.error || `Failed to run workflow: ${response.statusText}`);\n }\n\n const payload = await this.parseJsonResponse<{ ok: boolean; data?: { runId: string; status: string } }>(response);\n return this.unwrapData<{ runId: string; status: string }>(payload);\n }\n}\n","/**\n * REST handler: GET /workflows/jobs/:jobId\n * Proxies to workflow daemon GET /api/v1/jobs/:jobId\n */\n\nimport { defineHandler, type RestInput, type PluginContextV3 } from '@kb-labs/sdk';\nimport { getWorkflowDaemonUrl } from '../http-client';\n\ninterface JobDetailParams {\n jobId: string;\n}\n\ninterface JobStatusInfo {\n id: string;\n type: string;\n status: 'pending' | 'running' | 'completed' | 'failed' | 'cancelled';\n tenantId?: string;\n createdAt?: string;\n startedAt?: string;\n finishedAt?: string;\n result?: unknown;\n error?: string;\n progress?: number;\n progressMessage?: string;\n}\n\ninterface ApiEnvelope<T> {\n ok: boolean;\n data?: T;\n error?: string;\n}\n\nexport default defineHandler({\n async execute(\n ctx: PluginContextV3,\n input: RestInput<unknown, unknown, JobDetailParams>\n ): Promise<JobStatusInfo> {\n const daemonUrl = getWorkflowDaemonUrl();\n const { jobId } = input.params!;\n\n if (!jobId) {\n throw new Error('Missing jobId parameter');\n }\n\n const url = `${daemonUrl}/api/v1/jobs/${jobId}`;\n ctx.platform.logger.info(`[job-detail-handler] Fetching job ${jobId} from ${url}`);\n\n try {\n const response = await fetch(url, {\n method: 'GET',\n headers: {\n 'Content-Type': 'application/json',\n },\n });\n\n if (!response.ok) {\n const errorData = await response.json().catch(() => ({ error: 'Unknown error' })) as { error?: string };\n ctx.platform.logger.error(`[job-detail-handler] Failed to fetch job: ${JSON.stringify(errorData)}`);\n throw new Error(errorData.error || `Failed to fetch job ${jobId}`);\n }\n\n const payload = (await response.json()) as ApiEnvelope<JobStatusInfo>;\n if (!payload.ok || !payload.data) {\n throw new Error(payload.error || `Failed to fetch job ${jobId}`);\n }\n const data = payload.data;\n ctx.platform.logger.info(`[job-detail-handler] Fetched job ${jobId}, status: ${data.status}`);\n return data;\n } catch (error) {\n ctx.platform.logger.error(\n '[job-detail-handler] Error fetching job detail',\n error instanceof Error ? error : undefined\n );\n throw error;\n }\n },\n});\n"]}
1
+ {"version":3,"sources":["../../src/http-client.ts","../../src/rest/job-detail-handler.ts"],"names":[],"mappings":";;;AA6DA,IAAM,kBAAA,GAAqB,uBAAA;AASpB,SAAS,oBAAA,GAA+B;AAC7C,EAAA,OAAO,MAAA,CAAO,qBAAqB,CAAA,IAAK,kBAAA;AAC1C;;;ACxCA,IAAO,6BAAQ,aAAA,CAAc;AAAA,EAC3B,MAAM,OAAA,CACJ,GAAA,EACA,KAAA,EACwB;AACxB,IAAA,MAAM,YAAY,oBAAA,EAAqB;AACvC,IAAA,MAAM,EAAE,KAAA,EAAM,GAAI,KAAA,CAAM,MAAA;AAExB,IAAA,IAAI,CAAC,KAAA,EAAO;AACV,MAAA,MAAM,IAAI,MAAM,yBAAyB,CAAA;AAAA,IAC3C;AAEA,IAAA,MAAM,GAAA,GAAM,CAAA,EAAG,SAAS,CAAA,aAAA,EAAgB,KAAK,CAAA,CAAA;AAC7C,IAAA,GAAA,CAAI,SAAS,MAAA,CAAO,IAAA,CAAK,qCAAqC,KAAK,CAAA,MAAA,EAAS,GAAG,CAAA,CAAE,CAAA;AAEjF,IAAA,IAAI;AACF,MAAA,MAAM,QAAA,GAAW,MAAM,KAAA,CAAM,GAAA,EAAK;AAAA,QAChC,MAAA,EAAQ,KAAA;AAAA,QACR,OAAA,EAAS;AAAA,UACP,cAAA,EAAgB;AAAA;AAClB,OACD,CAAA;AAED,MAAA,IAAI,CAAC,SAAS,EAAA,EAAI;AAChB,QAAA,MAAM,SAAA,GAAY,MAAM,QAAA,CAAS,IAAA,EAAK,CAAE,MAAM,OAAO,EAAE,KAAA,EAAO,eAAA,EAAgB,CAAE,CAAA;AAChF,QAAA,GAAA,CAAI,QAAA,CAAS,OAAO,KAAA,CAAM,CAAA,0CAAA,EAA6C,KAAK,SAAA,CAAU,SAAS,CAAC,CAAA,CAAE,CAAA;AAClG,QAAA,MAAM,IAAI,KAAA,CAAM,SAAA,CAAU,KAAA,IAAS,CAAA,oBAAA,EAAuB,KAAK,CAAA,CAAE,CAAA;AAAA,MACnE;AAEA,MAAA,MAAM,OAAA,GAAW,MAAM,QAAA,CAAS,IAAA,EAAK;AACrC,MAAA,IAAI,CAAC,OAAA,CAAQ,EAAA,IAAM,CAAC,QAAQ,IAAA,EAAM;AAChC,QAAA,MAAM,IAAI,KAAA,CAAM,OAAA,CAAQ,KAAA,IAAS,CAAA,oBAAA,EAAuB,KAAK,CAAA,CAAE,CAAA;AAAA,MACjE;AACA,MAAA,MAAM,OAAO,OAAA,CAAQ,IAAA;AACrB,MAAA,GAAA,CAAI,QAAA,CAAS,OAAO,IAAA,CAAK,CAAA,iCAAA,EAAoC,KAAK,CAAA,UAAA,EAAa,IAAA,CAAK,MAAM,CAAA,CAAE,CAAA;AAC5F,MAAA,OAAO,IAAA;AAAA,IACT,SAAS,KAAA,EAAO;AACd,MAAA,GAAA,CAAI,SAAS,MAAA,CAAO,KAAA;AAAA,QAClB,gDAAA;AAAA,QACA,KAAA,YAAiB,QAAQ,KAAA,GAAQ;AAAA,OACnC;AACA,MAAA,MAAM,KAAA;AAAA,IACR;AAAA,EACF;AACF,CAAC","file":"job-detail-handler.js","sourcesContent":["/**\n * HTTP client for interacting with Workflow Daemon\n */\nimport type {\n WorkflowRunRequest,\n JobStatusInfo,\n JobStepsResponse,\n JobLogsResponse,\n CronListResponse,\n} from '@kb-labs/workflow-contracts';\nimport { useEnv } from '@kb-labs/sdk';\n\n/** Metrics data returned by GET /metrics */\nexport interface WorkflowMetricsData {\n runs: {\n total: number;\n queued: number;\n running: number;\n completed: number;\n failed: number;\n cancelled: number;\n };\n jobs: {\n total: number;\n queued: number;\n running: number;\n completed: number;\n failed: number;\n };\n [key: string]: unknown;\n}\n\n/** Extended job status returned by GET /api/v1/jobs/:id — includes nested jobs/steps */\nexport interface JobStatusDetail extends Omit<JobStatusInfo, 'result'> {\n result?: {\n ok: boolean;\n summary?: string;\n error?: { code: string; message: string };\n };\n jobs?: Array<{\n id: string;\n name: string;\n status: string;\n startedAt?: string;\n finishedAt?: string;\n durationMs?: number;\n error?: string;\n steps?: Array<{\n id: string;\n name: string;\n status: string;\n handler?: string;\n startedAt?: string;\n finishedAt?: string;\n durationMs?: number;\n outputs?: Record<string, unknown>;\n error?: { message?: string } | string;\n }>;\n }>;\n}\n\nconst DEFAULT_DAEMON_URL = 'http://localhost:7778';\n\nexport interface DaemonClientOptions {\n url?: string;\n}\n\n/**\n * Get workflow daemon URL from environment or default\n */\nexport function getWorkflowDaemonUrl(): string {\n return useEnv('WORKFLOW_DAEMON_URL') ?? DEFAULT_DAEMON_URL;\n}\n\nexport class WorkflowDaemonClient {\n private readonly baseUrl: string;\n\n constructor(options: DaemonClientOptions = {}) {\n this.baseUrl = options.url ?? useEnv('WORKFLOW_DAEMON_URL') ?? DEFAULT_DAEMON_URL;\n }\n\n /**\n * Validate response Content-Type and parse JSON safely\n */\n private async parseJsonResponse<T>(response: Response): Promise<T> {\n const contentType = response.headers.get('content-type');\n if (!contentType?.includes('application/json')) {\n throw new Error(`Invalid Content-Type: expected application/json, got ${contentType}`);\n }\n return response.json() as Promise<T>;\n }\n\n private unwrapData<T>(payload: unknown): T {\n if (\n payload\n && typeof payload === 'object'\n && 'ok' in payload\n && (payload as { ok?: boolean }).ok === true\n && 'data' in payload\n ) {\n return (payload as { data: T }).data;\n }\n return payload as T;\n }\n\n /**\n * Validate and encode job ID to prevent path traversal attacks\n */\n private validateAndEncodeJobId(jobId: string): string {\n // Validate job ID format (alphanumeric, hyphens, underscores only)\n if (!/^[a-zA-Z0-9_-]+$/.test(jobId)) {\n throw new Error(`Invalid job ID format: ${jobId}`);\n }\n // Encode for URL safety (defense in depth)\n return encodeURIComponent(jobId);\n }\n\n /**\n * Health check\n */\n async health(): Promise<{ ok: boolean; service: string }> {\n const response = await fetch(`${this.baseUrl}/health`);\n if (!response.ok) {\n throw new Error(`Health check failed: ${response.statusText}`);\n }\n return this.parseJsonResponse(response);\n }\n\n /**\n * Get workflow metrics (raw metrics data from daemon)\n */\n async getMetrics(): Promise<WorkflowMetricsData> {\n const response = await fetch(`${this.baseUrl}/metrics`);\n if (!response.ok) {\n throw new Error(`Failed to get metrics: ${response.statusText}`);\n }\n const data = await this.parseJsonResponse<unknown>(response);\n return this.unwrapData<WorkflowMetricsData>(data);\n }\n\n /**\n * Get job status with full details (jobs, steps, outputs)\n */\n async getJobStatus(jobId: string): Promise<JobStatusDetail> {\n const encodedJobId = this.validateAndEncodeJobId(jobId);\n const response = await fetch(`${this.baseUrl}/api/v1/jobs/${encodedJobId}`);\n if (response.status === 404) {\n throw new Error(`Job ${jobId} not found`);\n }\n if (!response.ok) {\n throw new Error(`Failed to get job status: ${response.statusText}`);\n }\n const data = await this.parseJsonResponse<unknown>(response);\n return this.unwrapData<JobStatusDetail>(data);\n }\n\n /**\n * Get job steps with outputs\n */\n async getJobSteps(jobId: string): Promise<JobStepsResponse> {\n const encodedJobId = this.validateAndEncodeJobId(jobId);\n const response = await fetch(`${this.baseUrl}/api/v1/jobs/${encodedJobId}/steps`);\n if (response.status === 404) {\n throw new Error(`Job ${jobId} not found`);\n }\n if (!response.ok) {\n throw new Error(`Failed to get job steps: ${response.statusText}`);\n }\n const data = await this.parseJsonResponse<unknown>(response);\n return this.unwrapData<JobStepsResponse>(data);\n }\n\n /**\n * Get job logs\n */\n async getJobLogs(jobId: string): Promise<JobLogsResponse['logs']> {\n const encodedJobId = this.validateAndEncodeJobId(jobId);\n const response = await fetch(`${this.baseUrl}/api/v1/jobs/${encodedJobId}/logs`);\n if (response.status === 404) {\n throw new Error(`Job ${jobId} not found`);\n }\n if (!response.ok) {\n throw new Error(`Failed to get job logs: ${response.statusText}`);\n }\n const data = await this.parseJsonResponse<unknown>(response);\n const unwrapped = this.unwrapData<JobLogsResponse>(data);\n return unwrapped.logs ?? [];\n }\n\n /**\n * Get active executions\n */\n async getExecutions(): Promise<JobStatusInfo[]> {\n const response = await fetch(`${this.baseUrl}/api/v1/jobs`);\n if (!response.ok) {\n throw new Error(`Failed to get executions: ${response.statusText}`);\n }\n const data = await this.parseJsonResponse<unknown>(response);\n const unwrapped = this.unwrapData<{ jobs?: JobStatusInfo[] }>(data);\n const jobs = unwrapped.jobs ?? [];\n return jobs.filter((job) => job.status === 'running' || job.status === 'pending');\n }\n\n /**\n * Get cron jobs\n */\n async getCronJobs(): Promise<CronListResponse> {\n const response = await fetch(`${this.baseUrl}/api/v1/cron`);\n if (!response.ok) {\n throw new Error(`Failed to get cron jobs: ${response.statusText}`);\n }\n const data = await this.parseJsonResponse<unknown>(response);\n return this.unwrapData<CronListResponse>(data);\n }\n\n /**\n * Submit a job for execution\n */\n async submitJob(params: {\n handler: string;\n input?: unknown;\n priority?: number;\n }): Promise<{ id: string; status: string }> {\n const response = await fetch(`${this.baseUrl}/api/v1/jobs`, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n },\n body: JSON.stringify({\n type: params.handler,\n payload: params.input,\n priority: params.priority,\n }),\n });\n\n if (!response.ok) {\n const error = (await response.json().catch(() => ({ error: response.statusText }))) as {\n error?: string;\n };\n throw new Error(error.error || `Failed to submit job: ${response.statusText}`);\n }\n\n const payload = await this.parseJsonResponse<unknown>(response);\n const data = this.unwrapData<{ jobId: string }>(payload);\n return { id: data.jobId, status: 'pending' };\n }\n\n /**\n * Run workflow by ID\n */\n async runWorkflow(\n workflowId: string,\n request: WorkflowRunRequest = {}\n ): Promise<{ runId: string; status: string }> {\n const response = await fetch(\n `${this.baseUrl}/api/v1/workflows/${encodeURIComponent(workflowId)}/runs`,\n {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n },\n body: JSON.stringify(request),\n }\n );\n\n if (!response.ok) {\n const error = (await response.json().catch(() => ({ error: response.statusText }))) as {\n error?: string;\n };\n throw new Error(error.error || `Failed to run workflow: ${response.statusText}`);\n }\n\n const payload = await this.parseJsonResponse<{ ok: boolean; data?: { runId: string; status: string } }>(response);\n return this.unwrapData<{ runId: string; status: string }>(payload);\n }\n}\n","/**\n * REST handler: GET /workflows/jobs/:jobId\n * Proxies to workflow daemon GET /api/v1/jobs/:jobId\n */\n\nimport { defineHandler, type RestInput, type PluginContextV3 } from '@kb-labs/sdk';\nimport { getWorkflowDaemonUrl } from '../http-client';\n\ninterface JobDetailParams {\n jobId: string;\n}\n\ninterface JobStatusInfo {\n id: string;\n type: string;\n status: 'pending' | 'running' | 'completed' | 'failed' | 'cancelled';\n tenantId?: string;\n createdAt?: string;\n startedAt?: string;\n finishedAt?: string;\n result?: unknown;\n error?: string;\n progress?: number;\n progressMessage?: string;\n}\n\ninterface ApiEnvelope<T> {\n ok: boolean;\n data?: T;\n error?: string;\n}\n\nexport default defineHandler({\n async execute(\n ctx: PluginContextV3,\n input: RestInput<unknown, unknown, JobDetailParams>\n ): Promise<JobStatusInfo> {\n const daemonUrl = getWorkflowDaemonUrl();\n const { jobId } = input.params!;\n\n if (!jobId) {\n throw new Error('Missing jobId parameter');\n }\n\n const url = `${daemonUrl}/api/v1/jobs/${jobId}`;\n ctx.platform.logger.info(`[job-detail-handler] Fetching job ${jobId} from ${url}`);\n\n try {\n const response = await fetch(url, {\n method: 'GET',\n headers: {\n 'Content-Type': 'application/json',\n },\n });\n\n if (!response.ok) {\n const errorData = await response.json().catch(() => ({ error: 'Unknown error' })) as { error?: string };\n ctx.platform.logger.error(`[job-detail-handler] Failed to fetch job: ${JSON.stringify(errorData)}`);\n throw new Error(errorData.error || `Failed to fetch job ${jobId}`);\n }\n\n const payload = (await response.json()) as ApiEnvelope<JobStatusInfo>;\n if (!payload.ok || !payload.data) {\n throw new Error(payload.error || `Failed to fetch job ${jobId}`);\n }\n const data = payload.data;\n ctx.platform.logger.info(`[job-detail-handler] Fetched job ${jobId}, status: ${data.status}`);\n return data;\n } catch (error) {\n ctx.platform.logger.error(\n '[job-detail-handler] Error fetching job detail',\n error instanceof Error ? error : undefined\n );\n throw error;\n }\n },\n});\n"]}
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/http-client.ts","../../src/rest/job-logs-handler.ts"],"names":[],"mappings":";;;AAMA,IAAM,kBAAA,GAAqB,uBAAA;AASpB,SAAS,oBAAA,GAA+B;AAC7C,EAAA,OAAO,MAAA,CAAO,qBAAqB,CAAA,IAAK,kBAAA;AAC1C;;;ACRA,IAAO,2BAAQ,aAAA,CAAc;AAAA,EAC3B,MAAM,OAAA,CACJ,GAAA,EACA,KAAA,EAC0B;AAC1B,IAAA,MAAM,YAAY,oBAAA,EAAqB;AACvC,IAAA,MAAM,EAAE,KAAA,EAAM,GAAI,KAAA,CAAM,MAAA;AACxB,IAAA,MAAM,EAAE,KAAA,EAAO,MAAA,EAAQ,OAAM,GAAI,KAAA,CAAM,SAAS,EAAC;AAGjD,IAAA,MAAM,cAAA,GAAiB,KAAA,GAAQ,IAAA,CAAK,GAAA,CAAI,KAAK,GAAA,CAAI,QAAA,CAAS,KAAA,EAAO,EAAE,CAAA,IAAK,GAAA,EAAK,CAAC,CAAA,EAAG,GAAI,CAAA,GAAI,GAAA;AACzF,IAAA,MAAM,eAAA,GAAkB,MAAA,GAAS,IAAA,CAAK,GAAA,CAAI,QAAA,CAAS,QAAQ,EAAE,CAAA,IAAK,CAAA,EAAG,CAAC,CAAA,GAAI,CAAA;AAC1E,IAAA,MAAM,cAAc,CAAC,MAAA,EAAQ,MAAA,EAAQ,OAAA,EAAS,SAAS,KAAK,CAAA;AAC5D,IAAA,MAAM,iBAAiB,KAAA,IAAS,WAAA,CAAY,QAAA,CAAS,KAAK,IAAI,KAAA,GAAQ,KAAA;AAEtE,IAAA,MAAM,MAAA,GAAS,IAAI,eAAA,EAAgB;AACnC,IAAA,MAAA,CAAO,MAAA,CAAO,OAAA,EAAS,MAAA,CAAO,cAAc,CAAC,CAAA;AAC7C,IAAA,MAAA,CAAO,MAAA,CAAO,QAAA,EAAU,MAAA,CAAO,eAAe,CAAC,CAAA;AAC/C,IAAA,MAAA,CAAO,MAAA,CAAO,SAAS,cAAc,CAAA;AAErC,IAAA,MAAM,GAAA,GAAM,GAAG,SAAS,CAAA,aAAA,EAAgB,mBAAmB,KAAK,CAAC,SAAS,MAAM,CAAA,CAAA;AAEhF,IAAA,GAAA,CAAI,QAAA,CAAS,MAAA,CAAO,IAAA,CAAK,CAAA,sCAAA,EAAyC,GAAG,CAAA,CAAE,CAAA;AAEvE,IAAA,IAAI;AACF,MAAA,MAAM,QAAA,GAAW,MAAM,KAAA,CAAM,GAAG,CAAA;AAEhC,MAAA,IAAI,CAAC,SAAS,EAAA,EAAI;AAChB,QAAA,IAAI,QAAA,CAAS,WAAW,GAAA,EAAK;AAC3B,UAAA,MAAM,IAAI,MAAM,eAAe,CAAA;AAAA,QACjC;AACA,QAAA,MAAM,IAAI,MAAM,CAAA,KAAA,EAAQ,QAAA,CAAS,MAAM,CAAA,EAAA,EAAK,QAAA,CAAS,UAAU,CAAA,CAAE,CAAA;AAAA,MACnE;AAEA,MAAA,MAAM,MAAA,GAAU,MAAM,QAAA,CAAS,IAAA,EAAK;AAEpC,MAAA,IAAI,CAAC,MAAA,CAAO,EAAA,IAAM,CAAC,OAAO,IAAA,EAAM;AAC9B,QAAA,MAAM,IAAI,KAAA,CAAM,MAAA,CAAO,KAAA,IAAS,0BAA0B,CAAA;AAAA,MAC5D;AAEA,MAAA,OAAO,MAAA,CAAO,IAAA;AAAA,IAChB,SAAS,KAAA,EAAO;AACd,MAAA,GAAA,CAAI,SAAS,MAAA,CAAO,KAAA;AAAA,QAClB,4CAAA;AAAA,QACA,KAAA,YAAiB,QAAQ,KAAA,GAAQ;AAAA,OACnC;AACA,MAAA,MAAM,KAAA;AAAA,IACR;AAAA,EACF;AACF,CAAC","file":"job-logs-handler.js","sourcesContent":["/**\n * HTTP client for interacting with Workflow Daemon\n */\nimport type { WorkflowRunRequest } from '@kb-labs/workflow-contracts';\nimport { useEnv } from '@kb-labs/sdk';\n\nconst DEFAULT_DAEMON_URL = 'http://localhost:7778';\n\nexport interface DaemonClientOptions {\n url?: string;\n}\n\n/**\n * Get workflow daemon URL from environment or default\n */\nexport function getWorkflowDaemonUrl(): string {\n return useEnv('WORKFLOW_DAEMON_URL') ?? DEFAULT_DAEMON_URL;\n}\n\nexport class WorkflowDaemonClient {\n private readonly baseUrl: string;\n\n constructor(options: DaemonClientOptions = {}) {\n this.baseUrl = options.url ?? useEnv('WORKFLOW_DAEMON_URL') ?? DEFAULT_DAEMON_URL;\n }\n\n /**\n * Validate response Content-Type and parse JSON safely\n */\n private async parseJsonResponse<T>(response: Response): Promise<T> {\n const contentType = response.headers.get('content-type');\n if (!contentType?.includes('application/json')) {\n throw new Error(`Invalid Content-Type: expected application/json, got ${contentType}`);\n }\n return response.json() as Promise<T>;\n }\n\n private unwrapData<T>(payload: unknown): T {\n if (\n payload\n && typeof payload === 'object'\n && 'ok' in payload\n && (payload as any).ok === true\n && 'data' in payload\n ) {\n return (payload as { data: T }).data;\n }\n return payload as T;\n }\n\n /**\n * Validate and encode job ID to prevent path traversal attacks\n */\n private validateAndEncodeJobId(jobId: string): string {\n // Validate job ID format (alphanumeric, hyphens, underscores only)\n if (!/^[a-zA-Z0-9_-]+$/.test(jobId)) {\n throw new Error(`Invalid job ID format: ${jobId}`);\n }\n // Encode for URL safety (defense in depth)\n return encodeURIComponent(jobId);\n }\n\n /**\n * Health check\n */\n async health(): Promise<{ ok: boolean; service: string }> {\n const response = await fetch(`${this.baseUrl}/health`);\n if (!response.ok) {\n throw new Error(`Health check failed: ${response.statusText}`);\n }\n return this.parseJsonResponse(response);\n }\n\n /**\n * Get workflow metrics\n */\n async getMetrics(): Promise<any> {\n const response = await fetch(`${this.baseUrl}/metrics`);\n if (!response.ok) {\n throw new Error(`Failed to get metrics: ${response.statusText}`);\n }\n const data = await this.parseJsonResponse<any>(response);\n return this.unwrapData(data);\n }\n\n /**\n * Get job status with full details (jobs, steps, outputs)\n */\n async getJobStatus(jobId: string): Promise<any> {\n const encodedJobId = this.validateAndEncodeJobId(jobId);\n const response = await fetch(`${this.baseUrl}/api/v1/jobs/${encodedJobId}`);\n if (response.status === 404) {\n throw new Error(`Job ${jobId} not found`);\n }\n if (!response.ok) {\n throw new Error(`Failed to get job status: ${response.statusText}`);\n }\n const data = await this.parseJsonResponse<any>(response);\n return this.unwrapData(data);\n }\n\n /**\n * Get job steps with outputs\n */\n async getJobSteps(jobId: string): Promise<any> {\n const encodedJobId = this.validateAndEncodeJobId(jobId);\n const response = await fetch(`${this.baseUrl}/api/v1/jobs/${encodedJobId}/steps`);\n if (response.status === 404) {\n throw new Error(`Job ${jobId} not found`);\n }\n if (!response.ok) {\n throw new Error(`Failed to get job steps: ${response.statusText}`);\n }\n const data = await this.parseJsonResponse<any>(response);\n return this.unwrapData(data);\n }\n\n /**\n * Get job logs\n */\n async getJobLogs(jobId: string): Promise<any[]> {\n const encodedJobId = this.validateAndEncodeJobId(jobId);\n const response = await fetch(`${this.baseUrl}/api/v1/jobs/${encodedJobId}/logs`);\n if (response.status === 404) {\n throw new Error(`Job ${jobId} not found`);\n }\n if (!response.ok) {\n throw new Error(`Failed to get job logs: ${response.statusText}`);\n }\n const data = await this.parseJsonResponse<any>(response);\n const unwrapped = this.unwrapData<{ logs: any[] }>(data);\n return unwrapped.logs ?? [];\n }\n\n /**\n * Get active executions\n */\n async getExecutions(): Promise<any[]> {\n const response = await fetch(`${this.baseUrl}/api/v1/jobs`);\n if (!response.ok) {\n throw new Error(`Failed to get executions: ${response.statusText}`);\n }\n const data = await this.parseJsonResponse<any>(response);\n const unwrapped = this.unwrapData<{ jobs?: any[] }>(data);\n const jobs = unwrapped.jobs ?? [];\n return jobs.filter((job: any) => job.status === 'running' || job.status === 'pending');\n }\n\n /**\n * Get cron jobs\n */\n async getCronJobs(): Promise<{\n crons: any[];\n }> {\n const response = await fetch(`${this.baseUrl}/api/v1/cron`);\n if (!response.ok) {\n throw new Error(`Failed to get cron jobs: ${response.statusText}`);\n }\n const data = await this.parseJsonResponse<any>(response);\n return this.unwrapData(data);\n }\n\n /**\n * Submit a job for execution\n */\n async submitJob(params: {\n handler: string;\n input?: unknown;\n priority?: number;\n }): Promise<{ id: string; status: string }> {\n const response = await fetch(`${this.baseUrl}/api/v1/jobs`, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n },\n body: JSON.stringify({\n type: params.handler,\n payload: params.input,\n priority: params.priority,\n }),\n });\n\n if (!response.ok) {\n const error = (await response.json().catch(() => ({ error: response.statusText }))) as {\n error?: string;\n };\n throw new Error(error.error || `Failed to submit job: ${response.statusText}`);\n }\n\n const payload = await this.parseJsonResponse<any>(response);\n const data = this.unwrapData<{ jobId: string }>(payload);\n return { id: data.jobId, status: 'pending' };\n }\n\n /**\n * Run workflow by ID\n */\n async runWorkflow(\n workflowId: string,\n request: WorkflowRunRequest = {}\n ): Promise<{ runId: string; status: string }> {\n const response = await fetch(\n `${this.baseUrl}/api/v1/workflows/${encodeURIComponent(workflowId)}/runs`,\n {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n },\n body: JSON.stringify(request),\n }\n );\n\n if (!response.ok) {\n const error = (await response.json().catch(() => ({ error: response.statusText }))) as {\n error?: string;\n };\n throw new Error(error.error || `Failed to run workflow: ${response.statusText}`);\n }\n\n const payload = await this.parseJsonResponse<{ ok: boolean; data?: { runId: string; status: string } }>(response);\n return this.unwrapData<{ runId: string; status: string }>(payload);\n }\n}\n","/**\n * @module @kb-labs/workflow-cli/rest/job-logs-handler\n * REST handler for job logs (proxy to daemon)\n */\n\nimport { defineHandler, type RestInput, type PluginContextV3 } from '@kb-labs/sdk';\nimport type { JobLogsResponse } from '@kb-labs/workflow-contracts';\nimport { getWorkflowDaemonUrl } from '../http-client.js';\n\nexport default defineHandler({\n async execute(\n ctx: PluginContextV3,\n input: RestInput<{ limit?: string; offset?: string; level?: string }, unknown, { jobId: string }>\n ): Promise<JobLogsResponse> {\n const daemonUrl = getWorkflowDaemonUrl();\n const { jobId } = input.params!;\n const { limit, offset, level } = input.query || {};\n\n // Validate query parameters\n const validatedLimit = limit ? Math.min(Math.max(parseInt(limit, 10) || 100, 1), 1000) : 100;\n const validatedOffset = offset ? Math.max(parseInt(offset, 10) || 0, 0) : 0;\n const validLevels = ['info', 'warn', 'error', 'debug', 'all'];\n const validatedLevel = level && validLevels.includes(level) ? level : 'all';\n\n const params = new URLSearchParams();\n params.append('limit', String(validatedLimit));\n params.append('offset', String(validatedOffset));\n params.append('level', validatedLevel);\n\n const url = `${daemonUrl}/api/v1/jobs/${encodeURIComponent(jobId)}/logs?${params}`;\n\n ctx.platform.logger.info(`[job-logs-handler] Fetching logs from ${url}`);\n\n try {\n const response = await fetch(url);\n\n if (!response.ok) {\n if (response.status === 404) {\n throw new Error('Job not found');\n }\n throw new Error(`HTTP ${response.status}: ${response.statusText}`);\n }\n\n const result = (await response.json()) as { ok: boolean; data?: JobLogsResponse; error?: string };\n\n if (!result.ok || !result.data) {\n throw new Error(result.error || 'Failed to fetch job logs');\n }\n\n return result.data;\n } catch (error) {\n ctx.platform.logger.error(\n '[job-logs-handler] Error fetching job logs',\n error instanceof Error ? error : undefined\n );\n throw error;\n }\n },\n});\n"]}
1
+ {"version":3,"sources":["../../src/http-client.ts","../../src/rest/job-logs-handler.ts"],"names":[],"mappings":";;;AA6DA,IAAM,kBAAA,GAAqB,uBAAA;AASpB,SAAS,oBAAA,GAA+B;AAC7C,EAAA,OAAO,MAAA,CAAO,qBAAqB,CAAA,IAAK,kBAAA;AAC1C;;;AC/DA,IAAO,2BAAQ,aAAA,CAAc;AAAA,EAC3B,MAAM,OAAA,CACJ,GAAA,EACA,KAAA,EAC0B;AAC1B,IAAA,MAAM,YAAY,oBAAA,EAAqB;AACvC,IAAA,MAAM,EAAE,KAAA,EAAM,GAAI,KAAA,CAAM,MAAA;AACxB,IAAA,MAAM,EAAE,KAAA,EAAO,MAAA,EAAQ,OAAM,GAAI,KAAA,CAAM,SAAS,EAAC;AAGjD,IAAA,MAAM,cAAA,GAAiB,KAAA,GAAQ,IAAA,CAAK,GAAA,CAAI,KAAK,GAAA,CAAI,QAAA,CAAS,KAAA,EAAO,EAAE,CAAA,IAAK,GAAA,EAAK,CAAC,CAAA,EAAG,GAAI,CAAA,GAAI,GAAA;AACzF,IAAA,MAAM,eAAA,GAAkB,MAAA,GAAS,IAAA,CAAK,GAAA,CAAI,QAAA,CAAS,QAAQ,EAAE,CAAA,IAAK,CAAA,EAAG,CAAC,CAAA,GAAI,CAAA;AAC1E,IAAA,MAAM,cAAc,CAAC,MAAA,EAAQ,MAAA,EAAQ,OAAA,EAAS,SAAS,KAAK,CAAA;AAC5D,IAAA,MAAM,iBAAiB,KAAA,IAAS,WAAA,CAAY,QAAA,CAAS,KAAK,IAAI,KAAA,GAAQ,KAAA;AAEtE,IAAA,MAAM,MAAA,GAAS,IAAI,eAAA,EAAgB;AACnC,IAAA,MAAA,CAAO,MAAA,CAAO,OAAA,EAAS,MAAA,CAAO,cAAc,CAAC,CAAA;AAC7C,IAAA,MAAA,CAAO,MAAA,CAAO,QAAA,EAAU,MAAA,CAAO,eAAe,CAAC,CAAA;AAC/C,IAAA,MAAA,CAAO,MAAA,CAAO,SAAS,cAAc,CAAA;AAErC,IAAA,MAAM,GAAA,GAAM,GAAG,SAAS,CAAA,aAAA,EAAgB,mBAAmB,KAAK,CAAC,SAAS,MAAM,CAAA,CAAA;AAEhF,IAAA,GAAA,CAAI,QAAA,CAAS,MAAA,CAAO,IAAA,CAAK,CAAA,sCAAA,EAAyC,GAAG,CAAA,CAAE,CAAA;AAEvE,IAAA,IAAI;AACF,MAAA,MAAM,QAAA,GAAW,MAAM,KAAA,CAAM,GAAG,CAAA;AAEhC,MAAA,IAAI,CAAC,SAAS,EAAA,EAAI;AAChB,QAAA,IAAI,QAAA,CAAS,WAAW,GAAA,EAAK;AAC3B,UAAA,MAAM,IAAI,MAAM,eAAe,CAAA;AAAA,QACjC;AACA,QAAA,MAAM,IAAI,MAAM,CAAA,KAAA,EAAQ,QAAA,CAAS,MAAM,CAAA,EAAA,EAAK,QAAA,CAAS,UAAU,CAAA,CAAE,CAAA;AAAA,MACnE;AAEA,MAAA,MAAM,MAAA,GAAU,MAAM,QAAA,CAAS,IAAA,EAAK;AAEpC,MAAA,IAAI,CAAC,MAAA,CAAO,EAAA,IAAM,CAAC,OAAO,IAAA,EAAM;AAC9B,QAAA,MAAM,IAAI,KAAA,CAAM,MAAA,CAAO,KAAA,IAAS,0BAA0B,CAAA;AAAA,MAC5D;AAEA,MAAA,OAAO,MAAA,CAAO,IAAA;AAAA,IAChB,SAAS,KAAA,EAAO;AACd,MAAA,GAAA,CAAI,SAAS,MAAA,CAAO,KAAA;AAAA,QAClB,4CAAA;AAAA,QACA,KAAA,YAAiB,QAAQ,KAAA,GAAQ;AAAA,OACnC;AACA,MAAA,MAAM,KAAA;AAAA,IACR;AAAA,EACF;AACF,CAAC","file":"job-logs-handler.js","sourcesContent":["/**\n * HTTP client for interacting with Workflow Daemon\n */\nimport type {\n WorkflowRunRequest,\n JobStatusInfo,\n JobStepsResponse,\n JobLogsResponse,\n CronListResponse,\n} from '@kb-labs/workflow-contracts';\nimport { useEnv } from '@kb-labs/sdk';\n\n/** Metrics data returned by GET /metrics */\nexport interface WorkflowMetricsData {\n runs: {\n total: number;\n queued: number;\n running: number;\n completed: number;\n failed: number;\n cancelled: number;\n };\n jobs: {\n total: number;\n queued: number;\n running: number;\n completed: number;\n failed: number;\n };\n [key: string]: unknown;\n}\n\n/** Extended job status returned by GET /api/v1/jobs/:id — includes nested jobs/steps */\nexport interface JobStatusDetail extends Omit<JobStatusInfo, 'result'> {\n result?: {\n ok: boolean;\n summary?: string;\n error?: { code: string; message: string };\n };\n jobs?: Array<{\n id: string;\n name: string;\n status: string;\n startedAt?: string;\n finishedAt?: string;\n durationMs?: number;\n error?: string;\n steps?: Array<{\n id: string;\n name: string;\n status: string;\n handler?: string;\n startedAt?: string;\n finishedAt?: string;\n durationMs?: number;\n outputs?: Record<string, unknown>;\n error?: { message?: string } | string;\n }>;\n }>;\n}\n\nconst DEFAULT_DAEMON_URL = 'http://localhost:7778';\n\nexport interface DaemonClientOptions {\n url?: string;\n}\n\n/**\n * Get workflow daemon URL from environment or default\n */\nexport function getWorkflowDaemonUrl(): string {\n return useEnv('WORKFLOW_DAEMON_URL') ?? DEFAULT_DAEMON_URL;\n}\n\nexport class WorkflowDaemonClient {\n private readonly baseUrl: string;\n\n constructor(options: DaemonClientOptions = {}) {\n this.baseUrl = options.url ?? useEnv('WORKFLOW_DAEMON_URL') ?? DEFAULT_DAEMON_URL;\n }\n\n /**\n * Validate response Content-Type and parse JSON safely\n */\n private async parseJsonResponse<T>(response: Response): Promise<T> {\n const contentType = response.headers.get('content-type');\n if (!contentType?.includes('application/json')) {\n throw new Error(`Invalid Content-Type: expected application/json, got ${contentType}`);\n }\n return response.json() as Promise<T>;\n }\n\n private unwrapData<T>(payload: unknown): T {\n if (\n payload\n && typeof payload === 'object'\n && 'ok' in payload\n && (payload as { ok?: boolean }).ok === true\n && 'data' in payload\n ) {\n return (payload as { data: T }).data;\n }\n return payload as T;\n }\n\n /**\n * Validate and encode job ID to prevent path traversal attacks\n */\n private validateAndEncodeJobId(jobId: string): string {\n // Validate job ID format (alphanumeric, hyphens, underscores only)\n if (!/^[a-zA-Z0-9_-]+$/.test(jobId)) {\n throw new Error(`Invalid job ID format: ${jobId}`);\n }\n // Encode for URL safety (defense in depth)\n return encodeURIComponent(jobId);\n }\n\n /**\n * Health check\n */\n async health(): Promise<{ ok: boolean; service: string }> {\n const response = await fetch(`${this.baseUrl}/health`);\n if (!response.ok) {\n throw new Error(`Health check failed: ${response.statusText}`);\n }\n return this.parseJsonResponse(response);\n }\n\n /**\n * Get workflow metrics (raw metrics data from daemon)\n */\n async getMetrics(): Promise<WorkflowMetricsData> {\n const response = await fetch(`${this.baseUrl}/metrics`);\n if (!response.ok) {\n throw new Error(`Failed to get metrics: ${response.statusText}`);\n }\n const data = await this.parseJsonResponse<unknown>(response);\n return this.unwrapData<WorkflowMetricsData>(data);\n }\n\n /**\n * Get job status with full details (jobs, steps, outputs)\n */\n async getJobStatus(jobId: string): Promise<JobStatusDetail> {\n const encodedJobId = this.validateAndEncodeJobId(jobId);\n const response = await fetch(`${this.baseUrl}/api/v1/jobs/${encodedJobId}`);\n if (response.status === 404) {\n throw new Error(`Job ${jobId} not found`);\n }\n if (!response.ok) {\n throw new Error(`Failed to get job status: ${response.statusText}`);\n }\n const data = await this.parseJsonResponse<unknown>(response);\n return this.unwrapData<JobStatusDetail>(data);\n }\n\n /**\n * Get job steps with outputs\n */\n async getJobSteps(jobId: string): Promise<JobStepsResponse> {\n const encodedJobId = this.validateAndEncodeJobId(jobId);\n const response = await fetch(`${this.baseUrl}/api/v1/jobs/${encodedJobId}/steps`);\n if (response.status === 404) {\n throw new Error(`Job ${jobId} not found`);\n }\n if (!response.ok) {\n throw new Error(`Failed to get job steps: ${response.statusText}`);\n }\n const data = await this.parseJsonResponse<unknown>(response);\n return this.unwrapData<JobStepsResponse>(data);\n }\n\n /**\n * Get job logs\n */\n async getJobLogs(jobId: string): Promise<JobLogsResponse['logs']> {\n const encodedJobId = this.validateAndEncodeJobId(jobId);\n const response = await fetch(`${this.baseUrl}/api/v1/jobs/${encodedJobId}/logs`);\n if (response.status === 404) {\n throw new Error(`Job ${jobId} not found`);\n }\n if (!response.ok) {\n throw new Error(`Failed to get job logs: ${response.statusText}`);\n }\n const data = await this.parseJsonResponse<unknown>(response);\n const unwrapped = this.unwrapData<JobLogsResponse>(data);\n return unwrapped.logs ?? [];\n }\n\n /**\n * Get active executions\n */\n async getExecutions(): Promise<JobStatusInfo[]> {\n const response = await fetch(`${this.baseUrl}/api/v1/jobs`);\n if (!response.ok) {\n throw new Error(`Failed to get executions: ${response.statusText}`);\n }\n const data = await this.parseJsonResponse<unknown>(response);\n const unwrapped = this.unwrapData<{ jobs?: JobStatusInfo[] }>(data);\n const jobs = unwrapped.jobs ?? [];\n return jobs.filter((job) => job.status === 'running' || job.status === 'pending');\n }\n\n /**\n * Get cron jobs\n */\n async getCronJobs(): Promise<CronListResponse> {\n const response = await fetch(`${this.baseUrl}/api/v1/cron`);\n if (!response.ok) {\n throw new Error(`Failed to get cron jobs: ${response.statusText}`);\n }\n const data = await this.parseJsonResponse<unknown>(response);\n return this.unwrapData<CronListResponse>(data);\n }\n\n /**\n * Submit a job for execution\n */\n async submitJob(params: {\n handler: string;\n input?: unknown;\n priority?: number;\n }): Promise<{ id: string; status: string }> {\n const response = await fetch(`${this.baseUrl}/api/v1/jobs`, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n },\n body: JSON.stringify({\n type: params.handler,\n payload: params.input,\n priority: params.priority,\n }),\n });\n\n if (!response.ok) {\n const error = (await response.json().catch(() => ({ error: response.statusText }))) as {\n error?: string;\n };\n throw new Error(error.error || `Failed to submit job: ${response.statusText}`);\n }\n\n const payload = await this.parseJsonResponse<unknown>(response);\n const data = this.unwrapData<{ jobId: string }>(payload);\n return { id: data.jobId, status: 'pending' };\n }\n\n /**\n * Run workflow by ID\n */\n async runWorkflow(\n workflowId: string,\n request: WorkflowRunRequest = {}\n ): Promise<{ runId: string; status: string }> {\n const response = await fetch(\n `${this.baseUrl}/api/v1/workflows/${encodeURIComponent(workflowId)}/runs`,\n {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n },\n body: JSON.stringify(request),\n }\n );\n\n if (!response.ok) {\n const error = (await response.json().catch(() => ({ error: response.statusText }))) as {\n error?: string;\n };\n throw new Error(error.error || `Failed to run workflow: ${response.statusText}`);\n }\n\n const payload = await this.parseJsonResponse<{ ok: boolean; data?: { runId: string; status: string } }>(response);\n return this.unwrapData<{ runId: string; status: string }>(payload);\n }\n}\n","/**\n * @module @kb-labs/workflow-cli/rest/job-logs-handler\n * REST handler for job logs (proxy to daemon)\n */\n\nimport { defineHandler, type RestInput, type PluginContextV3 } from '@kb-labs/sdk';\nimport type { JobLogsResponse } from '@kb-labs/workflow-contracts';\nimport { getWorkflowDaemonUrl } from '../http-client.js';\n\nexport default defineHandler({\n async execute(\n ctx: PluginContextV3,\n input: RestInput<{ limit?: string; offset?: string; level?: string }, unknown, { jobId: string }>\n ): Promise<JobLogsResponse> {\n const daemonUrl = getWorkflowDaemonUrl();\n const { jobId } = input.params!;\n const { limit, offset, level } = input.query || {};\n\n // Validate query parameters\n const validatedLimit = limit ? Math.min(Math.max(parseInt(limit, 10) || 100, 1), 1000) : 100;\n const validatedOffset = offset ? Math.max(parseInt(offset, 10) || 0, 0) : 0;\n const validLevels = ['info', 'warn', 'error', 'debug', 'all'];\n const validatedLevel = level && validLevels.includes(level) ? level : 'all';\n\n const params = new URLSearchParams();\n params.append('limit', String(validatedLimit));\n params.append('offset', String(validatedOffset));\n params.append('level', validatedLevel);\n\n const url = `${daemonUrl}/api/v1/jobs/${encodeURIComponent(jobId)}/logs?${params}`;\n\n ctx.platform.logger.info(`[job-logs-handler] Fetching logs from ${url}`);\n\n try {\n const response = await fetch(url);\n\n if (!response.ok) {\n if (response.status === 404) {\n throw new Error('Job not found');\n }\n throw new Error(`HTTP ${response.status}: ${response.statusText}`);\n }\n\n const result = (await response.json()) as { ok: boolean; data?: JobLogsResponse; error?: string };\n\n if (!result.ok || !result.data) {\n throw new Error(result.error || 'Failed to fetch job logs');\n }\n\n return result.data;\n } catch (error) {\n ctx.platform.logger.error(\n '[job-logs-handler] Error fetching job logs',\n error instanceof Error ? error : undefined\n );\n throw error;\n }\n },\n});\n"]}