@gentbajko/slopify 3.2.1 → 3.2.3
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/adapters/image/codex.js +14 -0
- package/dist/edge/http/actions.js +2 -2
- package/dist/extension/slopify-studio-chrome.zip +0 -0
- package/dist/extension/slopify-studio-firefox.zip +0 -0
- package/dist/patch-notes/3.2.2.md +15 -0
- package/dist/patch-notes/3.2.3.md +21 -0
- package/dist/patch-notes/index.json +12 -0
- package/dist/slices/fixes/rules.js +3 -1
- package/dist/slices/rebuild/runtime-provider.js +2 -1
- package/dist/slices/rebuild/runtime-shorts.js +14 -5
- package/dist/slices/rebuild/soften.js +15 -0
- package/dist/web/assets/{index-D4sTCEIy.js → index-CB4df3Jm.js} +27 -27
- package/dist/web/assets/{pdf-DjcINuld.js → pdf-BKeG-67K.js} +1 -1
- package/dist/web/index.html +1 -1
- package/package.json +1 -1
|
@@ -149,6 +149,8 @@ export function codexImageArgs(req, directory) {
|
|
|
149
149
|
codexImageInstructions(req, reference, directory),
|
|
150
150
|
];
|
|
151
151
|
}
|
|
152
|
+
// How a blocked image shows: the tool's error on stderr, or the agent's reply.
|
|
153
|
+
const blocked = /moderation_blocked|rejected by the safety system|safety (?:filter|system)|content polic|refus/i;
|
|
152
154
|
const failure = z.object({ error: z.object({ message: z.string() }) });
|
|
153
155
|
const turnUsage = z.object({
|
|
154
156
|
usage: z
|
|
@@ -226,6 +228,8 @@ export function codexImage(deps) {
|
|
|
226
228
|
}
|
|
227
229
|
let completed = false;
|
|
228
230
|
let unavailable = false;
|
|
231
|
+
// What the agent said last: when its image tool is blocked it draws nothing and says why.
|
|
232
|
+
let said = "";
|
|
229
233
|
for await (const line of lines(bounded(run.stdout, maxEventBytes), req.signal)) {
|
|
230
234
|
if (line.trim() === "")
|
|
231
235
|
continue;
|
|
@@ -288,6 +292,8 @@ export function codexImage(deps) {
|
|
|
288
292
|
}
|
|
289
293
|
else if (event.type === "item.completed") {
|
|
290
294
|
const { item: value } = cliShaped(binary, item, event.value);
|
|
295
|
+
if (value.type === "agent_message" && value.text)
|
|
296
|
+
said = value.text;
|
|
291
297
|
if (value.type === "agent_message" &&
|
|
292
298
|
/image.generation.*unavailable|cannot generate images|no image tool/i.test(value.text ?? ""))
|
|
293
299
|
unavailable = true;
|
|
@@ -315,6 +321,14 @@ export function codexImage(deps) {
|
|
|
315
321
|
kind: "unsupported",
|
|
316
322
|
message: `The Codex CLI cannot make images. ${cannotDraw}`,
|
|
317
323
|
});
|
|
324
|
+
// The image tool's safety system refused every picture, so the run ended without one.
|
|
325
|
+
if (threadId !== undefined &&
|
|
326
|
+
codexImageCount(env, threadId) === 0 &&
|
|
327
|
+
(blocked.test(run.stderr()) || blocked.test(said)))
|
|
328
|
+
throw providerError({
|
|
329
|
+
kind: "refusal",
|
|
330
|
+
message: refusedImage("The Codex CLI", redact(said.trim())),
|
|
331
|
+
});
|
|
318
332
|
const image = codexGeneratedImage(env, threadId, startedAt);
|
|
319
333
|
const limits = await readingOf(before, deps.readLimits);
|
|
320
334
|
return {
|
|
@@ -11,7 +11,7 @@ import { revisionControlSchema, } from "../../slices/control/revision-control-sc
|
|
|
11
11
|
import { recoverProject } from "../../slices/rebuild/recovery.js";
|
|
12
12
|
import { recoveryResultSchema } from "../../slices/rebuild/recovery-model.js";
|
|
13
13
|
import { resumable } from "../../slices/rebuild/recovery-repo.js";
|
|
14
|
-
import { clearSoftening,
|
|
14
|
+
import { clearSoftening, requestSoftening, softenableKeys } from "../../slices/rebuild/soften.js";
|
|
15
15
|
import { adoptBaseline } from "../../slices/revisions/adopt.js";
|
|
16
16
|
import { currentRevisionId } from "../../slices/revisions/repo.js";
|
|
17
17
|
import { allowsCustomModel } from "../../slices/settings/models.js";
|
|
@@ -218,7 +218,7 @@ export function actionRoutes(deps) {
|
|
|
218
218
|
// AI model when the retry draws them again (`slices/rebuild/soften.ts`).
|
|
219
219
|
.post("/:id/stages/:kind/soften", zValidator("param", stageParam, onInvalid), zValidator("json", revisionControlSchema, onInvalid), async (c) => {
|
|
220
220
|
const { id, kind } = c.req.valid("param");
|
|
221
|
-
const keys =
|
|
221
|
+
const keys = softenableKeys(deps.db, id, kind);
|
|
222
222
|
if (keys.length === 0)
|
|
223
223
|
return problem(c, {
|
|
224
224
|
status: 409,
|
|
Binary file
|
|
Binary file
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
# Slopify 3.2.2
|
|
2
|
+
|
|
3
|
+
Released 1 October 2026.
|
|
4
|
+
|
|
5
|
+
3.2.2 names a refused short's picture for what it is and lets Soften and retry fix it.
|
|
6
|
+
|
|
7
|
+
## Shorts
|
|
8
|
+
|
|
9
|
+
- When the Codex CLI's image tool refuses a picture under its safety rules, the step now says so ("refused to make this image under its content rules", with Codex's own reason) instead of "the saved file could not be read".
|
|
10
|
+
- A short's refused picture can be fixed with **Soften and retry** in the Video section, as Images and Thumbnail pictures already could: the project's AI model rewords the prompt without what the filter flagged, then the picture is drawn again.
|
|
11
|
+
|
|
12
|
+
## Upgrading from 3.2.1
|
|
13
|
+
|
|
14
|
+
- **Nothing becomes outdated.**
|
|
15
|
+
- A short that already failed with "the saved file could not be read" needs one more **Try again**; if the picture is refused again, the Soften and retry button appears.
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
# Slopify 3.2.3
|
|
2
|
+
|
|
3
|
+
Released 1 October 2026.
|
|
4
|
+
|
|
5
|
+
3.2.3 makes Soften and retry actually reword a short's refused picture, and shows a render's progress on Home.
|
|
6
|
+
|
|
7
|
+
## Shorts
|
|
8
|
+
|
|
9
|
+
- **Soften and retry** on a short's picture now draws the reworded prompt. In 3.2.2 the button appeared, but the picture was asked for with its original wording again and was refused again.
|
|
10
|
+
|
|
11
|
+
## Home
|
|
12
|
+
|
|
13
|
+
- **Running now** shows what a running step is doing and how far it is ("Rendering the video (45%)"), as the project page does, instead of a step count that stood still through the whole render.
|
|
14
|
+
|
|
15
|
+
## Thumbnails
|
|
16
|
+
|
|
17
|
+
- Regenerating a thumbnail asks with its own sentence: it makes a new thumbnail and leaves the video and shorts alone. It used the image's wording, which said the video would be rendered again.
|
|
18
|
+
|
|
19
|
+
## Upgrading from 3.2.2
|
|
20
|
+
|
|
21
|
+
- **Nothing becomes outdated.**
|
|
@@ -1,4 +1,16 @@
|
|
|
1
1
|
[
|
|
2
|
+
{
|
|
3
|
+
"id": "3.2.3",
|
|
4
|
+
"title": "Slopify 3.2.3",
|
|
5
|
+
"version": "3.2.3",
|
|
6
|
+
"date": "2026-10-01"
|
|
7
|
+
},
|
|
8
|
+
{
|
|
9
|
+
"id": "3.2.2",
|
|
10
|
+
"title": "Slopify 3.2.2",
|
|
11
|
+
"version": "3.2.2",
|
|
12
|
+
"date": "2026-10-01"
|
|
13
|
+
},
|
|
2
14
|
{
|
|
3
15
|
"id": "3.2.1",
|
|
4
16
|
"title": "Slopify 3.2.1",
|
|
@@ -11,6 +11,8 @@ const retiredModel = /\bunknown model\b|\bmodel\b[^\n]{0,80}?\b(?:not found|does
|
|
|
11
11
|
const keyTrouble = /did not accept the API key|API key is saved|invalid api key|key may be wrong/i;
|
|
12
12
|
// Stages whose failed call draws an image from a prompt, which Soften and retry can reword.
|
|
13
13
|
const drawn = ["images", "thumbnail"];
|
|
14
|
+
// A short's still is drawn in the Video stage; its failure names it ("Short 4 image 6: …").
|
|
15
|
+
const shortStill = /^Short \d+ image \d+:/;
|
|
14
16
|
export function fixFor(step) {
|
|
15
17
|
const reason = step.reason ?? "";
|
|
16
18
|
const cli = cliOf(step.provider, reason);
|
|
@@ -24,7 +26,7 @@ export function fixFor(step) {
|
|
|
24
26
|
if (diskFull.test(reason))
|
|
25
27
|
return { kind: "free-space", label: "Free space" };
|
|
26
28
|
if (step.kind === "refusal" || refusedWords.test(reason))
|
|
27
|
-
return drawn.includes(step.stage)
|
|
29
|
+
return drawn.includes(step.stage) || (step.stage === "video" && shortStill.test(reason))
|
|
28
30
|
? { kind: "refused", label: "Soften and retry", soften: true }
|
|
29
31
|
: { kind: "refused", label: "Edit the prompt", soften: false };
|
|
30
32
|
if (retiredModel.test(reason))
|
|
@@ -171,7 +171,8 @@ export async function executeProviderRecipe(deps, context, providers, piece) {
|
|
|
171
171
|
}
|
|
172
172
|
// Soften and retry (`soften.ts`): the project's AI model rewords a refused image prompt
|
|
173
173
|
// before the image is drawn again. Undefined when nobody asked, or the project has no model.
|
|
174
|
-
|
|
174
|
+
// A short's stills are drawn in `runtime-shorts.ts`, which asks the same way.
|
|
175
|
+
export async function softenIfAsked(deps, context, providers, piece, prompt, label) {
|
|
175
176
|
if (!softeningRequested(deps.db, context.work.projectId, piece.key))
|
|
176
177
|
return undefined;
|
|
177
178
|
const llm = executionView(deps, context.work.projectId, context.work.revisionId)?.revision.config
|
|
@@ -21,8 +21,10 @@ import { figureShots } from "./runtime-export-edit.js";
|
|
|
21
21
|
import { exportSnapshot, revisionAudio } from "./runtime-export-inputs.js";
|
|
22
22
|
import { imageCall } from "./runtime-image.js";
|
|
23
23
|
import { savedCatalogue } from "./runtime-plan.js";
|
|
24
|
+
import { softenIfAsked } from "./runtime-provider.js";
|
|
24
25
|
import { preparedResult, preparedTexts, publishResult } from "./runtime-publication.js";
|
|
25
26
|
import { wordsSchema } from "./runtime-subtitles.js";
|
|
27
|
+
import { clearSoftening } from "./soften.js";
|
|
26
28
|
// The Shorts step's four kinds of work (`recipe-shorts.ts`): the pick, each clip's image
|
|
27
29
|
// prompts, each image, and each clip's render. Each is one piece of its own, so a failure
|
|
28
30
|
// names the clip it belongs to and Try again redoes only what did not finish.
|
|
@@ -173,9 +175,14 @@ async function image(deps, context, providers, piece, clip, index) {
|
|
|
173
175
|
const input = piece.input;
|
|
174
176
|
if (input.kind !== "image")
|
|
175
177
|
throw setupError();
|
|
176
|
-
const
|
|
177
|
-
|
|
178
|
-
|
|
178
|
+
const wrapped = providers.forPiece(piece.id);
|
|
179
|
+
const label = `Short ${String(clip.number)} image ${String(index)}`;
|
|
180
|
+
// Soften and retry reworded this still's refused prompt: draw that one instead.
|
|
181
|
+
const softened = await softenIfAsked(deps, context, wrapped, piece, input.prompt, label);
|
|
182
|
+
if (softened === "held")
|
|
183
|
+
return "held";
|
|
184
|
+
const prompt = softened ?? input.prompt;
|
|
185
|
+
const made = await wrapped.image(imageCall(deps, context.work.projectId, { ...input, prompt }, label));
|
|
179
186
|
if (!made.ok)
|
|
180
187
|
return "held";
|
|
181
188
|
const asset = writeAsset(deps, context.work.projectId, made.value.mime === "image/jpeg" ? "short-image.jpg" : "short-image.png", made.value.bytes);
|
|
@@ -183,11 +190,13 @@ async function image(deps, context, providers, piece, clip, index) {
|
|
|
183
190
|
short: clip.number,
|
|
184
191
|
sentences: [clip.first, clip.last],
|
|
185
192
|
index,
|
|
186
|
-
prompt
|
|
193
|
+
prompt,
|
|
187
194
|
provider: input.provider,
|
|
188
195
|
model: input.model,
|
|
189
196
|
});
|
|
190
|
-
await publishResult(deps, context, piece, [output], { prompt
|
|
197
|
+
await publishResult(deps, context, piece, [output], { prompt }, asset);
|
|
198
|
+
if (softened !== undefined)
|
|
199
|
+
clearSoftening(deps.db, context.work.projectId, [piece.key]);
|
|
191
200
|
return "done";
|
|
192
201
|
}
|
|
193
202
|
async function render(deps, context, piece, snapshot, clip) {
|
|
@@ -15,6 +15,21 @@ export function refusedKeys(db, projectId, stage) {
|
|
|
15
15
|
.all(projectId, stage)
|
|
16
16
|
.map((row) => String(row.work_key));
|
|
17
17
|
}
|
|
18
|
+
// A short's still (`shorts:N:image:M`) is drawn in the Video stage, beside steps that draw
|
|
19
|
+
// nothing; only those stills can be softened there.
|
|
20
|
+
const shortStill = /^shorts:\d+:image:\d+$/;
|
|
21
|
+
// The refused steps Soften and retry can reword: every refused image of Images or Thumbnail,
|
|
22
|
+
// and the refused short stills of Video.
|
|
23
|
+
export function softenableKeys(db, projectId, stage) {
|
|
24
|
+
return softenable(stage, refusedKeys(db, projectId, stage));
|
|
25
|
+
}
|
|
26
|
+
export function softenable(stage, refused) {
|
|
27
|
+
if (stage === "images" || stage === "thumbnail")
|
|
28
|
+
return [...refused];
|
|
29
|
+
if (stage === "video")
|
|
30
|
+
return refused.filter((key) => shortStill.test(key));
|
|
31
|
+
return [];
|
|
32
|
+
}
|
|
18
33
|
export function requestSoftening(db, projectId, keys, at) {
|
|
19
34
|
for (const key of keys)
|
|
20
35
|
db.prepare("INSERT INTO prompt_softening(project_id,work_key,requested_at) VALUES (?,?,?) ON CONFLICT(project_id,work_key) DO UPDATE SET requested_at=excluded.requested_at").run(projectId, key, at);
|