@mean-weasel/lineage 0.1.15 → 0.1.16
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/CHANGELOG.md +6 -0
- package/README.md +23 -2
- package/dist/cli/lineage-dev.js +728 -40
- package/dist/cli/lineage-dev.js.map +4 -4
- package/dist/cli/lineage-preview.js +728 -40
- package/dist/cli/lineage-preview.js.map +4 -4
- package/dist/cli/lineage.js +728 -40
- package/dist/cli/lineage.js.map +4 -4
- package/dist/runtime-build.json +4 -4
- package/dist/server.js +841 -81
- package/dist/server.js.map +4 -4
- package/dist/web/assets/{app-CDxSvoD3.css → app-DwTFac4u.css} +1 -1
- package/dist/web/assets/app-JYTx_Sd4.js +16 -0
- package/dist/web/index.html +2 -2
- package/package.json +1 -1
- package/dist/web/assets/app-CYUgf6rX.js +0 -16
package/dist/cli/lineage.js
CHANGED
|
@@ -8,6 +8,32 @@ import { dirname as dirname6, join as join10, resolve as resolve8 } from "node:p
|
|
|
8
8
|
import { spawn } from "node:child_process";
|
|
9
9
|
import { fileURLToPath as fileURLToPath3 } from "node:url";
|
|
10
10
|
|
|
11
|
+
// src/shared/edgeSummary.ts
|
|
12
|
+
var edgeSummaryWordLimit = 2;
|
|
13
|
+
var EdgeSummaryValidationError = class extends Error {
|
|
14
|
+
constructor(code, message) {
|
|
15
|
+
super(message);
|
|
16
|
+
this.code = code;
|
|
17
|
+
this.name = "EdgeSummaryValidationError";
|
|
18
|
+
}
|
|
19
|
+
code;
|
|
20
|
+
};
|
|
21
|
+
function normalizeEdgeSummary(value) {
|
|
22
|
+
if (value === void 0 || value === null) return void 0;
|
|
23
|
+
if (typeof value !== "string") throw new EdgeSummaryValidationError("not_text", "Edge summary must be text");
|
|
24
|
+
const words = value.trim().split(/\s+/u).filter(Boolean);
|
|
25
|
+
if (words.length === 0) return void 0;
|
|
26
|
+
if (words.length > edgeSummaryWordLimit) {
|
|
27
|
+
throw new EdgeSummaryValidationError("too_many_words", `Edge summary must contain at most ${edgeSummaryWordLimit} words`);
|
|
28
|
+
}
|
|
29
|
+
return words.join(" ");
|
|
30
|
+
}
|
|
31
|
+
function requireEdgeSummary(value) {
|
|
32
|
+
const summary = normalizeEdgeSummary(value);
|
|
33
|
+
if (!summary) throw new EdgeSummaryValidationError("required", "Edge summary is required");
|
|
34
|
+
return summary;
|
|
35
|
+
}
|
|
36
|
+
|
|
11
37
|
// src/server/agentClaims.ts
|
|
12
38
|
import { createHash as createHash4, randomBytes, timingSafeEqual as timingSafeEqual2 } from "node:crypto";
|
|
13
39
|
|
|
@@ -654,13 +680,13 @@ function profileManifestPath(selector) {
|
|
|
654
680
|
if (!profileIdPattern.test(value)) throw new Error(`Invalid profile ID: ${value}`);
|
|
655
681
|
return { manifestPath: join4(lineageProfileRoot(), value, "profile.json"), namedProfileId: value };
|
|
656
682
|
}
|
|
657
|
-
function requiredString(
|
|
658
|
-
const value =
|
|
683
|
+
function requiredString(record2, key) {
|
|
684
|
+
const value = record2[key];
|
|
659
685
|
if (typeof value !== "string" || !value.trim()) throw new Error(`Profile manifest requires a non-empty ${key}`);
|
|
660
686
|
return value.trim();
|
|
661
687
|
}
|
|
662
|
-
function optionalString(
|
|
663
|
-
const value =
|
|
688
|
+
function optionalString(record2, key) {
|
|
689
|
+
const value = record2[key];
|
|
664
690
|
if (value === void 0) return void 0;
|
|
665
691
|
if (typeof value !== "string" || !value.trim()) throw new Error(`Profile manifest ${key} must be a non-empty string`);
|
|
666
692
|
return value.trim();
|
|
@@ -711,15 +737,15 @@ function resolveLineageProfile(selector) {
|
|
|
711
737
|
throw new Error(`Could not parse profile manifest ${manifestPath}: ${error instanceof Error ? error.message : String(error)}`, { cause: error });
|
|
712
738
|
}
|
|
713
739
|
if (!raw || typeof raw !== "object" || Array.isArray(raw)) throw new Error("Profile manifest must be a JSON object");
|
|
714
|
-
const
|
|
715
|
-
const schemaVersion = requiredString(
|
|
740
|
+
const record2 = raw;
|
|
741
|
+
const schemaVersion = requiredString(record2, "schema_version");
|
|
716
742
|
if (schemaVersion !== lineageProfileSchemaVersion) throw new Error(`Unsupported profile schema_version: ${schemaVersion}`);
|
|
717
|
-
const profileId = requiredString(
|
|
743
|
+
const profileId = requiredString(record2, "profile_id");
|
|
718
744
|
if (!profileIdPattern.test(profileId)) throw new Error(`Invalid profile ID: ${profileId}`);
|
|
719
745
|
if (namedProfileId && namedProfileId !== profileId) {
|
|
720
746
|
throw new Error(`Named profile ${namedProfileId} does not match immutable manifest profile_id ${profileId}`);
|
|
721
747
|
}
|
|
722
|
-
const expectedRaw =
|
|
748
|
+
const expectedRaw = record2.expected_runtime;
|
|
723
749
|
if (expectedRaw !== void 0 && (!expectedRaw || typeof expectedRaw !== "object" || Array.isArray(expectedRaw))) {
|
|
724
750
|
throw new Error("Profile expected_runtime must be an object");
|
|
725
751
|
}
|
|
@@ -728,18 +754,18 @@ function resolveLineageProfile(selector) {
|
|
|
728
754
|
const expectedVersion = expected ? optionalString(expected, "version") : void 0;
|
|
729
755
|
const expectedCodeFingerprint = validateFingerprint(expected ? optionalString(expected, "code_fingerprint") : void 0, "expected_runtime.code_fingerprint");
|
|
730
756
|
const expectedCodeOrigin = validateCodeOrigin(expected?.code_origin);
|
|
731
|
-
const migrationsRaw =
|
|
757
|
+
const migrationsRaw = record2.required_schema_migrations;
|
|
732
758
|
if (migrationsRaw !== void 0 && (!Array.isArray(migrationsRaw) || migrationsRaw.some((value) => typeof value !== "string" || !value.trim()))) {
|
|
733
759
|
throw new Error("Profile required_schema_migrations must be an array of non-empty strings");
|
|
734
760
|
}
|
|
735
|
-
const environment = validateEnvironment(requiredString(
|
|
761
|
+
const environment = validateEnvironment(requiredString(record2, "environment"));
|
|
736
762
|
const expectedChannel = validateChannel(expected?.channel);
|
|
737
763
|
if (expectedChannel && expectedChannel !== environmentChannel(environment)) {
|
|
738
764
|
throw new Error(`Profile environment ${environment} conflicts with expected runtime channel ${expectedChannel}`);
|
|
739
765
|
}
|
|
740
766
|
const manifest = {
|
|
741
|
-
asset_root: resolveManifestPath(manifestPath, requiredString(
|
|
742
|
-
database_path: resolveManifestPath(manifestPath, requiredString(
|
|
767
|
+
asset_root: resolveManifestPath(manifestPath, requiredString(record2, "asset_root")),
|
|
768
|
+
database_path: resolveManifestPath(manifestPath, requiredString(record2, "database_path")),
|
|
743
769
|
environment,
|
|
744
770
|
...expected ? {
|
|
745
771
|
expected_runtime: {
|
|
@@ -753,7 +779,7 @@ function resolveLineageProfile(selector) {
|
|
|
753
779
|
profile_id: profileId,
|
|
754
780
|
...migrationsRaw ? { required_schema_migrations: migrationsRaw.map((value) => value.trim()) } : {},
|
|
755
781
|
schema_version: lineageProfileSchemaVersion,
|
|
756
|
-
service_origin: validateServiceOrigin(requiredString(
|
|
782
|
+
service_origin: validateServiceOrigin(requiredString(record2, "service_origin"))
|
|
757
783
|
};
|
|
758
784
|
return { ...manifest, manifest_path: manifestPath, profile_fingerprint: lineageProfileFingerprint(manifest) };
|
|
759
785
|
}
|
|
@@ -1622,6 +1648,10 @@ function lineageDb() {
|
|
|
1622
1648
|
child_asset_id text not null references assets(id),
|
|
1623
1649
|
relation_type text not null check (relation_type in ('derived_from')),
|
|
1624
1650
|
created_at text not null,
|
|
1651
|
+
summary text,
|
|
1652
|
+
summary_created_by text check (summary_created_by in ('human', 'agent', 'system')),
|
|
1653
|
+
summary_updated_by text check (summary_updated_by in ('human', 'agent', 'system')),
|
|
1654
|
+
summary_updated_at text,
|
|
1625
1655
|
unique (project_id, parent_asset_id, child_asset_id, relation_type)
|
|
1626
1656
|
);
|
|
1627
1657
|
create index if not exists edges_parent on asset_edges(project_id, parent_asset_id);
|
|
@@ -1905,6 +1935,7 @@ function lineageDb() {
|
|
|
1905
1935
|
imported_asset_id text not null references assets(id),
|
|
1906
1936
|
parent_asset_id text not null references assets(id),
|
|
1907
1937
|
imported_at text not null,
|
|
1938
|
+
edge_summary text,
|
|
1908
1939
|
unique(job_id, output_index),
|
|
1909
1940
|
unique(job_id, file_path)
|
|
1910
1941
|
);
|
|
@@ -1993,6 +2024,11 @@ function lineageDb() {
|
|
|
1993
2024
|
);
|
|
1994
2025
|
create index if not exists agent_claim_events_claim_created on agent_claim_events(claim_id, created_at);
|
|
1995
2026
|
`);
|
|
2027
|
+
ensureColumn(database, "asset_edges", "summary", "text");
|
|
2028
|
+
ensureColumn(database, "asset_edges", "summary_created_by", "text check (summary_created_by in ('human', 'agent', 'system'))");
|
|
2029
|
+
ensureColumn(database, "asset_edges", "summary_updated_by", "text check (summary_updated_by in ('human', 'agent', 'system'))");
|
|
2030
|
+
ensureColumn(database, "asset_edges", "summary_updated_at", "text");
|
|
2031
|
+
ensureColumn(database, "generation_job_outputs", "edge_summary", "text");
|
|
1996
2032
|
migrateAssetSelections(database);
|
|
1997
2033
|
dropLegacyAssetSelectionRootUnique(database);
|
|
1998
2034
|
ensureColumn(database, "asset_selections", "notes", "text");
|
|
@@ -3118,6 +3154,14 @@ function cancelLineageTask(project, fields) {
|
|
|
3118
3154
|
database.close();
|
|
3119
3155
|
}
|
|
3120
3156
|
}
|
|
3157
|
+
function cancelLineageIterateTasksForAssets(project, fields) {
|
|
3158
|
+
const targetIds = fields.assetIds ? new Set(fields.assetIds) : void 0;
|
|
3159
|
+
return listLineageTasks(project, fields.rootAssetId, ["pending"]).tasks.filter((task) => task.task_type === "iterate").filter((task) => !targetIds || targetIds.has(task.target_asset_id)).map((task) => cancelLineageTask(project, {
|
|
3160
|
+
actor: fields.actor,
|
|
3161
|
+
confirmWrite: fields.confirmWrite,
|
|
3162
|
+
taskId: task.id
|
|
3163
|
+
}));
|
|
3164
|
+
}
|
|
3121
3165
|
function resolveLineageTask(project, fields) {
|
|
3122
3166
|
const normalizedProject = normalizeProject(project);
|
|
3123
3167
|
const actor = normalizeActor(fields.actor, "Resolve actor");
|
|
@@ -3483,6 +3527,23 @@ function rerollRequestId(project, root, node, timestamp) {
|
|
|
3483
3527
|
function rowString(value) {
|
|
3484
3528
|
return typeof value === "string" && value.length > 0 ? value : void 0;
|
|
3485
3529
|
}
|
|
3530
|
+
function lineageEdgeFromRow(row) {
|
|
3531
|
+
const summary = rowString(row.summary);
|
|
3532
|
+
const summaryCreatedBy = rowString(row.summary_created_by);
|
|
3533
|
+
const summaryUpdatedBy = rowString(row.summary_updated_by);
|
|
3534
|
+
const summaryUpdatedAt = rowString(row.summary_updated_at);
|
|
3535
|
+
return {
|
|
3536
|
+
id: String(row.id),
|
|
3537
|
+
parent_asset_id: String(row.parent_asset_id),
|
|
3538
|
+
child_asset_id: String(row.child_asset_id),
|
|
3539
|
+
relation_type: row.relation_type,
|
|
3540
|
+
created_at: String(row.created_at),
|
|
3541
|
+
...summary ? { summary } : {},
|
|
3542
|
+
...summaryCreatedBy ? { summary_created_by: summaryCreatedBy } : {},
|
|
3543
|
+
...summaryUpdatedBy ? { summary_updated_by: summaryUpdatedBy } : {},
|
|
3544
|
+
...summaryUpdatedAt ? { summary_updated_at: summaryUpdatedAt } : {}
|
|
3545
|
+
};
|
|
3546
|
+
}
|
|
3486
3547
|
function rerollRequestFrom(row) {
|
|
3487
3548
|
return {
|
|
3488
3549
|
id: String(row.id),
|
|
@@ -3582,6 +3643,9 @@ function localPreviewUrl(project, localPath) {
|
|
|
3582
3643
|
return `/api/assets/local-preview?${params.toString()}`;
|
|
3583
3644
|
}
|
|
3584
3645
|
function linkLineageAssets(project, fields) {
|
|
3646
|
+
const summary = normalizeEdgeSummary(fields.summary);
|
|
3647
|
+
if (summary && !fields.summaryActor) throw new LineageError("Lineage edge summary requires an explicit author");
|
|
3648
|
+
if (!summary && fields.summaryActor) throw new LineageError("Lineage edge summary author requires a summary");
|
|
3585
3649
|
const database = lineageDb();
|
|
3586
3650
|
requireAsset2(database, project, fields.parentAssetId);
|
|
3587
3651
|
requireAsset2(database, project, fields.childAssetId);
|
|
@@ -3600,22 +3664,66 @@ function linkLineageAssets(project, fields) {
|
|
|
3600
3664
|
database.close();
|
|
3601
3665
|
throw error;
|
|
3602
3666
|
}
|
|
3667
|
+
const createdAt = nowIso();
|
|
3603
3668
|
const edge = {
|
|
3604
3669
|
id: edgeId(project, fields.parentAssetId, fields.childAssetId),
|
|
3605
3670
|
parent_asset_id: fields.parentAssetId,
|
|
3606
3671
|
child_asset_id: fields.childAssetId,
|
|
3607
3672
|
relation_type: "derived_from",
|
|
3608
|
-
created_at:
|
|
3673
|
+
created_at: createdAt,
|
|
3674
|
+
...summary ? {
|
|
3675
|
+
summary,
|
|
3676
|
+
summary_created_by: fields.summaryActor,
|
|
3677
|
+
summary_updated_by: fields.summaryActor,
|
|
3678
|
+
summary_updated_at: createdAt
|
|
3679
|
+
} : {}
|
|
3609
3680
|
};
|
|
3610
3681
|
if (!fields.confirmWrite) {
|
|
3611
3682
|
database.close();
|
|
3612
3683
|
return { ok: true, dryRun: true, edge };
|
|
3613
3684
|
}
|
|
3614
|
-
database.prepare(`
|
|
3615
|
-
insert into asset_edges (
|
|
3616
|
-
|
|
3685
|
+
const inserted = database.prepare(`
|
|
3686
|
+
insert into asset_edges (
|
|
3687
|
+
id, project_id, parent_asset_id, child_asset_id, relation_type, created_at,
|
|
3688
|
+
summary, summary_created_by, summary_updated_by, summary_updated_at
|
|
3689
|
+
)
|
|
3690
|
+
values (?, ?, ?, ?, 'derived_from', ?, ?, ?, ?, ?)
|
|
3617
3691
|
on conflict(project_id, parent_asset_id, child_asset_id, relation_type) do nothing
|
|
3618
|
-
`).run(
|
|
3692
|
+
`).run(
|
|
3693
|
+
edge.id,
|
|
3694
|
+
project,
|
|
3695
|
+
edge.parent_asset_id,
|
|
3696
|
+
edge.child_asset_id,
|
|
3697
|
+
edge.created_at,
|
|
3698
|
+
edge.summary || null,
|
|
3699
|
+
edge.summary_created_by || null,
|
|
3700
|
+
edge.summary_updated_by || null,
|
|
3701
|
+
edge.summary_updated_at || null
|
|
3702
|
+
);
|
|
3703
|
+
if (inserted.changes === 0) {
|
|
3704
|
+
const existingRow = database.prepare(`
|
|
3705
|
+
select id, parent_asset_id, child_asset_id, relation_type, created_at,
|
|
3706
|
+
summary, summary_created_by, summary_updated_by, summary_updated_at
|
|
3707
|
+
from asset_edges
|
|
3708
|
+
where project_id = ? and parent_asset_id = ? and child_asset_id = ? and relation_type = 'derived_from'
|
|
3709
|
+
`).get(project, edge.parent_asset_id, edge.child_asset_id);
|
|
3710
|
+
if (!existingRow) {
|
|
3711
|
+
database.close();
|
|
3712
|
+
throw new LineageError("Lineage edge conflict could not be resolved", 409);
|
|
3713
|
+
}
|
|
3714
|
+
const existingEdge = lineageEdgeFromRow(existingRow);
|
|
3715
|
+
if (summary && (existingEdge.summary !== summary || existingEdge.summary_created_by !== fields.summaryActor || existingEdge.summary_updated_by !== fields.summaryActor || !existingEdge.summary_updated_at)) {
|
|
3716
|
+
database.close();
|
|
3717
|
+
throw new LineageError("Lineage edge already exists with a different summary or provenance", 409);
|
|
3718
|
+
}
|
|
3719
|
+
database.close();
|
|
3720
|
+
return {
|
|
3721
|
+
ok: true,
|
|
3722
|
+
idempotent: true,
|
|
3723
|
+
message: `Already linked ${existingEdge.child_asset_id} from ${existingEdge.parent_asset_id}`,
|
|
3724
|
+
edge: existingEdge
|
|
3725
|
+
};
|
|
3726
|
+
}
|
|
3619
3727
|
database.close();
|
|
3620
3728
|
return { ok: true, message: `Linked ${edge.child_asset_id} from ${edge.parent_asset_id}`, edge };
|
|
3621
3729
|
}
|
|
@@ -3627,7 +3735,13 @@ function descendants(database, project, root) {
|
|
|
3627
3735
|
const parent = queue.shift();
|
|
3628
3736
|
if (seen.has(parent)) continue;
|
|
3629
3737
|
seen.add(parent);
|
|
3630
|
-
const rows = database.prepare(
|
|
3738
|
+
const rows = database.prepare(`
|
|
3739
|
+
select id, parent_asset_id, child_asset_id, relation_type, created_at,
|
|
3740
|
+
summary, summary_created_by, summary_updated_by, summary_updated_at
|
|
3741
|
+
from asset_edges
|
|
3742
|
+
where project_id = ? and parent_asset_id = ?
|
|
3743
|
+
order by created_at
|
|
3744
|
+
`).all(project, parent).map(lineageEdgeFromRow);
|
|
3631
3745
|
edges.push(...rows);
|
|
3632
3746
|
queue.push(...rows.map((row) => row.child_asset_id));
|
|
3633
3747
|
}
|
|
@@ -4013,7 +4127,7 @@ function lineageCommand(command, project, rootAssetId) {
|
|
|
4013
4127
|
return `${lineagePublicPackageCommand()} ${command} --project ${shellQuote(project)} --root ${shellQuote(rootAssetId)} ${lineageRuntimeSelector()} --json`;
|
|
4014
4128
|
}
|
|
4015
4129
|
function linkChildCommand(project, rootAssetId) {
|
|
4016
|
-
return `${lineagePublicPackageCommand()} link-child --project ${shellQuote(project)} --root ${shellQuote(rootAssetId)} --child <asset-id> --confirm-write ${lineageRuntimeSelector()} --json`;
|
|
4130
|
+
return `${lineagePublicPackageCommand()} link-child --project ${shellQuote(project)} --root ${shellQuote(rootAssetId)} --child <asset-id> --summary "<one-or-two-words>" --confirm-write ${lineageRuntimeSelector()} --json`;
|
|
4017
4131
|
}
|
|
4018
4132
|
function rerollImportGuidance(rootAssetId, targetAssetId) {
|
|
4019
4133
|
return `Use lineage reroll plan --root ${rootAssetId} --target ${targetAssetId} and lineage reroll import instead.`;
|
|
@@ -4062,6 +4176,7 @@ function getLineageBrief(project, rootAssetId) {
|
|
|
4062
4176
|
};
|
|
4063
4177
|
}
|
|
4064
4178
|
function linkSelectedLineageChild(project, fields) {
|
|
4179
|
+
const summary = fields.summaryActor ? requireEdgeSummary(fields.summary) : fields.summary;
|
|
4065
4180
|
const next = getLineageNextAsset(project, fields.rootAssetId);
|
|
4066
4181
|
if (!next.next_asset) throw new LineageError("Cannot link child until a next base is selected or unambiguous");
|
|
4067
4182
|
const rerollRequests = listLineageRerollRequests(project, next.root_asset_id).requests;
|
|
@@ -4090,7 +4205,9 @@ function linkSelectedLineageChild(project, fields) {
|
|
|
4090
4205
|
childAssetId: fields.childAssetId,
|
|
4091
4206
|
confirmWrite: fields.confirmWrite,
|
|
4092
4207
|
claimToken: fields.claimToken,
|
|
4093
|
-
parentAssetId: next.next_asset.asset_id
|
|
4208
|
+
parentAssetId: next.next_asset.asset_id,
|
|
4209
|
+
summary,
|
|
4210
|
+
summaryActor: fields.summaryActor
|
|
4094
4211
|
});
|
|
4095
4212
|
return {
|
|
4096
4213
|
...result,
|
|
@@ -4110,7 +4227,141 @@ function linkSelectedLineageChild(project, fields) {
|
|
|
4110
4227
|
import { existsSync as existsSync8, realpathSync as realpathSync2, statSync as statSync4 } from "node:fs";
|
|
4111
4228
|
import { relative as relative3, resolve as resolve5 } from "node:path";
|
|
4112
4229
|
import { randomUUID as randomUUID3 } from "node:crypto";
|
|
4113
|
-
|
|
4230
|
+
|
|
4231
|
+
// src/shared/generationOutputManifest.ts
|
|
4232
|
+
var generationOutputManifestSchemaVersion = "lineage.generation_output_manifest.v1";
|
|
4233
|
+
var GenerationOutputManifestError = class extends Error {
|
|
4234
|
+
constructor(message) {
|
|
4235
|
+
super(message);
|
|
4236
|
+
this.name = "GenerationOutputManifestError";
|
|
4237
|
+
}
|
|
4238
|
+
};
|
|
4239
|
+
function record(value, label) {
|
|
4240
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
4241
|
+
throw new GenerationOutputManifestError(`${label} must be an object`);
|
|
4242
|
+
}
|
|
4243
|
+
return value;
|
|
4244
|
+
}
|
|
4245
|
+
function exactKeys(value, allowed, label) {
|
|
4246
|
+
const allowedKeys = new Set(allowed);
|
|
4247
|
+
const unsupported = Object.keys(value).filter((key) => !allowedKeys.has(key)).sort();
|
|
4248
|
+
if (unsupported.length > 0) {
|
|
4249
|
+
throw new GenerationOutputManifestError(`${label} contains unsupported field: ${unsupported[0]}`);
|
|
4250
|
+
}
|
|
4251
|
+
}
|
|
4252
|
+
function nonEmptyText(value, label) {
|
|
4253
|
+
if (typeof value !== "string" || !value.trim()) throw new GenerationOutputManifestError(`${label} is required`);
|
|
4254
|
+
return value.trim();
|
|
4255
|
+
}
|
|
4256
|
+
function expectedGenerationOutputParents(job) {
|
|
4257
|
+
if (!Number.isInteger(job.expected_output_count) || job.expected_output_count <= 0) {
|
|
4258
|
+
throw new GenerationOutputManifestError("Generation job expected output count must be a positive integer");
|
|
4259
|
+
}
|
|
4260
|
+
const parents = job.inputs.filter((input) => input.role === "lineage_next_base").sort((left, right) => left.position - right.position);
|
|
4261
|
+
if (parents.length === 0) throw new GenerationOutputManifestError("Generation job has no lineage_next_base input");
|
|
4262
|
+
const parentIds = /* @__PURE__ */ new Set();
|
|
4263
|
+
const parentPositions = /* @__PURE__ */ new Set();
|
|
4264
|
+
for (const parent of parents) {
|
|
4265
|
+
if (typeof parent.asset_id !== "string" || !parent.asset_id || parent.asset_id.trim() !== parent.asset_id) {
|
|
4266
|
+
throw new GenerationOutputManifestError("Generation job has invalid lineage parent asset id");
|
|
4267
|
+
}
|
|
4268
|
+
if (!Number.isInteger(parent.position) || parent.position < 0 || parentPositions.has(parent.position)) {
|
|
4269
|
+
throw new GenerationOutputManifestError("Generation job has invalid lineage parent positions");
|
|
4270
|
+
}
|
|
4271
|
+
if (parentIds.has(parent.asset_id)) throw new GenerationOutputManifestError(`Generation job has duplicate lineage parent: ${parent.asset_id}`);
|
|
4272
|
+
parentIds.add(parent.asset_id);
|
|
4273
|
+
parentPositions.add(parent.position);
|
|
4274
|
+
}
|
|
4275
|
+
if (job.expected_output_count % parents.length !== 0) {
|
|
4276
|
+
throw new GenerationOutputManifestError("Generation job has invalid parent mapping");
|
|
4277
|
+
}
|
|
4278
|
+
const outputsPerParent = job.expected_output_count / parents.length;
|
|
4279
|
+
return Array.from({ length: job.expected_output_count }, (_value, outputIndex) => {
|
|
4280
|
+
return parents[Math.floor(outputIndex / outputsPerParent)].asset_id;
|
|
4281
|
+
});
|
|
4282
|
+
}
|
|
4283
|
+
function createGenerationOutputManifestDraft(job) {
|
|
4284
|
+
return {
|
|
4285
|
+
schema_version: generationOutputManifestSchemaVersion,
|
|
4286
|
+
job_id: job.id,
|
|
4287
|
+
outputs: expectedGenerationOutputParents(job).map((parentAssetId, outputIndex) => ({
|
|
4288
|
+
output_index: outputIndex,
|
|
4289
|
+
file_path: "",
|
|
4290
|
+
parent_asset_id: parentAssetId,
|
|
4291
|
+
edge_summary: ""
|
|
4292
|
+
}))
|
|
4293
|
+
};
|
|
4294
|
+
}
|
|
4295
|
+
function parseGenerationOutputManifest(value, job, options) {
|
|
4296
|
+
if (!options || typeof options.resolveFilePath !== "function") {
|
|
4297
|
+
throw new GenerationOutputManifestError("Generation output manifest requires a file-path resolver");
|
|
4298
|
+
}
|
|
4299
|
+
const manifest = record(value, "Generation output manifest");
|
|
4300
|
+
exactKeys(manifest, ["schema_version", "job_id", "outputs"], "Generation output manifest");
|
|
4301
|
+
if (manifest.schema_version !== generationOutputManifestSchemaVersion) {
|
|
4302
|
+
throw new GenerationOutputManifestError(`Generation output manifest schema_version must be ${generationOutputManifestSchemaVersion}`);
|
|
4303
|
+
}
|
|
4304
|
+
if (manifest.job_id !== job.id) throw new GenerationOutputManifestError(`Generation output manifest job_id must be ${job.id}`);
|
|
4305
|
+
if (!Array.isArray(manifest.outputs)) throw new GenerationOutputManifestError("Generation output manifest outputs must be an array");
|
|
4306
|
+
const expectedParents = expectedGenerationOutputParents(job);
|
|
4307
|
+
if (manifest.outputs.length !== expectedParents.length) {
|
|
4308
|
+
throw new GenerationOutputManifestError(`Generation output manifest requires ${expectedParents.length} outputs, received ${manifest.outputs.length}`);
|
|
4309
|
+
}
|
|
4310
|
+
const seenIndexes = /* @__PURE__ */ new Set();
|
|
4311
|
+
const outputs = manifest.outputs.map((value2, position) => {
|
|
4312
|
+
const output = record(value2, `Generation output at position ${position}`);
|
|
4313
|
+
exactKeys(output, ["output_index", "file_path", "parent_asset_id", "edge_summary"], `Generation output at position ${position}`);
|
|
4314
|
+
const outputIndex = output.output_index;
|
|
4315
|
+
if (!Number.isInteger(outputIndex) || Number(outputIndex) < 0) {
|
|
4316
|
+
throw new GenerationOutputManifestError(`Generation output at position ${position} requires a non-negative integer output_index`);
|
|
4317
|
+
}
|
|
4318
|
+
const normalizedIndex = Number(outputIndex);
|
|
4319
|
+
if (seenIndexes.has(normalizedIndex)) throw new GenerationOutputManifestError(`Duplicate generation output_index: ${normalizedIndex}`);
|
|
4320
|
+
seenIndexes.add(normalizedIndex);
|
|
4321
|
+
const expectedParent = expectedParents[normalizedIndex];
|
|
4322
|
+
if (!expectedParent) throw new GenerationOutputManifestError(`Unknown generation output_index: ${normalizedIndex}`);
|
|
4323
|
+
const filePath = nonEmptyText(output.file_path, `Generation output ${normalizedIndex} file_path`);
|
|
4324
|
+
const parentAssetId = nonEmptyText(output.parent_asset_id, `Generation output ${normalizedIndex} parent_asset_id`);
|
|
4325
|
+
if (parentAssetId !== expectedParent) {
|
|
4326
|
+
throw new GenerationOutputManifestError(`Generation output ${normalizedIndex} must use parent_asset_id ${expectedParent}`);
|
|
4327
|
+
}
|
|
4328
|
+
let edgeSummary;
|
|
4329
|
+
try {
|
|
4330
|
+
edgeSummary = requireEdgeSummary(output.edge_summary);
|
|
4331
|
+
} catch (error) {
|
|
4332
|
+
if (error instanceof EdgeSummaryValidationError) {
|
|
4333
|
+
throw new GenerationOutputManifestError(`Generation output ${normalizedIndex}: ${error.message}`);
|
|
4334
|
+
}
|
|
4335
|
+
throw error;
|
|
4336
|
+
}
|
|
4337
|
+
return {
|
|
4338
|
+
output_index: normalizedIndex,
|
|
4339
|
+
file_path: filePath,
|
|
4340
|
+
parent_asset_id: parentAssetId,
|
|
4341
|
+
edge_summary: edgeSummary
|
|
4342
|
+
};
|
|
4343
|
+
});
|
|
4344
|
+
for (const outputIndex of expectedParents.keys()) {
|
|
4345
|
+
if (!seenIndexes.has(outputIndex)) throw new GenerationOutputManifestError(`Missing generation output_index: ${outputIndex}`);
|
|
4346
|
+
}
|
|
4347
|
+
outputs.sort((left, right) => left.output_index - right.output_index);
|
|
4348
|
+
const seenPaths = /* @__PURE__ */ new Set();
|
|
4349
|
+
const resolvedOutputs = outputs.map((output) => {
|
|
4350
|
+
const filePath = nonEmptyText(options.resolveFilePath(output.file_path), `Generation output ${output.output_index} resolved file_path`);
|
|
4351
|
+
if (seenPaths.has(filePath)) throw new GenerationOutputManifestError(`Duplicate generation output file_path: ${filePath}`);
|
|
4352
|
+
seenPaths.add(filePath);
|
|
4353
|
+
return { ...output, file_path: filePath };
|
|
4354
|
+
});
|
|
4355
|
+
return {
|
|
4356
|
+
schema_version: generationOutputManifestSchemaVersion,
|
|
4357
|
+
job_id: job.id,
|
|
4358
|
+
outputs: resolvedOutputs
|
|
4359
|
+
};
|
|
4360
|
+
}
|
|
4361
|
+
|
|
4362
|
+
// src/server/generationReceipts.ts
|
|
4363
|
+
var legacyAdapterVersion = "generation-receipts-v1";
|
|
4364
|
+
var manifestAdapterVersion = "generation-receipts-v2";
|
|
4114
4365
|
var provider = "codex-handoff";
|
|
4115
4366
|
var GenerationReceiptError = class extends Error {
|
|
4116
4367
|
constructor(message, status = 400) {
|
|
@@ -4129,6 +4380,70 @@ function parseJson(value, fallback) {
|
|
|
4129
4380
|
if (!value) return fallback;
|
|
4130
4381
|
return JSON.parse(value);
|
|
4131
4382
|
}
|
|
4383
|
+
function positiveInteger(value) {
|
|
4384
|
+
return Number.isInteger(value) && Number(value) > 0;
|
|
4385
|
+
}
|
|
4386
|
+
function resolveLineageSelection(project) {
|
|
4387
|
+
const rootAssetId = activeLineageWorkspaceRoot(project);
|
|
4388
|
+
if (!rootAssetId) throw new GenerationReceiptError("No active lineage workspace for generation planning");
|
|
4389
|
+
const next = getLineageNextAsset(project, rootAssetId);
|
|
4390
|
+
if (!next.next_asset) throw new GenerationReceiptError(`No clear lineage next asset: ${next.reason}`);
|
|
4391
|
+
if (next.strategy !== "selected") throw new GenerationReceiptError("Generation v1 requires an explicit selected lineage next base");
|
|
4392
|
+
if (next.selection_mode !== "multiple" && next.selection?.asset_id !== next.next_asset.asset_id) throw new GenerationReceiptError("Generation v1 requires one explicit selected lineage next base");
|
|
4393
|
+
return next;
|
|
4394
|
+
}
|
|
4395
|
+
function selectedParents(next) {
|
|
4396
|
+
const parents = next.next_assets.length > 0 ? next.next_assets : next.next_asset ? [next.next_asset] : [];
|
|
4397
|
+
if (parents.length === 0) throw new GenerationReceiptError("Missing lineage next base");
|
|
4398
|
+
return parents;
|
|
4399
|
+
}
|
|
4400
|
+
function parentMappings(next, perBaseCount) {
|
|
4401
|
+
return selectedParents(next).map((parent, parentIndex) => ({
|
|
4402
|
+
parent,
|
|
4403
|
+
output_indexes: Array.from({ length: perBaseCount }, (_value, index) => parentIndex * perBaseCount + index)
|
|
4404
|
+
}));
|
|
4405
|
+
}
|
|
4406
|
+
function buildHandoff(project, id, prompt, count, perBaseCount, next, inputs) {
|
|
4407
|
+
const parent = next.next_asset;
|
|
4408
|
+
if (!parent) throw new GenerationReceiptError("Missing lineage next base");
|
|
4409
|
+
const parents = parentMappings(next, perBaseCount);
|
|
4410
|
+
const outputManifest = createGenerationOutputManifestDraft({ id, expected_output_count: count, inputs });
|
|
4411
|
+
const importCommand = `${lineagePublicPackageCommand()} generate image import --project ${quote(project)} --job-id ${quote(id)} --manifest ${quote(".asset-scratch/generation-output-manifest.json")} --confirm-write ${lineageRuntimeSelector()} --json`;
|
|
4412
|
+
return {
|
|
4413
|
+
schema_version: "lineage.generation_handoff.v2",
|
|
4414
|
+
provider,
|
|
4415
|
+
project,
|
|
4416
|
+
job_id: id,
|
|
4417
|
+
prompt,
|
|
4418
|
+
expected_output_count: count,
|
|
4419
|
+
per_base_count: next.selection_mode === "multiple" ? perBaseCount : void 0,
|
|
4420
|
+
lineage: {
|
|
4421
|
+
root_asset_id: next.root_asset_id,
|
|
4422
|
+
parent_asset_id: parent.asset_id,
|
|
4423
|
+
selection_strategy: next.strategy,
|
|
4424
|
+
parent_title: parent.title,
|
|
4425
|
+
parent_local_path: parent.local_path,
|
|
4426
|
+
parent_s3_key: parent.s3_key,
|
|
4427
|
+
parents: parents.length > 1 ? parents.map((mapping) => ({
|
|
4428
|
+
parent_asset_id: mapping.parent.asset_id,
|
|
4429
|
+
parent_title: mapping.parent.title,
|
|
4430
|
+
parent_local_path: mapping.parent.local_path,
|
|
4431
|
+
parent_s3_key: mapping.parent.s3_key,
|
|
4432
|
+
output_indexes: mapping.output_indexes
|
|
4433
|
+
})) : void 0
|
|
4434
|
+
},
|
|
4435
|
+
instructions: [
|
|
4436
|
+
"Use Codex image generation outside Lineage server code.",
|
|
4437
|
+
"Write generated output files under .asset-scratch before import.",
|
|
4438
|
+
"Do not call live provider APIs from the CLI or server.",
|
|
4439
|
+
"Fill every output_manifest entry with its generated file path and a one- or two-word edge summary.",
|
|
4440
|
+
"Import the completed manifest with --confirm-write to persist every lineage child."
|
|
4441
|
+
],
|
|
4442
|
+
import_command: importCommand,
|
|
4443
|
+
output_manifest: outputManifest,
|
|
4444
|
+
guardrails: { live_generation: false, external_services: false, output_root: ".asset-scratch", confirm_write_required: true }
|
|
4445
|
+
};
|
|
4446
|
+
}
|
|
4132
4447
|
function buildRerollHandoff(project, id, prompt, rootAssetId, target, request) {
|
|
4133
4448
|
const importCommand = `${lineagePublicPackageCommand()} reroll import --project ${quote(project)} --job-id ${quote(id)} --file <.asset-scratch-file> --confirm-write ${lineageRuntimeSelector()} --json`;
|
|
4134
4449
|
return {
|
|
@@ -4157,6 +4472,19 @@ function buildRerollHandoff(project, id, prompt, rootAssetId, target, request) {
|
|
|
4157
4472
|
guardrails: { live_generation: false, external_services: false, output_root: ".asset-scratch", confirm_write_required: true }
|
|
4158
4473
|
};
|
|
4159
4474
|
}
|
|
4475
|
+
function inputsFrom(jobIdValue, project, next) {
|
|
4476
|
+
return selectedParents(next).map((parent, position) => ({
|
|
4477
|
+
id: `${jobIdValue}:input:${position}`,
|
|
4478
|
+
job_id: jobIdValue,
|
|
4479
|
+
project_id: project,
|
|
4480
|
+
asset_id: parent.asset_id,
|
|
4481
|
+
root_asset_id: next.root_asset_id,
|
|
4482
|
+
role: "lineage_next_base",
|
|
4483
|
+
position,
|
|
4484
|
+
selection_strategy: next.strategy,
|
|
4485
|
+
selection_snapshot: next
|
|
4486
|
+
}));
|
|
4487
|
+
}
|
|
4160
4488
|
function receiptFrom(row) {
|
|
4161
4489
|
return {
|
|
4162
4490
|
id: String(row.id),
|
|
@@ -4169,6 +4497,7 @@ function receiptFrom(row) {
|
|
|
4169
4497
|
};
|
|
4170
4498
|
}
|
|
4171
4499
|
function outputFrom(row) {
|
|
4500
|
+
const edgeSummary = typeof row.edge_summary === "string" && row.edge_summary.length > 0 ? row.edge_summary : void 0;
|
|
4172
4501
|
return {
|
|
4173
4502
|
id: String(row.id),
|
|
4174
4503
|
job_id: String(row.job_id),
|
|
@@ -4180,7 +4509,8 @@ function outputFrom(row) {
|
|
|
4180
4509
|
content_type: String(row.content_type),
|
|
4181
4510
|
imported_asset_id: String(row.imported_asset_id),
|
|
4182
4511
|
parent_asset_id: String(row.parent_asset_id),
|
|
4183
|
-
imported_at: String(row.imported_at)
|
|
4512
|
+
imported_at: String(row.imported_at),
|
|
4513
|
+
...edgeSummary ? { edge_summary: edgeSummary } : {}
|
|
4184
4514
|
};
|
|
4185
4515
|
}
|
|
4186
4516
|
function loadGenerationJob(database, project, id) {
|
|
@@ -4226,6 +4556,72 @@ function insertReceipt(database, id, type, command, payload) {
|
|
|
4226
4556
|
values (?, ?, ?, 'ok', ?, ?, ?)
|
|
4227
4557
|
`).run(`${id}:receipt:${type}:${Date.now()}`, id, type, command, JSON.stringify(payload), nowIso());
|
|
4228
4558
|
}
|
|
4559
|
+
function planImageGeneration(project = defaultProject, fields) {
|
|
4560
|
+
const prompt = fields.prompt.trim();
|
|
4561
|
+
if (!prompt) throw new GenerationReceiptError("Missing --prompt");
|
|
4562
|
+
if (!fields.fromLineageSelection) throw new GenerationReceiptError("Generation v1 requires --from-lineage-selection");
|
|
4563
|
+
const next = resolveLineageSelection(project);
|
|
4564
|
+
const parentCount = selectedParents(next).length;
|
|
4565
|
+
if (parentCount > 1 && !positiveInteger(fields.perBaseCount)) throw new GenerationReceiptError("Multi-parent generation requires --per-base-count");
|
|
4566
|
+
const perBaseCount = parentCount > 1 ? Number(fields.perBaseCount) : Number(fields.count ?? fields.perBaseCount);
|
|
4567
|
+
const count = parentCount * perBaseCount;
|
|
4568
|
+
if (!positiveInteger(perBaseCount)) throw new GenerationReceiptError("Generation count must be a positive integer");
|
|
4569
|
+
if (fields.count !== void 0 && fields.count !== count) throw new GenerationReceiptError(`Generation count mismatch: expected ${count} from selected bases, received ${fields.count}`);
|
|
4570
|
+
const id = jobId();
|
|
4571
|
+
const inputs = inputsFrom(id, project, next);
|
|
4572
|
+
const handoff = buildHandoff(project, id, prompt, count, perBaseCount, next, inputs);
|
|
4573
|
+
const mappings = parentMappings(next, perBaseCount).map((mapping) => ({ parent_asset_id: mapping.parent.asset_id, output_indexes: mapping.output_indexes }));
|
|
4574
|
+
const timestamp = nowIso();
|
|
4575
|
+
const preview = {
|
|
4576
|
+
id,
|
|
4577
|
+
project_id: project,
|
|
4578
|
+
provider,
|
|
4579
|
+
adapter_version: manifestAdapterVersion,
|
|
4580
|
+
source_mode: "lineage_selection",
|
|
4581
|
+
root_asset_id: next.root_asset_id,
|
|
4582
|
+
prompt,
|
|
4583
|
+
expected_output_count: count,
|
|
4584
|
+
status: "planned",
|
|
4585
|
+
output_dir: ".asset-scratch",
|
|
4586
|
+
handoff,
|
|
4587
|
+
created_at: timestamp,
|
|
4588
|
+
updated_at: timestamp,
|
|
4589
|
+
inputs,
|
|
4590
|
+
outputs: [],
|
|
4591
|
+
receipts: [{
|
|
4592
|
+
id: `${id}:receipt:plan:preview`,
|
|
4593
|
+
job_id: id,
|
|
4594
|
+
receipt_type: "plan",
|
|
4595
|
+
status: "ok",
|
|
4596
|
+
command: "generate image plan",
|
|
4597
|
+
payload: { prompt, expected_output_count: count, per_base_count: parentCount > 1 ? perBaseCount : void 0, lineage: handoff.lineage, parent_mappings: mappings },
|
|
4598
|
+
created_at: timestamp
|
|
4599
|
+
}]
|
|
4600
|
+
};
|
|
4601
|
+
if (fields.dryRun) return { ok: true, command: "generate image plan", project, dryRun: true, wouldWrite: true, job: preview };
|
|
4602
|
+
const database = lineageDb();
|
|
4603
|
+
try {
|
|
4604
|
+
database.exec("BEGIN IMMEDIATE");
|
|
4605
|
+
try {
|
|
4606
|
+
database.prepare(`
|
|
4607
|
+
insert into generation_jobs (
|
|
4608
|
+
id, project_id, provider, adapter_version, source_mode, root_asset_id, prompt,
|
|
4609
|
+
expected_output_count, status, output_dir, handoff_json, created_at, updated_at
|
|
4610
|
+
) values (?, ?, ?, ?, 'lineage_selection', ?, ?, ?, 'planned', ?, ?, ?, ?)
|
|
4611
|
+
`).run(id, project, provider, manifestAdapterVersion, next.root_asset_id, prompt, count, ".asset-scratch", JSON.stringify(handoff), timestamp, timestamp);
|
|
4612
|
+
const insertInput = database.prepare("insert into generation_job_inputs (id, job_id, project_id, asset_id, root_asset_id, role, position, selection_strategy, selection_snapshot_json) values (?, ?, ?, ?, ?, ?, ?, ?, ?)");
|
|
4613
|
+
for (const input of inputs) insertInput.run(input.id, id, project, input.asset_id, input.root_asset_id, input.role, input.position, input.selection_strategy, JSON.stringify(next));
|
|
4614
|
+
insertReceipt(database, id, "plan", "generate image plan", preview.receipts[0].payload);
|
|
4615
|
+
database.exec("COMMIT");
|
|
4616
|
+
} catch (error) {
|
|
4617
|
+
database.exec("ROLLBACK");
|
|
4618
|
+
throw error;
|
|
4619
|
+
}
|
|
4620
|
+
return { ok: true, command: "generate image plan", project, job: loadGenerationJob(database, project, id) };
|
|
4621
|
+
} finally {
|
|
4622
|
+
database.close();
|
|
4623
|
+
}
|
|
4624
|
+
}
|
|
4229
4625
|
function planImageReroll(project = defaultProject, fields) {
|
|
4230
4626
|
const prompt = fields.prompt.trim();
|
|
4231
4627
|
if (!prompt) throw new GenerationReceiptError("Missing --prompt");
|
|
@@ -4270,7 +4666,7 @@ function planImageReroll(project = defaultProject, fields) {
|
|
|
4270
4666
|
id,
|
|
4271
4667
|
project_id: project,
|
|
4272
4668
|
provider,
|
|
4273
|
-
adapter_version:
|
|
4669
|
+
adapter_version: legacyAdapterVersion,
|
|
4274
4670
|
source_mode: "lineage_reroll",
|
|
4275
4671
|
root_asset_id: snapshot.root_asset_id,
|
|
4276
4672
|
prompt,
|
|
@@ -4302,7 +4698,7 @@ function planImageReroll(project = defaultProject, fields) {
|
|
|
4302
4698
|
id, project_id, provider, adapter_version, source_mode, root_asset_id, prompt,
|
|
4303
4699
|
expected_output_count, status, output_dir, handoff_json, created_at, updated_at
|
|
4304
4700
|
) values (?, ?, ?, ?, 'lineage_reroll', ?, ?, 1, 'planned', ?, ?, ?, ?)
|
|
4305
|
-
`).run(id, project, provider,
|
|
4701
|
+
`).run(id, project, provider, legacyAdapterVersion, snapshot.root_asset_id, prompt, ".asset-scratch", JSON.stringify(handoff), timestamp, timestamp);
|
|
4306
4702
|
database.prepare("insert into generation_job_inputs (id, job_id, project_id, asset_id, root_asset_id, role, position, selection_strategy, selection_snapshot_json) values (?, ?, ?, ?, ?, ?, ?, ?, ?)").run(input.id, id, project, input.asset_id, input.root_asset_id, input.role, input.position, input.selection_strategy, JSON.stringify(input.selection_snapshot));
|
|
4307
4703
|
insertReceipt(database, id, "plan", "reroll plan", preview.receipts[0].payload);
|
|
4308
4704
|
database.exec("COMMIT");
|
|
@@ -4315,14 +4711,66 @@ function planImageReroll(project = defaultProject, fields) {
|
|
|
4315
4711
|
database.close();
|
|
4316
4712
|
}
|
|
4317
4713
|
}
|
|
4714
|
+
function parentForOutput(job, outputIndex) {
|
|
4715
|
+
const inputs = job.inputs.filter((input) => input.role === "lineage_next_base");
|
|
4716
|
+
if (inputs.length === 0) throw new GenerationReceiptError("Generation job has no lineage_next_base input");
|
|
4717
|
+
if (inputs.length === 1) return inputs[0].asset_id;
|
|
4718
|
+
if (job.expected_output_count % inputs.length !== 0) throw new GenerationReceiptError("Generation job has invalid parent mapping");
|
|
4719
|
+
return inputs[Math.floor(outputIndex / (job.expected_output_count / inputs.length))]?.asset_id || inputs[inputs.length - 1].asset_id;
|
|
4720
|
+
}
|
|
4721
|
+
function parentInputs(job) {
|
|
4722
|
+
const inputs = job.inputs.filter((input) => input.role === "lineage_next_base");
|
|
4723
|
+
if (inputs.length === 0) throw new GenerationReceiptError("Generation job has no lineage_next_base input");
|
|
4724
|
+
return inputs;
|
|
4725
|
+
}
|
|
4726
|
+
function parentFilesFor(job, parentFiles) {
|
|
4727
|
+
const inputs = parentInputs(job);
|
|
4728
|
+
const expectedPerParent = job.expected_output_count / inputs.length;
|
|
4729
|
+
if (!Number.isInteger(expectedPerParent)) throw new GenerationReceiptError("Generation job has invalid parent mapping");
|
|
4730
|
+
const allowedParents = new Set(inputs.map((input) => input.asset_id));
|
|
4731
|
+
const seenParents = /* @__PURE__ */ new Set();
|
|
4732
|
+
const mapped = [];
|
|
4733
|
+
for (const parentAssetId of Object.keys(parentFiles)) {
|
|
4734
|
+
if (!allowedParents.has(parentAssetId)) throw new GenerationReceiptError(`Unknown generation parent mapping: ${parentAssetId}`);
|
|
4735
|
+
if (seenParents.has(parentAssetId)) throw new GenerationReceiptError(`Duplicate generation parent mapping: ${parentAssetId}`);
|
|
4736
|
+
seenParents.add(parentAssetId);
|
|
4737
|
+
}
|
|
4738
|
+
for (const input of inputs) {
|
|
4739
|
+
const files = (parentFiles[input.asset_id] || []).map((file) => file.trim()).filter(Boolean);
|
|
4740
|
+
if (files.length === 0) throw new GenerationReceiptError(`Missing generation parent mapping for ${input.asset_id}`);
|
|
4741
|
+
if (files.length !== expectedPerParent) throw new GenerationReceiptError(`Parent ${input.asset_id} requires ${expectedPerParent} output file${expectedPerParent === 1 ? "" : "s"}, received ${files.length}`);
|
|
4742
|
+
for (const file of files) mapped.push({ file, parentAssetId: input.asset_id });
|
|
4743
|
+
}
|
|
4744
|
+
return mapped;
|
|
4745
|
+
}
|
|
4746
|
+
function orderedFilesFor(job, files) {
|
|
4747
|
+
return files.map((file, index) => ({ file, parentAssetId: parentForOutput(job, index) }));
|
|
4748
|
+
}
|
|
4749
|
+
function inspectImageGeneration(project = defaultProject, jobIdValue) {
|
|
4750
|
+
if (!jobIdValue) throw new GenerationReceiptError("Missing --job-id");
|
|
4751
|
+
const database = lineageDb();
|
|
4752
|
+
try {
|
|
4753
|
+
return { ok: true, command: "generate image inspect", project, job: loadGenerationJob(database, project, jobIdValue) };
|
|
4754
|
+
} finally {
|
|
4755
|
+
database.close();
|
|
4756
|
+
}
|
|
4757
|
+
}
|
|
4318
4758
|
function isPathInside(child, parent) {
|
|
4319
4759
|
const rel = relative3(parent, child);
|
|
4320
4760
|
return Boolean(rel) && !rel.startsWith("..") && !rel.startsWith("/");
|
|
4321
4761
|
}
|
|
4322
|
-
function
|
|
4762
|
+
function scratchCandidate(file) {
|
|
4323
4763
|
const scratchRoot = resolve5(repoRoot, ".asset-scratch");
|
|
4324
4764
|
const candidate = file.startsWith(".asset-scratch/") || resolve5(file).startsWith(scratchRoot) ? resolve5(repoRoot, file) : resolve5(scratchRoot, file);
|
|
4325
4765
|
if (!isPathInside(candidate, scratchRoot)) throw new GenerationReceiptError(`Import file must be under .asset-scratch: ${file}`);
|
|
4766
|
+
return { candidate, scratchRoot };
|
|
4767
|
+
}
|
|
4768
|
+
function resolveScratchManifestPath(file) {
|
|
4769
|
+
const { candidate, scratchRoot } = scratchCandidate(file);
|
|
4770
|
+
return relative3(scratchRoot, candidate);
|
|
4771
|
+
}
|
|
4772
|
+
function resolveScratchFile(file) {
|
|
4773
|
+
const { candidate, scratchRoot } = scratchCandidate(file);
|
|
4326
4774
|
if (!existsSync8(candidate)) throw new GenerationReceiptError(`Missing import file: ${file}`, 404);
|
|
4327
4775
|
const realScratchRoot = realpathSync2(scratchRoot);
|
|
4328
4776
|
const realCandidate = realpathSync2(candidate);
|
|
@@ -4338,6 +4786,165 @@ function resolveScratchFile(file) {
|
|
|
4338
4786
|
assetId: `local-${checksum.slice(0, 12)}`
|
|
4339
4787
|
};
|
|
4340
4788
|
}
|
|
4789
|
+
function generationManifestConflict() {
|
|
4790
|
+
throw new GenerationReceiptError("Generation import already exists with different output, summary, or provenance", 409);
|
|
4791
|
+
}
|
|
4792
|
+
function confirmManifestRetry(project, job, manifest) {
|
|
4793
|
+
if (job.outputs.length !== manifest.outputs.length) generationManifestConflict();
|
|
4794
|
+
const database = lineageDb();
|
|
4795
|
+
try {
|
|
4796
|
+
for (const expected of manifest.outputs) {
|
|
4797
|
+
const recorded = job.outputs.find((output) => output.output_index === expected.output_index);
|
|
4798
|
+
if (!recorded || recorded.file_path !== expected.file_path || recorded.parent_asset_id !== expected.parent_asset_id || recorded.edge_summary !== expected.edge_summary) generationManifestConflict();
|
|
4799
|
+
const edge = database.prepare(`
|
|
4800
|
+
select summary, summary_created_by, summary_updated_by, summary_updated_at
|
|
4801
|
+
from asset_edges
|
|
4802
|
+
where project_id = ? and parent_asset_id = ? and child_asset_id = ? and relation_type = 'derived_from'
|
|
4803
|
+
`).get(project, recorded.parent_asset_id, recorded.imported_asset_id);
|
|
4804
|
+
if (!edge || edge.summary !== expected.edge_summary || edge.summary_created_by !== "agent" || edge.summary_updated_by !== "agent" || typeof edge.summary_updated_at !== "string" || edge.summary_updated_at.length === 0) generationManifestConflict();
|
|
4805
|
+
}
|
|
4806
|
+
return { ok: true, command: "generate image import", project, job, imported: job.outputs, idempotent: true };
|
|
4807
|
+
} finally {
|
|
4808
|
+
database.close();
|
|
4809
|
+
}
|
|
4810
|
+
}
|
|
4811
|
+
function importImageGenerationOutputs(project = defaultProject, fields) {
|
|
4812
|
+
if (!fields.jobId) throw new GenerationReceiptError("Missing --job-id");
|
|
4813
|
+
if (!fields.confirmWrite) throw new GenerationReceiptError("Generation import requires --confirm-write");
|
|
4814
|
+
const database = lineageDb();
|
|
4815
|
+
let job;
|
|
4816
|
+
try {
|
|
4817
|
+
job = loadGenerationJob(database, project, fields.jobId);
|
|
4818
|
+
} finally {
|
|
4819
|
+
database.close();
|
|
4820
|
+
}
|
|
4821
|
+
if (job.source_mode !== "lineage_selection") throw new GenerationReceiptError(`Generation job is not an image-selection job: ${job.source_mode}`);
|
|
4822
|
+
const hasManifestInput = fields.manifest !== void 0;
|
|
4823
|
+
const hasParentFilesInput = fields.parentFiles !== void 0;
|
|
4824
|
+
const hasFilesInput = fields.files !== void 0;
|
|
4825
|
+
if (hasManifestInput && (hasParentFilesInput || hasFilesInput)) {
|
|
4826
|
+
throw new GenerationReceiptError("Use --manifest or legacy --files/--parent-files, not both");
|
|
4827
|
+
}
|
|
4828
|
+
if (hasParentFilesInput && hasFilesInput) throw new GenerationReceiptError("Use --files or --parent-files, not both");
|
|
4829
|
+
let manifest;
|
|
4830
|
+
let parentFileRows;
|
|
4831
|
+
let mappingStrategy;
|
|
4832
|
+
if (job.adapter_version === manifestAdapterVersion) {
|
|
4833
|
+
if (!hasManifestInput) throw new GenerationReceiptError("New generation jobs require --manifest");
|
|
4834
|
+
manifest = parseGenerationOutputManifest(fields.manifest, job, { resolveFilePath: resolveScratchManifestPath });
|
|
4835
|
+
if (job.status === "imported") return confirmManifestRetry(project, job, manifest);
|
|
4836
|
+
if (job.status !== "planned") throw new GenerationReceiptError(`Generation job is not importable from status: ${job.status}`);
|
|
4837
|
+
parentFileRows = manifest.outputs.map((output) => ({
|
|
4838
|
+
edgeSummary: output.edge_summary,
|
|
4839
|
+
file: output.file_path,
|
|
4840
|
+
parentAssetId: output.parent_asset_id
|
|
4841
|
+
}));
|
|
4842
|
+
mappingStrategy = "generation_output_manifest_v1";
|
|
4843
|
+
} else if (job.adapter_version === legacyAdapterVersion) {
|
|
4844
|
+
if (hasManifestInput) throw new GenerationReceiptError("Already-planned legacy generation jobs require --files or --parent-files");
|
|
4845
|
+
if (job.status !== "planned") throw new GenerationReceiptError(`Generation job is not importable from status: ${job.status}`);
|
|
4846
|
+
const hasExplicitParentFiles = Boolean(fields.parentFiles && Object.keys(fields.parentFiles).length > 0);
|
|
4847
|
+
parentFileRows = hasExplicitParentFiles ? parentFilesFor(job, fields.parentFiles || {}) : orderedFilesFor(job, (fields.files || []).map((file) => file.trim()).filter(Boolean));
|
|
4848
|
+
mappingStrategy = hasExplicitParentFiles ? "explicit_parent_files" : "ordered_per_base";
|
|
4849
|
+
} else {
|
|
4850
|
+
throw new GenerationReceiptError(`Unsupported generation adapter version: ${job.adapter_version}`);
|
|
4851
|
+
}
|
|
4852
|
+
if (parentFileRows.length === 0) throw new GenerationReceiptError("Generation import requires --files or --parent-files");
|
|
4853
|
+
if (parentFileRows.length !== job.expected_output_count) {
|
|
4854
|
+
throw new GenerationReceiptError(`Output count mismatch: expected ${job.expected_output_count}, received ${parentFileRows.length}`);
|
|
4855
|
+
}
|
|
4856
|
+
const resolved = parentFileRows.map((row) => ({ ...resolveScratchFile(row.file), edgeSummary: row.edgeSummary, parentAssetId: row.parentAssetId }));
|
|
4857
|
+
const uniquePaths = new Set(resolved.map((file) => file.relativePath));
|
|
4858
|
+
if (uniquePaths.size !== resolved.length) throw new GenerationReceiptError("Generation import files must be unique");
|
|
4859
|
+
cancelLineageIterateTasksForAssets(project, {
|
|
4860
|
+
actor: "system",
|
|
4861
|
+
confirmWrite: false,
|
|
4862
|
+
rootAssetId: job.root_asset_id
|
|
4863
|
+
});
|
|
4864
|
+
indexLineageAssets(project);
|
|
4865
|
+
const writeDb = lineageDb();
|
|
4866
|
+
try {
|
|
4867
|
+
const timestamp = nowIso();
|
|
4868
|
+
writeDb.exec("BEGIN IMMEDIATE");
|
|
4869
|
+
try {
|
|
4870
|
+
for (const [index, file] of resolved.entries()) {
|
|
4871
|
+
const assetRow = writeDb.prepare("select id from assets where project_id = ? and id = ?").get(project, file.assetId);
|
|
4872
|
+
if (!assetRow) throw new GenerationReceiptError(`Indexed local asset was not found: ${file.relativePath}`);
|
|
4873
|
+
const outputId = `${fields.jobId}:output:${index}`;
|
|
4874
|
+
writeDb.prepare(`insert into generation_job_outputs (
|
|
4875
|
+
id, job_id, project_id, output_index, file_path, checksum_sha256, size_bytes, content_type,
|
|
4876
|
+
imported_asset_id, parent_asset_id, imported_at, edge_summary
|
|
4877
|
+
) values (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`).run(
|
|
4878
|
+
outputId,
|
|
4879
|
+
fields.jobId,
|
|
4880
|
+
project,
|
|
4881
|
+
index,
|
|
4882
|
+
file.relativePath,
|
|
4883
|
+
file.checksum,
|
|
4884
|
+
file.size,
|
|
4885
|
+
file.contentType,
|
|
4886
|
+
file.assetId,
|
|
4887
|
+
file.parentAssetId,
|
|
4888
|
+
timestamp,
|
|
4889
|
+
file.edgeSummary || null
|
|
4890
|
+
);
|
|
4891
|
+
const insertedEdge = writeDb.prepare(`insert into asset_edges (
|
|
4892
|
+
id, project_id, parent_asset_id, child_asset_id, relation_type, created_at,
|
|
4893
|
+
summary, summary_created_by, summary_updated_by, summary_updated_at
|
|
4894
|
+
) values (?, ?, ?, ?, 'derived_from', ?, ?, ?, ?, ?)
|
|
4895
|
+
on conflict(project_id, parent_asset_id, child_asset_id, relation_type) do nothing`).run(
|
|
4896
|
+
`${project}:${file.parentAssetId}:derived_from:${file.assetId}`,
|
|
4897
|
+
project,
|
|
4898
|
+
file.parentAssetId,
|
|
4899
|
+
file.assetId,
|
|
4900
|
+
timestamp,
|
|
4901
|
+
file.edgeSummary || null,
|
|
4902
|
+
file.edgeSummary ? "agent" : null,
|
|
4903
|
+
file.edgeSummary ? "agent" : null,
|
|
4904
|
+
file.edgeSummary ? timestamp : null
|
|
4905
|
+
);
|
|
4906
|
+
if (file.edgeSummary && insertedEdge.changes === 0) {
|
|
4907
|
+
const edge = writeDb.prepare(`
|
|
4908
|
+
select summary, summary_created_by, summary_updated_by, summary_updated_at
|
|
4909
|
+
from asset_edges
|
|
4910
|
+
where project_id = ? and parent_asset_id = ? and child_asset_id = ? and relation_type = 'derived_from'
|
|
4911
|
+
`).get(project, file.parentAssetId, file.assetId);
|
|
4912
|
+
if (!edge || edge.summary !== file.edgeSummary || edge.summary_created_by !== "agent" || edge.summary_updated_by !== "agent" || typeof edge.summary_updated_at !== "string" || edge.summary_updated_at.length === 0) generationManifestConflict();
|
|
4913
|
+
}
|
|
4914
|
+
}
|
|
4915
|
+
writeDb.prepare(`
|
|
4916
|
+
update generation_jobs
|
|
4917
|
+
set status = 'imported', imported_at = ?, updated_at = ?
|
|
4918
|
+
where project_id = ? and id = ?
|
|
4919
|
+
`).run(timestamp, timestamp, project, fields.jobId);
|
|
4920
|
+
insertReceipt(writeDb, fields.jobId, "import", "generate image import", {
|
|
4921
|
+
mapping_strategy: mappingStrategy,
|
|
4922
|
+
files: resolved.map((file, index) => ({
|
|
4923
|
+
output_index: index,
|
|
4924
|
+
file_path: file.relativePath,
|
|
4925
|
+
imported_asset_id: file.assetId,
|
|
4926
|
+
parent_asset_id: file.parentAssetId,
|
|
4927
|
+
...file.edgeSummary ? { edge_summary: file.edgeSummary } : {}
|
|
4928
|
+
})),
|
|
4929
|
+
selection_reset: { root_asset_id: job.root_asset_id, cleared: true }
|
|
4930
|
+
});
|
|
4931
|
+
writeDb.prepare("delete from asset_selections where project_id = ? and root_asset_id = ?").run(project, job.root_asset_id);
|
|
4932
|
+
writeDb.exec("COMMIT");
|
|
4933
|
+
} catch (error) {
|
|
4934
|
+
writeDb.exec("ROLLBACK");
|
|
4935
|
+
throw error;
|
|
4936
|
+
}
|
|
4937
|
+
cancelLineageIterateTasksForAssets(project, {
|
|
4938
|
+
actor: "system",
|
|
4939
|
+
confirmWrite: true,
|
|
4940
|
+
rootAssetId: job.root_asset_id
|
|
4941
|
+
});
|
|
4942
|
+
const importedJob = loadGenerationJob(writeDb, project, fields.jobId);
|
|
4943
|
+
return { ok: true, command: "generate image import", project, job: importedJob, imported: importedJob.outputs };
|
|
4944
|
+
} finally {
|
|
4945
|
+
writeDb.close();
|
|
4946
|
+
}
|
|
4947
|
+
}
|
|
4341
4948
|
function importImageRerollOutput(project = defaultProject, fields) {
|
|
4342
4949
|
if (!fields.jobId) throw new GenerationReceiptError("Missing --job-id");
|
|
4343
4950
|
if (!fields.confirmWrite) throw new GenerationReceiptError("Generation import requires --confirm-write");
|
|
@@ -5241,22 +5848,23 @@ var ManagedWriterRoutingError = class extends Error {
|
|
|
5241
5848
|
status;
|
|
5242
5849
|
};
|
|
5243
5850
|
function managedWriterTimeoutMs(command, args) {
|
|
5244
|
-
const
|
|
5245
|
-
|
|
5851
|
+
const positions = args.filter((arg) => !arg.startsWith("-"));
|
|
5852
|
+
const slowImport = command === "reroll" && positions[0] === "import" || command === "generate" && positions[0] === "image" && positions[1] === "import";
|
|
5853
|
+
return slowImport ? 5 * 6e4 : 3e4;
|
|
5246
5854
|
}
|
|
5247
5855
|
function errorMessage(body, fallback) {
|
|
5248
5856
|
if (!body || typeof body !== "object" || Array.isArray(body)) return fallback;
|
|
5249
|
-
const
|
|
5250
|
-
if (typeof
|
|
5251
|
-
if (typeof
|
|
5857
|
+
const record2 = body;
|
|
5858
|
+
if (typeof record2.message === "string") return record2.message;
|
|
5859
|
+
if (typeof record2.error === "string") return record2.error;
|
|
5252
5860
|
return fallback;
|
|
5253
5861
|
}
|
|
5254
5862
|
function agentClaimError(body, status) {
|
|
5255
5863
|
if (!body || typeof body !== "object" || Array.isArray(body)) return void 0;
|
|
5256
|
-
const
|
|
5257
|
-
if (typeof
|
|
5258
|
-
const conflicts = Array.isArray(
|
|
5259
|
-
return new AgentClaimError(
|
|
5864
|
+
const record2 = body;
|
|
5865
|
+
if (typeof record2.message !== "string" || typeof record2.error !== "string") return void 0;
|
|
5866
|
+
const conflicts = Array.isArray(record2.conflicts) ? record2.conflicts : [];
|
|
5867
|
+
return new AgentClaimError(record2.message, status, record2.error, conflicts);
|
|
5260
5868
|
}
|
|
5261
5869
|
function assertResponseIdentity(body, profile, channel) {
|
|
5262
5870
|
if (!body || typeof body !== "object" || Array.isArray(body)) {
|
|
@@ -5354,6 +5962,26 @@ function readOptions(args, name) {
|
|
|
5354
5962
|
}
|
|
5355
5963
|
return values;
|
|
5356
5964
|
}
|
|
5965
|
+
function readJsonFile(path, label) {
|
|
5966
|
+
try {
|
|
5967
|
+
return JSON.parse(readFileSync6(resolve8(path), "utf8"));
|
|
5968
|
+
} catch (error) {
|
|
5969
|
+
throw new Error(`${label} must be a readable JSON file: ${error instanceof Error ? error.message : String(error)}`, { cause: error });
|
|
5970
|
+
}
|
|
5971
|
+
}
|
|
5972
|
+
function parentFilesOption(value) {
|
|
5973
|
+
if (value === void 0) return void 0;
|
|
5974
|
+
const mappings = {};
|
|
5975
|
+
for (const rawMapping of value.split(";")) {
|
|
5976
|
+
const separator = rawMapping.indexOf("=");
|
|
5977
|
+
const parent = separator >= 0 ? rawMapping.slice(0, separator).trim() : "";
|
|
5978
|
+
const files = separator >= 0 ? rawMapping.slice(separator + 1).split(",").map((file) => file.trim()).filter(Boolean) : [];
|
|
5979
|
+
if (!parent || files.length === 0) throw new Error("lineage generate image import --parent-files requires parent=file[,file][;parent=file]");
|
|
5980
|
+
if (Object.hasOwn(mappings, parent)) throw new Error(`Duplicate generation parent mapping: ${parent}`);
|
|
5981
|
+
mappings[parent] = files;
|
|
5982
|
+
}
|
|
5983
|
+
return mappings;
|
|
5984
|
+
}
|
|
5357
5985
|
function resolveStartOptions(config, args) {
|
|
5358
5986
|
const profile = prepareCliProfile(config, args);
|
|
5359
5987
|
const serviceUrl = profile ? new URL(profile.service_origin) : void 0;
|
|
@@ -5446,7 +6074,11 @@ Usage:
|
|
|
5446
6074
|
${config.binName} brief [--project <project>] [--root <asset-id>] [--db <path>] [--json]
|
|
5447
6075
|
${config.binName} inspect --asset-id <asset-id> [--project <project>] [--db <path>] [--json]
|
|
5448
6076
|
${config.binName} selection packet [--project <project>] [--workspace <id-or-root>|--root <asset-id>] [--channel <channel>] [--campaign <campaign>] [--context-notes <text>] [--label <label>] [--schema v2] [--out <path>] [--strict] [--db <path>] [--json]
|
|
5449
|
-
${config.binName} link-child --root <asset-id> --child <asset-id> [--project <project>] [--claim-token <claim-id.secret>] [--confirm-write] [--db <path>] [--json]
|
|
6077
|
+
${config.binName} link-child --root <asset-id> --child <asset-id> --summary "<one-or-two-words>" [--project <project>] [--claim-token <claim-id.secret>] [--confirm-write] [--db <path>] [--json]
|
|
6078
|
+
${config.binName} generate image plan --prompt <text> --from-lineage-selection [--count <count>|--per-base-count <count>] [--project <project>] [--dry-run] [--db <path>] [--json]
|
|
6079
|
+
${config.binName} generate image inspect --job-id <job-id> [--project <project>] [--db <path>] [--json]
|
|
6080
|
+
${config.binName} generate image import --job-id <job-id> --manifest <json-file> --confirm-write [--project <project>] [--db <path>] [--json]
|
|
6081
|
+
${config.binName} generate image import --job-id <legacy-job-id> (--files <file,file>|--parent-files <parent=file;parent=file>) --confirm-write [--project <project>] [--db <path>] [--json]
|
|
5450
6082
|
${config.binName} reroll list --root <asset-id> [--project <project>] [--db <path>] [--json]
|
|
5451
6083
|
${config.binName} reroll mark --root <asset-id> --target <asset-id> [--notes <text>] [--requested-by agent|human|system] [--project <project>] [--confirm-write] [--db <path>] [--json]
|
|
5452
6084
|
${config.binName} reroll cancel --root <asset-id> --target <asset-id> [--project <project>] [--confirm-write] [--db <path>] [--json]
|
|
@@ -5568,7 +6200,8 @@ function resolveDataCommandOptions(args) {
|
|
|
5568
6200
|
dbPath: readOption(args, "--db"),
|
|
5569
6201
|
json: args.includes("--json"),
|
|
5570
6202
|
project: readOption(args, "--project") || process.env.LINEAGE_DEFAULT_PRODUCT || defaultProduct,
|
|
5571
|
-
rootAssetId: readOption(args, "--root")
|
|
6203
|
+
rootAssetId: readOption(args, "--root"),
|
|
6204
|
+
summary: readOption(args, "--summary")
|
|
5572
6205
|
};
|
|
5573
6206
|
if (options.dbPath) process.env.LINEAGE_DB = options.dbPath;
|
|
5574
6207
|
return options;
|
|
@@ -5611,13 +6244,57 @@ function runLineageDataCommand(command, args) {
|
|
|
5611
6244
|
}
|
|
5612
6245
|
if (command === "link-child") {
|
|
5613
6246
|
if (!options.childAssetId) throw new Error("lineage link-child requires --child");
|
|
6247
|
+
const summary = requireEdgeSummary(options.summary);
|
|
5614
6248
|
return linkSelectedLineageChild(options.project, {
|
|
5615
6249
|
childAssetId: options.childAssetId,
|
|
5616
6250
|
claimToken: options.claimToken,
|
|
5617
6251
|
confirmWrite: options.confirmWrite,
|
|
5618
|
-
rootAssetId: options.rootAssetId || options.assetId
|
|
6252
|
+
rootAssetId: options.rootAssetId || options.assetId,
|
|
6253
|
+
summary,
|
|
6254
|
+
summaryActor: "agent"
|
|
5619
6255
|
});
|
|
5620
6256
|
}
|
|
6257
|
+
if (command === "generate") {
|
|
6258
|
+
const [resource, subcommand] = positionalArgs(args);
|
|
6259
|
+
if (resource !== "image") throw new Error(`Unknown generate command: ${resource}`);
|
|
6260
|
+
if (subcommand === "plan") {
|
|
6261
|
+
const prompt = readOption(args, "--prompt");
|
|
6262
|
+
const rawCount = readOption(args, "--count");
|
|
6263
|
+
const rawPerBaseCount = readOption(args, "--per-base-count");
|
|
6264
|
+
if (!prompt) throw new Error("lineage generate image plan requires --prompt");
|
|
6265
|
+
return planImageGeneration(options.project, {
|
|
6266
|
+
count: rawCount === void 0 ? void 0 : Number(rawCount),
|
|
6267
|
+
dryRun: args.includes("--dry-run"),
|
|
6268
|
+
fromLineageSelection: args.includes("--from-lineage-selection"),
|
|
6269
|
+
perBaseCount: rawPerBaseCount === void 0 ? void 0 : Number(rawPerBaseCount),
|
|
6270
|
+
prompt
|
|
6271
|
+
});
|
|
6272
|
+
}
|
|
6273
|
+
if (subcommand === "inspect") {
|
|
6274
|
+
const jobId2 = readOption(args, "--job-id");
|
|
6275
|
+
if (!jobId2) throw new Error("lineage generate image inspect requires --job-id");
|
|
6276
|
+
return inspectImageGeneration(options.project, jobId2);
|
|
6277
|
+
}
|
|
6278
|
+
if (subcommand === "import") {
|
|
6279
|
+
const jobId2 = readOption(args, "--job-id");
|
|
6280
|
+
const manifestPath = readOption(args, "--manifest");
|
|
6281
|
+
const filesValue = readOption(args, "--files");
|
|
6282
|
+
const parentFilesValue = readOption(args, "--parent-files");
|
|
6283
|
+
if (!jobId2) throw new Error("lineage generate image import requires --job-id");
|
|
6284
|
+
if (manifestPath !== void 0 && (filesValue !== void 0 || parentFilesValue !== void 0)) {
|
|
6285
|
+
throw new Error("Use --manifest or legacy --files/--parent-files, not both");
|
|
6286
|
+
}
|
|
6287
|
+
if (filesValue !== void 0 && parentFilesValue !== void 0) throw new Error("Use --files or --parent-files, not both");
|
|
6288
|
+
return importImageGenerationOutputs(options.project, {
|
|
6289
|
+
confirmWrite: options.confirmWrite,
|
|
6290
|
+
files: filesValue === void 0 ? void 0 : filesValue.split(",").map((file) => file.trim()).filter(Boolean),
|
|
6291
|
+
jobId: jobId2,
|
|
6292
|
+
manifest: manifestPath === void 0 ? void 0 : readJsonFile(manifestPath, "Generation output manifest"),
|
|
6293
|
+
parentFiles: parentFilesOption(parentFilesValue)
|
|
6294
|
+
});
|
|
6295
|
+
}
|
|
6296
|
+
throw new Error(`Unknown generate image command: ${subcommand}`);
|
|
6297
|
+
}
|
|
5621
6298
|
if (command === "reroll") {
|
|
5622
6299
|
const subcommand = positionalArgs(args)[0] || "";
|
|
5623
6300
|
if (subcommand === "list") {
|
|
@@ -5771,6 +6448,15 @@ function printDataResult(command, result, json) {
|
|
|
5771
6448
|
if (link.warning) console.log(`Warning: ${link.warning}`);
|
|
5772
6449
|
return;
|
|
5773
6450
|
}
|
|
6451
|
+
if (command === "generate" && result && typeof result === "object" && "job" in result) {
|
|
6452
|
+
const generation = result;
|
|
6453
|
+
if (generation.imported) {
|
|
6454
|
+
console.log(`${generation.idempotent ? "Already imported" : "Imported"} ${generation.imported.length} output(s) for ${generation.job?.id || "job"}`);
|
|
6455
|
+
} else {
|
|
6456
|
+
console.log(`Generation job ${generation.job?.id || "job"} ${generation.job?.status || "unknown"}`);
|
|
6457
|
+
}
|
|
6458
|
+
return;
|
|
6459
|
+
}
|
|
5774
6460
|
if (command === "reroll" && result && typeof result === "object") {
|
|
5775
6461
|
if ("requests" in result) {
|
|
5776
6462
|
const listed = result;
|
|
@@ -5916,7 +6602,9 @@ function printRuntimeResult(result, json) {
|
|
|
5916
6602
|
}
|
|
5917
6603
|
function lineageCliRequiresWriterLease(command, args) {
|
|
5918
6604
|
if (command === "next" || command === "brief" || command === "inspect" || command === "selection") return false;
|
|
5919
|
-
const
|
|
6605
|
+
const positions = positionalArgs(args);
|
|
6606
|
+
const subcommand = positions[0] || "";
|
|
6607
|
+
if (command === "generate") return positions[0] !== "image" || positions[1] !== "inspect";
|
|
5920
6608
|
if (command === "reroll") return subcommand !== "list";
|
|
5921
6609
|
if (command === "tasks") return subcommand !== "list" && subcommand !== "inspect";
|
|
5922
6610
|
if (command === "db") return subcommand !== "info";
|
|
@@ -6256,7 +6944,7 @@ async function runLineageCli(config, args = process.argv.slice(2)) {
|
|
|
6256
6944
|
else console.error(`${config.binName}: ${message2}`);
|
|
6257
6945
|
process.exit(1);
|
|
6258
6946
|
}
|
|
6259
|
-
if (command === "next" || command === "brief" || command === "inspect" || command === "selection" || command === "link-child" || command === "reroll" || command === "tasks") {
|
|
6947
|
+
if (command === "next" || command === "brief" || command === "inspect" || command === "selection" || command === "link-child" || command === "generate" || command === "reroll" || command === "tasks") {
|
|
6260
6948
|
const commandArgs = normalizedArgs.slice(1);
|
|
6261
6949
|
const json2 = commandArgs.includes("--json");
|
|
6262
6950
|
try {
|