@gentbajko/slopify 3.2.0 → 3.2.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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.1.md +13 -0
- package/dist/patch-notes/3.2.2.md +15 -0
- package/dist/patch-notes/index.json +12 -0
- package/dist/slices/fixes/rules.js +3 -1
- package/dist/slices/rebuild/recipe-preparation.js +5 -1
- package/dist/slices/rebuild/soften.js +15 -0
- package/dist/slices/revisions/schema.js +7 -1
- package/dist/web/assets/{index-DBLI2o4h.js → index-Bw98Bh7V.js} +27 -27
- package/dist/web/assets/{pdf-CViHVF9X.js → pdf-Dh7upqzO.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,13 @@
|
|
|
1
|
+
# Slopify 3.2.1
|
|
2
|
+
|
|
3
|
+
Released 30 September 2026.
|
|
4
|
+
|
|
5
|
+
3.2.1 lets one narration chunk be re-tagged without re-recording the whole narration.
|
|
6
|
+
|
|
7
|
+
## Narration
|
|
8
|
+
|
|
9
|
+
- A chunk whose text is edited in Edit project → Narration can carry its own delivery note. The note is added to the narration prep prompt for that chunk only, so its delivery cues can change (a calmer opening, say) while every other chunk keeps its recording.
|
|
10
|
+
|
|
11
|
+
## Upgrading from 3.2.0
|
|
12
|
+
|
|
13
|
+
- **Nothing becomes outdated.**
|
|
@@ -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.
|
|
@@ -1,4 +1,16 @@
|
|
|
1
1
|
[
|
|
2
|
+
{
|
|
3
|
+
"id": "3.2.2",
|
|
4
|
+
"title": "Slopify 3.2.2",
|
|
5
|
+
"version": "3.2.2",
|
|
6
|
+
"date": "2026-10-01"
|
|
7
|
+
},
|
|
8
|
+
{
|
|
9
|
+
"id": "3.2.1",
|
|
10
|
+
"title": "Slopify 3.2.1",
|
|
11
|
+
"version": "3.2.1",
|
|
12
|
+
"date": "2026-09-30"
|
|
13
|
+
},
|
|
2
14
|
{
|
|
3
15
|
"id": "3.2.0",
|
|
4
16
|
"title": "Slopify 3.2.0",
|
|
@@ -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))
|
|
@@ -26,8 +26,12 @@ export function preparationFuture(context, segment, dependency, additionalDepend
|
|
|
26
26
|
}, [...new Set([dependency.key, ...additionalDependencies.map((row) => row.key)])]);
|
|
27
27
|
}
|
|
28
28
|
export function preparationForGroup(context, logicalKey, source, segment, dependsOn, maxCharacters, spans = [], aliases = [], speaker) {
|
|
29
|
+
// A delivery note for this chunk alone (Edit project → Narration), after the project's prompt.
|
|
30
|
+
const override = context.content.narrationOverrides[logicalKey];
|
|
31
|
+
const note = override?.kind === "text" ? override.direction : undefined;
|
|
32
|
+
const prompt = renderedPrompt(context, "narration");
|
|
29
33
|
const preparation = recipe(context, `narration:prepare:${segment}:${logicalKey}`, "audio", {
|
|
30
|
-
...llmInput(context, preparationMessages(
|
|
34
|
+
...llmInput(context, preparationMessages(note === undefined ? prompt : `${prompt}\n\n${note}`, source, aliases, context.config.language, speaker)),
|
|
31
35
|
preparation: { format: "inworld-tts-2", version: 1, source, logicalKey, segment },
|
|
32
36
|
}, dependsOn, { unresolved: source.trim() === "" });
|
|
33
37
|
if (source.trim() === "")
|
|
@@ -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);
|
|
@@ -55,7 +55,13 @@ export const revisionContentSchema = z
|
|
|
55
55
|
.strict()),
|
|
56
56
|
narrationOverrides: z.record(workKey, z.discriminatedUnion("kind", [
|
|
57
57
|
z.object({ kind: z.literal("asset"), assetId: id }).strict(),
|
|
58
|
-
z
|
|
58
|
+
z
|
|
59
|
+
.object({
|
|
60
|
+
kind: z.literal("text"),
|
|
61
|
+
text: z.string().trim().min(1).max(500000),
|
|
62
|
+
direction: z.string().trim().min(1).max(20000).optional(),
|
|
63
|
+
})
|
|
64
|
+
.strict(),
|
|
59
65
|
])),
|
|
60
66
|
narrationSources: z
|
|
61
67
|
.record(workKey, z
|