pixelkiln 0.20.0 → 0.20.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.
package/dist/index.d.cts CHANGED
@@ -54,6 +54,20 @@ interface ProviderSubmission {
54
54
  /** Authoritative provider details known at submission, such as a live quote. */
55
55
  metadata?: ProviderMetadata;
56
56
  }
57
+ /** Durable progress recorded after a provider accepts one part of a submission. */
58
+ interface SubmissionCheckpoint extends ProviderSubmission {
59
+ /** False while more external requests are required for the same asset. */
60
+ complete: boolean;
61
+ }
62
+ /** Optional state for providers that submit one asset through several requests. */
63
+ interface SubmitContext {
64
+ /** Persist accepted remote identity before the provider starts its next request. */
65
+ checkpoint(value: SubmissionCheckpoint): Promise<void>;
66
+ /** Incomplete checkpoint from an earlier run of this exact resolved spec. */
67
+ previousJobId?: string;
68
+ /** Provider-owned details saved with the previous checkpoint. */
69
+ previousMetadata?: ProviderMetadata;
70
+ }
57
71
  interface CandidateSelection {
58
72
  objectId: string;
59
73
  sourceUrl: string | null;
@@ -191,7 +205,7 @@ interface Provider {
191
205
  /** This backend's own submission constraints. Falls back to
192
206
  * `DEFAULT_RATE_LIMIT` when absent — see that constant's doc. */
193
207
  rateLimit?(): RateLimit;
194
- submit(spec: ResolvedSpec, styleImages: ResolvedStyleImage[]): Promise<ProviderSubmission>;
208
+ submit(spec: ResolvedSpec, styleImages: ResolvedStyleImage[], context?: SubmitContext): Promise<ProviderSubmission>;
195
209
  poll(jobId: string, generator: Generator, context?: PollContext): Promise<JobState>;
196
210
  /**
197
211
  * Promote one candidate from a review-status job to a standalone asset.
@@ -3530,6 +3544,8 @@ declare const LockEntrySchema: z.ZodObject<{
3530
3544
  }>, "many">>;
3531
3545
  /** Set at submit time, before the request is awaited, so a crash is recoverable. */
3532
3546
  jobId: z.ZodDefault<z.ZodNullable<z.ZodString>>;
3547
+ /** False only when a provider checkpoint says more requests remain. */
3548
+ submissionComplete: z.ZodOptional<z.ZodBoolean>;
3533
3549
  /** For `1dir`: the multi-candidate parent object awaiting selection. */
3534
3550
  reviewObjectId: z.ZodDefault<z.ZodNullable<z.ZodString>>;
3535
3551
  /** The chosen candidate's own object id, once selected. */
@@ -3636,6 +3652,7 @@ declare const LockEntrySchema: z.ZodObject<{
3636
3652
  role?: string | undefined;
3637
3653
  mediaType?: "image/png" | "image/gif" | undefined;
3638
3654
  }[] | undefined;
3655
+ submissionComplete?: boolean | undefined;
3639
3656
  }, {
3640
3657
  width: number;
3641
3658
  height: number;
@@ -3661,6 +3678,7 @@ declare const LockEntrySchema: z.ZodObject<{
3661
3678
  mediaType?: "image/png" | "image/gif" | undefined;
3662
3679
  }[] | undefined;
3663
3680
  jobId?: string | null | undefined;
3681
+ submissionComplete?: boolean | undefined;
3664
3682
  reviewObjectId?: string | null | undefined;
3665
3683
  objectId?: string | null | undefined;
3666
3684
  candidateIndex?: number | null | undefined;
@@ -3739,6 +3757,8 @@ declare const LockSchema: z.ZodObject<{
3739
3757
  }>, "many">>;
3740
3758
  /** Set at submit time, before the request is awaited, so a crash is recoverable. */
3741
3759
  jobId: z.ZodDefault<z.ZodNullable<z.ZodString>>;
3760
+ /** False only when a provider checkpoint says more requests remain. */
3761
+ submissionComplete: z.ZodOptional<z.ZodBoolean>;
3742
3762
  /** For `1dir`: the multi-candidate parent object awaiting selection. */
3743
3763
  reviewObjectId: z.ZodDefault<z.ZodNullable<z.ZodString>>;
3744
3764
  /** The chosen candidate's own object id, once selected. */
@@ -3845,6 +3865,7 @@ declare const LockSchema: z.ZodObject<{
3845
3865
  role?: string | undefined;
3846
3866
  mediaType?: "image/png" | "image/gif" | undefined;
3847
3867
  }[] | undefined;
3868
+ submissionComplete?: boolean | undefined;
3848
3869
  }, {
3849
3870
  width: number;
3850
3871
  height: number;
@@ -3870,6 +3891,7 @@ declare const LockSchema: z.ZodObject<{
3870
3891
  mediaType?: "image/png" | "image/gif" | undefined;
3871
3892
  }[] | undefined;
3872
3893
  jobId?: string | null | undefined;
3894
+ submissionComplete?: boolean | undefined;
3873
3895
  reviewObjectId?: string | null | undefined;
3874
3896
  objectId?: string | null | undefined;
3875
3897
  candidateIndex?: number | null | undefined;
@@ -3939,6 +3961,7 @@ declare const LockSchema: z.ZodObject<{
3939
3961
  role?: string | undefined;
3940
3962
  mediaType?: "image/png" | "image/gif" | undefined;
3941
3963
  }[] | undefined;
3964
+ submissionComplete?: boolean | undefined;
3942
3965
  }>;
3943
3966
  version: 2;
3944
3967
  }, {
@@ -3967,6 +3990,7 @@ declare const LockSchema: z.ZodObject<{
3967
3990
  mediaType?: "image/png" | "image/gif" | undefined;
3968
3991
  }[] | undefined;
3969
3992
  jobId?: string | null | undefined;
3993
+ submissionComplete?: boolean | undefined;
3970
3994
  reviewObjectId?: string | null | undefined;
3971
3995
  objectId?: string | null | undefined;
3972
3996
  candidateIndex?: number | null | undefined;
@@ -4526,7 +4550,7 @@ declare class ComfyUIProvider implements Provider {
4526
4550
  estimate(spec: ResolvedSpec): CostEstimate;
4527
4551
  validate(spec: ResolvedSpec, styleImages: ResolvedStyleImage[]): void;
4528
4552
  rateLimit(): RateLimit;
4529
- submit(spec: ResolvedSpec, styleImages: ResolvedStyleImage[]): Promise<ProviderSubmission>;
4553
+ submit(spec: ResolvedSpec, styleImages: ResolvedStyleImage[], context?: SubmitContext): Promise<ProviderSubmission>;
4530
4554
  poll(jobId: string, generator: Generator, context?: PollContext): Promise<JobState>;
4531
4555
  selectCandidate(jobId: string, index: number): Promise<{
4532
4556
  objectId: string;
@@ -7655,4 +7679,4 @@ interface WorkspaceStatusReport {
7655
7679
  */
7656
7680
  declare function workspaceStatus(ws: Workspace, dir: string): Promise<WorkspaceStatusReport>;
7657
7681
 
7658
- export { type AdoptResult, type ApproveQualityOptions, type ApprovedQualityReview, type ArtifactBundleManifest, type ArtifactBundleOptions, type ArtifactBundleResult, type ArtifactFile, type ArtifactProvenance, type ArtifactSource, type ArtifactVerification, type Asset, type AssetAudit, AssetSchema, type AuditEvaluation, type AuditThresholds, type AuditViolation, type Balance, type BalanceChange, type BalanceInfo, type CacheHealthOptions, type CacheHealthReport, type CandidateSelection, type ComfyUIBinding, type ComfyUIBindings, type ComfyUIFramesOptions, type ComfyUIImageInput, type ComfyUIOptions, ComfyUIProvider, type ContentCacheHealth, type ContentCacheIssue, type CostEstimate, type CostUnit, DEFAULT_QUALITY_TOLERANCES, DEFAULT_RATE_LIMIT, type DoctorCheck, type DoctorLevel, type DoctorOptions, type DoctorReport, FAKE_PNG, type FakeOptions, FakeProvider, type FetchResult, type Generator, GeneratorSchema, type GenericTileset, type GridConfidence, GridConfidenceSchema, type ImageQualityMetrics, ImageQualityMetricsSchema, InheritedStyleSchema, type InstallRecipeOptions, type InstallRecipeResult, type JobState, type LoadedManifest, type LoadedRecipe, type Lock, type LockEntry, LockEntrySchema, type LockOutput, LockSchema, MAX_DOWNLOAD_BYTES, MAX_RETRIES, type ManagedArtifactBundleOptions, type Manifest, ManifestInputSchema, ManifestSchema, type MapObject, MediaType, type MountPlacement, type MountedSheet, type NormalizedTileRules, type Orphan, type OrphanGroups, type OutputSelection, type OutputSource, PIXEL_ART_FIXER_REVISION, PIXEL_ART_FIXER_URL, type PackedFrame, type PackedSheet, type PackedSource, type PendingQualityReview, type PickResult, type PixelArtFixerDetection, PixelLabClient, PixelLabError, type PixelLabObject, PixelLabProvider, type Plan, type PlanItem, type PlanState, type PollContext, type PollOptions, type PollResult, type Provider, type ProviderFactory, type ProviderInputContext, type ProviderInputScalar, type ProviderInputValue, type ProviderMetadata, type ProviderMode, type ProviderOptionContext, type ProviderSubmission, type QualityBaseline, QualityBaselineSchema, type QualityCheck, type QualityProfile, type QualityProfileInspection, QualityProfileSchema, type QualityProfileState, type QualityRegressionCaseResult, type QualityRegressionReport, type QualitySnapshotResult, QualityToleranceSchema, type QualityTolerances, type RateLimit, type Recipe, type RecipeFileVerification, type RecipeModelVerification, RecipeSchema, type RecipeVerification, type RefineAudit, type RefineFrameSetOptions, type RefineFrameSetResult, type RefineOptions, type RefineQualityProfilesOptions, type RefineQualityProfilesResult, type RefineRecordOptions, type RefineResult, type RemoteAsset, type RemoteHashCacheHealth, type ResolvedOutput, type ResolvedProviderInputs, type ResolvedProviderOptions, type ResolvedQualityInput, type ResolvedQualityProfile, type ResolvedRevision, type ResolvedSpec, type ResolvedStyleImage, type ResumeAction, type ResumeCommand, type RetroDiffusionOptions, RetroDiffusionProvider, type ReviewReadyInfo, type Revision, type RevisionMode, RevisionModeSchema, type RevisionReadiness, RevisionSchema, type SalvageAction, type SalvageResult, type ScannedAsset, type ScenarioOptions, ScenarioProvider, type SheetGroup, type SiblingManifest, type SpriteInput, type Style, type StyleAudit, type StyleInput, StyleInputSchema, StyleSchema, type SubmitOptions, type SubmitResult, type TilesPro, type TilesetExport, type TilesetExportOptions, type TilesetFormat, UnsupportedCapabilityError, type Workspace, type WorkspaceClaims, type WorkspaceDiagnostic, type WorkspaceProject, WorkspaceProjectSchema, type WorkspaceProjectStatus, WorkspaceSchema, type WorkspaceStatusReport, adopt, applyTags, approveQualityRecord, auditStyle, availableProviders, backoffMs, buildManifest, buildPlan, bundledRecipeRoot, cacheFileName, candidateCount, checkQualityBaseline, checkQualityRecord, clientFromEnv, colorDistance, countNumberedDescriptions, createArtifactBundleManifest, createProvider, currentEntryOutputPath, currentOutputPath, detectMediaType, doctor, evaluateAudit, expectedOutputPath, exportTileset, fallbackOutputRole, fetchAssets, findOrphans, formatCost, generationCost, groupOrphansByStyle, hex, idFromPrompt, imageMetadata, inspectCaches, inspectQualityProfile, inspectRevisionReadiness, installRecipe, isSensitiveSourceUrl, listBundledRecipes, loadClaims, loadLock, loadManifest, loadSiblingManifests, loadWorkspace, lockKey, matchOrphanStyle, measureBalanceChange, measureDecodedImageQuality, measureImageQuality, mediaExtension, mediaTypeFromExtension, mergePalettes, mountSprites, mountStyle, normalizeLockOutputPaths, normalizeRefinementPalette, normalizeTileRules, outliers, outputId, packSprites, packStyle, paletteDistance, parseLock, parseWorkspace, pngSize, poll, portableOutputPath, primaryOutput, providerCredentialEnvs, providerFactory, pushTags, qualityFrameOutputPath, qualityRecordPath, quantizeToPalette, readArtifactBundleManifest, readQualityBaseline, recipeDigest, refineAsset, refineFrameSet, refineQualityProfiles, registerProvider, remove, renderSalvageSheet, renderSheet, requireApprovedQualitySources, requireBalance, requireDelete, requireList, requireRevisionReady, requireSelectCandidate, resolveEntryOutputs, resolveOutputPath, resolvePackInputs, resolveProject, resolveQualityInputs, resolveRecipe, resolveSpecEntryOutputs, resolveSpecOutputs, resolveSpecs, resolveStyleImages, resolveStyleOutputs, resumeActions, resumeCommandForStatus, retryAfterMs, runPicker, runSalvage, saveLock, saveWorkspace, scanAssets, selectEntryOutput, sha256, sha256File, shouldPersistSourceUrl, shouldRetry, slugify, snapshotQualityBaseline, specHash, spendByUnit, styleImagesBase64, submit, summarize, tagAdopted, tileFeatureOutputCount, tileVariationCount, tilesCost, toPortablePath, totalSpend, upsert, validateCostEstimate, validateMedia, validateWorkspace, verifyArtifactBundle, verifyRecipe, withArtifactManifest, workspaceClaims, workspaceStatus, writeArtifactBundle, writeManagedArtifactBundle };
7682
+ export { type AdoptResult, type ApproveQualityOptions, type ApprovedQualityReview, type ArtifactBundleManifest, type ArtifactBundleOptions, type ArtifactBundleResult, type ArtifactFile, type ArtifactProvenance, type ArtifactSource, type ArtifactVerification, type Asset, type AssetAudit, AssetSchema, type AuditEvaluation, type AuditThresholds, type AuditViolation, type Balance, type BalanceChange, type BalanceInfo, type CacheHealthOptions, type CacheHealthReport, type CandidateSelection, type ComfyUIBinding, type ComfyUIBindings, type ComfyUIFramesOptions, type ComfyUIImageInput, type ComfyUIOptions, ComfyUIProvider, type ContentCacheHealth, type ContentCacheIssue, type CostEstimate, type CostUnit, DEFAULT_QUALITY_TOLERANCES, DEFAULT_RATE_LIMIT, type DoctorCheck, type DoctorLevel, type DoctorOptions, type DoctorReport, FAKE_PNG, type FakeOptions, FakeProvider, type FetchResult, type Generator, GeneratorSchema, type GenericTileset, type GridConfidence, GridConfidenceSchema, type ImageQualityMetrics, ImageQualityMetricsSchema, InheritedStyleSchema, type InstallRecipeOptions, type InstallRecipeResult, type JobState, type LoadedManifest, type LoadedRecipe, type Lock, type LockEntry, LockEntrySchema, type LockOutput, LockSchema, MAX_DOWNLOAD_BYTES, MAX_RETRIES, type ManagedArtifactBundleOptions, type Manifest, ManifestInputSchema, ManifestSchema, type MapObject, MediaType, type MountPlacement, type MountedSheet, type NormalizedTileRules, type Orphan, type OrphanGroups, type OutputSelection, type OutputSource, PIXEL_ART_FIXER_REVISION, PIXEL_ART_FIXER_URL, type PackedFrame, type PackedSheet, type PackedSource, type PendingQualityReview, type PickResult, type PixelArtFixerDetection, PixelLabClient, PixelLabError, type PixelLabObject, PixelLabProvider, type Plan, type PlanItem, type PlanState, type PollContext, type PollOptions, type PollResult, type Provider, type ProviderFactory, type ProviderInputContext, type ProviderInputScalar, type ProviderInputValue, type ProviderMetadata, type ProviderMode, type ProviderOptionContext, type ProviderSubmission, type QualityBaseline, QualityBaselineSchema, type QualityCheck, type QualityProfile, type QualityProfileInspection, QualityProfileSchema, type QualityProfileState, type QualityRegressionCaseResult, type QualityRegressionReport, type QualitySnapshotResult, QualityToleranceSchema, type QualityTolerances, type RateLimit, type Recipe, type RecipeFileVerification, type RecipeModelVerification, RecipeSchema, type RecipeVerification, type RefineAudit, type RefineFrameSetOptions, type RefineFrameSetResult, type RefineOptions, type RefineQualityProfilesOptions, type RefineQualityProfilesResult, type RefineRecordOptions, type RefineResult, type RemoteAsset, type RemoteHashCacheHealth, type ResolvedOutput, type ResolvedProviderInputs, type ResolvedProviderOptions, type ResolvedQualityInput, type ResolvedQualityProfile, type ResolvedRevision, type ResolvedSpec, type ResolvedStyleImage, type ResumeAction, type ResumeCommand, type RetroDiffusionOptions, RetroDiffusionProvider, type ReviewReadyInfo, type Revision, type RevisionMode, RevisionModeSchema, type RevisionReadiness, RevisionSchema, type SalvageAction, type SalvageResult, type ScannedAsset, type ScenarioOptions, ScenarioProvider, type SheetGroup, type SiblingManifest, type SpriteInput, type Style, type StyleAudit, type StyleInput, StyleInputSchema, StyleSchema, type SubmissionCheckpoint, type SubmitContext, type SubmitOptions, type SubmitResult, type TilesPro, type TilesetExport, type TilesetExportOptions, type TilesetFormat, UnsupportedCapabilityError, type Workspace, type WorkspaceClaims, type WorkspaceDiagnostic, type WorkspaceProject, WorkspaceProjectSchema, type WorkspaceProjectStatus, WorkspaceSchema, type WorkspaceStatusReport, adopt, applyTags, approveQualityRecord, auditStyle, availableProviders, backoffMs, buildManifest, buildPlan, bundledRecipeRoot, cacheFileName, candidateCount, checkQualityBaseline, checkQualityRecord, clientFromEnv, colorDistance, countNumberedDescriptions, createArtifactBundleManifest, createProvider, currentEntryOutputPath, currentOutputPath, detectMediaType, doctor, evaluateAudit, expectedOutputPath, exportTileset, fallbackOutputRole, fetchAssets, findOrphans, formatCost, generationCost, groupOrphansByStyle, hex, idFromPrompt, imageMetadata, inspectCaches, inspectQualityProfile, inspectRevisionReadiness, installRecipe, isSensitiveSourceUrl, listBundledRecipes, loadClaims, loadLock, loadManifest, loadSiblingManifests, loadWorkspace, lockKey, matchOrphanStyle, measureBalanceChange, measureDecodedImageQuality, measureImageQuality, mediaExtension, mediaTypeFromExtension, mergePalettes, mountSprites, mountStyle, normalizeLockOutputPaths, normalizeRefinementPalette, normalizeTileRules, outliers, outputId, packSprites, packStyle, paletteDistance, parseLock, parseWorkspace, pngSize, poll, portableOutputPath, primaryOutput, providerCredentialEnvs, providerFactory, pushTags, qualityFrameOutputPath, qualityRecordPath, quantizeToPalette, readArtifactBundleManifest, readQualityBaseline, recipeDigest, refineAsset, refineFrameSet, refineQualityProfiles, registerProvider, remove, renderSalvageSheet, renderSheet, requireApprovedQualitySources, requireBalance, requireDelete, requireList, requireRevisionReady, requireSelectCandidate, resolveEntryOutputs, resolveOutputPath, resolvePackInputs, resolveProject, resolveQualityInputs, resolveRecipe, resolveSpecEntryOutputs, resolveSpecOutputs, resolveSpecs, resolveStyleImages, resolveStyleOutputs, resumeActions, resumeCommandForStatus, retryAfterMs, runPicker, runSalvage, saveLock, saveWorkspace, scanAssets, selectEntryOutput, sha256, sha256File, shouldPersistSourceUrl, shouldRetry, slugify, snapshotQualityBaseline, specHash, spendByUnit, styleImagesBase64, submit, summarize, tagAdopted, tileFeatureOutputCount, tileVariationCount, tilesCost, toPortablePath, totalSpend, upsert, validateCostEstimate, validateMedia, validateWorkspace, verifyArtifactBundle, verifyRecipe, withArtifactManifest, workspaceClaims, workspaceStatus, writeArtifactBundle, writeManagedArtifactBundle };
package/dist/index.d.ts CHANGED
@@ -54,6 +54,20 @@ interface ProviderSubmission {
54
54
  /** Authoritative provider details known at submission, such as a live quote. */
55
55
  metadata?: ProviderMetadata;
56
56
  }
57
+ /** Durable progress recorded after a provider accepts one part of a submission. */
58
+ interface SubmissionCheckpoint extends ProviderSubmission {
59
+ /** False while more external requests are required for the same asset. */
60
+ complete: boolean;
61
+ }
62
+ /** Optional state for providers that submit one asset through several requests. */
63
+ interface SubmitContext {
64
+ /** Persist accepted remote identity before the provider starts its next request. */
65
+ checkpoint(value: SubmissionCheckpoint): Promise<void>;
66
+ /** Incomplete checkpoint from an earlier run of this exact resolved spec. */
67
+ previousJobId?: string;
68
+ /** Provider-owned details saved with the previous checkpoint. */
69
+ previousMetadata?: ProviderMetadata;
70
+ }
57
71
  interface CandidateSelection {
58
72
  objectId: string;
59
73
  sourceUrl: string | null;
@@ -191,7 +205,7 @@ interface Provider {
191
205
  /** This backend's own submission constraints. Falls back to
192
206
  * `DEFAULT_RATE_LIMIT` when absent — see that constant's doc. */
193
207
  rateLimit?(): RateLimit;
194
- submit(spec: ResolvedSpec, styleImages: ResolvedStyleImage[]): Promise<ProviderSubmission>;
208
+ submit(spec: ResolvedSpec, styleImages: ResolvedStyleImage[], context?: SubmitContext): Promise<ProviderSubmission>;
195
209
  poll(jobId: string, generator: Generator, context?: PollContext): Promise<JobState>;
196
210
  /**
197
211
  * Promote one candidate from a review-status job to a standalone asset.
@@ -3530,6 +3544,8 @@ declare const LockEntrySchema: z.ZodObject<{
3530
3544
  }>, "many">>;
3531
3545
  /** Set at submit time, before the request is awaited, so a crash is recoverable. */
3532
3546
  jobId: z.ZodDefault<z.ZodNullable<z.ZodString>>;
3547
+ /** False only when a provider checkpoint says more requests remain. */
3548
+ submissionComplete: z.ZodOptional<z.ZodBoolean>;
3533
3549
  /** For `1dir`: the multi-candidate parent object awaiting selection. */
3534
3550
  reviewObjectId: z.ZodDefault<z.ZodNullable<z.ZodString>>;
3535
3551
  /** The chosen candidate's own object id, once selected. */
@@ -3636,6 +3652,7 @@ declare const LockEntrySchema: z.ZodObject<{
3636
3652
  role?: string | undefined;
3637
3653
  mediaType?: "image/png" | "image/gif" | undefined;
3638
3654
  }[] | undefined;
3655
+ submissionComplete?: boolean | undefined;
3639
3656
  }, {
3640
3657
  width: number;
3641
3658
  height: number;
@@ -3661,6 +3678,7 @@ declare const LockEntrySchema: z.ZodObject<{
3661
3678
  mediaType?: "image/png" | "image/gif" | undefined;
3662
3679
  }[] | undefined;
3663
3680
  jobId?: string | null | undefined;
3681
+ submissionComplete?: boolean | undefined;
3664
3682
  reviewObjectId?: string | null | undefined;
3665
3683
  objectId?: string | null | undefined;
3666
3684
  candidateIndex?: number | null | undefined;
@@ -3739,6 +3757,8 @@ declare const LockSchema: z.ZodObject<{
3739
3757
  }>, "many">>;
3740
3758
  /** Set at submit time, before the request is awaited, so a crash is recoverable. */
3741
3759
  jobId: z.ZodDefault<z.ZodNullable<z.ZodString>>;
3760
+ /** False only when a provider checkpoint says more requests remain. */
3761
+ submissionComplete: z.ZodOptional<z.ZodBoolean>;
3742
3762
  /** For `1dir`: the multi-candidate parent object awaiting selection. */
3743
3763
  reviewObjectId: z.ZodDefault<z.ZodNullable<z.ZodString>>;
3744
3764
  /** The chosen candidate's own object id, once selected. */
@@ -3845,6 +3865,7 @@ declare const LockSchema: z.ZodObject<{
3845
3865
  role?: string | undefined;
3846
3866
  mediaType?: "image/png" | "image/gif" | undefined;
3847
3867
  }[] | undefined;
3868
+ submissionComplete?: boolean | undefined;
3848
3869
  }, {
3849
3870
  width: number;
3850
3871
  height: number;
@@ -3870,6 +3891,7 @@ declare const LockSchema: z.ZodObject<{
3870
3891
  mediaType?: "image/png" | "image/gif" | undefined;
3871
3892
  }[] | undefined;
3872
3893
  jobId?: string | null | undefined;
3894
+ submissionComplete?: boolean | undefined;
3873
3895
  reviewObjectId?: string | null | undefined;
3874
3896
  objectId?: string | null | undefined;
3875
3897
  candidateIndex?: number | null | undefined;
@@ -3939,6 +3961,7 @@ declare const LockSchema: z.ZodObject<{
3939
3961
  role?: string | undefined;
3940
3962
  mediaType?: "image/png" | "image/gif" | undefined;
3941
3963
  }[] | undefined;
3964
+ submissionComplete?: boolean | undefined;
3942
3965
  }>;
3943
3966
  version: 2;
3944
3967
  }, {
@@ -3967,6 +3990,7 @@ declare const LockSchema: z.ZodObject<{
3967
3990
  mediaType?: "image/png" | "image/gif" | undefined;
3968
3991
  }[] | undefined;
3969
3992
  jobId?: string | null | undefined;
3993
+ submissionComplete?: boolean | undefined;
3970
3994
  reviewObjectId?: string | null | undefined;
3971
3995
  objectId?: string | null | undefined;
3972
3996
  candidateIndex?: number | null | undefined;
@@ -4526,7 +4550,7 @@ declare class ComfyUIProvider implements Provider {
4526
4550
  estimate(spec: ResolvedSpec): CostEstimate;
4527
4551
  validate(spec: ResolvedSpec, styleImages: ResolvedStyleImage[]): void;
4528
4552
  rateLimit(): RateLimit;
4529
- submit(spec: ResolvedSpec, styleImages: ResolvedStyleImage[]): Promise<ProviderSubmission>;
4553
+ submit(spec: ResolvedSpec, styleImages: ResolvedStyleImage[], context?: SubmitContext): Promise<ProviderSubmission>;
4530
4554
  poll(jobId: string, generator: Generator, context?: PollContext): Promise<JobState>;
4531
4555
  selectCandidate(jobId: string, index: number): Promise<{
4532
4556
  objectId: string;
@@ -7655,4 +7679,4 @@ interface WorkspaceStatusReport {
7655
7679
  */
7656
7680
  declare function workspaceStatus(ws: Workspace, dir: string): Promise<WorkspaceStatusReport>;
7657
7681
 
7658
- export { type AdoptResult, type ApproveQualityOptions, type ApprovedQualityReview, type ArtifactBundleManifest, type ArtifactBundleOptions, type ArtifactBundleResult, type ArtifactFile, type ArtifactProvenance, type ArtifactSource, type ArtifactVerification, type Asset, type AssetAudit, AssetSchema, type AuditEvaluation, type AuditThresholds, type AuditViolation, type Balance, type BalanceChange, type BalanceInfo, type CacheHealthOptions, type CacheHealthReport, type CandidateSelection, type ComfyUIBinding, type ComfyUIBindings, type ComfyUIFramesOptions, type ComfyUIImageInput, type ComfyUIOptions, ComfyUIProvider, type ContentCacheHealth, type ContentCacheIssue, type CostEstimate, type CostUnit, DEFAULT_QUALITY_TOLERANCES, DEFAULT_RATE_LIMIT, type DoctorCheck, type DoctorLevel, type DoctorOptions, type DoctorReport, FAKE_PNG, type FakeOptions, FakeProvider, type FetchResult, type Generator, GeneratorSchema, type GenericTileset, type GridConfidence, GridConfidenceSchema, type ImageQualityMetrics, ImageQualityMetricsSchema, InheritedStyleSchema, type InstallRecipeOptions, type InstallRecipeResult, type JobState, type LoadedManifest, type LoadedRecipe, type Lock, type LockEntry, LockEntrySchema, type LockOutput, LockSchema, MAX_DOWNLOAD_BYTES, MAX_RETRIES, type ManagedArtifactBundleOptions, type Manifest, ManifestInputSchema, ManifestSchema, type MapObject, MediaType, type MountPlacement, type MountedSheet, type NormalizedTileRules, type Orphan, type OrphanGroups, type OutputSelection, type OutputSource, PIXEL_ART_FIXER_REVISION, PIXEL_ART_FIXER_URL, type PackedFrame, type PackedSheet, type PackedSource, type PendingQualityReview, type PickResult, type PixelArtFixerDetection, PixelLabClient, PixelLabError, type PixelLabObject, PixelLabProvider, type Plan, type PlanItem, type PlanState, type PollContext, type PollOptions, type PollResult, type Provider, type ProviderFactory, type ProviderInputContext, type ProviderInputScalar, type ProviderInputValue, type ProviderMetadata, type ProviderMode, type ProviderOptionContext, type ProviderSubmission, type QualityBaseline, QualityBaselineSchema, type QualityCheck, type QualityProfile, type QualityProfileInspection, QualityProfileSchema, type QualityProfileState, type QualityRegressionCaseResult, type QualityRegressionReport, type QualitySnapshotResult, QualityToleranceSchema, type QualityTolerances, type RateLimit, type Recipe, type RecipeFileVerification, type RecipeModelVerification, RecipeSchema, type RecipeVerification, type RefineAudit, type RefineFrameSetOptions, type RefineFrameSetResult, type RefineOptions, type RefineQualityProfilesOptions, type RefineQualityProfilesResult, type RefineRecordOptions, type RefineResult, type RemoteAsset, type RemoteHashCacheHealth, type ResolvedOutput, type ResolvedProviderInputs, type ResolvedProviderOptions, type ResolvedQualityInput, type ResolvedQualityProfile, type ResolvedRevision, type ResolvedSpec, type ResolvedStyleImage, type ResumeAction, type ResumeCommand, type RetroDiffusionOptions, RetroDiffusionProvider, type ReviewReadyInfo, type Revision, type RevisionMode, RevisionModeSchema, type RevisionReadiness, RevisionSchema, type SalvageAction, type SalvageResult, type ScannedAsset, type ScenarioOptions, ScenarioProvider, type SheetGroup, type SiblingManifest, type SpriteInput, type Style, type StyleAudit, type StyleInput, StyleInputSchema, StyleSchema, type SubmitOptions, type SubmitResult, type TilesPro, type TilesetExport, type TilesetExportOptions, type TilesetFormat, UnsupportedCapabilityError, type Workspace, type WorkspaceClaims, type WorkspaceDiagnostic, type WorkspaceProject, WorkspaceProjectSchema, type WorkspaceProjectStatus, WorkspaceSchema, type WorkspaceStatusReport, adopt, applyTags, approveQualityRecord, auditStyle, availableProviders, backoffMs, buildManifest, buildPlan, bundledRecipeRoot, cacheFileName, candidateCount, checkQualityBaseline, checkQualityRecord, clientFromEnv, colorDistance, countNumberedDescriptions, createArtifactBundleManifest, createProvider, currentEntryOutputPath, currentOutputPath, detectMediaType, doctor, evaluateAudit, expectedOutputPath, exportTileset, fallbackOutputRole, fetchAssets, findOrphans, formatCost, generationCost, groupOrphansByStyle, hex, idFromPrompt, imageMetadata, inspectCaches, inspectQualityProfile, inspectRevisionReadiness, installRecipe, isSensitiveSourceUrl, listBundledRecipes, loadClaims, loadLock, loadManifest, loadSiblingManifests, loadWorkspace, lockKey, matchOrphanStyle, measureBalanceChange, measureDecodedImageQuality, measureImageQuality, mediaExtension, mediaTypeFromExtension, mergePalettes, mountSprites, mountStyle, normalizeLockOutputPaths, normalizeRefinementPalette, normalizeTileRules, outliers, outputId, packSprites, packStyle, paletteDistance, parseLock, parseWorkspace, pngSize, poll, portableOutputPath, primaryOutput, providerCredentialEnvs, providerFactory, pushTags, qualityFrameOutputPath, qualityRecordPath, quantizeToPalette, readArtifactBundleManifest, readQualityBaseline, recipeDigest, refineAsset, refineFrameSet, refineQualityProfiles, registerProvider, remove, renderSalvageSheet, renderSheet, requireApprovedQualitySources, requireBalance, requireDelete, requireList, requireRevisionReady, requireSelectCandidate, resolveEntryOutputs, resolveOutputPath, resolvePackInputs, resolveProject, resolveQualityInputs, resolveRecipe, resolveSpecEntryOutputs, resolveSpecOutputs, resolveSpecs, resolveStyleImages, resolveStyleOutputs, resumeActions, resumeCommandForStatus, retryAfterMs, runPicker, runSalvage, saveLock, saveWorkspace, scanAssets, selectEntryOutput, sha256, sha256File, shouldPersistSourceUrl, shouldRetry, slugify, snapshotQualityBaseline, specHash, spendByUnit, styleImagesBase64, submit, summarize, tagAdopted, tileFeatureOutputCount, tileVariationCount, tilesCost, toPortablePath, totalSpend, upsert, validateCostEstimate, validateMedia, validateWorkspace, verifyArtifactBundle, verifyRecipe, withArtifactManifest, workspaceClaims, workspaceStatus, writeArtifactBundle, writeManagedArtifactBundle };
7682
+ export { type AdoptResult, type ApproveQualityOptions, type ApprovedQualityReview, type ArtifactBundleManifest, type ArtifactBundleOptions, type ArtifactBundleResult, type ArtifactFile, type ArtifactProvenance, type ArtifactSource, type ArtifactVerification, type Asset, type AssetAudit, AssetSchema, type AuditEvaluation, type AuditThresholds, type AuditViolation, type Balance, type BalanceChange, type BalanceInfo, type CacheHealthOptions, type CacheHealthReport, type CandidateSelection, type ComfyUIBinding, type ComfyUIBindings, type ComfyUIFramesOptions, type ComfyUIImageInput, type ComfyUIOptions, ComfyUIProvider, type ContentCacheHealth, type ContentCacheIssue, type CostEstimate, type CostUnit, DEFAULT_QUALITY_TOLERANCES, DEFAULT_RATE_LIMIT, type DoctorCheck, type DoctorLevel, type DoctorOptions, type DoctorReport, FAKE_PNG, type FakeOptions, FakeProvider, type FetchResult, type Generator, GeneratorSchema, type GenericTileset, type GridConfidence, GridConfidenceSchema, type ImageQualityMetrics, ImageQualityMetricsSchema, InheritedStyleSchema, type InstallRecipeOptions, type InstallRecipeResult, type JobState, type LoadedManifest, type LoadedRecipe, type Lock, type LockEntry, LockEntrySchema, type LockOutput, LockSchema, MAX_DOWNLOAD_BYTES, MAX_RETRIES, type ManagedArtifactBundleOptions, type Manifest, ManifestInputSchema, ManifestSchema, type MapObject, MediaType, type MountPlacement, type MountedSheet, type NormalizedTileRules, type Orphan, type OrphanGroups, type OutputSelection, type OutputSource, PIXEL_ART_FIXER_REVISION, PIXEL_ART_FIXER_URL, type PackedFrame, type PackedSheet, type PackedSource, type PendingQualityReview, type PickResult, type PixelArtFixerDetection, PixelLabClient, PixelLabError, type PixelLabObject, PixelLabProvider, type Plan, type PlanItem, type PlanState, type PollContext, type PollOptions, type PollResult, type Provider, type ProviderFactory, type ProviderInputContext, type ProviderInputScalar, type ProviderInputValue, type ProviderMetadata, type ProviderMode, type ProviderOptionContext, type ProviderSubmission, type QualityBaseline, QualityBaselineSchema, type QualityCheck, type QualityProfile, type QualityProfileInspection, QualityProfileSchema, type QualityProfileState, type QualityRegressionCaseResult, type QualityRegressionReport, type QualitySnapshotResult, QualityToleranceSchema, type QualityTolerances, type RateLimit, type Recipe, type RecipeFileVerification, type RecipeModelVerification, RecipeSchema, type RecipeVerification, type RefineAudit, type RefineFrameSetOptions, type RefineFrameSetResult, type RefineOptions, type RefineQualityProfilesOptions, type RefineQualityProfilesResult, type RefineRecordOptions, type RefineResult, type RemoteAsset, type RemoteHashCacheHealth, type ResolvedOutput, type ResolvedProviderInputs, type ResolvedProviderOptions, type ResolvedQualityInput, type ResolvedQualityProfile, type ResolvedRevision, type ResolvedSpec, type ResolvedStyleImage, type ResumeAction, type ResumeCommand, type RetroDiffusionOptions, RetroDiffusionProvider, type ReviewReadyInfo, type Revision, type RevisionMode, RevisionModeSchema, type RevisionReadiness, RevisionSchema, type SalvageAction, type SalvageResult, type ScannedAsset, type ScenarioOptions, ScenarioProvider, type SheetGroup, type SiblingManifest, type SpriteInput, type Style, type StyleAudit, type StyleInput, StyleInputSchema, StyleSchema, type SubmissionCheckpoint, type SubmitContext, type SubmitOptions, type SubmitResult, type TilesPro, type TilesetExport, type TilesetExportOptions, type TilesetFormat, UnsupportedCapabilityError, type Workspace, type WorkspaceClaims, type WorkspaceDiagnostic, type WorkspaceProject, WorkspaceProjectSchema, type WorkspaceProjectStatus, WorkspaceSchema, type WorkspaceStatusReport, adopt, applyTags, approveQualityRecord, auditStyle, availableProviders, backoffMs, buildManifest, buildPlan, bundledRecipeRoot, cacheFileName, candidateCount, checkQualityBaseline, checkQualityRecord, clientFromEnv, colorDistance, countNumberedDescriptions, createArtifactBundleManifest, createProvider, currentEntryOutputPath, currentOutputPath, detectMediaType, doctor, evaluateAudit, expectedOutputPath, exportTileset, fallbackOutputRole, fetchAssets, findOrphans, formatCost, generationCost, groupOrphansByStyle, hex, idFromPrompt, imageMetadata, inspectCaches, inspectQualityProfile, inspectRevisionReadiness, installRecipe, isSensitiveSourceUrl, listBundledRecipes, loadClaims, loadLock, loadManifest, loadSiblingManifests, loadWorkspace, lockKey, matchOrphanStyle, measureBalanceChange, measureDecodedImageQuality, measureImageQuality, mediaExtension, mediaTypeFromExtension, mergePalettes, mountSprites, mountStyle, normalizeLockOutputPaths, normalizeRefinementPalette, normalizeTileRules, outliers, outputId, packSprites, packStyle, paletteDistance, parseLock, parseWorkspace, pngSize, poll, portableOutputPath, primaryOutput, providerCredentialEnvs, providerFactory, pushTags, qualityFrameOutputPath, qualityRecordPath, quantizeToPalette, readArtifactBundleManifest, readQualityBaseline, recipeDigest, refineAsset, refineFrameSet, refineQualityProfiles, registerProvider, remove, renderSalvageSheet, renderSheet, requireApprovedQualitySources, requireBalance, requireDelete, requireList, requireRevisionReady, requireSelectCandidate, resolveEntryOutputs, resolveOutputPath, resolvePackInputs, resolveProject, resolveQualityInputs, resolveRecipe, resolveSpecEntryOutputs, resolveSpecOutputs, resolveSpecs, resolveStyleImages, resolveStyleOutputs, resumeActions, resumeCommandForStatus, retryAfterMs, runPicker, runSalvage, saveLock, saveWorkspace, scanAssets, selectEntryOutput, sha256, sha256File, shouldPersistSourceUrl, shouldRetry, slugify, snapshotQualityBaseline, specHash, spendByUnit, styleImagesBase64, submit, summarize, tagAdopted, tileFeatureOutputCount, tileVariationCount, tilesCost, toPortablePath, totalSpend, upsert, validateCostEstimate, validateMedia, validateWorkspace, verifyArtifactBundle, verifyRecipe, withArtifactManifest, workspaceClaims, workspaceStatus, writeArtifactBundle, writeManagedArtifactBundle };
package/dist/index.js CHANGED
@@ -349,6 +349,8 @@ var LockEntrySchema = z.object({
349
349
  ).optional(),
350
350
  /** Set at submit time, before the request is awaited, so a crash is recoverable. */
351
351
  jobId: z.string().nullable().default(null),
352
+ /** False only when a provider checkpoint says more requests remain. */
353
+ submissionComplete: z.boolean().optional(),
352
354
  /** For `1dir`: the multi-candidate parent object awaiting selection. */
353
355
  reviewObjectId: z.string().nullable().default(null),
354
356
  /** The chosen candidate's own object id, once selected. */
@@ -2781,7 +2783,7 @@ var ComfyUIProvider = class _ComfyUIProvider {
2781
2783
  rateLimit() {
2782
2784
  return { spacingMs: 0, maxInFlight: 1 };
2783
2785
  }
2784
- async submit(spec, styleImages) {
2786
+ async submit(spec, styleImages, context) {
2785
2787
  this.validate(spec, styleImages);
2786
2788
  const options = resolvedOptions(spec);
2787
2789
  const uploadedImages = /* @__PURE__ */ new Map();
@@ -2823,22 +2825,35 @@ var ComfyUIProvider = class _ComfyUIProvider {
2823
2825
  if (spec.generator === "frames") {
2824
2826
  const frames = options.frames;
2825
2827
  const values = spec.providerInputs[frames.vary];
2826
- const promptIds = [];
2827
- for (let index = 0; index < values.length; index++) {
2828
+ let promptIds = [];
2829
+ if (context?.previousJobId) {
2830
+ const previous = decodeFrameSetJob(context.previousJobId);
2831
+ if (previous.outputNodeId !== options.outputNodeId || previous.expectedCount !== values.length) {
2832
+ throw new Error("Saved ComfyUI frame checkpoint does not match the current frame set");
2833
+ }
2834
+ promptIds = [...previous.promptIds];
2835
+ }
2836
+ for (let index = promptIds.length; index < values.length; index++) {
2828
2837
  promptIds.push(await this.client.submit(await buildWorkflow(index)));
2838
+ const frameJob2 = {
2839
+ expectedCount: values.length,
2840
+ promptIds: [...promptIds],
2841
+ outputNodeId: options.outputNodeId
2842
+ };
2843
+ await context?.checkpoint({
2844
+ jobId: encodeFrameSetJob(frameJob2),
2845
+ metadata: comfyFrameSubmissionMetadata(spec, frameJob2),
2846
+ complete: promptIds.length === values.length
2847
+ });
2829
2848
  }
2849
+ const frameJob = {
2850
+ expectedCount: values.length,
2851
+ promptIds,
2852
+ outputNodeId: options.outputNodeId
2853
+ };
2830
2854
  return {
2831
- jobId: encodeFrameSetJob({ promptIds, outputNodeId: options.outputNodeId }),
2832
- metadata: {
2833
- inputs,
2834
- frameSet: {
2835
- count: values.length,
2836
- vary: frames.vary,
2837
- seedStep: frames.seedStep,
2838
- fps: spec.quality?.fps ?? 12,
2839
- promptIds
2840
- }
2841
- }
2855
+ jobId: encodeFrameSetJob(frameJob),
2856
+ metadata: comfyFrameSubmissionMetadata(spec, frameJob)
2842
2857
  };
2843
2858
  }
2844
2859
  const workflow = await buildWorkflow();
@@ -2895,6 +2910,9 @@ var ComfyUIProvider = class _ComfyUIProvider {
2895
2910
  async poll(jobId, generator, context) {
2896
2911
  if (generator === "frames") {
2897
2912
  const frameJob = decodeFrameSetJob(jobId);
2913
+ if (frameJob.promptIds.length < frameJob.expectedCount) {
2914
+ return { status: "processing" };
2915
+ }
2898
2916
  const images2 = [];
2899
2917
  for (let index = 0; index < frameJob.promptIds.length; index++) {
2900
2918
  const promptId2 = frameJob.promptIds[index];
@@ -3167,6 +3185,19 @@ function comfyFrameMetadata(spec, job, images, fps) {
3167
3185
  }
3168
3186
  };
3169
3187
  }
3188
+ function comfyFrameSubmissionMetadata(spec, job) {
3189
+ const options = resolvedOptions(spec);
3190
+ return {
3191
+ inputs: providerInputProvenance(spec.providerInputs),
3192
+ frameSet: {
3193
+ count: job.expectedCount,
3194
+ vary: options.frames.vary,
3195
+ seedStep: options.frames.seedStep,
3196
+ fps: spec.quality?.fps ?? 12,
3197
+ promptIds: job.promptIds
3198
+ }
3199
+ };
3200
+ }
3170
3201
  function isImageBinding(workflow, binding) {
3171
3202
  const node = workflow[binding.nodeId];
3172
3203
  return binding.input === "image" && (node?.class_type === "LoadImage" || node?.class_type === "LoadImageMask");
@@ -3222,10 +3253,18 @@ function decodeFrameSetJob(jobId) {
3222
3253
  } catch {
3223
3254
  throw new Error(`Invalid ComfyUI frame-set job id "${jobId}"`);
3224
3255
  }
3225
- if (!isObject(value) || typeof value.outputNodeId !== "string" || !value.outputNodeId || !Array.isArray(value.promptIds) || value.promptIds.length < 2 || value.promptIds.length > 64 || !value.promptIds.every((id) => typeof id === "string" && id.length > 0)) {
3256
+ if (!isObject(value) || !Array.isArray(value.promptIds)) {
3226
3257
  throw new Error(`Invalid ComfyUI frame-set job id "${jobId}"`);
3227
3258
  }
3228
- return { outputNodeId: value.outputNodeId, promptIds: value.promptIds };
3259
+ const expectedCount = value.expectedCount === void 0 ? value.promptIds.length : value.expectedCount;
3260
+ if (typeof value.outputNodeId !== "string" || !value.outputNodeId || typeof expectedCount !== "number" || !Number.isSafeInteger(expectedCount) || expectedCount < 2 || expectedCount > 64 || value.promptIds.length < 1 || value.promptIds.length > expectedCount || !value.promptIds.every((id) => typeof id === "string" && id.length > 0)) {
3261
+ throw new Error(`Invalid ComfyUI frame-set job id "${jobId}"`);
3262
+ }
3263
+ return {
3264
+ expectedCount,
3265
+ outputNodeId: value.outputNodeId,
3266
+ promptIds: value.promptIds
3267
+ };
3229
3268
  }
3230
3269
  function decodeJob(jobId) {
3231
3270
  const split = jobId.lastIndexOf("#");
@@ -6171,6 +6210,7 @@ function resumeActions(specs, lock) {
6171
6210
  const key = lockKey(spec.styleId, spec.assetId);
6172
6211
  const entry = lock.entries[key];
6173
6212
  if (!entry || entry.specHash !== spec.specHash) continue;
6213
+ if (entry.submissionComplete === false) continue;
6174
6214
  const command = resumeCommandForStatus(entry.status);
6175
6215
  if (!command) continue;
6176
6216
  if (command === "poll" && !entry.jobId) continue;
@@ -6221,10 +6261,13 @@ async function buildPlan(specs, lock, opts = {}) {
6221
6261
  reason = `${entry.error ?? "download failed"}; run pixelkiln fetch${force} (no generation cost)`;
6222
6262
  } else if (entry.status === "failed") {
6223
6263
  state = "failed";
6224
- reason = entry.error ?? "previous attempt failed";
6264
+ reason = entry.jobId && entry.submissionComplete === false ? `${entry.error ?? "submission interrupted"}; saved checkpoint will resume` : entry.error ?? "previous attempt failed";
6225
6265
  } else if (entry.status === "selected") {
6226
6266
  state = "recoverable";
6227
6267
  reason = "provider output is selected; run pixelkiln fetch (no generation cost)";
6268
+ } else if ((entry.status === "pending" || entry.status === "processing") && entry.jobId && entry.submissionComplete === false) {
6269
+ state = "failed";
6270
+ reason = "submission stopped after a saved checkpoint; rerun pixelkiln submit to resume";
6228
6271
  } else if (entry.status === "pending" || entry.status === "processing") {
6229
6272
  state = "in-flight";
6230
6273
  reason = entry.jobId ? "awaiting processing; run pixelkiln poll" : "submission state has no job id; run pixelkiln doctor before retrying";
@@ -7472,6 +7515,11 @@ async function submit(provider, loaded, items, lock, lockPath, opts = {}) {
7472
7515
  if (since < spacing) await sleep2(spacing - since);
7473
7516
  await requireRevisionReady(spec, lock);
7474
7517
  const previousEntry = lock.entries[key];
7518
+ const resumesCheckpoint = Boolean(
7519
+ previousEntry?.specHash === spec.specHash && previousEntry.provider === provider.id && previousEntry.generator === spec.generator && previousEntry.submissionComplete === false && previousEntry.jobId
7520
+ );
7521
+ const previousJobId = resumesCheckpoint ? previousEntry.jobId : void 0;
7522
+ const previousMetadata = resumesCheckpoint ? previousEntry.providerMetadata[provider.id] : void 0;
7475
7523
  const supersededOutputs = previousEntry?.outputs.length ? previousEntry.outputs : previousEntry?.supersededOutputs ?? [];
7476
7524
  upsert(lock, key, {
7477
7525
  styleId: spec.styleId,
@@ -7490,7 +7538,8 @@ async function submit(provider, loaded, items, lock, lockPath, opts = {}) {
7490
7538
  ...spec.revision.strength == null ? {} : { strength: spec.revision.strength }
7491
7539
  } : null,
7492
7540
  status: "pending",
7493
- jobId: null,
7541
+ jobId: previousJobId ?? null,
7542
+ ...previousJobId ? { submissionComplete: false } : { submissionComplete: void 0 },
7494
7543
  objectId: null,
7495
7544
  reviewObjectId: null,
7496
7545
  candidateIndex: null,
@@ -7499,10 +7548,10 @@ async function submit(provider, loaded, items, lock, lockPath, opts = {}) {
7499
7548
  // Keep the old ownership proof while new bytes are pending. Fetch may
7500
7549
  // replace that file only while its hash still matches this record.
7501
7550
  supersededOutputs,
7502
- providerMetadata: {},
7551
+ providerMetadata: resumesCheckpoint ? previousEntry.providerMetadata : {},
7503
7552
  sourceUrl: null,
7504
7553
  sourceUrls: [],
7505
- submittedAt: (/* @__PURE__ */ new Date()).toISOString(),
7554
+ submittedAt: resumesCheckpoint ? previousEntry.submittedAt : (/* @__PURE__ */ new Date()).toISOString(),
7506
7555
  cost: estimate.amount,
7507
7556
  costUnit: estimate.unit,
7508
7557
  // Persist routing before the request starts. If the process stops while
@@ -7515,9 +7564,32 @@ async function submit(provider, loaded, items, lock, lockPath, opts = {}) {
7515
7564
  lastSubmitAt = Date.now();
7516
7565
  try {
7517
7566
  const refs = styleImages.get(spec.styleId) ?? [];
7518
- const { jobId, metadata } = await provider.submit(spec, refs);
7567
+ const { jobId, metadata } = await provider.submit(spec, refs, {
7568
+ ...previousJobId ? { previousJobId } : {},
7569
+ ...previousMetadata ? { previousMetadata } : {},
7570
+ checkpoint: async (checkpoint) => {
7571
+ if (!checkpoint.jobId) throw new Error("Provider checkpoint returned an empty job id");
7572
+ const entry = lock.entries[key];
7573
+ upsert(lock, key, {
7574
+ jobId: checkpoint.jobId,
7575
+ submissionComplete: checkpoint.complete,
7576
+ reviewObjectId: checkpoint.complete && (spec.generator === "frames" || estimate.candidates > 1 && !spec.tileFeature) ? checkpoint.jobId : null,
7577
+ status: checkpoint.complete ? "processing" : "pending",
7578
+ error: null,
7579
+ providerMetadata: checkpoint.metadata ? {
7580
+ ...entry.providerMetadata,
7581
+ [provider.id]: {
7582
+ ...entry.providerMetadata[provider.id],
7583
+ ...checkpoint.metadata
7584
+ }
7585
+ } : entry.providerMetadata
7586
+ });
7587
+ await saveLock(lockPath, lock);
7588
+ }
7589
+ });
7519
7590
  upsert(lock, key, {
7520
7591
  jobId,
7592
+ submissionComplete: true,
7521
7593
  // A multi-candidate generator routes through review; record the parent
7522
7594
  // so `pick` knows where to look.
7523
7595
  reviewObjectId: spec.generator === "frames" || estimate.candidates > 1 && !spec.tileFeature ? jobId : null,
@@ -7531,10 +7603,23 @@ async function submit(provider, loaded, items, lock, lockPath, opts = {}) {
7531
7603
  ` ${key} \u2192 ${jobId} (${spec.width}x${spec.height}` + (estimate.candidates > 1 ? `, ${estimate.candidates} ${spec.tileFeature ? "outputs" : "candidates"}` : "") + `, ${estimate.amount})`
7532
7604
  );
7533
7605
  } catch (err) {
7534
- failed++;
7535
7606
  const message5 = err instanceof Error ? err.message : String(err);
7536
- upsert(lock, key, { status: "failed", error: message5, cost: 0 });
7537
- log(` FAILED ${key}: ${message5}`);
7607
+ const entry = lock.entries[key];
7608
+ if (entry.jobId && entry.submissionComplete === true) {
7609
+ upsert(lock, key, { status: "processing", error: null });
7610
+ inFlight.set(entry.jobId, spec);
7611
+ submitted++;
7612
+ spent += estimate.amount;
7613
+ log(` ${key} \u2192 ${entry.jobId} (recovered from completed checkpoint)`);
7614
+ } else {
7615
+ failed++;
7616
+ upsert(lock, key, {
7617
+ status: "failed",
7618
+ error: message5,
7619
+ cost: entry.jobId ? estimate.amount : 0
7620
+ });
7621
+ log(` FAILED ${key}: ${message5}`);
7622
+ }
7538
7623
  }
7539
7624
  await saveLock(lockPath, lock);
7540
7625
  }
@@ -7552,12 +7637,19 @@ async function poll(provider, lock, lockPath, opts = {}) {
7552
7637
  const specByKey = new Map((opts.specs ?? []).map((s) => [lockKey(s.styleId, s.assetId), s]));
7553
7638
  const selectedKeys = opts.specs ? new Set(specByKey.keys()) : null;
7554
7639
  const result = { review: 0, completed: 0, failed: 0, stillRunning: 0 };
7640
+ const incompleteSubmissions = Object.entries(lock.entries).filter(
7641
+ ([key, entry]) => entry.provider === provider.id && (!selectedKeys || selectedKeys.has(key)) && entry.jobId && entry.submissionComplete === false && (entry.status === "pending" || entry.status === "processing")
7642
+ );
7643
+ for (const [key] of incompleteSubmissions) {
7644
+ log(` incomplete submission ${key}; rerun \`pixelkiln submit\` to resume`);
7645
+ }
7646
+ result.stillRunning = incompleteSubmissions.length;
7555
7647
  const pending = () => Object.entries(lock.entries).filter(
7556
- ([key, e]) => e.provider === provider.id && (!selectedKeys || selectedKeys.has(key)) && e.jobId && (e.status === "pending" || e.status === "processing")
7648
+ ([key, e]) => e.provider === provider.id && (!selectedKeys || selectedKeys.has(key)) && e.jobId && e.submissionComplete !== false && (e.status === "pending" || e.status === "processing")
7557
7649
  );
7558
7650
  while (pending().length > 0) {
7559
7651
  if (Date.now() - started > timeout) {
7560
- result.stillRunning = pending().length;
7652
+ result.stillRunning += pending().length;
7561
7653
  log(` timed out with ${result.stillRunning} job(s) still running \u2014 re-run \`poll\` to resume`);
7562
7654
  break;
7563
7655
  }
@@ -8206,7 +8298,10 @@ function renderSheet(groups) {
8206
8298
  .loop { display:flex; align-items:flex-start; gap:12px; margin-bottom:12px; }
8207
8299
  .loop img { image-rendering:pixelated; border:2px solid var(--accent); padding:7px;
8208
8300
  background:var(--panel-deep); max-width:min(var(--preview),calc(100vw - 62px)); height:auto; }
8209
- .loop-copy { color:var(--dim); font-size:11px; max-width:32ch; }
8301
+ .loop-copy { color:var(--dim); font-size:11px; max-width:32ch; display:flex;
8302
+ flex-direction:column; gap:8px; }
8303
+ .loop-copy p { margin:0; }
8304
+ .loop-copy button { align-self:flex-start; padding:5px 9px; font-size:10.5px; }
8210
8305
  .cand { border:2px solid var(--line-strong); border-radius:0; padding:7px; background:var(--panel-deep);
8211
8306
  cursor:pointer; display:flex; flex:0 0 auto; flex-direction:column; align-items:center; gap:5px;
8212
8307
  position:relative; max-width:100%; }
@@ -8310,15 +8405,49 @@ GROUPS.forEach((g, gi) => {
8310
8405
  preview.alt = 'Animated frame-set preview';
8311
8406
  const copy = document.createElement('div');
8312
8407
  copy.className = 'loop-copy';
8313
- copy.textContent = g.frameUrls.length + ' ordered frames \xB7 ' + (g.fps || 12) +
8408
+ const copyText = document.createElement('p');
8409
+ copyText.textContent = g.frameUrls.length + ' ordered frames \xB7 ' + (g.fps || 12) +
8314
8410
  ' fps. Accepting keeps every frame; one bad frame means leave the set unchosen.';
8411
+ const playback = document.createElement('button');
8412
+ playback.type = 'button';
8413
+ let userPaused = window.matchMedia('(prefers-reduced-motion: reduce)').matches;
8414
+ let loopVisible = true;
8415
+ let pageVisible = !document.hidden;
8416
+ let loopTimer = null;
8417
+ let loopIndex = 0;
8418
+ const stopLoop = () => {
8419
+ if (loopTimer !== null) window.clearInterval(loopTimer);
8420
+ loopTimer = null;
8421
+ };
8422
+ const syncLoop = () => {
8423
+ stopLoop();
8424
+ playback.textContent = userPaused ? 'Play preview' : 'Pause preview';
8425
+ playback.setAttribute('aria-pressed', String(userPaused));
8426
+ if (!userPaused && loopVisible && pageVisible) {
8427
+ loopTimer = window.setInterval(() => {
8428
+ loopIndex = (loopIndex + 1) % g.frameUrls.length;
8429
+ preview.src = g.frameUrls[loopIndex];
8430
+ }, Math.max(16, Math.round(1000 / (g.fps || 12))));
8431
+ }
8432
+ };
8433
+ playback.addEventListener('click', () => {
8434
+ userPaused = !userPaused;
8435
+ syncLoop();
8436
+ });
8437
+ copy.append(copyText, playback);
8315
8438
  loop.append(preview, copy);
8316
8439
  review.insertBefore(loop, frames);
8317
- let loopIndex = 0;
8318
- setInterval(() => {
8319
- loopIndex = (loopIndex + 1) % g.frameUrls.length;
8320
- preview.src = g.frameUrls[loopIndex];
8321
- }, Math.max(16, Math.round(1000 / (g.fps || 12))));
8440
+ document.addEventListener('visibilitychange', () => {
8441
+ pageVisible = !document.hidden;
8442
+ syncLoop();
8443
+ });
8444
+ syncLoop();
8445
+ if ('IntersectionObserver' in window) {
8446
+ new IntersectionObserver((entries) => {
8447
+ loopVisible = entries[0]?.isIntersecting ?? false;
8448
+ syncLoop();
8449
+ }).observe(loop);
8450
+ }
8322
8451
  }
8323
8452
  g.frameUrls.forEach((url, i) => {
8324
8453
  const c = document.createElement('button');