tubeless 0.2.0 → 0.2.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (56) hide show
  1. package/dist/cli/cli-command.d.ts +2 -2
  2. package/dist/cli/cli-command.js +28 -45
  3. package/dist/cli/cli-command.js.map +1 -1
  4. package/dist/cli/cli-pipeline-command.js +1 -1
  5. package/dist/cli/cli-pipeline-command.js.map +1 -1
  6. package/dist/core/child-execution.js +65 -4
  7. package/dist/core/child-execution.js.map +1 -1
  8. package/dist/core/child-progress.d.ts +6 -0
  9. package/dist/core/child-progress.js +48 -0
  10. package/dist/core/child-progress.js.map +1 -0
  11. package/dist/core/mapped-child-progress.d.ts +4 -2
  12. package/dist/core/mapped-child-progress.js.map +1 -1
  13. package/dist/core/pipeline-steps.d.ts +3 -3
  14. package/dist/core/pipeline-steps.js +12 -23
  15. package/dist/core/pipeline-steps.js.map +1 -1
  16. package/dist/core/pipeline-types.d.ts +8 -1
  17. package/dist/core/pipeline-types.js.map +1 -1
  18. package/dist/render/render.js +2 -6
  19. package/dist/render/render.js.map +1 -1
  20. package/dist/reporter/interactive-reporter.d.ts +1 -0
  21. package/dist/reporter/interactive-reporter.js +50 -15
  22. package/dist/reporter/interactive-reporter.js.map +1 -1
  23. package/dist/reporter/live-ticker-worker.js +8 -42
  24. package/dist/reporter/live-ticker-worker.js.map +1 -1
  25. package/dist/reporter/live-ticker.d.ts +10 -0
  26. package/dist/reporter/live-ticker.js +50 -47
  27. package/dist/reporter/live-ticker.js.map +1 -1
  28. package/dist/run-store/run-store-ndjson.js +8 -2
  29. package/dist/run-store/run-store-ndjson.js.map +1 -1
  30. package/dist/run-store/run-store.js +14 -33
  31. package/dist/run-store/run-store.js.map +1 -1
  32. package/dist/studio/run-store-ui-client-source.js +1 -1
  33. package/dist/studio/run-store-ui-client.d.ts +0 -1
  34. package/dist/studio/run-store-ui-client.js +5 -35
  35. package/dist/studio/run-store-ui-client.js.map +1 -1
  36. package/dist/studio/run-store-ui-page.d.ts +1 -1
  37. package/dist/studio/run-store-ui-page.js +1 -1
  38. package/dist/studio/run-store-ui-page.js.map +1 -1
  39. package/dist/tracing/tracing-internal.js +31 -5
  40. package/dist/tracing/tracing-internal.js.map +1 -1
  41. package/dist/workbench/pipeline-module.d.ts +2 -12
  42. package/dist/workbench/pipeline-module.js +4 -11
  43. package/dist/workbench/pipeline-module.js.map +1 -1
  44. package/dist/workbench/workbench-project-loader.js +1 -1
  45. package/dist/workbench/workbench-project-loader.js.map +1 -1
  46. package/dist/workbench/workbench-ui.js +1 -1
  47. package/dist/workbench/workbench-ui.js.map +1 -1
  48. package/docs/agent-guide.md +8 -0
  49. package/docs/api-reference.md +12 -12
  50. package/docs/api-report.json +12 -12
  51. package/docs/child-pipeline-composition.md +51 -19
  52. package/docs/cli.md +25 -0
  53. package/docs/recipes.md +2 -2
  54. package/examples/catalog/tubeless.project.ts +6 -0
  55. package/examples/fan-out-progress.ts +16 -2
  56. package/package.json +2 -1
@@ -1 +1 @@
1
- {"version":3,"file":"run-store.js","sourceRoot":"","sources":["../../src/run-store/run-store.ts"],"names":[],"mappings":"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","sourcesContent":["import {\n RUN_MODEL_VERSION,\n type PipelineRunStatus,\n type PipelineStepLifecycleStatus,\n type PipelineStepProgressDetail,\n} from \"../core/pipeline.js\";\nimport { hasVisibleStepProgress } from \"../core/progress.js\";\nimport type {\n PipelineTraceAttributeValue,\n PipelineTraceError,\n PipelineTraceEvent,\n PipelineTraceExporter,\n} from \"../tracing/tracing.js\";\n\n/** One trace event after it has been appended to a durable local store. */\nexport interface StoredPipelineEvent extends PipelineTraceEvent {\n /** Store-local, monotonically increasing sequence. `0` is a valid first id. */\n id: number;\n}\n\nexport interface PipelineRunEventQuery {\n /** Return events strictly after this store-local sequence. */\n afterId?: number;\n /** Restrict results to one pipeline definition. */\n pipelineId?: string;\n /** Restrict results to one run. */\n runId?: string;\n /** Maximum rows to return. Defaults to the store implementation's safe limit. */\n limit?: number;\n}\n\n/** Read-only event source consumed by run history and the local studio. */\nexport interface PipelineRunEventReader {\n close(): void | Promise<void>;\n listEvents(query?: PipelineRunEventQuery): Promise<readonly StoredPipelineEvent[]>;\n}\n\n/**\n * Append-only persistence boundary used by the local studio.\n * SQLite `export()` may return before the row is durable; call `flush()` or\n * `close()` before another connection can observe the tail.\n */\nexport interface PipelineRunEventStore extends PipelineRunEventReader, PipelineTraceExporter {}\n\nexport type StoredPipelineRunStatus = PipelineRunStatus | \"running\";\n\nexport interface StoredNestedPipeline {\n mode: \"for-each\" | \"single\";\n pipelineId: string;\n stepCount: number;\n stepIds: string[];\n}\n\nexport interface StoredRemote {\n engine: string;\n target?: string;\n}\n\nexport interface StoredPipelineLog {\n attemptId?: string;\n id: number;\n level: \"error\" | \"log\" | \"warn\";\n message: string;\n stepId?: string;\n timestampMs: number;\n}\n\nexport interface StoredPipelineAttempt {\n attemptId: string;\n durationMs?: number;\n finishedAtMs?: number;\n retries: number[];\n startedAtMs: number;\n status: Exclude<PipelineStepLifecycleStatus, \"planned\">;\n}\n\nexport interface StoredPipelineStep {\n /** One execution attempt; `retries` carries `reportAttempt` telemetry. */\n attempt?: StoredPipelineAttempt;\n description?: string;\n durationMs?: number;\n finishedAtMs?: number;\n id: string;\n name?: string;\n nestedPipeline?: StoredNestedPipeline;\n remote?: StoredRemote;\n progress?: {\n completed: number;\n detailCount?: number;\n details?: PipelineStepProgressDetail[];\n message?: string;\n total?: number;\n };\n startedAtMs?: number;\n status: PipelineStepLifecycleStatus;\n}\n\nexport interface StoredPipelineRun {\n dryRun: boolean;\n durationMs?: number;\n error?: PipelineTraceError;\n eventCount: number;\n finishedAtMs?: number;\n logCount: number;\n logs: StoredPipelineLog[];\n parentRunId?: string;\n pipelineId: string;\n runId: string;\n startedAtMs: number;\n status: StoredPipelineRunStatus;\n steps: StoredPipelineStep[];\n /** Run-record schema version. Projector output is always `RUN_MODEL_VERSION`. */\n version: typeof RUN_MODEL_VERSION;\n}\n\nexport interface StoredPipelineDefinitionStep {\n dependencies: string[];\n description?: string;\n dryRun: string;\n id: string;\n name?: string;\n nestedPipeline?: StoredNestedPipeline;\n remote?: StoredRemote;\n optionalDependencies: string[];\n runtimeSkipPossible: boolean;\n skipAfterFailureOf: string[];\n}\n\nexport interface StoredPipelineDefinition {\n activeRuns: number;\n firstSeenAtMs: number;\n lastSeenAtMs: number;\n pipelineId: string;\n runCount: number;\n steps: StoredPipelineDefinitionStep[];\n targetIds: string[];\n}\n\nexport interface PipelineRunStoreSnapshot {\n activeRunCount: number;\n completedRunCount: number;\n definitions: StoredPipelineDefinition[];\n failedRunCount: number;\n generatedAtMs: number;\n lastEventId: number;\n runs: StoredPipelineRun[];\n}\n\nfunction isStringValue(value: PipelineTraceAttributeValue | undefined): value is string {\n return typeof value === \"string\";\n}\n\nfunction isNumberValue(value: PipelineTraceAttributeValue | undefined): value is number {\n return typeof value === \"number\";\n}\n\nfunction isBooleanValue(value: PipelineTraceAttributeValue | undefined): value is boolean {\n return typeof value === \"boolean\";\n}\n\nfunction stringAttribute(\n attributes: Readonly<Record<string, PipelineTraceAttributeValue | undefined>>,\n key: string\n): string | undefined {\n const value = attributes[key];\n return isStringValue(value) ? value : undefined;\n}\n\nfunction numberAttribute(\n attributes: Readonly<Record<string, PipelineTraceAttributeValue | undefined>>,\n key: string\n): number | undefined {\n const value = attributes[key];\n return isNumberValue(value) ? value : undefined;\n}\n\nfunction booleanAttribute(\n attributes: Readonly<Record<string, PipelineTraceAttributeValue | undefined>>,\n key: string\n): boolean | undefined {\n const value = attributes[key];\n return isBooleanValue(value) ? value : undefined;\n}\n\nfunction stringArrayAttribute(\n attributes: Readonly<Record<string, PipelineTraceAttributeValue | undefined>>,\n key: string\n): string[] {\n const value = stringAttribute(attributes, key);\n if (!value) return [];\n try {\n const parsed: unknown = JSON.parse(value);\n return Array.isArray(parsed)\n ? parsed.filter((item): item is string => typeof item === \"string\")\n : [];\n } catch {\n return [];\n }\n}\n\nfunction parseProgressDetails(\n attributes: Readonly<Record<string, PipelineTraceAttributeValue | undefined>>\n): PipelineStepProgressDetail[] | undefined {\n const value = stringAttribute(attributes, \"details\");\n if (!value) return undefined;\n try {\n const parsed: unknown = JSON.parse(value);\n if (!Array.isArray(parsed)) return undefined;\n const details = parsed.flatMap((item): PipelineStepProgressDetail[] => {\n if (item === null || Array.isArray(item) || !(item instanceof Object)) return [];\n // SAFETY: JSON.parse leaves object fields untyped; isStringValue keeps strings only.\n const id = item.id as PipelineTraceAttributeValue | undefined;\n if (!(\"id\" in item) || !isStringValue(id) || id.length === 0) return [];\n const detail: PipelineStepProgressDetail = { id: id.slice(0, 4_096) };\n // SAFETY: JSON.parse leaves object fields untyped; isStringValue keeps strings only.\n const label = (\"label\" in item ? item.label : undefined) as\n | PipelineTraceAttributeValue\n | undefined;\n if (isStringValue(label) && label) {\n detail.label = label.slice(0, 4_096);\n }\n // SAFETY: JSON.parse leaves object fields untyped; isStringValue keeps strings only.\n const status = (\"status\" in item ? item.status : undefined) as\n | PipelineTraceAttributeValue\n | undefined;\n if (\n isStringValue(status) &&\n (status === \"completed\" ||\n status === \"failed\" ||\n status === \"pending\" ||\n status === \"running\" ||\n status === \"skipped\")\n ) {\n detail.status = status;\n }\n return [detail];\n });\n return details.length > 0 ? details.slice(0, 128) : undefined;\n } catch {\n return undefined;\n }\n}\n\nfunction parseNestedPipeline(\n attributes: Readonly<Record<string, PipelineTraceAttributeValue | undefined>>\n): NonNullable<StoredPipelineStep[\"nestedPipeline\"]> | undefined {\n const value = stringAttribute(attributes, \"nested_pipeline\");\n if (!value) return undefined;\n try {\n const parsed: unknown = JSON.parse(value);\n if (parsed === null || Array.isArray(parsed) || !(parsed instanceof Object)) return undefined;\n if (!(\"mode\" in parsed) || !(\"pipelineId\" in parsed) || !(\"stepIds\" in parsed))\n return undefined;\n if (parsed.mode !== \"for-each\" && parsed.mode !== \"single\") return undefined;\n // SAFETY: JSON.parse leaves object fields untyped; isStringValue keeps strings only.\n const pipelineId = parsed.pipelineId as PipelineTraceAttributeValue | undefined;\n if (!isStringValue(pipelineId) || pipelineId.length === 0) return undefined;\n if (!Array.isArray(parsed.stepIds)) return undefined;\n const stepIds = parsed.stepIds\n .filter(isStringValue)\n .slice(0, 128)\n .map((item) => item.slice(0, 4_096));\n // SAFETY: JSON.parse leaves object fields untyped; isNumberValue keeps numbers only.\n const recorded = (\"step_count\" in parsed ? parsed.step_count : undefined) as\n | PipelineTraceAttributeValue\n | undefined;\n const recordedCount =\n isNumberValue(recorded) && Number.isFinite(recorded) ? Math.floor(recorded) : stepIds.length;\n return {\n mode: parsed.mode,\n pipelineId: pipelineId.slice(0, 4_096),\n stepCount: Math.max(stepIds.length, recordedCount),\n stepIds,\n };\n } catch {\n return undefined;\n }\n}\nfunction parseRemote(\n attributes: Readonly<Record<string, PipelineTraceAttributeValue | undefined>>\n): StoredRemote | undefined {\n const value = stringAttribute(attributes, \"remote\");\n if (!value) return undefined;\n try {\n const parsed: unknown = JSON.parse(value);\n if (parsed === null || Array.isArray(parsed) || !(parsed instanceof Object)) return undefined;\n if (!(\"engine\" in parsed)) return undefined;\n // SAFETY: JSON.parse returned a plain object and we confirmed an engine key exists.\n const engine = parsed.engine as PipelineTraceAttributeValue | undefined;\n if (!isStringValue(engine) || engine.length === 0) return undefined;\n const remote: StoredRemote = { engine: engine.slice(0, 4_096) };\n if (\"target\" in parsed) {\n // SAFETY: target is optional metadata on the same parsed remote object.\n const target = parsed.target as PipelineTraceAttributeValue | undefined;\n if (isStringValue(target) && target.length > 0) remote.target = target.slice(0, 4_096);\n }\n return remote;\n } catch {\n return undefined;\n }\n}\n\nfunction terminalStepStatus(event: StoredPipelineEvent): StoredPipelineStep[\"status\"] | undefined {\n switch (event.name) {\n case \"step.cancelled\":\n return \"cancelled\";\n case \"step.complete\":\n // Shipped trace name; projected snapshot uses the live success token.\n return \"completed\";\n case \"step.failed\":\n return \"failed\";\n case \"step.skipped\":\n return \"skipped\";\n default:\n return undefined;\n }\n}\n\nfunction attemptStatus(status: StoredPipelineStep[\"status\"]): StoredPipelineAttempt[\"status\"] {\n return status === \"planned\" ? \"running\" : status;\n}\n\ninterface MutableRunProjection {\n completed: StoredPipelineEvent | undefined;\n eventCount: number;\n first: StoredPipelineEvent;\n logCount: number;\n logs: StoredPipelineLog[];\n retainLogs: boolean;\n started: StoredPipelineEvent;\n stepOrder: string[];\n steps: Map<string, StoredPipelineStep>;\n}\n\ninterface MutablePipelineProjection {\n definitionRunId: string | undefined;\n definitionRunStartedAtMs: number;\n definitionRunStartedEventId: number;\n firstSeenAtMs: number;\n lastSeenAtMs: number;\n latestSteps: Map<string, StoredPipelineDefinitionStep>;\n runStartedAtMs: Map<string, number>;\n runStartedEventIds: Map<string, number>;\n runTargetIds: Map<string, string[]>;\n targetIds: string[];\n}\n\nfunction applyRunEvent(projection: MutableRunProjection, event: StoredPipelineEvent): void {\n projection.eventCount += 1;\n if (event.name === \"pipeline.started\" && projection.started.name !== \"pipeline.started\") {\n projection.started = event;\n }\n if (event.name === \"pipeline.completed\") projection.completed = event;\n if (event.name === \"pipeline.log\") {\n projection.logCount += 1;\n if (!projection.retainLogs) return;\n const level = stringAttribute(event.attributes, \"level\");\n const log: StoredPipelineLog = {\n id: event.id,\n level: level === \"error\" || level === \"warn\" ? level : \"log\",\n message: stringAttribute(event.attributes, \"message\") ?? \"\",\n timestampMs: event.timestampMs,\n };\n if (event.attemptId) log.attemptId = event.attemptId;\n if (event.stepId) log.stepId = event.stepId;\n projection.logs.push(log);\n return;\n }\n if (!event.stepId || !event.name.startsWith(\"step.\")) return;\n\n let step = projection.steps.get(event.stepId);\n if (!step) {\n step = { id: event.stepId, status: \"planned\" };\n projection.steps.set(event.stepId, step);\n projection.stepOrder.push(event.stepId);\n }\n if (event.name === \"step.planned\") {\n step.name = stringAttribute(event.attributes, \"name\");\n step.description = stringAttribute(event.attributes, \"description\");\n const nestedPipeline = parseNestedPipeline(event.attributes);\n if (nestedPipeline) step.nestedPipeline = nestedPipeline;\n const remote = parseRemote(event.attributes);\n if (remote) step.remote = remote;\n return;\n }\n if (event.attemptId) {\n let attempt = step.attempt;\n if (!attempt) {\n attempt = {\n attemptId: event.attemptId,\n retries: [],\n startedAtMs: event.timestampMs,\n status: \"running\",\n };\n step.attempt = attempt;\n }\n if (event.name === \"step.attempted\") {\n const retry = numberAttribute(event.attributes, \"attempt\");\n if (retry !== undefined) attempt.retries.push(retry);\n }\n const terminal = terminalStepStatus(event);\n if (terminal) {\n attempt.status = attemptStatus(terminal);\n attempt.finishedAtMs = event.timestampMs;\n attempt.durationMs = event.durationMs;\n }\n }\n if (event.name === \"step.running\") {\n step.status = \"running\";\n step.startedAtMs ??= event.timestampMs;\n const completedUnits = numberAttribute(event.attributes, \"completed\");\n if (completedUnits !== undefined) {\n const progress: StoredPipelineStep[\"progress\"] = { completed: completedUnits };\n const message = stringAttribute(event.attributes, \"message\");\n if (message) progress.message = message;\n const total = numberAttribute(event.attributes, \"total\");\n if (total !== undefined) progress.total = total;\n const details = parseProgressDetails(event.attributes);\n if (details) progress.details = details;\n const detailCount = numberAttribute(event.attributes, \"detail_count\");\n if (detailCount !== undefined) progress.detailCount = detailCount;\n else if (details) progress.detailCount = details.length;\n if (!step.progress || hasVisibleStepProgress(progress)) step.progress = progress;\n }\n return;\n }\n const terminal = terminalStepStatus(event);\n if (terminal) {\n step.status = terminal;\n step.finishedAtMs = event.timestampMs;\n step.durationMs = event.durationMs;\n if (event.durationMs !== undefined) step.startedAtMs = event.timestampMs - event.durationMs;\n }\n}\n\nfunction createRunProjection(event: StoredPipelineEvent, retainLogs = true): MutableRunProjection {\n const projection: MutableRunProjection = {\n completed: undefined,\n eventCount: 0,\n first: event,\n logCount: 0,\n logs: [],\n retainLogs,\n started: event,\n stepOrder: [],\n steps: new Map(),\n };\n applyRunEvent(projection, event);\n return projection;\n}\n\nfunction cloneStep(step: StoredPipelineStep): StoredPipelineStep {\n const cloned: StoredPipelineStep = { ...step };\n if (step.attempt) cloned.attempt = { ...step.attempt, retries: [...step.attempt.retries] };\n if (step.progress) {\n cloned.progress = { ...step.progress };\n if (step.progress.details)\n cloned.progress.details = step.progress.details.map((detail) => ({ ...detail }));\n }\n if (step.nestedPipeline) {\n cloned.nestedPipeline = { ...step.nestedPipeline, stepIds: [...step.nestedPipeline.stepIds] };\n }\n if (step.remote) cloned.remote = { ...step.remote };\n return cloned;\n}\n\nfunction materializeRun(projection: MutableRunProjection): StoredPipelineRun {\n const { completed, eventCount, first, logs, started, stepOrder, steps } = projection;\n const statusValue = completed && stringAttribute(completed.attributes, \"status\");\n const status: StoredPipelineRunStatus =\n statusValue === \"cancelled\" || statusValue === \"completed\" || statusValue === \"failed\"\n ? statusValue\n : \"running\";\n\n const run: StoredPipelineRun = {\n dryRun: booleanAttribute(started.attributes, \"dry_run\") ?? false,\n eventCount,\n logCount: projection.logCount,\n logs: projection.retainLogs ? logs.map((log) => ({ ...log })) : [],\n pipelineId: first.pipelineId,\n runId: first.runId,\n startedAtMs: started.timestampMs,\n status,\n steps: stepOrder.map((stepId) => cloneStep(steps.get(stepId)!)),\n version: RUN_MODEL_VERSION,\n };\n if (completed?.durationMs !== undefined) run.durationMs = completed.durationMs;\n if (completed?.error) run.error = completed.error;\n if (completed) run.finishedAtMs = completed.timestampMs;\n if (first.parentRunId) run.parentRunId = first.parentRunId;\n return run;\n}\n\n/** Fold one run's append-only events into a UI-friendly current snapshot. */\nexport function projectPipelineRun(events: readonly StoredPipelineEvent[]): StoredPipelineRun {\n if (events.length === 0) throw new Error(\"Cannot project an empty pipeline run event list.\");\n const ordered = [...events].sort((left, right) => left.id - right.id);\n const projection = createRunProjection(ordered[0]!);\n for (const event of ordered.slice(1)) applyRunEvent(projection, event);\n return materializeRun(projection);\n}\n\nfunction definitionStep(event: StoredPipelineEvent): StoredPipelineDefinitionStep {\n const step: StoredPipelineDefinitionStep = {\n dependencies: stringArrayAttribute(event.attributes, \"dependencies\"),\n dryRun: stringAttribute(event.attributes, \"dry_run\") ?? \"run\",\n id: event.stepId!,\n optionalDependencies: stringArrayAttribute(event.attributes, \"optional_dependencies\"),\n runtimeSkipPossible: booleanAttribute(event.attributes, \"runtime_skip_possible\") ?? false,\n skipAfterFailureOf: stringArrayAttribute(event.attributes, \"skip_after_failure_of\"),\n };\n const description = stringAttribute(event.attributes, \"description\");\n if (description) step.description = description;\n const name = stringAttribute(event.attributes, \"name\");\n if (name) step.name = name;\n const nestedPipeline = parseNestedPipeline(event.attributes);\n if (nestedPipeline) step.nestedPipeline = nestedPipeline;\n const remote = parseRemote(event.attributes);\n if (remote) step.remote = remote;\n return step;\n}\n\nfunction createPipelineProjection(event: StoredPipelineEvent): MutablePipelineProjection {\n const projection: MutablePipelineProjection = {\n definitionRunId: undefined,\n definitionRunStartedAtMs: Number.NEGATIVE_INFINITY,\n definitionRunStartedEventId: -1,\n firstSeenAtMs: event.timestampMs,\n lastSeenAtMs: event.timestampMs,\n latestSteps: new Map(),\n runStartedAtMs: new Map(),\n runStartedEventIds: new Map(),\n runTargetIds: new Map(),\n targetIds: [],\n };\n applyPipelineEvent(projection, event);\n return projection;\n}\n\nfunction isNewerObservedDefinition(\n startedAtMs: number,\n startedEventId: number,\n projection: MutablePipelineProjection\n): boolean {\n if (startedAtMs !== projection.definitionRunStartedAtMs) {\n return startedAtMs > projection.definitionRunStartedAtMs;\n }\n return startedEventId > projection.definitionRunStartedEventId;\n}\n\nfunction applyPipelineEvent(\n projection: MutablePipelineProjection,\n event: StoredPipelineEvent\n): void {\n projection.lastSeenAtMs = event.timestampMs;\n if (event.name === \"pipeline.started\") {\n projection.runStartedAtMs.set(event.runId, event.timestampMs);\n projection.runStartedEventIds.set(event.runId, event.id);\n projection.runTargetIds.set(event.runId, stringArrayAttribute(event.attributes, \"target_ids\"));\n }\n if (event.name !== \"step.planned\" || !event.stepId) return;\n const runStartedEventId = projection.runStartedEventIds.get(event.runId);\n const runStartedAtMs = projection.runStartedAtMs.get(event.runId);\n if (runStartedEventId === undefined || runStartedAtMs === undefined) return;\n if (isNewerObservedDefinition(runStartedAtMs, runStartedEventId, projection)) {\n projection.definitionRunId = event.runId;\n projection.definitionRunStartedAtMs = runStartedAtMs;\n projection.definitionRunStartedEventId = runStartedEventId;\n projection.latestSteps.clear();\n projection.targetIds = projection.runTargetIds.get(event.runId) ?? [];\n }\n if (event.runId === projection.definitionRunId) {\n projection.latestSteps.set(event.stepId, definitionStep(event));\n }\n}\n\nfunction cloneDefinitionStep(step: StoredPipelineDefinitionStep): StoredPipelineDefinitionStep {\n const cloned: StoredPipelineDefinitionStep = {\n ...step,\n dependencies: [...step.dependencies],\n optionalDependencies: [...step.optionalDependencies],\n skipAfterFailureOf: [...step.skipAfterFailureOf],\n };\n if (step.nestedPipeline) {\n cloned.nestedPipeline = { ...step.nestedPipeline, stepIds: [...step.nestedPipeline.stepIds] };\n }\n if (step.remote) cloned.remote = { ...step.remote };\n return cloned;\n}\n\nexport interface PipelineRunProjector {\n append(events: readonly StoredPipelineEvent[]): void;\n clear(): void;\n snapshot(now?: number): PipelineRunStoreSnapshot;\n}\n\n/**\n * Incrementally fold accepted store events into the current run-history snapshot.\n * `{ retainLogs: false }` keeps `logCount` without retaining or cloning log bodies.\n */\nexport function createPipelineRunProjector(\n options: { readonly retainLogs?: boolean } = {}\n): PipelineRunProjector {\n const retainLogs = options.retainLogs !== false;\n const pipelines = new Map<string, MutablePipelineProjection>();\n const runs = new Map<string, MutableRunProjection>();\n let cached: PipelineRunStoreSnapshot | undefined;\n let lastAcceptedId: number | undefined;\n\n function applyEvent(event: StoredPipelineEvent): void {\n const run = runs.get(event.runId);\n if (run) applyRunEvent(run, event);\n else runs.set(event.runId, createRunProjection(event, retainLogs));\n\n const pipeline = pipelines.get(event.pipelineId);\n if (pipeline) applyPipelineEvent(pipeline, event);\n else pipelines.set(event.pipelineId, createPipelineProjection(event));\n }\n\n function materialize(generatedAtMs: number): PipelineRunStoreSnapshot {\n const projectedRuns = [...runs.values()]\n .map(materializeRun)\n .sort((left, right) => right.startedAtMs - left.startedAtMs);\n const runsByPipeline = new Map<string, StoredPipelineRun[]>();\n for (const run of projectedRuns) {\n const pipelineRuns = runsByPipeline.get(run.pipelineId) ?? [];\n pipelineRuns.push(run);\n runsByPipeline.set(run.pipelineId, pipelineRuns);\n }\n const definitions = [...runsByPipeline.entries()]\n .map(([pipelineId, pipelineRuns]): StoredPipelineDefinition => {\n const pipeline = pipelines.get(pipelineId)!;\n return {\n activeRuns: pipelineRuns.filter(({ status }) => status === \"running\").length,\n firstSeenAtMs: pipeline.firstSeenAtMs,\n lastSeenAtMs: pipeline.lastSeenAtMs,\n pipelineId,\n runCount: pipelineRuns.length,\n steps: [...pipeline.latestSteps.values()].map(cloneDefinitionStep),\n targetIds: [...pipeline.targetIds],\n };\n })\n .sort((left, right) => right.lastSeenAtMs - left.lastSeenAtMs);\n\n return {\n activeRunCount: projectedRuns.filter(({ status }) => status === \"running\").length,\n completedRunCount: projectedRuns.filter(({ status }) => status === \"completed\").length,\n definitions,\n failedRunCount: projectedRuns.filter(({ status }) => status === \"failed\").length,\n generatedAtMs,\n lastEventId: lastAcceptedId ?? 0,\n runs: projectedRuns,\n };\n }\n\n return {\n append(events) {\n const incoming = [...events]\n .filter((event) => lastAcceptedId === undefined || event.id > lastAcceptedId)\n .sort((left, right) => left.id - right.id);\n if (incoming.length === 0) return;\n for (const event of incoming) {\n if (lastAcceptedId !== undefined && event.id <= lastAcceptedId) continue;\n applyEvent(event);\n lastAcceptedId = event.id;\n cached = undefined;\n }\n },\n clear() {\n cached = undefined;\n lastAcceptedId = undefined;\n pipelines.clear();\n runs.clear();\n },\n snapshot(now) {\n if (!cached) {\n cached = materialize(now ?? Date.now());\n return cached;\n }\n if (now === undefined || now === cached.generatedAtMs) return cached;\n cached = { ...cached, generatedAtMs: now };\n return cached;\n },\n };\n}\n\n/** Fold an event stream into run history and observed pipeline definitions. */\nexport function projectPipelineRunStore(\n events: readonly StoredPipelineEvent[],\n generatedAtMs = Date.now()\n): PipelineRunStoreSnapshot {\n const projector = createPipelineRunProjector();\n projector.append(events);\n return projector.snapshot(generatedAtMs);\n}\n"]}
1
+ {"version":3,"file":"run-store.js","sourceRoot":"","sources":["../../src/run-store/run-store.ts"],"names":[],"mappings":"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","sourcesContent":["import {\n RUN_MODEL_VERSION,\n type PipelineRunStatus,\n type PipelineStepLifecycleStatus,\n type PipelineStepProgressDetail,\n} from \"../core/pipeline.js\";\nimport { hasVisibleStepProgress } from \"../core/progress.js\";\nimport type {\n PipelineTraceAttributeValue,\n PipelineTraceError,\n PipelineTraceEvent,\n PipelineTraceExporter,\n} from \"../tracing/tracing.js\";\n\n/** One trace event after it has been appended to a durable local store. */\nexport interface StoredPipelineEvent extends PipelineTraceEvent {\n /** Store-local, monotonically increasing sequence. `0` is a valid first id. */\n id: number;\n}\n\nexport interface PipelineRunEventQuery {\n /** Return events strictly after this store-local sequence. */\n afterId?: number;\n /** Restrict results to one pipeline definition. */\n pipelineId?: string;\n /** Restrict results to one run. */\n runId?: string;\n /** Maximum rows to return. Defaults to the store implementation's safe limit. */\n limit?: number;\n}\n\n/** Read-only event source consumed by run history and the local studio. */\nexport interface PipelineRunEventReader {\n close(): void | Promise<void>;\n listEvents(query?: PipelineRunEventQuery): Promise<readonly StoredPipelineEvent[]>;\n}\n\n/**\n * Append-only persistence boundary used by the local studio.\n * SQLite `export()` may return before the row is durable; call `flush()` or\n * `close()` before another connection can observe the tail.\n */\nexport interface PipelineRunEventStore extends PipelineRunEventReader, PipelineTraceExporter {}\n\nexport type StoredPipelineRunStatus = PipelineRunStatus | \"running\";\n\nexport interface StoredNestedPipeline {\n mode: \"for-each\" | \"single\";\n pipelineId: string;\n stepCount: number;\n stepIds: string[];\n}\n\nexport interface StoredRemote {\n engine: string;\n target?: string;\n}\n\nexport interface StoredPipelineLog {\n attemptId?: string;\n id: number;\n level: \"error\" | \"log\" | \"warn\";\n message: string;\n stepId?: string;\n timestampMs: number;\n}\n\nexport interface StoredPipelineAttempt {\n attemptId: string;\n durationMs?: number;\n finishedAtMs?: number;\n retries: number[];\n startedAtMs: number;\n status: Exclude<PipelineStepLifecycleStatus, \"planned\">;\n}\n\nexport interface StoredPipelineStep {\n /** One execution attempt; `retries` carries `reportAttempt` telemetry. */\n attempt?: StoredPipelineAttempt;\n description?: string;\n durationMs?: number;\n finishedAtMs?: number;\n id: string;\n name?: string;\n nestedPipeline?: StoredNestedPipeline;\n remote?: StoredRemote;\n progress?: {\n completed: number;\n detailCount?: number;\n details?: PipelineStepProgressDetail[];\n message?: string;\n total?: number;\n };\n startedAtMs?: number;\n status: PipelineStepLifecycleStatus;\n}\n\nexport interface StoredPipelineRun {\n dryRun: boolean;\n durationMs?: number;\n error?: PipelineTraceError;\n eventCount: number;\n finishedAtMs?: number;\n logCount: number;\n logs: StoredPipelineLog[];\n parentRunId?: string;\n pipelineId: string;\n runId: string;\n startedAtMs: number;\n status: StoredPipelineRunStatus;\n steps: StoredPipelineStep[];\n /** Run-record schema version. Projector output is always `RUN_MODEL_VERSION`. */\n version: typeof RUN_MODEL_VERSION;\n}\n\nexport interface StoredPipelineDefinitionStep {\n dependencies: string[];\n description?: string;\n dryRun: string;\n id: string;\n name?: string;\n nestedPipeline?: StoredNestedPipeline;\n remote?: StoredRemote;\n optionalDependencies: string[];\n runtimeSkipPossible: boolean;\n skipAfterFailureOf: string[];\n}\n\nexport interface StoredPipelineDefinition {\n activeRuns: number;\n firstSeenAtMs: number;\n lastSeenAtMs: number;\n pipelineId: string;\n runCount: number;\n steps: StoredPipelineDefinitionStep[];\n targetIds: string[];\n}\n\nexport interface PipelineRunStoreSnapshot {\n activeRunCount: number;\n completedRunCount: number;\n definitions: StoredPipelineDefinition[];\n failedRunCount: number;\n generatedAtMs: number;\n lastEventId: number;\n runs: StoredPipelineRun[];\n}\n\nfunction isStringValue(value: PipelineTraceAttributeValue | undefined): value is string {\n return typeof value === \"string\";\n}\n\nfunction isNumberValue(value: PipelineTraceAttributeValue | undefined): value is number {\n return typeof value === \"number\";\n}\n\nfunction isBooleanValue(value: PipelineTraceAttributeValue | undefined): value is boolean {\n return typeof value === \"boolean\";\n}\n\nfunction stringAttribute(\n attributes: Readonly<Record<string, PipelineTraceAttributeValue | undefined>>,\n key: string\n): string | undefined {\n const value = attributes[key];\n return isStringValue(value) ? value : undefined;\n}\n\nfunction numberAttribute(\n attributes: Readonly<Record<string, PipelineTraceAttributeValue | undefined>>,\n key: string\n): number | undefined {\n const value = attributes[key];\n return isNumberValue(value) ? value : undefined;\n}\n\nfunction booleanAttribute(\n attributes: Readonly<Record<string, PipelineTraceAttributeValue | undefined>>,\n key: string\n): boolean | undefined {\n const value = attributes[key];\n return isBooleanValue(value) ? value : undefined;\n}\n\nfunction stringArrayAttribute(\n attributes: Readonly<Record<string, PipelineTraceAttributeValue | undefined>>,\n key: string\n): string[] {\n const value = stringAttribute(attributes, key);\n if (!value) return [];\n try {\n const parsed: unknown = JSON.parse(value);\n return Array.isArray(parsed)\n ? parsed.filter((item): item is string => typeof item === \"string\")\n : [];\n } catch {\n return [];\n }\n}\n\nfunction parseProgressDetails(\n attributes: Readonly<Record<string, PipelineTraceAttributeValue | undefined>>\n): PipelineStepProgressDetail[] | undefined {\n const value = stringAttribute(attributes, \"details\");\n if (!value) return undefined;\n try {\n const parsed: unknown = JSON.parse(value);\n if (!Array.isArray(parsed)) return undefined;\n const details = parsed.flatMap((item): PipelineStepProgressDetail[] => {\n if (item === null || Array.isArray(item) || !(item instanceof Object)) return [];\n // SAFETY: JSON.parse leaves object fields untyped; isStringValue keeps strings only.\n const id = item.id as PipelineTraceAttributeValue | undefined;\n if (!(\"id\" in item) || !isStringValue(id) || id.length === 0) return [];\n const detail: PipelineStepProgressDetail = { id: id.slice(0, 4_096) };\n // SAFETY: parsed fields are checked by the same guards as other trace attributes.\n const fields = item as Record<string, PipelineTraceAttributeValue | undefined>;\n const name = fields.name;\n if (isStringValue(name) && name) detail.name = name.slice(0, 4_096);\n for (const key of [\"depth\", \"completed\", \"total\"] as const) {\n const number = fields[key];\n if (isNumberValue(number) && Number.isFinite(number)) detail[key] = number;\n }\n // SAFETY: JSON.parse leaves object fields untyped; isStringValue keeps strings only.\n const label = (\"label\" in item ? item.label : undefined) as\n | PipelineTraceAttributeValue\n | undefined;\n if (isStringValue(label) && label) {\n detail.label = label.slice(0, 4_096);\n }\n // SAFETY: JSON.parse leaves object fields untyped; isStringValue keeps strings only.\n const status = (\"status\" in item ? item.status : undefined) as\n | PipelineTraceAttributeValue\n | undefined;\n if (\n isStringValue(status) &&\n (status === \"cancelled\" ||\n status === \"completed\" ||\n status === \"failed\" ||\n status === \"pending\" ||\n status === \"running\" ||\n status === \"skipped\")\n ) {\n detail.status = status;\n }\n return [detail];\n });\n return details.length > 0 ? details.slice(0, 128) : undefined;\n } catch {\n return undefined;\n }\n}\n\nfunction parseNestedPipeline(\n attributes: Readonly<Record<string, PipelineTraceAttributeValue | undefined>>\n): NonNullable<StoredPipelineStep[\"nestedPipeline\"]> | undefined {\n const value = stringAttribute(attributes, \"nested_pipeline\");\n if (!value) return undefined;\n try {\n const parsed: unknown = JSON.parse(value);\n if (parsed === null || Array.isArray(parsed) || !(parsed instanceof Object)) return undefined;\n if (!(\"mode\" in parsed) || !(\"pipelineId\" in parsed) || !(\"stepIds\" in parsed))\n return undefined;\n if (parsed.mode !== \"for-each\" && parsed.mode !== \"single\") return undefined;\n // SAFETY: JSON.parse leaves object fields untyped; isStringValue keeps strings only.\n const pipelineId = parsed.pipelineId as PipelineTraceAttributeValue | undefined;\n if (!isStringValue(pipelineId) || pipelineId.length === 0) return undefined;\n if (!Array.isArray(parsed.stepIds)) return undefined;\n const stepIds = parsed.stepIds\n .filter(isStringValue)\n .slice(0, 128)\n .map((item) => item.slice(0, 4_096));\n // SAFETY: JSON.parse leaves object fields untyped; isNumberValue keeps numbers only.\n const recorded = (\"step_count\" in parsed ? parsed.step_count : undefined) as\n | PipelineTraceAttributeValue\n | undefined;\n const recordedCount =\n isNumberValue(recorded) && Number.isFinite(recorded) ? Math.floor(recorded) : stepIds.length;\n return {\n mode: parsed.mode,\n pipelineId: pipelineId.slice(0, 4_096),\n stepCount: Math.max(stepIds.length, recordedCount),\n stepIds,\n };\n } catch {\n return undefined;\n }\n}\nfunction parseRemote(\n attributes: Readonly<Record<string, PipelineTraceAttributeValue | undefined>>\n): StoredRemote | undefined {\n const value = stringAttribute(attributes, \"remote\");\n if (!value) return undefined;\n try {\n const parsed: unknown = JSON.parse(value);\n if (parsed === null || Array.isArray(parsed) || !(parsed instanceof Object)) return undefined;\n if (!(\"engine\" in parsed)) return undefined;\n // SAFETY: JSON.parse returned a plain object and we confirmed an engine key exists.\n const engine = parsed.engine as PipelineTraceAttributeValue | undefined;\n if (!isStringValue(engine) || engine.length === 0) return undefined;\n const remote: StoredRemote = { engine: engine.slice(0, 4_096) };\n if (\"target\" in parsed) {\n // SAFETY: target is optional metadata on the same parsed remote object.\n const target = parsed.target as PipelineTraceAttributeValue | undefined;\n if (isStringValue(target) && target.length > 0) remote.target = target.slice(0, 4_096);\n }\n return remote;\n } catch {\n return undefined;\n }\n}\n\nfunction terminalStepStatus(event: StoredPipelineEvent): StoredPipelineStep[\"status\"] | undefined {\n switch (event.name) {\n case \"step.cancelled\":\n return \"cancelled\";\n case \"step.complete\":\n // Shipped trace name; projected snapshot uses the live success token.\n return \"completed\";\n case \"step.failed\":\n return \"failed\";\n case \"step.skipped\":\n return \"skipped\";\n default:\n return undefined;\n }\n}\n\nfunction attemptStatus(status: StoredPipelineStep[\"status\"]): StoredPipelineAttempt[\"status\"] {\n return status === \"planned\" ? \"running\" : status;\n}\n\ninterface MutableRunProjection {\n completed: StoredPipelineEvent | undefined;\n eventCount: number;\n first: StoredPipelineEvent;\n logCount: number;\n logs: StoredPipelineLog[];\n retainLogs: boolean;\n started: StoredPipelineEvent;\n stepOrder: string[];\n steps: Map<string, StoredPipelineStep>;\n}\n\ninterface MutablePipelineProjection {\n definitionRunId: string | undefined;\n definitionRunStartedAtMs: number;\n definitionRunStartedEventId: number;\n firstSeenAtMs: number;\n lastSeenAtMs: number;\n latestSteps: Map<string, StoredPipelineDefinitionStep>;\n runStartedAtMs: Map<string, number>;\n runStartedEventIds: Map<string, number>;\n runTargetIds: Map<string, string[]>;\n targetIds: string[];\n}\n\nfunction applyRunEvent(projection: MutableRunProjection, event: StoredPipelineEvent): void {\n projection.eventCount += 1;\n if (event.name === \"pipeline.started\" && projection.started.name !== \"pipeline.started\") {\n projection.started = event;\n }\n if (event.name === \"pipeline.completed\") projection.completed = event;\n if (event.name === \"pipeline.log\") {\n projection.logCount += 1;\n if (!projection.retainLogs) return;\n const level = stringAttribute(event.attributes, \"level\");\n const log: StoredPipelineLog = {\n id: event.id,\n level: level === \"error\" || level === \"warn\" ? level : \"log\",\n message: stringAttribute(event.attributes, \"message\") ?? \"\",\n timestampMs: event.timestampMs,\n };\n if (event.attemptId) log.attemptId = event.attemptId;\n if (event.stepId) log.stepId = event.stepId;\n projection.logs.push(log);\n return;\n }\n if (!event.stepId || !event.name.startsWith(\"step.\")) return;\n\n let step = projection.steps.get(event.stepId);\n if (!step) {\n step = { id: event.stepId, status: \"planned\" };\n projection.steps.set(event.stepId, step);\n projection.stepOrder.push(event.stepId);\n }\n if (event.name === \"step.planned\") {\n step.name = stringAttribute(event.attributes, \"name\");\n step.description = stringAttribute(event.attributes, \"description\");\n const nestedPipeline = parseNestedPipeline(event.attributes);\n if (nestedPipeline) step.nestedPipeline = nestedPipeline;\n const remote = parseRemote(event.attributes);\n if (remote) step.remote = remote;\n return;\n }\n if (event.attemptId) {\n let attempt = step.attempt;\n if (!attempt) {\n attempt = {\n attemptId: event.attemptId,\n retries: [],\n startedAtMs: event.timestampMs,\n status: \"running\",\n };\n step.attempt = attempt;\n }\n if (event.name === \"step.attempted\") {\n const retry = numberAttribute(event.attributes, \"attempt\");\n if (retry !== undefined) attempt.retries.push(retry);\n }\n const terminal = terminalStepStatus(event);\n if (terminal) {\n attempt.status = attemptStatus(terminal);\n attempt.finishedAtMs = event.timestampMs;\n attempt.durationMs = event.durationMs;\n }\n }\n if (event.name === \"step.running\") {\n step.status = \"running\";\n step.startedAtMs ??= event.timestampMs;\n const completedUnits = numberAttribute(event.attributes, \"completed\");\n if (completedUnits !== undefined) {\n const progress: StoredPipelineStep[\"progress\"] = { completed: completedUnits };\n const message = stringAttribute(event.attributes, \"message\");\n if (message) progress.message = message;\n const total = numberAttribute(event.attributes, \"total\");\n if (total !== undefined) progress.total = total;\n const details = parseProgressDetails(event.attributes);\n if (details) progress.details = details;\n const detailCount = numberAttribute(event.attributes, \"detail_count\");\n if (detailCount !== undefined) progress.detailCount = detailCount;\n else if (details) progress.detailCount = details.length;\n if (!step.progress || hasVisibleStepProgress(progress)) step.progress = progress;\n }\n return;\n }\n const terminal = terminalStepStatus(event);\n if (terminal) {\n step.status = terminal;\n step.finishedAtMs = event.timestampMs;\n step.durationMs = event.durationMs;\n if (event.durationMs !== undefined) step.startedAtMs = event.timestampMs - event.durationMs;\n }\n}\n\nfunction createRunProjection(event: StoredPipelineEvent, retainLogs = true): MutableRunProjection {\n const projection: MutableRunProjection = {\n completed: undefined,\n eventCount: 0,\n first: event,\n logCount: 0,\n logs: [],\n retainLogs,\n started: event,\n stepOrder: [],\n steps: new Map(),\n };\n applyRunEvent(projection, event);\n return projection;\n}\n\nfunction materializeRun(projection: MutableRunProjection): StoredPipelineRun {\n const { completed, eventCount, first, logs, started, stepOrder, steps } = projection;\n const statusValue = completed && stringAttribute(completed.attributes, \"status\");\n const status: StoredPipelineRunStatus =\n statusValue === \"cancelled\" || statusValue === \"completed\" || statusValue === \"failed\"\n ? statusValue\n : \"running\";\n\n const run: StoredPipelineRun = {\n dryRun: booleanAttribute(started.attributes, \"dry_run\") ?? false,\n eventCount,\n logCount: projection.logCount,\n logs: projection.retainLogs ? logs.map((log) => ({ ...log })) : [],\n pipelineId: first.pipelineId,\n runId: first.runId,\n startedAtMs: started.timestampMs,\n status,\n steps: stepOrder.map((stepId) => structuredClone(steps.get(stepId)!)),\n version: RUN_MODEL_VERSION,\n };\n if (completed?.durationMs !== undefined) run.durationMs = completed.durationMs;\n if (completed?.error) run.error = completed.error;\n if (completed) run.finishedAtMs = completed.timestampMs;\n if (first.parentRunId) run.parentRunId = first.parentRunId;\n return run;\n}\n\n/** Fold one run's append-only events into a UI-friendly current snapshot. */\nexport function projectPipelineRun(events: readonly StoredPipelineEvent[]): StoredPipelineRun {\n if (events.length === 0) throw new Error(\"Cannot project an empty pipeline run event list.\");\n const ordered = [...events].sort((left, right) => left.id - right.id);\n const projection = createRunProjection(ordered[0]!);\n for (const event of ordered.slice(1)) applyRunEvent(projection, event);\n return materializeRun(projection);\n}\n\nfunction definitionStep(event: StoredPipelineEvent): StoredPipelineDefinitionStep {\n const step: StoredPipelineDefinitionStep = {\n dependencies: stringArrayAttribute(event.attributes, \"dependencies\"),\n dryRun: stringAttribute(event.attributes, \"dry_run\") ?? \"run\",\n id: event.stepId!,\n optionalDependencies: stringArrayAttribute(event.attributes, \"optional_dependencies\"),\n runtimeSkipPossible: booleanAttribute(event.attributes, \"runtime_skip_possible\") ?? false,\n skipAfterFailureOf: stringArrayAttribute(event.attributes, \"skip_after_failure_of\"),\n };\n const description = stringAttribute(event.attributes, \"description\");\n if (description) step.description = description;\n const name = stringAttribute(event.attributes, \"name\");\n if (name) step.name = name;\n const nestedPipeline = parseNestedPipeline(event.attributes);\n if (nestedPipeline) step.nestedPipeline = nestedPipeline;\n const remote = parseRemote(event.attributes);\n if (remote) step.remote = remote;\n return step;\n}\n\nfunction createPipelineProjection(event: StoredPipelineEvent): MutablePipelineProjection {\n const projection: MutablePipelineProjection = {\n definitionRunId: undefined,\n definitionRunStartedAtMs: Number.NEGATIVE_INFINITY,\n definitionRunStartedEventId: -1,\n firstSeenAtMs: event.timestampMs,\n lastSeenAtMs: event.timestampMs,\n latestSteps: new Map(),\n runStartedAtMs: new Map(),\n runStartedEventIds: new Map(),\n runTargetIds: new Map(),\n targetIds: [],\n };\n applyPipelineEvent(projection, event);\n return projection;\n}\n\nfunction isNewerObservedDefinition(\n startedAtMs: number,\n startedEventId: number,\n projection: MutablePipelineProjection\n): boolean {\n if (startedAtMs !== projection.definitionRunStartedAtMs) {\n return startedAtMs > projection.definitionRunStartedAtMs;\n }\n return startedEventId > projection.definitionRunStartedEventId;\n}\n\nfunction applyPipelineEvent(\n projection: MutablePipelineProjection,\n event: StoredPipelineEvent\n): void {\n projection.lastSeenAtMs = event.timestampMs;\n if (event.name === \"pipeline.started\") {\n projection.runStartedAtMs.set(event.runId, event.timestampMs);\n projection.runStartedEventIds.set(event.runId, event.id);\n projection.runTargetIds.set(event.runId, stringArrayAttribute(event.attributes, \"target_ids\"));\n }\n if (event.name !== \"step.planned\" || !event.stepId) return;\n const runStartedEventId = projection.runStartedEventIds.get(event.runId);\n const runStartedAtMs = projection.runStartedAtMs.get(event.runId);\n if (runStartedEventId === undefined || runStartedAtMs === undefined) return;\n if (isNewerObservedDefinition(runStartedAtMs, runStartedEventId, projection)) {\n projection.definitionRunId = event.runId;\n projection.definitionRunStartedAtMs = runStartedAtMs;\n projection.definitionRunStartedEventId = runStartedEventId;\n projection.latestSteps.clear();\n projection.targetIds = projection.runTargetIds.get(event.runId) ?? [];\n }\n if (event.runId === projection.definitionRunId) {\n projection.latestSteps.set(event.stepId, definitionStep(event));\n }\n}\n\nexport interface PipelineRunProjector {\n append(events: readonly StoredPipelineEvent[]): void;\n clear(): void;\n snapshot(now?: number): PipelineRunStoreSnapshot;\n}\n\n/**\n * Incrementally fold accepted store events into the current run-history snapshot.\n * `{ retainLogs: false }` keeps `logCount` without retaining or cloning log bodies.\n */\nexport function createPipelineRunProjector(\n options: { readonly retainLogs?: boolean } = {}\n): PipelineRunProjector {\n const retainLogs = options.retainLogs !== false;\n const pipelines = new Map<string, MutablePipelineProjection>();\n const runs = new Map<string, MutableRunProjection>();\n let cached: PipelineRunStoreSnapshot | undefined;\n let lastAcceptedId: number | undefined;\n\n function applyEvent(event: StoredPipelineEvent): void {\n const run = runs.get(event.runId);\n if (run) applyRunEvent(run, event);\n else runs.set(event.runId, createRunProjection(event, retainLogs));\n\n const pipeline = pipelines.get(event.pipelineId);\n if (pipeline) applyPipelineEvent(pipeline, event);\n else pipelines.set(event.pipelineId, createPipelineProjection(event));\n }\n\n function materialize(generatedAtMs: number): PipelineRunStoreSnapshot {\n const projectedRuns = [...runs.values()]\n .map(materializeRun)\n .sort((left, right) => right.startedAtMs - left.startedAtMs);\n const runsByPipeline = new Map<string, StoredPipelineRun[]>();\n for (const run of projectedRuns) {\n const pipelineRuns = runsByPipeline.get(run.pipelineId) ?? [];\n pipelineRuns.push(run);\n runsByPipeline.set(run.pipelineId, pipelineRuns);\n }\n const definitions = [...runsByPipeline.entries()]\n .map(([pipelineId, pipelineRuns]): StoredPipelineDefinition => {\n const pipeline = pipelines.get(pipelineId)!;\n return {\n activeRuns: pipelineRuns.filter(({ status }) => status === \"running\").length,\n firstSeenAtMs: pipeline.firstSeenAtMs,\n lastSeenAtMs: pipeline.lastSeenAtMs,\n pipelineId,\n runCount: pipelineRuns.length,\n steps: [...pipeline.latestSteps.values()].map((step) => structuredClone(step)),\n targetIds: [...pipeline.targetIds],\n };\n })\n .sort((left, right) => right.lastSeenAtMs - left.lastSeenAtMs);\n\n return {\n activeRunCount: projectedRuns.filter(({ status }) => status === \"running\").length,\n completedRunCount: projectedRuns.filter(({ status }) => status === \"completed\").length,\n definitions,\n failedRunCount: projectedRuns.filter(({ status }) => status === \"failed\").length,\n generatedAtMs,\n lastEventId: lastAcceptedId ?? 0,\n runs: projectedRuns,\n };\n }\n\n return {\n append(events) {\n const incoming = [...events]\n .filter((event) => lastAcceptedId === undefined || event.id > lastAcceptedId)\n .sort((left, right) => left.id - right.id);\n if (incoming.length === 0) return;\n for (const event of incoming) {\n if (lastAcceptedId !== undefined && event.id <= lastAcceptedId) continue;\n applyEvent(event);\n lastAcceptedId = event.id;\n cached = undefined;\n }\n },\n clear() {\n cached = undefined;\n lastAcceptedId = undefined;\n pipelines.clear();\n runs.clear();\n },\n snapshot(now) {\n if (!cached) {\n cached = materialize(now ?? Date.now());\n return cached;\n }\n if (now === undefined || now === cached.generatedAtMs) return cached;\n cached = { ...cached, generatedAtMs: now };\n return cached;\n },\n };\n}\n\n/** Fold an event stream into run history and observed pipeline definitions. */\nexport function projectPipelineRunStore(\n events: readonly StoredPipelineEvent[],\n generatedAtMs = Date.now()\n): PipelineRunStoreSnapshot {\n const projector = createPipelineRunProjector();\n projector.append(events);\n return projector.snapshot(generatedAtMs);\n}\n"]}
@@ -1 +1 @@
1
- export const PIPELINE_RUN_STUDIO_CLIENT_SOURCE = "const EMPTY_STUDIO_RUNS = [];\n/** Derived run hierarchy for one studio snapshot. Rebuild when the snapshot is replaced. */\nfunction createStudioRunIndex(runs) {\n const runsById = new Map();\n const childrenByParentId = new Map();\n const parentIdByRunId = new Map();\n for (const run of runs) {\n if (!runsById.has(run.runId))\n runsById.set(run.runId, run);\n const parentRunId = run.parentRunId;\n if (!parentIdByRunId.has(run.runId))\n parentIdByRunId.set(run.runId, parentRunId);\n if (!parentRunId)\n continue;\n const siblings = childrenByParentId.get(parentRunId);\n if (siblings)\n siblings.push(run);\n else\n childrenByParentId.set(parentRunId, [run]);\n }\n for (const siblings of childrenByParentId.values()) {\n siblings.sort((left, right) => right.startedAtMs - left.startedAtMs);\n }\n const roots = [];\n const rootIds = new Set();\n for (const run of runs) {\n const parentRunId = parentIdByRunId.get(run.runId);\n if (parentRunId && runsById.has(parentRunId))\n continue;\n roots.push(run);\n rootIds.add(run.runId);\n }\n const rootIdByRunId = new Map();\n for (const run of runs) {\n if (rootIdByRunId.has(run.runId))\n continue;\n const path = [];\n const onPath = new Set();\n let currentId = run.runId;\n let resolved;\n while (currentId) {\n if (rootIdByRunId.has(currentId)) {\n resolved = rootIdByRunId.get(currentId);\n break;\n }\n if (rootIds.has(currentId)) {\n resolved = currentId;\n break;\n }\n if (onPath.has(currentId)) {\n resolved = undefined;\n break;\n }\n onPath.add(currentId);\n path.push(currentId);\n const parentRunId = parentIdByRunId.get(currentId);\n if (!parentRunId || !runsById.has(parentRunId)) {\n resolved = currentId;\n break;\n }\n currentId = parentRunId;\n }\n if (resolved !== undefined)\n rootIdByRunId.set(resolved, resolved);\n for (const id of path)\n rootIdByRunId.set(id, resolved);\n }\n const descendantCountById = new Map();\n const subtreeRunningById = new Map();\n for (const run of runs) {\n if (descendantCountById.has(run.runId))\n continue;\n const stack = [{ exiting: false, id: run.runId }];\n const visiting = new Set();\n while (stack.length > 0) {\n const frame = stack.pop();\n if (!frame)\n break;\n if (frame.exiting) {\n visiting.delete(frame.id);\n let count = 0;\n let running = runsById.get(frame.id)?.status === \"running\";\n for (const child of childrenByParentId.get(frame.id) ?? EMPTY_STUDIO_RUNS) {\n const childCount = descendantCountById.get(child.runId);\n if (childCount === undefined)\n continue;\n count += 1 + childCount;\n running = running || subtreeRunningById.get(child.runId) === true;\n }\n descendantCountById.set(frame.id, count);\n subtreeRunningById.set(frame.id, running);\n continue;\n }\n if (descendantCountById.has(frame.id) || visiting.has(frame.id))\n continue;\n visiting.add(frame.id);\n stack.push({ exiting: true, id: frame.id });\n for (const child of childrenByParentId.get(frame.id) ?? EMPTY_STUDIO_RUNS) {\n if (!descendantCountById.has(child.runId) && !visiting.has(child.runId)) {\n stack.push({ exiting: false, id: child.runId });\n }\n }\n }\n }\n function runById(runId) {\n return runId == null ? undefined : runsById.get(runId);\n }\n function childrenOf(runId) {\n return childrenByParentId.get(runId) ?? EMPTY_STUDIO_RUNS;\n }\n function ancestorsOf(runId) {\n const ancestors = [];\n const seen = new Set();\n let current = runById(runId);\n while (current) {\n const parentRunId = parentIdByRunId.get(current.runId);\n if (!parentRunId || seen.has(parentRunId))\n break;\n seen.add(parentRunId);\n const parent = runsById.get(parentRunId);\n if (!parent)\n break;\n ancestors.unshift(parent);\n current = parent;\n }\n return ancestors;\n }\n function descendantsOf(runId) {\n const result = [];\n const seen = new Set([runId]);\n const stack = childrenOf(runId).slice().reverse();\n while (stack.length > 0) {\n const child = stack.pop();\n if (!child || seen.has(child.runId))\n continue;\n seen.add(child.runId);\n result.push(child);\n const nested = childrenByParentId.get(child.runId);\n if (!nested)\n continue;\n for (let index = nested.length - 1; index >= 0; index -= 1) {\n const next = nested[index];\n if (next)\n stack.push(next);\n }\n }\n return result;\n }\n function matchingRootIds(query) {\n const needle = query.toLowerCase();\n const matched = new Set();\n if (!needle) {\n for (const root of roots)\n matched.add(root.runId);\n return matched;\n }\n for (const run of runs) {\n if (!run.pipelineId.toLowerCase().includes(needle) &&\n !run.runId.toLowerCase().includes(needle)) {\n continue;\n }\n const rootId = rootIdByRunId.get(run.runId);\n if (rootId)\n matched.add(rootId);\n }\n return matched;\n }\n return {\n roots,\n ancestorsOf,\n childrenOf,\n descendantCount(runId) {\n return descendantCountById.get(runId) ?? 0;\n },\n descendantsOf,\n matchingRootIds,\n rootRunId(runId) {\n return runId == null ? runId : (rootIdByRunId.get(runId) ?? runId);\n },\n runById,\n subtreeIsRunning(runId) {\n return subtreeRunningById.get(runId) === true;\n },\n };\n}\n/** Browser client for the local studio page. Compiled JS is inlined into the served HTML. */\nfunction initStudio() {\n const state = {\n snapshot: null,\n runIndex: createStudioRunIndex([]),\n detail: null,\n detailFingerprint: null,\n commands: [],\n view: \"runs\",\n selectedRunId: null,\n query: \"\",\n loading: false,\n launching: false,\n planning: false,\n clearing: false,\n cancelling: false,\n canCancel: false,\n canClearHistory: false,\n planVersion: 0,\n };\n const $ = (selector) => {\n const node = document.querySelector(selector);\n if (!(node instanceof HTMLElement))\n throw new Error(\"Missing \" + selector);\n // SAFETY: Studio markup uses HTML elements that expose value/checked/disabled.\n return node;\n };\n const nodes = (selector) => Array.from(document.querySelectorAll(selector), (node) => {\n // SAFETY: Studio event selectors only match HTML elements on this page.\n return node;\n });\n const queryNode = (selector) => {\n const node = document.querySelector(selector);\n if (!(node instanceof HTMLElement))\n return null;\n // SAFETY: Parameter controls in the launch form are HTML inputs, selects, or textareas.\n return node;\n };\n function setConnected(connected) {\n $(\"#connectionLabel\").textContent = connected ? \"Connected · local\" : \"Connection lost\";\n $(\".pulse\").classList.toggle(\"lost\", !connected);\n }\n const esc = (value) => String(value ?? \"\").replace(/[&<>\"']/g, (char) => char === \"&\"\n ? \"&amp;\"\n : char === \"<\"\n ? \"&lt;\"\n : char === \">\"\n ? \"&gt;\"\n : char === '\"'\n ? \"&quot;\"\n : \"&#039;\");\n const statusMark = (value) => value === \"completed\"\n ? \"✓\"\n : value === \"skipped\"\n ? \"×\"\n : value === \"cancelled\"\n ? \"■\"\n : value === \"failed\"\n ? \"!\"\n : value === \"planned\"\n ? \"…\"\n : \"\";\n const status = (value) => '<span class=\"status ' +\n esc(value) +\n '\"><i class=\"status-mark\" aria-hidden=\"true\">' +\n statusMark(value) +\n \"</i>\" +\n esc(value) +\n \"</span>\";\n const duration = (ms) => ms == null\n ? \"—\"\n : ms < 1000\n ? Math.max(0, Math.round(ms)) + \" ms\"\n : ms < 60000\n ? (ms / 1000).toFixed(ms < 10000 ? 1 : 0) + \" s\"\n : Math.floor(ms / 60000) + \"m \" + Math.round((ms % 60000) / 1000) + \"s\";\n const clock = (ms) => {\n if (!Number.isFinite(ms) || Math.abs(ms) > 8.64e15)\n return \"\";\n return new Intl.DateTimeFormat(undefined, {\n hour: \"2-digit\",\n minute: \"2-digit\",\n second: \"2-digit\",\n }).format(ms);\n };\n const dateTime = (ms) => {\n if (!Number.isFinite(ms) || Math.abs(ms) > 8.64e15)\n return \"\";\n return new Intl.DateTimeFormat(undefined, { dateStyle: \"medium\", timeStyle: \"medium\" }).format(ms);\n };\n const isoTime = (ms) => {\n if (!Number.isFinite(ms) || Math.abs(ms) > 8.64e15)\n return \"\";\n return new Date(ms).toISOString();\n };\n const relative = (ms) => {\n const delta = Math.max(0, Date.now() - ms);\n if (delta < 60000)\n return Math.floor(delta / 1000) + \"s ago\";\n if (delta < 3600000)\n return Math.floor(delta / 60000) + \"m ago\";\n if (delta < 86400000)\n return Math.floor(delta / 3600000) + \"h ago\";\n return Math.floor(delta / 86400000) + \"d ago\";\n };\n const shortId = (id) => (id.length > 24 ? id.slice(0, 12) + \"…\" + id.slice(-7) : id);\n function selectedCommand() {\n return state.commands.find((command) => command.id === $(\"#launchCommand\").value);\n }\n function parameterLabel(parameter) {\n return parameter.key\n .replace(/([a-z0-9])([A-Z])/g, \"$1 $2\")\n .replace(/[-_]+/g, \" \")\n .replace(/\\b\\w/g, (letter) => letter.toUpperCase());\n }\n function parameterHint(parameter) {\n const details = [];\n if (parameter.description)\n details.push(parameter.description);\n if (parameter.environment)\n details.push(\"Environment fallback: \" + parameter.environment);\n if (parameter.multiple)\n details.push(parameter.choices ? \"Choose one or more values.\" : \"Enter one value per line.\");\n if (parameter.mustExist)\n details.push(\"Must be an existing \" + (parameter.pathKind || \"path\") + \".\");\n return details.join(\" \");\n }\n function parameterControl(parameter, index, scope) {\n const id = scope + \"-param-\" + index;\n const data = \" data-\" + scope + '-parameter-index=\"' + index + '\"';\n const hint = parameterHint(parameter);\n const labelText = parameterLabel(parameter);\n if (parameter.type === \"boolean\") {\n return ('<label class=\"boolean-field\" for=\"' +\n id +\n '\"><span><strong>' +\n esc(labelText) +\n \"</strong><small>\" +\n esc(hint || \"Enable this option.\") +\n '</small></span><input id=\"' +\n id +\n '\" type=\"checkbox\"' +\n data +\n (parameter.default ? \" checked\" : \"\") +\n \"></label>\");\n }\n const required = parameter.required ? \" required\" : \"\";\n const label = '<label for=\"' +\n id +\n '\">' +\n esc(labelText) +\n (parameter.required ? '<span class=\"required-mark\">required</span>' : \"\") +\n \"</label>\";\n let control;\n if (parameter.choices) {\n const emptyOption = parameter.multiple || parameter.required\n ? \"\"\n : '<option value=\"\">Use default or leave unset</option>';\n const options = parameter.choices\n .map((choice) => '<option value=\"' +\n esc(choice) +\n '\"' +\n (choice === parameter.default ? \" selected\" : \"\") +\n \">\" +\n esc(choice) +\n \"</option>\")\n .join(\"\");\n control =\n '<select id=\"' +\n id +\n '\"' +\n data +\n (parameter.multiple ? \" multiple\" : \"\") +\n required +\n \">\" +\n emptyOption +\n options +\n \"</select>\";\n }\n else if (parameter.multiple) {\n control =\n '<textarea id=\"' +\n id +\n '\"' +\n data +\n ' spellcheck=\"false\" placeholder=\"One value per line\"></textarea>';\n }\n else {\n const inputType = parameter.type === \"number\" ? \"number\" : \"text\";\n const value = parameter.default === undefined ? \"\" : String(parameter.default);\n const constraints = parameter.type === \"number\"\n ? (parameter.integer ? ' step=\"1\"' : ' step=\"any\"') +\n (parameter.min === undefined ? \"\" : ' min=\"' + esc(parameter.min) + '\"') +\n (parameter.max === undefined ? \"\" : ' max=\"' + esc(parameter.max) + '\"')\n : \"\";\n const placeholder = parameter.type === \"path\"\n ? parameter.pathKind === \"directory\"\n ? \"./directory\"\n : \"./file\"\n : \"\";\n control =\n '<input id=\"' +\n id +\n '\" type=\"' +\n inputType +\n '\"' +\n data +\n ' value=\"' +\n esc(value) +\n '\" placeholder=\"' +\n placeholder +\n '\"' +\n constraints +\n required +\n \">\";\n }\n return ('<div class=\"field\">' +\n label +\n control +\n (hint ? '<div class=\"field-hint\">' + esc(hint) + \"</div>\" : \"\") +\n \"</div>\");\n }\n function renderParameters(command, scope, keys) {\n return command.parameters\n .map((parameter, index) => keys.has(parameter.key) ? parameterControl(parameter, index, scope) : \"\")\n .join(\"\");\n }\n function bindExclusiveSelections(command, scope) {\n const fields = command.parameters.flatMap((parameter, index) => {\n if (!parameter.exclusive || !parameter.multiple)\n return [];\n const field = queryNode(\"[data-\" + scope + '-parameter-index=\"' + index + '\"]');\n return field ? [field] : [];\n });\n const clear = (field) => {\n if (field.tagName === \"SELECT\")\n Array.from(field.options).forEach((option) => {\n option.selected = false;\n });\n else\n field.value = \"\";\n };\n fields.forEach((field) => {\n field.addEventListener(\"change\", () => {\n if (field.selectedOptions?.length || field.value) {\n fields.forEach((other) => {\n if (other !== field)\n clear(other);\n });\n }\n });\n });\n }\n function renderCommandForm() {\n const command = selectedCommand();\n $(\"#launchTitle\").textContent = command?.name || \"Run a pipeline\";\n $(\"#launchDescription\").textContent = command\n ? commandDescription(command)\n : \"Choose a declared pipeline and provide its inputs.\";\n if (!command) {\n $(\"#parameterFields\").innerHTML = \"\";\n $(\"#previewPlan\").classList.add(\"hidden\");\n return;\n }\n const domainKeys = new Set(command.parameters\n .filter((parameter) => parameter.group !== \"execution\")\n .map((parameter) => parameter.key));\n const executionKeys = new Set(command.parameters\n .filter((parameter) => parameter.group === \"execution\")\n .map((parameter) => parameter.key));\n const section = (title, note, keys) => keys.size\n ? '<section class=\"form-section\"><div class=\"form-section-head\"><strong>' +\n title +\n \"</strong><span>\" +\n note +\n '</span></div><div class=\"parameter-grid\">' +\n renderParameters(command, \"run\", keys) +\n \"</div></section>\"\n : \"\";\n $(\"#parameterFields\").innerHTML =\n section(\"Pipeline inputs\", domainKeys.size + \" parameter\" + (domainKeys.size === 1 ? \"\" : \"s\"), domainKeys) + section(\"Execution controls\", \"Built into Tubeless\", executionKeys);\n bindExclusiveSelections(command, \"run\");\n $(\"#previewPlan\").classList.toggle(\"hidden\", !command.canPlan);\n invalidatePlan();\n }\n function parameterValues(parameter, index, scope) {\n const control = queryNode(\"[data-\" + scope + '-parameter-index=\"' + index + '\"]');\n if (!control)\n return [];\n if (parameter.type === \"boolean\")\n return [control.checked];\n const rawValues = parameter.multiple\n ? control.tagName === \"SELECT\"\n ? Array.from(control.selectedOptions).map((option) => option.value)\n : control.value\n .split(/\\r?\\n/)\n .map((value) => value.trim())\n .filter(Boolean)\n : [control.value];\n return parameter.type === \"number\"\n ? rawValues.map((value) => (value === \"\" ? value : Number(value)))\n : rawValues;\n }\n function parameterLaunchValues(command) {\n const values = {};\n command.parameters.forEach((parameter, index) => {\n const parameterValue = parameterValues(parameter, index, \"run\");\n if (parameter.type === \"boolean\") {\n const checked = parameterValue[0];\n if (checked !== Boolean(parameter.default))\n values[parameter.key] = checked;\n return;\n }\n if (parameter.multiple) {\n values[parameter.key] =\n parameter.type === \"number\"\n ? parameterValue.filter((value) => typeof value === \"number\")\n : parameterValue.filter((value) => typeof value === \"string\");\n return;\n }\n const single = parameterValue[0];\n if (single !== \"\" && single !== undefined)\n values[parameter.key] = single;\n });\n return values;\n }\n function currentPlanInput(command) {\n const input = {};\n command.parameters.forEach((parameter, index) => {\n if (parameter.group !== \"execution\")\n return;\n const values = parameterValues(parameter, index, \"run\");\n if (parameter.type === \"boolean\") {\n if (values[0] === true)\n input[parameter.key] = true;\n return;\n }\n if (parameter.multiple && values.length) {\n input[parameter.key] = values.filter((value) => typeof value === \"string\");\n }\n });\n // SAFETY: Execution controls are plan selections; the server parser ignores unknown keys.\n return input;\n }\n function invalidatePlan() {\n state.planVersion += 1;\n $(\"#planResult\").classList.add(\"hidden\");\n $(\"#planResult\").innerHTML = \"\";\n }\n function showLaunchError(message) {\n $(\"#launchError\").textContent = message;\n $(\"#launchError\").classList.toggle(\"hidden\", !message);\n }\n function closeLaunch() {\n if (state.launching || state.planning)\n return;\n $(\"#launchModal\").classList.add(\"hidden\");\n showLaunchError(\"\");\n }\n function openLaunch(commandId) {\n if (commandId)\n $(\"#launchCommand\").value = commandId;\n $(\"#launchModal\").classList.remove(\"hidden\");\n renderCommandForm();\n const firstField = $(\"#parameterFields\").querySelector(\"input, select, textarea\");\n if (firstField instanceof HTMLElement)\n firstField.focus();\n }\n function showToast(message) {\n const toast = $(\"#toast\");\n toast.textContent = message;\n toast.classList.remove(\"hidden\");\n setTimeout(() => toast.classList.add(\"hidden\"), 4200);\n }\n function showClearHistoryError(message) {\n $(\"#clearHistoryError\").textContent = message;\n $(\"#clearHistoryError\").classList.toggle(\"hidden\", !message);\n }\n function closeClearHistory() {\n if (state.clearing)\n return;\n $(\"#clearHistoryModal\").classList.add(\"hidden\");\n showClearHistoryError(\"\");\n }\n function openClearHistory() {\n if (!state.canClearHistory)\n return;\n const runCount = state.snapshot?.runs.length ?? 0;\n const eventCount = state.snapshot?.runs.reduce((total, run) => total + run.eventCount, 0) ?? 0;\n const activeRunCount = state.snapshot?.activeRunCount ?? 0;\n $(\"#clearHistoryCopy\").textContent =\n \"Remove \" +\n runCount +\n \" recorded run\" +\n (runCount === 1 ? \"\" : \"s\") +\n \" and \" +\n eventCount +\n \" event\" +\n (eventCount === 1 ? \"\" : \"s\") +\n \" from this SQLite store.\" +\n (activeRunCount\n ? \" \" +\n activeRunCount +\n \" recorded run\" +\n (activeRunCount === 1 ? \" is\" : \"s are\") +\n \" still marked active; continue only if no external process is writing to this store.\"\n : \"\");\n $(\"#clearHistoryModal\").classList.remove(\"hidden\");\n $(\"#confirmClearHistory\").focus();\n }\n async function clearHistory() {\n if (state.clearing)\n return;\n state.clearing = true;\n const button = $(\"#confirmClearHistory\");\n button.disabled = true;\n button.textContent = \"Clearing…\";\n showClearHistoryError(\"\");\n try {\n const response = await fetch(\"/api/history\", {\n method: \"DELETE\",\n headers: { \"x-tubeless-studio-clear-history\": \"1\" },\n });\n const result = await response.json();\n if (!response.ok || !result.cleared)\n throw new Error(result.error || \"History could not be cleared.\");\n state.selectedRunId = null;\n $(\"#clearHistoryModal\").classList.add(\"hidden\");\n showToast(\"Cleared \" + result.eventCount + \" recorded event\" + (result.eventCount === 1 ? \"\" : \"s\"));\n await refresh(true);\n }\n catch (error) {\n showClearHistoryError(error instanceof Error ? error.message || String(error) : String(error));\n }\n finally {\n state.clearing = false;\n button.disabled = false;\n button.textContent = \"Clear history\";\n }\n }\n function metrics(snapshot) {\n const terminal = snapshot.completedRunCount + snapshot.failedRunCount;\n const success = terminal ? Math.round((snapshot.completedRunCount / terminal) * 100) : 0;\n const pipelineCount = state.commands.length || snapshot.definitions.length;\n return [\n [\n \"Active now\",\n snapshot.activeRunCount,\n snapshot.activeRunCount ? \"Live execution in progress\" : \"No work in flight\",\n ],\n [\"Recorded runs\", snapshot.runs.length, \"Append-only local history\"],\n [\"Success rate\", success + \"%\", terminal + \" terminal runs\"],\n [\n \"Pipelines\",\n pipelineCount,\n state.commands.length ? \"Available to configure\" : \"Observed in run history\",\n ],\n ]\n .map(([label, value, note]) => '<article class=\"metric\"><div class=\"metric-label\">' +\n label +\n '</div><div class=\"metric-value\">' +\n value +\n '</div><div class=\"metric-note\">' +\n note +\n \"</div></article>\")\n .join(\"\");\n }\n function empty(title, copy) {\n return ('<div class=\"empty\"><div><div class=\"empty-icon\"><svg width=\"20\" height=\"20\" viewBox=\"0 0 20 20\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.4\"><path d=\"M4 5.5h12M4 10h12M4 14.5h8\"/></svg></div><strong>' +\n esc(title) +\n \"</strong><p>\" +\n esc(copy) +\n \"</p></div></div>\");\n }\n function commandDescription(command) {\n return command.description || \"Run this typed pipeline command.\";\n }\n function commandCatalog(commands) {\n return ('<section class=\"sheet\"><div class=\"sheet-head\"><div><div class=\"sheet-title\">Available pipelines</div><div class=\"sheet-subtitle\">Declared by the local studio manifest</div></div><span class=\"sheet-subtitle\">' +\n commands.length +\n ' shown</span></div><div class=\"catalog\">' +\n commands\n .map((command) => '<article class=\"catalog-card\"><h3>' +\n esc(command.name) +\n \"</h3><p>\" +\n esc(commandDescription(command)) +\n '</p><div class=\"catalog-actions\"><button class=\"primary-button\" data-command-id=\"' +\n esc(command.id) +\n '\">Configure</button></div></article>')\n .join(\"\") +\n \"</div></section>\");\n }\n function runRow(run) {\n const activeStep = run.steps.find((step) => step.status === \"running\");\n const nestedCount = state.runIndex.descendantCount(run.runId);\n const activity = run.status === \"running\"\n ? '<div class=\"run-activity\"><strong>' +\n esc(activeStep?.name || activeStep?.id || \"Starting\") +\n \"</strong><span>\" +\n esc(activeStep?.progress?.message || \"Execution in progress\") +\n \"</span></div>\"\n : \"\";\n return ('<button class=\"run-row ' +\n esc(run.status) +\n \" \" +\n (rootRunId(state.selectedRunId) === run.runId ? \"selected\" : \"\") +\n '\" data-run-id=\"' +\n esc(run.runId) +\n '\"><div class=\"run-primary\">' +\n status(run.status) +\n \"<strong>\" +\n esc(run.pipelineId) +\n '</strong><time class=\"run-time\" datetime=\"' +\n isoTime(run.startedAtMs) +\n '\" title=\"' +\n esc(dateTime(run.startedAtMs)) +\n '\">' +\n relative(run.startedAtMs) +\n '</time></div><div class=\"run-secondary\"><code>' +\n esc(shortId(run.runId)) +\n '</code><i class=\"dot\"></i><span>' +\n duration(run.durationMs) +\n '</span><i class=\"dot\"></i><span>' +\n run.steps.length +\n \" steps</span>\" +\n (nestedCount\n ? '<i class=\"dot\"></i><span>' +\n nestedCount +\n \" nested run\" +\n (nestedCount === 1 ? \"\" : \"s\") +\n \"</span>\"\n : \"\") +\n (run.dryRun ? '<i class=\"dot\"></i><span>dry run</span>' : \"\") +\n \"</div>\" +\n activity +\n \"</button>\");\n }\n function stepRow(step) {\n const progressTotal = step.progress?.total;\n const progressWidth = progressTotal\n ? Math.max(0, Math.min(100, ((step.progress?.completed ?? 0) / progressTotal) * 100))\n : step.status === \"completed\"\n ? 100\n : 18;\n const execution = step.attempt\n ? '<span class=\"execution-summary\" title=\"' +\n esc(step.attempt.attemptId) +\n '\"><b>Execution</b> · ' +\n esc(shortId(step.attempt.attemptId)) +\n (step.attempt.retries.length\n ? \" · \" +\n step.attempt.retries.length +\n \" retr\" +\n (step.attempt.retries.length === 1 ? \"y\" : \"ies\")\n : \"\") +\n \"</span>\"\n : \"\";\n const nested = step.nestedPipeline;\n const remote = step.remote;\n const nestedCount = nested ? (nested.stepCount ?? nested.stepIds.length) : 0;\n const nestedCountLabel = nested && nested.stepIds.length < nestedCount\n ? nested.stepIds.length + \" of \" + nestedCount + \" declared steps\"\n : nestedCount + \" declared steps\";\n const nestedDetail = nested\n ? '<div class=\"plan-nested\"><strong>' +\n esc(nested.pipelineId) +\n \"</strong><span>\" +\n nestedCountLabel +\n (nested.mode === \"for-each\" ? \" per runtime item\" : \"\") +\n \"</span>\" +\n nested.stepIds.map((stepId) => \"<code>\" + esc(stepId) + \"</code>\").join(\"\") +\n \"</div>\"\n : \"\";\n const remoteDetail = remote\n ? '<div class=\"plan-nested\"><strong>' +\n esc(remote.engine) +\n \"</strong>\" +\n (remote.target ? \"<span>\" + esc(remote.target) + \"</span>\" : \"\") +\n \"</div>\"\n : \"\";\n const detailCount = step.progress?.detailCount;\n const truncatedDetails = detailCount && step.progress?.details && step.progress.details.length < detailCount\n ? '<div class=\"progress-detail-truncated\">Showing ' +\n step.progress.details.length +\n \" of \" +\n detailCount +\n \" items</div>\"\n : \"\";\n const detailRows = step.progress?.details?.length\n ? '<div class=\"progress-details\">' +\n step.progress.details\n .map((detail) => '<div class=\"progress-detail ' +\n esc(detail.status || \"running\") +\n '\"><b>' +\n esc(detail.id) +\n \"</b>\" +\n (detail.label ? \"<span>\" + esc(detail.label) + \"</span>\" : \"\") +\n \"</div>\")\n .join(\"\") +\n truncatedDetails +\n \"</div>\"\n : \"\";\n const progress = step.progress\n ? '<div class=\"progress\"><i class=\"w' +\n Math.round(progressWidth) +\n '\"></i></div><div class=\"progress-copy\">' +\n esc(step.progress.message ||\n step.progress.completed + (progressTotal ? \" / \" + progressTotal : \"\") + \" complete\") +\n \"</div>\" +\n detailRows\n : \"\";\n return ('<article class=\"step ' +\n esc(step.status) +\n '\">' +\n stepStatusIcon(step.status) +\n '<div class=\"step-head\"><strong>' +\n esc(step.name || step.id) +\n \"</strong>\" +\n (step.name ? \"<code>\" + esc(step.id) + \"</code>\" : \"\") +\n '<span class=\"step-duration\">' +\n duration(step.durationMs) +\n \"</span></div>\" +\n (step.description\n ? '<div class=\"step-description\">' + esc(step.description) + \"</div>\"\n : \"\") +\n nestedDetail +\n remoteDetail +\n (execution ? '<div class=\"execution\">' + execution + \"</div>\" : \"\") +\n progress +\n \"</article>\");\n }\n function stepStatusIcon(value) {\n const label = value.charAt(0).toUpperCase() + value.slice(1);\n const icon = value === \"completed\"\n ? '<svg viewBox=\"0 0 12 12\"><path d=\"m2 6 2.4 2.4L10 3\"/></svg>'\n : value === \"running\"\n ? \"<i></i>\"\n : value === \"skipped\"\n ? '<svg viewBox=\"0 0 12 12\"><path d=\"m3 3 6 6M9 3 3 9\"/></svg>'\n : value === \"cancelled\"\n ? '<svg viewBox=\"0 0 12 12\"><rect x=\"3\" y=\"3\" width=\"6\" height=\"6\" rx=\"1\" fill=\"currentColor\" stroke=\"none\"/></svg>'\n : value === \"failed\"\n ? \"!\"\n : \"…\";\n return ('<span class=\"step-status-icon ' +\n esc(value) +\n '\" role=\"img\" aria-label=\"' +\n esc(label) +\n '\" title=\"' +\n esc(label) +\n '\">' +\n icon +\n \"</span>\");\n }\n function stepSummary(run) {\n const labels = {\n running: \"running\",\n failed: \"failed\",\n cancelled: \"cancelled\",\n skipped: \"skipped\",\n completed: \"completed\",\n planned: \"planned\",\n };\n const order = [\"running\", \"failed\", \"cancelled\", \"skipped\", \"completed\", \"planned\"];\n return (order\n .map((value) => [value, run.steps.filter((step) => step.status === value).length])\n .filter(([, count]) => count)\n .map(([value, count]) => count + \" \" + labels[value])\n .join(\" · \") || \"No steps\");\n }\n function runDetail(run) {\n if (!run)\n return ('<div class=\"sheet detail\">' +\n empty(\"Select a run\", \"Choose a run from the history to inspect its steps, retry telemetry, logs, and errors.\") +\n \"</div>\");\n const ancestors = ancestorsOf(run.runId);\n const parentage = ancestors.length\n ? '<div class=\"run-parentage\">' +\n ancestors\n .map((ancestor) => '<button type=\"button\" data-detail-run-id=\"' +\n esc(ancestor.runId) +\n '\">' +\n esc(ancestor.pipelineId) +\n \"</button><span>/</span>\")\n .join(\"\") +\n \"<span>\" +\n esc(run.pipelineId) +\n \"</span></div>\"\n : \"\";\n const children = childrenOf(run.runId);\n const nested = children.length\n ? '<div class=\"section-title\"><span>Nested runs</span><span>' +\n children.length +\n \" direct · \" +\n state.runIndex.descendantCount(run.runId) +\n ' total</span></div><div class=\"nested-runs\">' +\n children\n .map((child) => '<button class=\"nested-run\" type=\"button\" data-detail-run-id=\"' +\n esc(child.runId) +\n '\">' +\n status(child.status) +\n \"<strong>\" +\n esc(child.pipelineId) +\n \"</strong><small>\" +\n duration(child.durationMs) +\n \" · \" +\n child.steps.length +\n \" steps\" +\n (state.runIndex.descendantCount(child.runId)\n ? \" · \" + state.runIndex.descendantCount(child.runId) + \" nested\"\n : \"\") +\n \"</small></button>\")\n .join(\"\") +\n \"</div>\"\n : \"\";\n const error = run.error\n ? '<div class=\"section-title\"><span>Error</span></div><div class=\"error-card\"><div class=\"error-code\">' +\n esc(run.error.code) +\n \" · \" +\n esc(run.error.phase) +\n '</div><div class=\"error-message\">' +\n esc(run.error.message) +\n \"</div></div>\"\n : \"\";\n const logs = run.logs.length\n ? '<div class=\"section-title\"><span>Logs</span><span>' +\n run.logs.length +\n '</span></div><div class=\"logs\">' +\n run.logs\n .map((log) => '<div class=\"log-line\"><time class=\"log-time\">' +\n clock(log.timestampMs) +\n '</time><span class=\"log-level ' +\n esc(log.level) +\n '\">' +\n esc(log.level) +\n '</span><span class=\"log-message\">' +\n (log.stepId ? \"<b>\" + esc(log.stepId) + \"</b> · \" : \"\") +\n esc(log.message) +\n \"</span></div>\")\n .join(\"\") +\n \"</div>\"\n : \"\";\n return ('<article class=\"sheet detail\"><div class=\"detail-body\">' +\n parentage +\n '<div class=\"detail-heading\"><div class=\"detail-heading-copy\"><div class=\"detail-kicker\">' +\n (run.parentRunId ? \"Nested run\" : \"Top-level run\") +\n \" · \" +\n relative(run.startedAtMs) +\n \"</div><h2>\" +\n esc(run.pipelineId) +\n '</h2><div class=\"run-id\">' +\n esc(run.runId) +\n '</div></div><div class=\"detail-heading-actions\">' +\n status(run.status) +\n (state.canCancel &&\n run.status === \"running\" &&\n !run.parentRunId &&\n Array.isArray(state.snapshot?.liveRunIds) &&\n state.snapshot.liveRunIds.includes(run.runId)\n ? '<button class=\"danger-button\" type=\"button\" data-cancel-run-id=\"' +\n esc(run.runId) +\n '\"' +\n (state.cancelling ? \" disabled\" : \"\") +\n \">Cancel run</button>\"\n : \"\") +\n '</div></div><div class=\"detail-meta\"><div><label>Started</label><span title=\"' +\n esc(isoTime(run.startedAtMs)) +\n '\">' +\n esc(dateTime(run.startedAtMs)) +\n \"</span></div><div><label>Duration</label><span>\" +\n duration(run.durationMs) +\n \"</span></div><div><label>Steps</label><span>\" +\n run.steps.length +\n \"</span></div><div><label>Events</label><span>\" +\n run.eventCount +\n \"</span></div></div>\" +\n nested +\n '<div class=\"section-title\"><span>Step timeline</span><span>' +\n stepSummary(run) +\n \"</span></div>\" +\n (run.steps.length\n ? '<div class=\"step-list\">' + run.steps.map(stepRow).join(\"\") + \"</div>\"\n : empty(\"No planned steps\", \"This run ended before a step plan was recorded.\")) +\n error +\n logs +\n \"</div></article>\");\n }\n function childrenOf(runId) {\n return state.runIndex.childrenOf(runId);\n }\n function ancestorsOf(runId) {\n return state.runIndex.ancestorsOf(runId);\n }\n function rootRunId(runId) {\n return state.runIndex.rootRunId(runId);\n }\n function renderRuns() {\n const matchedRoots = state.query ? state.runIndex.matchingRootIds(state.query) : null;\n const roots = state.runIndex.roots\n .filter((run) => !matchedRoots || matchedRoots.has(run.runId))\n .sort((left, right) => Number(state.runIndex.subtreeIsRunning(right.runId)) -\n Number(state.runIndex.subtreeIsRunning(left.runId)) ||\n right.startedAtMs - left.startedAtMs);\n if (!state.selectedRunId || !roots.some((run) => run.runId === rootRunId(state.selectedRunId)))\n state.selectedRunId = roots[0]?.runId ?? null;\n const selected = state.detail?.run?.runId === state.selectedRunId ? state.detail.run : null;\n const activeRuns = [];\n const historicalRuns = [];\n for (const root of roots) {\n if (state.runIndex.subtreeIsRunning(root.runId))\n activeRuns.push(root);\n else\n historicalRuns.push(root);\n }\n const activeList = activeRuns.length\n ? '<div class=\"run-group\">Running now · ' +\n activeRuns.length +\n \"</div>\" +\n activeRuns.map(runRow).join(\"\")\n : \"\";\n const historyList = historicalRuns.length\n ? '<div class=\"run-group\">Recent · ' +\n historicalRuns.length +\n \"</div>\" +\n historicalRuns.map(runRow).join(\"\")\n : \"\";\n $(\"#content\").innerHTML =\n '<div class=\"content-grid\"><section class=\"sheet\"><div class=\"sheet-head\"><div><div class=\"sheet-title\">Pipeline runs</div><div class=\"sheet-subtitle\">Top-level runs · nested work stays with its parent</div></div><span class=\"sheet-subtitle\">' +\n roots.length +\n \" top-level · \" +\n (state.snapshot?.runs.length ?? 0) +\n ' total</span></div><div class=\"run-list\">' +\n (roots.length\n ? activeList + historyList\n : empty(\"No recorded runs\", \"Choose Pipelines to start a run and create local history.\")) +\n \"</div></section>\" +\n runDetail(selected) +\n \"</div>\";\n nodes(\"[data-run-id]\").forEach((button) => button.addEventListener(\"click\", () => {\n void selectRun(button.dataset.runId);\n }));\n nodes(\"[data-detail-run-id]\").forEach((button) => button.addEventListener(\"click\", () => {\n void selectRun(button.dataset.detailRunId);\n }));\n nodes(\"[data-cancel-run-id]\").forEach((button) => button.addEventListener(\"click\", (event) => {\n event.stopPropagation();\n void cancelRun(button.dataset.cancelRunId);\n }));\n }\n function renderPipelines() {\n const query = state.query.toLowerCase();\n const commands = state.commands.filter((command) => !query ||\n command.name.toLowerCase().includes(query) ||\n commandDescription(command).toLowerCase().includes(query));\n $(\"#content\").innerHTML = commands.length\n ? commandCatalog(commands)\n : '<div class=\"sheet\">' +\n empty(\"No pipelines found\", \"Try a different pipeline name or description.\") +\n \"</div>\";\n nodes(\"[data-command-id]\").forEach((button) => button.addEventListener(\"click\", () => openLaunch(button.dataset.commandId)));\n }\n function render() {\n if (!state.snapshot)\n return;\n const isPipelines = state.view === \"pipelines\";\n $(\"#pageTitle\").textContent = isPipelines ? \"Pipelines\" : \"Runs\";\n $(\"#pageLede\").textContent = isPipelines\n ? \"Choose a declared workflow to configure and run.\"\n : \"Live work and durable history in one place.\";\n $(\"#search\").placeholder = isPipelines ? \"Filter pipelines\" : \"Filter runs or IDs\";\n $(\"#metrics\").innerHTML = metrics(state.snapshot);\n $(\"#pipelineCount\").textContent = String(state.commands.length);\n $(\"#runCount\").textContent = String(state.snapshot.runs.length);\n $(\"#clearHistoryButton\").disabled = state.snapshot.runs.length === 0;\n $(\"#clearHistoryButton\").title =\n state.snapshot.activeRunCount > 0\n ? \"Clear history, including runs left active by an interrupted process\"\n : \"Clear history\";\n nodes(\"[data-view]\").forEach((button) => button.classList.toggle(\"active\", button.dataset.view === state.view));\n if (isPipelines)\n renderPipelines();\n else\n renderRuns();\n }\n function selectedRunFingerprint() {\n const run = state.runIndex.runById(state.selectedRunId);\n return run ? [run.runId, run.eventCount, run.status].join(\":\") : null;\n }\n async function loadSelectedRunDetail() {\n if (!state.selectedRunId) {\n state.detail = null;\n state.detailFingerprint = null;\n return;\n }\n const requestedRunId = state.selectedRunId;\n const fingerprint = selectedRunFingerprint();\n if (!fingerprint) {\n state.detail = null;\n state.detailFingerprint = null;\n return;\n }\n if (fingerprint === state.detailFingerprint && state.detail)\n return;\n const response = await fetch(\"/api/runs/\" + encodeURIComponent(requestedRunId), {\n cache: \"no-store\",\n });\n if (state.selectedRunId !== requestedRunId)\n return;\n if (response.status === 404) {\n state.detail = null;\n state.detailFingerprint = null;\n return;\n }\n if (!response.ok)\n return;\n // SAFETY: The run detail endpoint returns `{ run }` for the requested id.\n const detail = (await response.json());\n if (state.selectedRunId !== requestedRunId)\n return;\n state.detail = detail;\n state.detailFingerprint = fingerprint;\n }\n async function selectRun(runId) {\n state.selectedRunId = runId ?? null;\n render();\n await loadSelectedRunDetail();\n render();\n }\n async function refresh(manual = false) {\n if (state.loading)\n return;\n state.loading = true;\n if (manual)\n $(\"#refresh\").classList.add(\"spinning\");\n try {\n const response = await fetch(\"/api/snapshot\", { cache: \"no-store\" });\n if (!response.ok)\n throw new Error(\"Snapshot request failed\");\n // SAFETY: The snapshot endpoint returns a studio snapshot with a runs array.\n const snapshot = (await response.json());\n state.snapshot = snapshot;\n state.runIndex = createStudioRunIndex(snapshot.runs);\n render();\n await loadSelectedRunDetail();\n setConnected(true);\n render();\n }\n catch {\n /* Snapshot fetch failed; keep the last rendered run list and mark the connection lost. */\n setConnected(false);\n }\n finally {\n state.loading = false;\n $(\"#refresh\").classList.remove(\"spinning\");\n }\n }\n async function loadCommands() {\n try {\n const response = await fetch(\"/api/commands\", { cache: \"no-store\" });\n if (!response.ok)\n return;\n const payload = await response.json();\n state.commands = Array.isArray(payload.commands) ? payload.commands : [];\n $(\"#pipelineNav\").classList.toggle(\"hidden\", state.commands.length === 0);\n $(\"#launchButton\").classList.toggle(\"hidden\", state.commands.length === 0);\n $(\"#launchCommand\").innerHTML = state.commands\n .map((command) => '<option value=\"' + esc(command.id) + '\">' + esc(command.name) + \"</option>\")\n .join(\"\");\n if (state.commands.length > 0)\n state.view = \"pipelines\";\n renderCommandForm();\n render();\n }\n catch {\n /* Optional studio endpoints; keep the last successful client state. */\n }\n }\n async function loadCapabilities() {\n try {\n const response = await fetch(\"/api/capabilities\", { cache: \"no-store\" });\n if (!response.ok)\n return;\n const capabilities = await response.json();\n state.canClearHistory = capabilities.canClearHistory === true;\n state.canCancel = capabilities.canCancel === true;\n $(\"#clearHistoryButton\").classList.toggle(\"hidden\", !state.canClearHistory);\n render();\n }\n catch {\n /* Optional studio endpoints; keep the last successful client state. */\n }\n }\n function renderPlan(plan) {\n const selected = plan.steps.filter((step) => step.selected && !step.skipReason).length;\n const errors = plan.errors.length\n ? '<div class=\"launch-error\">' +\n plan.errors.map((error) => esc(error.message)).join(\"<br>\") +\n \"</div>\"\n : \"\";\n const steps = plan.steps\n .map((step) => {\n const disposition = !step.selected\n ? \"Not selected\"\n : step.skipReason === \"dry-run\"\n ? \"Dry-run skip\"\n : step.skipReason\n ? \"Skipped\"\n : \"Will run\";\n const detail = step.description ||\n (step.dependencies.length\n ? \"After \" + step.dependencies.join(\", \")\n : \"No required dependencies\");\n const nested = step.nestedPipeline;\n const remote = step.remote;\n const kind = remote\n ? \"Remote step\"\n : nested\n ? nested.mode === \"for-each\"\n ? \"Pipeline fan-out\"\n : \"Nested pipeline\"\n : \"Step\";\n const nestedDetail = nested\n ? '<div class=\"plan-nested\"><strong>' +\n esc(nested.pipelineId) +\n \"</strong><span>\" +\n nested.stepIds.length +\n \" declared steps\" +\n (nested.mode === \"for-each\" ? \" per runtime item\" : \"\") +\n \"</span>\" +\n nested.stepIds.map((stepId) => \"<code>\" + esc(stepId) + \"</code>\").join(\"\") +\n \"</div>\"\n : \"\";\n const remoteDetail = remote\n ? '<div class=\"plan-nested\"><strong>' +\n esc(remote.engine) +\n \"</strong>\" +\n (remote.target ? \"<span>\" + esc(remote.target) + \"</span>\" : \"\") +\n \"</div>\"\n : \"\";\n return ('<div class=\"plan-step\"><div class=\"plan-step-title\"><strong>' +\n esc(step.name || step.id) +\n '</strong><span class=\"plan-kind' +\n (nested ? \" pipeline\" : \"\") +\n '\">' +\n kind +\n \"</span></div><small>\" +\n esc(detail) +\n '</small><span class=\"plan-disposition' +\n (disposition === \"Will run\" ? \"\" : \" skipped\") +\n '\">' +\n disposition +\n \"</span>\" +\n nestedDetail +\n remoteDetail +\n \"</div>\");\n })\n .join(\"\");\n $(\"#planResult\").innerHTML =\n errors +\n '<div class=\"plan-summary\"><strong>' +\n esc(plan.pipelineId) +\n \"</strong><span>\" +\n selected +\n \" of \" +\n plan.steps.length +\n \" steps will run\" +\n (plan.dryRun ? \" · dry run\" : \"\") +\n '</span></div><div class=\"plan-steps\">' +\n steps +\n \"</div>\";\n $(\"#planResult\").classList.remove(\"hidden\");\n }\n async function previewPlan() {\n const command = selectedCommand();\n if (!command?.canPlan || state.planning)\n return;\n state.planning = true;\n const planVersion = state.planVersion;\n $(\"#previewPlan\").disabled = true;\n $(\"#previewPlan\").textContent = \"Planning…\";\n $(\"#submitLaunch\").disabled = true;\n showLaunchError(\"\");\n try {\n const response = await fetch(\"/api/commands/\" + encodeURIComponent(command.id) + \"/plan\", {\n method: \"POST\",\n headers: { \"content-type\": \"application/json\", \"x-tubeless-studio-plan\": \"1\" },\n body: JSON.stringify(currentPlanInput(command)),\n });\n const result = await response.json();\n if (!response.ok || !result.plan)\n throw new Error(result.error || \"Plan request failed.\");\n if (state.planVersion === planVersion)\n renderPlan(result.plan);\n }\n catch (error) {\n showLaunchError(error instanceof Error ? error.message || String(error) : String(error));\n }\n finally {\n state.planning = false;\n $(\"#previewPlan\").disabled = false;\n $(\"#previewPlan\").textContent = \"Preview plan\";\n $(\"#submitLaunch\").disabled = false;\n }\n }\n async function cancelRun(runId) {\n if (!state.canCancel || !runId || state.cancelling)\n return;\n state.cancelling = true;\n render();\n try {\n const response = await fetch(\"/api/runs/\" + encodeURIComponent(runId) + \"/cancel\", {\n method: \"POST\",\n headers: { \"x-tubeless-studio-cancel\": \"1\" },\n });\n const result = await response.json();\n if (!response.ok || !result.cancelled)\n throw new Error(result.error || \"Run could not be cancelled.\");\n showToast(\"Run cancelled · \" + shortId(runId));\n setTimeout(() => refresh(true), 80);\n }\n catch (error) {\n showToast(error instanceof Error ? error.message || String(error) : String(error));\n }\n finally {\n state.cancelling = false;\n render();\n }\n }\n async function launch(event) {\n event.preventDefault();\n const command = selectedCommand();\n if (!command || state.launching)\n return;\n const values = parameterLaunchValues(command);\n state.launching = true;\n $(\"#previewPlan\").disabled = true;\n $(\"#submitLaunch\").disabled = true;\n $(\"#submitLaunch\").textContent = \"Starting…\";\n showLaunchError(\"\");\n try {\n const response = await fetch(\"/api/commands/\" + encodeURIComponent(command.id) + \"/runs\", {\n method: \"POST\",\n headers: { \"content-type\": \"application/json\", \"x-tubeless-studio-launch\": \"1\" },\n body: JSON.stringify({ values }),\n });\n const result = await response.json();\n if (!response.ok || !result.accepted)\n throw new Error((result.errors || [result.error || \"Launch failed.\"]).join(\"\\n\"));\n state.selectedRunId = result.runId;\n state.view = \"runs\";\n $(\"#launchModal\").classList.add(\"hidden\");\n renderCommandForm();\n showToast(\"Run accepted · \" + shortId(result.runId));\n setTimeout(() => refresh(true), 80);\n }\n catch (error) {\n showLaunchError(error instanceof Error ? error.message || String(error) : String(error));\n }\n finally {\n state.launching = false;\n $(\"#previewPlan\").disabled = false;\n $(\"#submitLaunch\").disabled = false;\n $(\"#submitLaunch\").textContent = \"Run\";\n }\n }\n nodes(\"[data-view]\").forEach((button) => button.addEventListener(\"click\", () => {\n state.view = button.dataset.view ?? state.view;\n render();\n }));\n $(\"#search\").addEventListener(\"input\", (event) => {\n const target = event.target;\n if (target instanceof HTMLInputElement) {\n state.query = target.value.trim();\n render();\n }\n });\n $(\"#refresh\").addEventListener(\"click\", () => refresh(true));\n $(\"#launchButton\").addEventListener(\"click\", () => openLaunch());\n $(\"#clearHistoryButton\").addEventListener(\"click\", openClearHistory);\n $(\"#confirmClearHistory\").addEventListener(\"click\", clearHistory);\n $(\"#closeClearHistory\").addEventListener(\"click\", closeClearHistory);\n $(\"#cancelClearHistory\").addEventListener(\"click\", closeClearHistory);\n $(\"#previewPlan\").addEventListener(\"click\", previewPlan);\n $(\"#closeLaunch\").addEventListener(\"click\", closeLaunch);\n $(\"#cancelLaunch\").addEventListener(\"click\", closeLaunch);\n $(\"#launchCommand\").addEventListener(\"change\", renderCommandForm);\n $(\"#runPane\").addEventListener(\"input\", invalidatePlan);\n $(\"#launchForm\").addEventListener(\"submit\", launch);\n $(\"#launchModal\").addEventListener(\"click\", (event) => {\n if (event.target === $(\"#launchModal\"))\n closeLaunch();\n });\n $(\"#clearHistoryModal\").addEventListener(\"click\", (event) => {\n if (event.target === $(\"#clearHistoryModal\"))\n closeClearHistory();\n });\n document.addEventListener(\"keydown\", (event) => {\n if (event.key === \"Escape\") {\n closeLaunch();\n closeClearHistory();\n }\n });\n loadCommands();\n loadCapabilities();\n refresh();\n setInterval(() => refresh(), 1200);\n}\ninitStudio();\n";
1
+ export const PIPELINE_RUN_STUDIO_CLIENT_SOURCE = "const EMPTY_STUDIO_RUNS = [];\n/** Derived run hierarchy for one studio snapshot. Rebuild when the snapshot is replaced. */\nexport function createStudioRunIndex(runs) {\n const runsById = new Map();\n const childrenByParentId = new Map();\n const parentIdByRunId = new Map();\n for (const run of runs) {\n if (!runsById.has(run.runId))\n runsById.set(run.runId, run);\n const parentRunId = run.parentRunId;\n if (!parentIdByRunId.has(run.runId))\n parentIdByRunId.set(run.runId, parentRunId);\n if (!parentRunId)\n continue;\n const siblings = childrenByParentId.get(parentRunId);\n if (siblings)\n siblings.push(run);\n else\n childrenByParentId.set(parentRunId, [run]);\n }\n for (const siblings of childrenByParentId.values()) {\n siblings.sort((left, right) => right.startedAtMs - left.startedAtMs);\n }\n const roots = [];\n const rootIds = new Set();\n for (const run of runs) {\n const parentRunId = parentIdByRunId.get(run.runId);\n if (parentRunId && runsById.has(parentRunId))\n continue;\n roots.push(run);\n rootIds.add(run.runId);\n }\n const rootIdByRunId = new Map();\n for (const run of runs) {\n if (rootIdByRunId.has(run.runId))\n continue;\n const path = [];\n const onPath = new Set();\n let currentId = run.runId;\n let resolved;\n while (currentId) {\n if (rootIdByRunId.has(currentId)) {\n resolved = rootIdByRunId.get(currentId);\n break;\n }\n if (rootIds.has(currentId)) {\n resolved = currentId;\n break;\n }\n if (onPath.has(currentId)) {\n resolved = undefined;\n break;\n }\n onPath.add(currentId);\n path.push(currentId);\n const parentRunId = parentIdByRunId.get(currentId);\n if (!parentRunId || !runsById.has(parentRunId)) {\n resolved = currentId;\n break;\n }\n currentId = parentRunId;\n }\n if (resolved !== undefined)\n rootIdByRunId.set(resolved, resolved);\n for (const id of path)\n rootIdByRunId.set(id, resolved);\n }\n const descendantCountById = new Map();\n const subtreeRunningById = new Map();\n for (const run of runs) {\n if (descendantCountById.has(run.runId))\n continue;\n const stack = [{ exiting: false, id: run.runId }];\n const visiting = new Set();\n while (stack.length > 0) {\n const frame = stack.pop();\n if (!frame)\n break;\n if (frame.exiting) {\n visiting.delete(frame.id);\n let count = 0;\n let running = runsById.get(frame.id)?.status === \"running\";\n for (const child of childrenByParentId.get(frame.id) ?? EMPTY_STUDIO_RUNS) {\n const childCount = descendantCountById.get(child.runId);\n if (childCount === undefined)\n continue;\n count += 1 + childCount;\n running = running || subtreeRunningById.get(child.runId) === true;\n }\n descendantCountById.set(frame.id, count);\n subtreeRunningById.set(frame.id, running);\n continue;\n }\n if (descendantCountById.has(frame.id) || visiting.has(frame.id))\n continue;\n visiting.add(frame.id);\n stack.push({ exiting: true, id: frame.id });\n for (const child of childrenByParentId.get(frame.id) ?? EMPTY_STUDIO_RUNS) {\n if (!descendantCountById.has(child.runId) && !visiting.has(child.runId)) {\n stack.push({ exiting: false, id: child.runId });\n }\n }\n }\n }\n function runById(runId) {\n return runId == null ? undefined : runsById.get(runId);\n }\n function childrenOf(runId) {\n return childrenByParentId.get(runId) ?? EMPTY_STUDIO_RUNS;\n }\n function ancestorsOf(runId) {\n const ancestors = [];\n const seen = new Set();\n let current = runById(runId);\n while (current) {\n const parentRunId = parentIdByRunId.get(current.runId);\n if (!parentRunId || seen.has(parentRunId))\n break;\n seen.add(parentRunId);\n const parent = runsById.get(parentRunId);\n if (!parent)\n break;\n ancestors.unshift(parent);\n current = parent;\n }\n return ancestors;\n }\n function matchingRootIds(query) {\n const needle = query.toLowerCase();\n const matched = new Set();\n if (!needle) {\n for (const root of roots)\n matched.add(root.runId);\n return matched;\n }\n for (const run of runs) {\n if (!run.pipelineId.toLowerCase().includes(needle) &&\n !run.runId.toLowerCase().includes(needle)) {\n continue;\n }\n const rootId = rootIdByRunId.get(run.runId);\n if (rootId)\n matched.add(rootId);\n }\n return matched;\n }\n return {\n roots,\n ancestorsOf,\n childrenOf,\n descendantCount(runId) {\n return descendantCountById.get(runId) ?? 0;\n },\n matchingRootIds,\n rootRunId(runId) {\n return runId == null ? runId : (rootIdByRunId.get(runId) ?? runId);\n },\n runById,\n subtreeIsRunning(runId) {\n return subtreeRunningById.get(runId) === true;\n },\n };\n}\n/** Browser client for the local studio page. Compiled JS is inlined into the served HTML. */\nexport function initStudio() {\n const state = {\n snapshot: null,\n runIndex: createStudioRunIndex([]),\n detail: null,\n detailFingerprint: null,\n commands: [],\n view: \"runs\",\n selectedRunId: null,\n query: \"\",\n loading: false,\n launching: false,\n planning: false,\n clearing: false,\n cancelling: false,\n canCancel: false,\n canClearHistory: false,\n planVersion: 0,\n };\n const $ = (selector) => {\n const node = document.querySelector(selector);\n if (!(node instanceof HTMLElement))\n throw new Error(\"Missing \" + selector);\n // SAFETY: Studio markup uses HTML elements that expose value/checked/disabled.\n return node;\n };\n const nodes = (selector) => Array.from(document.querySelectorAll(selector), (node) => {\n // SAFETY: Studio event selectors only match HTML elements on this page.\n return node;\n });\n const queryNode = (selector) => {\n const node = document.querySelector(selector);\n if (!(node instanceof HTMLElement))\n return null;\n // SAFETY: Parameter controls in the launch form are HTML inputs, selects, or textareas.\n return node;\n };\n function setConnected(connected) {\n $(\"#connectionLabel\").textContent = connected ? \"Connected · local\" : \"Connection lost\";\n $(\".pulse\").classList.toggle(\"lost\", !connected);\n }\n const esc = (value) => String(value ?? \"\").replace(/[&<>\"']/g, (char) => char === \"&\"\n ? \"&amp;\"\n : char === \"<\"\n ? \"&lt;\"\n : char === \">\"\n ? \"&gt;\"\n : char === '\"'\n ? \"&quot;\"\n : \"&#039;\");\n const statusMark = (value) => value === \"completed\"\n ? \"✓\"\n : value === \"skipped\"\n ? \"×\"\n : value === \"cancelled\"\n ? \"■\"\n : value === \"failed\"\n ? \"!\"\n : value === \"planned\"\n ? \"…\"\n : \"\";\n const status = (value) => '<span class=\"status ' +\n esc(value) +\n '\"><i class=\"status-mark\" aria-hidden=\"true\">' +\n statusMark(value) +\n \"</i>\" +\n esc(value) +\n \"</span>\";\n const duration = (ms) => ms == null\n ? \"—\"\n : ms < 1000\n ? Math.max(0, Math.round(ms)) + \" ms\"\n : ms < 60000\n ? (ms / 1000).toFixed(ms < 10000 ? 1 : 0) + \" s\"\n : Math.floor(ms / 60000) + \"m \" + Math.round((ms % 60000) / 1000) + \"s\";\n const clock = (ms) => {\n if (!Number.isFinite(ms) || Math.abs(ms) > 8.64e15)\n return \"\";\n return new Intl.DateTimeFormat(undefined, {\n hour: \"2-digit\",\n minute: \"2-digit\",\n second: \"2-digit\",\n }).format(ms);\n };\n const dateTime = (ms) => {\n if (!Number.isFinite(ms) || Math.abs(ms) > 8.64e15)\n return \"\";\n return new Intl.DateTimeFormat(undefined, { dateStyle: \"medium\", timeStyle: \"medium\" }).format(ms);\n };\n const isoTime = (ms) => {\n if (!Number.isFinite(ms) || Math.abs(ms) > 8.64e15)\n return \"\";\n return new Date(ms).toISOString();\n };\n const relative = (ms) => {\n const delta = Math.max(0, Date.now() - ms);\n if (delta < 60000)\n return Math.floor(delta / 1000) + \"s ago\";\n if (delta < 3600000)\n return Math.floor(delta / 60000) + \"m ago\";\n if (delta < 86400000)\n return Math.floor(delta / 3600000) + \"h ago\";\n return Math.floor(delta / 86400000) + \"d ago\";\n };\n const shortId = (id) => (id.length > 24 ? id.slice(0, 12) + \"…\" + id.slice(-7) : id);\n function selectedCommand() {\n return state.commands.find((command) => command.id === $(\"#launchCommand\").value);\n }\n function parameterLabel(parameter) {\n return parameter.key\n .replace(/([a-z0-9])([A-Z])/g, \"$1 $2\")\n .replace(/[-_]+/g, \" \")\n .replace(/\\b\\w/g, (letter) => letter.toUpperCase());\n }\n function parameterHint(parameter) {\n const details = [];\n if (parameter.description)\n details.push(parameter.description);\n if (parameter.environment)\n details.push(\"Environment fallback: \" + parameter.environment);\n if (parameter.multiple)\n details.push(parameter.choices ? \"Choose one or more values.\" : \"Enter one value per line.\");\n if (parameter.mustExist)\n details.push(\"Must be an existing \" + (parameter.pathKind || \"path\") + \".\");\n return details.join(\" \");\n }\n function parameterControl(parameter, index, scope) {\n const id = scope + \"-param-\" + index;\n const data = \" data-\" + scope + '-parameter-index=\"' + index + '\"';\n const hint = parameterHint(parameter);\n const labelText = parameterLabel(parameter);\n if (parameter.type === \"boolean\") {\n return ('<label class=\"boolean-field\" for=\"' +\n id +\n '\"><span><strong>' +\n esc(labelText) +\n \"</strong><small>\" +\n esc(hint || \"Enable this option.\") +\n '</small></span><input id=\"' +\n id +\n '\" type=\"checkbox\"' +\n data +\n (parameter.default ? \" checked\" : \"\") +\n \"></label>\");\n }\n const required = parameter.required ? \" required\" : \"\";\n const label = '<label for=\"' +\n id +\n '\">' +\n esc(labelText) +\n (parameter.required ? '<span class=\"required-mark\">required</span>' : \"\") +\n \"</label>\";\n let control;\n if (parameter.choices) {\n const emptyOption = parameter.multiple || parameter.required\n ? \"\"\n : '<option value=\"\">Use default or leave unset</option>';\n const options = parameter.choices\n .map((choice) => '<option value=\"' +\n esc(choice) +\n '\"' +\n (choice === parameter.default ? \" selected\" : \"\") +\n \">\" +\n esc(choice) +\n \"</option>\")\n .join(\"\");\n control =\n '<select id=\"' +\n id +\n '\"' +\n data +\n (parameter.multiple ? \" multiple\" : \"\") +\n required +\n \">\" +\n emptyOption +\n options +\n \"</select>\";\n }\n else if (parameter.multiple) {\n control =\n '<textarea id=\"' +\n id +\n '\"' +\n data +\n ' spellcheck=\"false\" placeholder=\"One value per line\"></textarea>';\n }\n else {\n const inputType = parameter.type === \"number\" ? \"number\" : \"text\";\n const value = parameter.default === undefined ? \"\" : String(parameter.default);\n const constraints = parameter.type === \"number\"\n ? (parameter.integer ? ' step=\"1\"' : ' step=\"any\"') +\n (parameter.min === undefined ? \"\" : ' min=\"' + esc(parameter.min) + '\"') +\n (parameter.max === undefined ? \"\" : ' max=\"' + esc(parameter.max) + '\"')\n : \"\";\n const placeholder = parameter.type === \"path\"\n ? parameter.pathKind === \"directory\"\n ? \"./directory\"\n : \"./file\"\n : \"\";\n control =\n '<input id=\"' +\n id +\n '\" type=\"' +\n inputType +\n '\"' +\n data +\n ' value=\"' +\n esc(value) +\n '\" placeholder=\"' +\n placeholder +\n '\"' +\n constraints +\n required +\n \">\";\n }\n return ('<div class=\"field\">' +\n label +\n control +\n (hint ? '<div class=\"field-hint\">' + esc(hint) + \"</div>\" : \"\") +\n \"</div>\");\n }\n function renderParameters(command, scope, keys) {\n return command.parameters\n .map((parameter, index) => keys.has(parameter.key) ? parameterControl(parameter, index, scope) : \"\")\n .join(\"\");\n }\n function bindExclusiveSelections(command, scope) {\n const fields = command.parameters.flatMap((parameter, index) => {\n if (!parameter.exclusive || !parameter.multiple)\n return [];\n const field = queryNode(\"[data-\" + scope + '-parameter-index=\"' + index + '\"]');\n return field ? [field] : [];\n });\n const clear = (field) => {\n if (field.tagName === \"SELECT\")\n Array.from(field.options).forEach((option) => {\n option.selected = false;\n });\n else\n field.value = \"\";\n };\n fields.forEach((field) => {\n field.addEventListener(\"change\", () => {\n if (field.selectedOptions?.length || field.value) {\n fields.forEach((other) => {\n if (other !== field)\n clear(other);\n });\n }\n });\n });\n }\n function renderCommandForm() {\n const command = selectedCommand();\n $(\"#launchTitle\").textContent = command?.name || \"Run a pipeline\";\n $(\"#launchDescription\").textContent = command\n ? commandDescription(command)\n : \"Choose a declared pipeline and provide its inputs.\";\n if (!command) {\n $(\"#parameterFields\").innerHTML = \"\";\n $(\"#previewPlan\").classList.add(\"hidden\");\n return;\n }\n const domainKeys = new Set(command.parameters\n .filter((parameter) => parameter.group !== \"execution\")\n .map((parameter) => parameter.key));\n const executionKeys = new Set(command.parameters\n .filter((parameter) => parameter.group === \"execution\")\n .map((parameter) => parameter.key));\n const section = (title, note, keys) => keys.size\n ? '<section class=\"form-section\"><div class=\"form-section-head\"><strong>' +\n title +\n \"</strong><span>\" +\n note +\n '</span></div><div class=\"parameter-grid\">' +\n renderParameters(command, \"run\", keys) +\n \"</div></section>\"\n : \"\";\n $(\"#parameterFields\").innerHTML =\n section(\"Pipeline inputs\", domainKeys.size + \" parameter\" + (domainKeys.size === 1 ? \"\" : \"s\"), domainKeys) + section(\"Execution controls\", \"Built into Tubeless\", executionKeys);\n bindExclusiveSelections(command, \"run\");\n $(\"#previewPlan\").classList.toggle(\"hidden\", !command.canPlan);\n invalidatePlan();\n }\n function parameterValues(parameter, index, scope) {\n const control = queryNode(\"[data-\" + scope + '-parameter-index=\"' + index + '\"]');\n if (!control)\n return [];\n if (parameter.type === \"boolean\")\n return [control.checked];\n const rawValues = parameter.multiple\n ? control.tagName === \"SELECT\"\n ? Array.from(control.selectedOptions).map((option) => option.value)\n : control.value\n .split(/\\r?\\n/)\n .map((value) => value.trim())\n .filter(Boolean)\n : [control.value];\n return parameter.type === \"number\"\n ? rawValues.map((value) => (value === \"\" ? value : Number(value)))\n : rawValues;\n }\n function parameterLaunchValues(command) {\n const values = {};\n command.parameters.forEach((parameter, index) => {\n const parameterValue = parameterValues(parameter, index, \"run\");\n if (parameter.type === \"boolean\") {\n const checked = parameterValue[0];\n if (checked !== Boolean(parameter.default))\n values[parameter.key] = checked;\n return;\n }\n if (parameter.multiple) {\n values[parameter.key] =\n parameter.type === \"number\"\n ? parameterValue.filter((value) => typeof value === \"number\")\n : parameterValue.filter((value) => typeof value === \"string\");\n return;\n }\n const single = parameterValue[0];\n if (single !== \"\" && single !== undefined)\n values[parameter.key] = single;\n });\n return values;\n }\n function currentPlanInput(command) {\n const input = {};\n command.parameters.forEach((parameter, index) => {\n if (parameter.group !== \"execution\")\n return;\n const values = parameterValues(parameter, index, \"run\");\n if (parameter.type === \"boolean\") {\n if (values[0] === true)\n input[parameter.key] = true;\n return;\n }\n if (parameter.multiple && values.length) {\n input[parameter.key] = values.filter((value) => typeof value === \"string\");\n }\n });\n // SAFETY: Execution controls are plan selections; the server parser ignores unknown keys.\n return input;\n }\n function invalidatePlan() {\n state.planVersion += 1;\n $(\"#planResult\").classList.add(\"hidden\");\n $(\"#planResult\").innerHTML = \"\";\n }\n function showLaunchError(message) {\n $(\"#launchError\").textContent = message;\n $(\"#launchError\").classList.toggle(\"hidden\", !message);\n }\n function closeLaunch() {\n if (state.launching || state.planning)\n return;\n $(\"#launchModal\").classList.add(\"hidden\");\n showLaunchError(\"\");\n }\n function openLaunch(commandId) {\n if (commandId)\n $(\"#launchCommand\").value = commandId;\n $(\"#launchModal\").classList.remove(\"hidden\");\n renderCommandForm();\n const firstField = $(\"#parameterFields\").querySelector(\"input, select, textarea\");\n if (firstField instanceof HTMLElement)\n firstField.focus();\n }\n function showToast(message) {\n const toast = $(\"#toast\");\n toast.textContent = message;\n toast.classList.remove(\"hidden\");\n setTimeout(() => toast.classList.add(\"hidden\"), 4200);\n }\n function showClearHistoryError(message) {\n $(\"#clearHistoryError\").textContent = message;\n $(\"#clearHistoryError\").classList.toggle(\"hidden\", !message);\n }\n function closeClearHistory() {\n if (state.clearing)\n return;\n $(\"#clearHistoryModal\").classList.add(\"hidden\");\n showClearHistoryError(\"\");\n }\n function openClearHistory() {\n if (!state.canClearHistory)\n return;\n const runCount = state.snapshot?.runs.length ?? 0;\n const eventCount = state.snapshot?.runs.reduce((total, run) => total + run.eventCount, 0) ?? 0;\n const activeRunCount = state.snapshot?.activeRunCount ?? 0;\n $(\"#clearHistoryCopy\").textContent =\n \"Remove \" +\n runCount +\n \" recorded run\" +\n (runCount === 1 ? \"\" : \"s\") +\n \" and \" +\n eventCount +\n \" event\" +\n (eventCount === 1 ? \"\" : \"s\") +\n \" from this SQLite store.\" +\n (activeRunCount\n ? \" \" +\n activeRunCount +\n \" recorded run\" +\n (activeRunCount === 1 ? \" is\" : \"s are\") +\n \" still marked active; continue only if no external process is writing to this store.\"\n : \"\");\n $(\"#clearHistoryModal\").classList.remove(\"hidden\");\n $(\"#confirmClearHistory\").focus();\n }\n async function clearHistory() {\n if (state.clearing)\n return;\n state.clearing = true;\n const button = $(\"#confirmClearHistory\");\n button.disabled = true;\n button.textContent = \"Clearing…\";\n showClearHistoryError(\"\");\n try {\n const response = await fetch(\"/api/history\", {\n method: \"DELETE\",\n headers: { \"x-tubeless-studio-clear-history\": \"1\" },\n });\n const result = await response.json();\n if (!response.ok || !result.cleared)\n throw new Error(result.error || \"History could not be cleared.\");\n state.selectedRunId = null;\n $(\"#clearHistoryModal\").classList.add(\"hidden\");\n showToast(\"Cleared \" + result.eventCount + \" recorded event\" + (result.eventCount === 1 ? \"\" : \"s\"));\n await refresh(true);\n }\n catch (error) {\n showClearHistoryError(error instanceof Error ? error.message || String(error) : String(error));\n }\n finally {\n state.clearing = false;\n button.disabled = false;\n button.textContent = \"Clear history\";\n }\n }\n function metrics(snapshot) {\n const terminal = snapshot.completedRunCount + snapshot.failedRunCount;\n const success = terminal ? Math.round((snapshot.completedRunCount / terminal) * 100) : 0;\n const pipelineCount = state.commands.length || snapshot.definitions.length;\n return [\n [\n \"Active now\",\n snapshot.activeRunCount,\n snapshot.activeRunCount ? \"Live execution in progress\" : \"No work in flight\",\n ],\n [\"Recorded runs\", snapshot.runs.length, \"Append-only local history\"],\n [\"Success rate\", success + \"%\", terminal + \" terminal runs\"],\n [\n \"Pipelines\",\n pipelineCount,\n state.commands.length ? \"Available to configure\" : \"Observed in run history\",\n ],\n ]\n .map(([label, value, note]) => '<article class=\"metric\"><div class=\"metric-label\">' +\n label +\n '</div><div class=\"metric-value\">' +\n value +\n '</div><div class=\"metric-note\">' +\n note +\n \"</div></article>\")\n .join(\"\");\n }\n function empty(title, copy) {\n return ('<div class=\"empty\"><div><div class=\"empty-icon\"><svg width=\"20\" height=\"20\" viewBox=\"0 0 20 20\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.4\"><path d=\"M4 5.5h12M4 10h12M4 14.5h8\"/></svg></div><strong>' +\n esc(title) +\n \"</strong><p>\" +\n esc(copy) +\n \"</p></div></div>\");\n }\n function commandDescription(command) {\n return command.description || \"Run this typed pipeline command.\";\n }\n function commandCatalog(commands) {\n return ('<section class=\"sheet\"><div class=\"sheet-head\"><div><div class=\"sheet-title\">Available pipelines</div><div class=\"sheet-subtitle\">Declared by the local studio manifest</div></div><span class=\"sheet-subtitle\">' +\n commands.length +\n ' shown</span></div><div class=\"catalog\">' +\n commands\n .map((command) => '<article class=\"catalog-card\"><h3>' +\n esc(command.name) +\n \"</h3><p>\" +\n esc(commandDescription(command)) +\n '</p><div class=\"catalog-actions\"><button class=\"primary-button\" data-command-id=\"' +\n esc(command.id) +\n '\">Configure</button></div></article>')\n .join(\"\") +\n \"</div></section>\");\n }\n function runRow(run) {\n const activeStep = run.steps.find((step) => step.status === \"running\");\n const nestedCount = state.runIndex.descendantCount(run.runId);\n const activity = run.status === \"running\"\n ? '<div class=\"run-activity\"><strong>' +\n esc(activeStep?.name || activeStep?.id || \"Starting\") +\n \"</strong><span>\" +\n esc(activeStep?.progress?.message || \"Execution in progress\") +\n \"</span></div>\"\n : \"\";\n return ('<button class=\"run-row ' +\n esc(run.status) +\n \" \" +\n (state.runIndex.rootRunId(state.selectedRunId) === run.runId ? \"selected\" : \"\") +\n '\" data-run-id=\"' +\n esc(run.runId) +\n '\"><div class=\"run-primary\">' +\n status(run.status) +\n \"<strong>\" +\n esc(run.pipelineId) +\n '</strong><time class=\"run-time\" datetime=\"' +\n isoTime(run.startedAtMs) +\n '\" title=\"' +\n esc(dateTime(run.startedAtMs)) +\n '\">' +\n relative(run.startedAtMs) +\n '</time></div><div class=\"run-secondary\"><code>' +\n esc(shortId(run.runId)) +\n '</code><i class=\"dot\"></i><span>' +\n duration(run.durationMs) +\n '</span><i class=\"dot\"></i><span>' +\n run.steps.length +\n \" steps</span>\" +\n (nestedCount\n ? '<i class=\"dot\"></i><span>' +\n nestedCount +\n \" nested run\" +\n (nestedCount === 1 ? \"\" : \"s\") +\n \"</span>\"\n : \"\") +\n (run.dryRun ? '<i class=\"dot\"></i><span>dry run</span>' : \"\") +\n \"</div>\" +\n activity +\n \"</button>\");\n }\n function stepRow(step) {\n const progressTotal = step.progress?.total;\n const progressWidth = progressTotal\n ? Math.max(0, Math.min(100, ((step.progress?.completed ?? 0) / progressTotal) * 100))\n : step.status === \"completed\"\n ? 100\n : 18;\n const execution = step.attempt\n ? '<span class=\"execution-summary\" title=\"' +\n esc(step.attempt.attemptId) +\n '\"><b>Execution</b> · ' +\n esc(shortId(step.attempt.attemptId)) +\n (step.attempt.retries.length\n ? \" · \" +\n step.attempt.retries.length +\n \" retr\" +\n (step.attempt.retries.length === 1 ? \"y\" : \"ies\")\n : \"\") +\n \"</span>\"\n : \"\";\n const nested = step.nestedPipeline;\n const remote = step.remote;\n const nestedCount = nested ? (nested.stepCount ?? nested.stepIds.length) : 0;\n const nestedCountLabel = nested && nested.stepIds.length < nestedCount\n ? nested.stepIds.length + \" of \" + nestedCount + \" declared steps\"\n : nestedCount + \" declared steps\";\n const nestedDetail = nested\n ? '<div class=\"plan-nested\"><strong>' +\n esc(nested.pipelineId) +\n \"</strong><span>\" +\n nestedCountLabel +\n (nested.mode === \"for-each\" ? \" per runtime item\" : \"\") +\n \"</span>\" +\n nested.stepIds.map((stepId) => \"<code>\" + esc(stepId) + \"</code>\").join(\"\") +\n \"</div>\"\n : \"\";\n const remoteDetail = remote\n ? '<div class=\"plan-nested\"><strong>' +\n esc(remote.engine) +\n \"</strong>\" +\n (remote.target ? \"<span>\" + esc(remote.target) + \"</span>\" : \"\") +\n \"</div>\"\n : \"\";\n const detailCount = step.progress?.detailCount;\n const truncatedDetails = detailCount && step.progress?.details && step.progress.details.length < detailCount\n ? '<div class=\"progress-detail-truncated\">Showing ' +\n step.progress.details.length +\n \" of \" +\n detailCount +\n \" items</div>\"\n : \"\";\n const detailRows = step.progress?.details?.length\n ? '<div class=\"progress-details\">' +\n step.progress.details\n .map((detail) => '<div class=\"progress-detail ' +\n esc(detail.status || \"running\") +\n '\"><b>' +\n esc(detail.id) +\n \"</b>\" +\n (detail.label ? \"<span>\" + esc(detail.label) + \"</span>\" : \"\") +\n \"</div>\")\n .join(\"\") +\n truncatedDetails +\n \"</div>\"\n : \"\";\n const progress = step.progress\n ? '<div class=\"progress\"><i class=\"w' +\n Math.round(progressWidth) +\n '\"></i></div><div class=\"progress-copy\">' +\n esc(step.progress.message ||\n step.progress.completed + (progressTotal ? \" / \" + progressTotal : \"\") + \" complete\") +\n \"</div>\" +\n detailRows\n : \"\";\n return ('<article class=\"step ' +\n esc(step.status) +\n '\">' +\n stepStatusIcon(step.status) +\n '<div class=\"step-head\"><strong>' +\n esc(step.name || step.id) +\n \"</strong>\" +\n (step.name ? \"<code>\" + esc(step.id) + \"</code>\" : \"\") +\n '<span class=\"step-duration\">' +\n duration(step.durationMs) +\n \"</span></div>\" +\n (step.description\n ? '<div class=\"step-description\">' + esc(step.description) + \"</div>\"\n : \"\") +\n nestedDetail +\n remoteDetail +\n (execution ? '<div class=\"execution\">' + execution + \"</div>\" : \"\") +\n progress +\n \"</article>\");\n }\n function stepStatusIcon(value) {\n const label = value.charAt(0).toUpperCase() + value.slice(1);\n const icon = value === \"completed\"\n ? '<svg viewBox=\"0 0 12 12\"><path d=\"m2 6 2.4 2.4L10 3\"/></svg>'\n : value === \"running\"\n ? \"<i></i>\"\n : value === \"skipped\"\n ? '<svg viewBox=\"0 0 12 12\"><path d=\"m3 3 6 6M9 3 3 9\"/></svg>'\n : value === \"cancelled\"\n ? '<svg viewBox=\"0 0 12 12\"><rect x=\"3\" y=\"3\" width=\"6\" height=\"6\" rx=\"1\" fill=\"currentColor\" stroke=\"none\"/></svg>'\n : value === \"failed\"\n ? \"!\"\n : \"…\";\n return ('<span class=\"step-status-icon ' +\n esc(value) +\n '\" role=\"img\" aria-label=\"' +\n esc(label) +\n '\" title=\"' +\n esc(label) +\n '\">' +\n icon +\n \"</span>\");\n }\n function stepSummary(run) {\n const labels = {\n running: \"running\",\n failed: \"failed\",\n cancelled: \"cancelled\",\n skipped: \"skipped\",\n completed: \"completed\",\n planned: \"planned\",\n };\n const order = [\"running\", \"failed\", \"cancelled\", \"skipped\", \"completed\", \"planned\"];\n return (order\n .map((value) => [value, run.steps.filter((step) => step.status === value).length])\n .filter(([, count]) => count)\n .map(([value, count]) => count + \" \" + labels[value])\n .join(\" · \") || \"No steps\");\n }\n function runDetail(run) {\n if (!run)\n return ('<div class=\"sheet detail\">' +\n empty(\"Select a run\", \"Choose a run from the history to inspect its steps, retry telemetry, logs, and errors.\") +\n \"</div>\");\n const ancestors = state.runIndex.ancestorsOf(run.runId);\n const parentage = ancestors.length\n ? '<div class=\"run-parentage\">' +\n ancestors\n .map((ancestor) => '<button type=\"button\" data-detail-run-id=\"' +\n esc(ancestor.runId) +\n '\">' +\n esc(ancestor.pipelineId) +\n \"</button><span>/</span>\")\n .join(\"\") +\n \"<span>\" +\n esc(run.pipelineId) +\n \"</span></div>\"\n : \"\";\n const children = state.runIndex.childrenOf(run.runId);\n const nested = children.length\n ? '<div class=\"section-title\"><span>Nested runs</span><span>' +\n children.length +\n \" direct · \" +\n state.runIndex.descendantCount(run.runId) +\n ' total</span></div><div class=\"nested-runs\">' +\n children\n .map((child) => '<button class=\"nested-run\" type=\"button\" data-detail-run-id=\"' +\n esc(child.runId) +\n '\">' +\n status(child.status) +\n \"<strong>\" +\n esc(child.pipelineId) +\n \"</strong><small>\" +\n duration(child.durationMs) +\n \" · \" +\n child.steps.length +\n \" steps\" +\n (state.runIndex.descendantCount(child.runId)\n ? \" · \" + state.runIndex.descendantCount(child.runId) + \" nested\"\n : \"\") +\n \"</small></button>\")\n .join(\"\") +\n \"</div>\"\n : \"\";\n const error = run.error\n ? '<div class=\"section-title\"><span>Error</span></div><div class=\"error-card\"><div class=\"error-code\">' +\n esc(run.error.code) +\n \" · \" +\n esc(run.error.phase) +\n '</div><div class=\"error-message\">' +\n esc(run.error.message) +\n \"</div></div>\"\n : \"\";\n const logs = run.logs.length\n ? '<div class=\"section-title\"><span>Logs</span><span>' +\n run.logs.length +\n '</span></div><div class=\"logs\">' +\n run.logs\n .map((log) => '<div class=\"log-line\"><time class=\"log-time\">' +\n clock(log.timestampMs) +\n '</time><span class=\"log-level ' +\n esc(log.level) +\n '\">' +\n esc(log.level) +\n '</span><span class=\"log-message\">' +\n (log.stepId ? \"<b>\" + esc(log.stepId) + \"</b> · \" : \"\") +\n esc(log.message) +\n \"</span></div>\")\n .join(\"\") +\n \"</div>\"\n : \"\";\n return ('<article class=\"sheet detail\"><div class=\"detail-body\">' +\n parentage +\n '<div class=\"detail-heading\"><div class=\"detail-heading-copy\"><div class=\"detail-kicker\">' +\n (run.parentRunId ? \"Nested run\" : \"Top-level run\") +\n \" · \" +\n relative(run.startedAtMs) +\n \"</div><h2>\" +\n esc(run.pipelineId) +\n '</h2><div class=\"run-id\">' +\n esc(run.runId) +\n '</div></div><div class=\"detail-heading-actions\">' +\n status(run.status) +\n (state.canCancel &&\n run.status === \"running\" &&\n !run.parentRunId &&\n Array.isArray(state.snapshot?.liveRunIds) &&\n state.snapshot.liveRunIds.includes(run.runId)\n ? '<button class=\"danger-button\" type=\"button\" data-cancel-run-id=\"' +\n esc(run.runId) +\n '\"' +\n (state.cancelling ? \" disabled\" : \"\") +\n \">Cancel run</button>\"\n : \"\") +\n '</div></div><div class=\"detail-meta\"><div><label>Started</label><span title=\"' +\n esc(isoTime(run.startedAtMs)) +\n '\">' +\n esc(dateTime(run.startedAtMs)) +\n \"</span></div><div><label>Duration</label><span>\" +\n duration(run.durationMs) +\n \"</span></div><div><label>Steps</label><span>\" +\n run.steps.length +\n \"</span></div><div><label>Events</label><span>\" +\n run.eventCount +\n \"</span></div></div>\" +\n nested +\n '<div class=\"section-title\"><span>Step timeline</span><span>' +\n stepSummary(run) +\n \"</span></div>\" +\n (run.steps.length\n ? '<div class=\"step-list\">' + run.steps.map(stepRow).join(\"\") + \"</div>\"\n : empty(\"No planned steps\", \"This run ended before a step plan was recorded.\")) +\n error +\n logs +\n \"</div></article>\");\n }\n function renderRuns() {\n const matchedRoots = state.query ? state.runIndex.matchingRootIds(state.query) : null;\n const roots = state.runIndex.roots\n .filter((run) => !matchedRoots || matchedRoots.has(run.runId))\n .sort((left, right) => Number(state.runIndex.subtreeIsRunning(right.runId)) -\n Number(state.runIndex.subtreeIsRunning(left.runId)) ||\n right.startedAtMs - left.startedAtMs);\n if (!state.selectedRunId ||\n !roots.some((run) => run.runId === state.runIndex.rootRunId(state.selectedRunId)))\n state.selectedRunId = roots[0]?.runId ?? null;\n const selected = state.detail?.run?.runId === state.selectedRunId ? state.detail.run : null;\n const activeRuns = [];\n const historicalRuns = [];\n for (const root of roots) {\n if (state.runIndex.subtreeIsRunning(root.runId))\n activeRuns.push(root);\n else\n historicalRuns.push(root);\n }\n const activeList = activeRuns.length\n ? '<div class=\"run-group\">Running now · ' +\n activeRuns.length +\n \"</div>\" +\n activeRuns.map(runRow).join(\"\")\n : \"\";\n const historyList = historicalRuns.length\n ? '<div class=\"run-group\">Recent · ' +\n historicalRuns.length +\n \"</div>\" +\n historicalRuns.map(runRow).join(\"\")\n : \"\";\n $(\"#content\").innerHTML =\n '<div class=\"content-grid\"><section class=\"sheet\"><div class=\"sheet-head\"><div><div class=\"sheet-title\">Pipeline runs</div><div class=\"sheet-subtitle\">Top-level runs · nested work stays with its parent</div></div><span class=\"sheet-subtitle\">' +\n roots.length +\n \" top-level · \" +\n (state.snapshot?.runs.length ?? 0) +\n ' total</span></div><div class=\"run-list\">' +\n (roots.length\n ? activeList + historyList\n : empty(\"No recorded runs\", \"Choose Pipelines to start a run and create local history.\")) +\n \"</div></section>\" +\n runDetail(selected) +\n \"</div>\";\n nodes(\"[data-run-id]\").forEach((button) => button.addEventListener(\"click\", () => {\n void selectRun(button.dataset.runId);\n }));\n nodes(\"[data-detail-run-id]\").forEach((button) => button.addEventListener(\"click\", () => {\n void selectRun(button.dataset.detailRunId);\n }));\n nodes(\"[data-cancel-run-id]\").forEach((button) => button.addEventListener(\"click\", (event) => {\n event.stopPropagation();\n void cancelRun(button.dataset.cancelRunId);\n }));\n }\n function renderPipelines() {\n const query = state.query.toLowerCase();\n const commands = state.commands.filter((command) => !query ||\n command.name.toLowerCase().includes(query) ||\n commandDescription(command).toLowerCase().includes(query));\n $(\"#content\").innerHTML = commands.length\n ? commandCatalog(commands)\n : '<div class=\"sheet\">' +\n empty(\"No pipelines found\", \"Try a different pipeline name or description.\") +\n \"</div>\";\n nodes(\"[data-command-id]\").forEach((button) => button.addEventListener(\"click\", () => openLaunch(button.dataset.commandId)));\n }\n function render() {\n if (!state.snapshot)\n return;\n const isPipelines = state.view === \"pipelines\";\n $(\"#pageTitle\").textContent = isPipelines ? \"Pipelines\" : \"Runs\";\n $(\"#pageLede\").textContent = isPipelines\n ? \"Choose a declared workflow to configure and run.\"\n : \"Live work and durable history in one place.\";\n $(\"#search\").placeholder = isPipelines ? \"Filter pipelines\" : \"Filter runs or IDs\";\n $(\"#metrics\").innerHTML = metrics(state.snapshot);\n $(\"#pipelineCount\").textContent = String(state.commands.length);\n $(\"#runCount\").textContent = String(state.snapshot.runs.length);\n $(\"#clearHistoryButton\").disabled = state.snapshot.runs.length === 0;\n $(\"#clearHistoryButton\").title =\n state.snapshot.activeRunCount > 0\n ? \"Clear history, including runs left active by an interrupted process\"\n : \"Clear history\";\n nodes(\"[data-view]\").forEach((button) => button.classList.toggle(\"active\", button.dataset.view === state.view));\n if (isPipelines)\n renderPipelines();\n else\n renderRuns();\n }\n function selectedRunFingerprint() {\n const run = state.runIndex.runById(state.selectedRunId);\n return run ? [run.runId, run.eventCount, run.status].join(\":\") : null;\n }\n async function loadSelectedRunDetail() {\n if (!state.selectedRunId) {\n state.detail = null;\n state.detailFingerprint = null;\n return;\n }\n const requestedRunId = state.selectedRunId;\n const fingerprint = selectedRunFingerprint();\n if (!fingerprint) {\n state.detail = null;\n state.detailFingerprint = null;\n return;\n }\n if (fingerprint === state.detailFingerprint && state.detail)\n return;\n const response = await fetch(\"/api/runs/\" + encodeURIComponent(requestedRunId), {\n cache: \"no-store\",\n });\n if (state.selectedRunId !== requestedRunId)\n return;\n if (response.status === 404) {\n state.detail = null;\n state.detailFingerprint = null;\n return;\n }\n if (!response.ok)\n return;\n // SAFETY: The run detail endpoint returns `{ run }` for the requested id.\n const detail = (await response.json());\n if (state.selectedRunId !== requestedRunId)\n return;\n state.detail = detail;\n state.detailFingerprint = fingerprint;\n }\n async function selectRun(runId) {\n state.selectedRunId = runId ?? null;\n render();\n await loadSelectedRunDetail();\n render();\n }\n async function refresh(manual = false) {\n if (state.loading)\n return;\n state.loading = true;\n if (manual)\n $(\"#refresh\").classList.add(\"spinning\");\n try {\n const response = await fetch(\"/api/snapshot\", { cache: \"no-store\" });\n if (!response.ok)\n throw new Error(\"Snapshot request failed\");\n // SAFETY: The snapshot endpoint returns a studio snapshot with a runs array.\n const snapshot = (await response.json());\n state.snapshot = snapshot;\n state.runIndex = createStudioRunIndex(snapshot.runs);\n render();\n await loadSelectedRunDetail();\n setConnected(true);\n render();\n }\n catch {\n /* Snapshot fetch failed; keep the last rendered run list and mark the connection lost. */\n setConnected(false);\n }\n finally {\n state.loading = false;\n $(\"#refresh\").classList.remove(\"spinning\");\n }\n }\n async function loadCommands() {\n try {\n const response = await fetch(\"/api/commands\", { cache: \"no-store\" });\n if (!response.ok)\n return;\n const payload = await response.json();\n state.commands = Array.isArray(payload.commands) ? payload.commands : [];\n $(\"#pipelineNav\").classList.toggle(\"hidden\", state.commands.length === 0);\n $(\"#launchButton\").classList.toggle(\"hidden\", state.commands.length === 0);\n $(\"#launchCommand\").innerHTML = state.commands\n .map((command) => '<option value=\"' + esc(command.id) + '\">' + esc(command.name) + \"</option>\")\n .join(\"\");\n if (state.commands.length > 0)\n state.view = \"pipelines\";\n renderCommandForm();\n render();\n }\n catch {\n /* Optional studio endpoints; keep the last successful client state. */\n }\n }\n async function loadCapabilities() {\n try {\n const response = await fetch(\"/api/capabilities\", { cache: \"no-store\" });\n if (!response.ok)\n return;\n const capabilities = await response.json();\n state.canClearHistory = capabilities.canClearHistory === true;\n state.canCancel = capabilities.canCancel === true;\n $(\"#clearHistoryButton\").classList.toggle(\"hidden\", !state.canClearHistory);\n render();\n }\n catch {\n /* Optional studio endpoints; keep the last successful client state. */\n }\n }\n function renderPlan(plan) {\n const selected = plan.steps.filter((step) => step.selected && !step.skipReason).length;\n const errors = plan.errors.length\n ? '<div class=\"launch-error\">' +\n plan.errors.map((error) => esc(error.message)).join(\"<br>\") +\n \"</div>\"\n : \"\";\n const steps = plan.steps\n .map((step) => {\n const disposition = !step.selected\n ? \"Not selected\"\n : step.skipReason === \"dry-run\"\n ? \"Dry-run skip\"\n : step.skipReason\n ? \"Skipped\"\n : \"Will run\";\n const detail = step.description ||\n (step.dependencies.length\n ? \"After \" + step.dependencies.join(\", \")\n : \"No required dependencies\");\n const nested = step.nestedPipeline;\n const remote = step.remote;\n const kind = remote\n ? \"Remote step\"\n : nested\n ? nested.mode === \"for-each\"\n ? \"Pipeline fan-out\"\n : \"Nested pipeline\"\n : \"Step\";\n const nestedDetail = nested\n ? '<div class=\"plan-nested\"><strong>' +\n esc(nested.pipelineId) +\n \"</strong><span>\" +\n nested.stepIds.length +\n \" declared steps\" +\n (nested.mode === \"for-each\" ? \" per runtime item\" : \"\") +\n \"</span>\" +\n nested.stepIds.map((stepId) => \"<code>\" + esc(stepId) + \"</code>\").join(\"\") +\n \"</div>\"\n : \"\";\n const remoteDetail = remote\n ? '<div class=\"plan-nested\"><strong>' +\n esc(remote.engine) +\n \"</strong>\" +\n (remote.target ? \"<span>\" + esc(remote.target) + \"</span>\" : \"\") +\n \"</div>\"\n : \"\";\n return ('<div class=\"plan-step\"><div class=\"plan-step-title\"><strong>' +\n esc(step.name || step.id) +\n '</strong><span class=\"plan-kind' +\n (nested ? \" pipeline\" : \"\") +\n '\">' +\n kind +\n \"</span></div><small>\" +\n esc(detail) +\n '</small><span class=\"plan-disposition' +\n (disposition === \"Will run\" ? \"\" : \" skipped\") +\n '\">' +\n disposition +\n \"</span>\" +\n nestedDetail +\n remoteDetail +\n \"</div>\");\n })\n .join(\"\");\n $(\"#planResult\").innerHTML =\n errors +\n '<div class=\"plan-summary\"><strong>' +\n esc(plan.pipelineId) +\n \"</strong><span>\" +\n selected +\n \" of \" +\n plan.steps.length +\n \" steps will run\" +\n (plan.dryRun ? \" · dry run\" : \"\") +\n '</span></div><div class=\"plan-steps\">' +\n steps +\n \"</div>\";\n $(\"#planResult\").classList.remove(\"hidden\");\n }\n async function previewPlan() {\n const command = selectedCommand();\n if (!command?.canPlan || state.planning)\n return;\n state.planning = true;\n const planVersion = state.planVersion;\n $(\"#previewPlan\").disabled = true;\n $(\"#previewPlan\").textContent = \"Planning…\";\n $(\"#submitLaunch\").disabled = true;\n showLaunchError(\"\");\n try {\n const response = await fetch(\"/api/commands/\" + encodeURIComponent(command.id) + \"/plan\", {\n method: \"POST\",\n headers: { \"content-type\": \"application/json\", \"x-tubeless-studio-plan\": \"1\" },\n body: JSON.stringify(currentPlanInput(command)),\n });\n const result = await response.json();\n if (!response.ok || !result.plan)\n throw new Error(result.error || \"Plan request failed.\");\n if (state.planVersion === planVersion)\n renderPlan(result.plan);\n }\n catch (error) {\n showLaunchError(error instanceof Error ? error.message || String(error) : String(error));\n }\n finally {\n state.planning = false;\n $(\"#previewPlan\").disabled = false;\n $(\"#previewPlan\").textContent = \"Preview plan\";\n $(\"#submitLaunch\").disabled = false;\n }\n }\n async function cancelRun(runId) {\n if (!state.canCancel || !runId || state.cancelling)\n return;\n state.cancelling = true;\n render();\n try {\n const response = await fetch(\"/api/runs/\" + encodeURIComponent(runId) + \"/cancel\", {\n method: \"POST\",\n headers: { \"x-tubeless-studio-cancel\": \"1\" },\n });\n const result = await response.json();\n if (!response.ok || !result.cancelled)\n throw new Error(result.error || \"Run could not be cancelled.\");\n showToast(\"Run cancelled · \" + shortId(runId));\n setTimeout(() => refresh(true), 80);\n }\n catch (error) {\n showToast(error instanceof Error ? error.message || String(error) : String(error));\n }\n finally {\n state.cancelling = false;\n render();\n }\n }\n async function launch(event) {\n event.preventDefault();\n const command = selectedCommand();\n if (!command || state.launching)\n return;\n const values = parameterLaunchValues(command);\n state.launching = true;\n $(\"#previewPlan\").disabled = true;\n $(\"#submitLaunch\").disabled = true;\n $(\"#submitLaunch\").textContent = \"Starting…\";\n showLaunchError(\"\");\n try {\n const response = await fetch(\"/api/commands/\" + encodeURIComponent(command.id) + \"/runs\", {\n method: \"POST\",\n headers: { \"content-type\": \"application/json\", \"x-tubeless-studio-launch\": \"1\" },\n body: JSON.stringify({ values }),\n });\n const result = await response.json();\n if (!response.ok || !result.accepted)\n throw new Error((result.errors || [result.error || \"Launch failed.\"]).join(\"\\n\"));\n state.selectedRunId = result.runId;\n state.view = \"runs\";\n $(\"#launchModal\").classList.add(\"hidden\");\n renderCommandForm();\n showToast(\"Run accepted · \" + shortId(result.runId));\n setTimeout(() => refresh(true), 80);\n }\n catch (error) {\n showLaunchError(error instanceof Error ? error.message || String(error) : String(error));\n }\n finally {\n state.launching = false;\n $(\"#previewPlan\").disabled = false;\n $(\"#submitLaunch\").disabled = false;\n $(\"#submitLaunch\").textContent = \"Run\";\n }\n }\n nodes(\"[data-view]\").forEach((button) => button.addEventListener(\"click\", () => {\n state.view = button.dataset.view ?? state.view;\n render();\n }));\n $(\"#search\").addEventListener(\"input\", (event) => {\n const target = event.target;\n if (target instanceof HTMLInputElement) {\n state.query = target.value.trim();\n render();\n }\n });\n $(\"#refresh\").addEventListener(\"click\", () => refresh(true));\n $(\"#launchButton\").addEventListener(\"click\", () => openLaunch());\n $(\"#clearHistoryButton\").addEventListener(\"click\", openClearHistory);\n $(\"#confirmClearHistory\").addEventListener(\"click\", clearHistory);\n $(\"#closeClearHistory\").addEventListener(\"click\", closeClearHistory);\n $(\"#cancelClearHistory\").addEventListener(\"click\", closeClearHistory);\n $(\"#previewPlan\").addEventListener(\"click\", previewPlan);\n $(\"#closeLaunch\").addEventListener(\"click\", closeLaunch);\n $(\"#cancelLaunch\").addEventListener(\"click\", closeLaunch);\n $(\"#launchCommand\").addEventListener(\"change\", renderCommandForm);\n $(\"#runPane\").addEventListener(\"input\", invalidatePlan);\n $(\"#launchForm\").addEventListener(\"submit\", launch);\n $(\"#launchModal\").addEventListener(\"click\", (event) => {\n if (event.target === $(\"#launchModal\"))\n closeLaunch();\n });\n $(\"#clearHistoryModal\").addEventListener(\"click\", (event) => {\n if (event.target === $(\"#clearHistoryModal\"))\n closeClearHistory();\n });\n document.addEventListener(\"keydown\", (event) => {\n if (event.key === \"Escape\") {\n closeLaunch();\n closeClearHistory();\n }\n });\n loadCommands();\n loadCapabilities();\n refresh();\n setInterval(() => refresh(), 1200);\n}\ninitStudio();\n";
@@ -4,7 +4,6 @@ export interface StudioRunIndex {
4
4
  ancestorsOf(runId: string | null | undefined): StoredPipelineRun[];
5
5
  childrenOf(runId: string): readonly StoredPipelineRun[];
6
6
  descendantCount(runId: string): number;
7
- descendantsOf(runId: string): StoredPipelineRun[];
8
7
  matchingRootIds(query: string): ReadonlySet<string>;
9
8
  rootRunId(runId: string | null | undefined): string | null | undefined;
10
9
  runById(runId: string | null | undefined): StoredPipelineRun | undefined;