@gentbajko/slopify 3.8.2 → 3.8.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.
@@ -0,0 +1,5 @@
1
+ # Slopify 3.8.3
2
+
3
+ Released 4 October 2026.
4
+
5
+ - **A narration chunk Slopify records again by itself really does get recorded.** If its first try found the project busy, Slopify kept "busy" as that try's answer and gave it back every time after, even across restarts, so the video stayed failed and the update waited on it forever. A busy answer is no longer kept: the next look asks again and records the chunk. Retries stuck this way before the update start on their own once it is installed.
@@ -1,4 +1,10 @@
1
1
  [
2
+ {
3
+ "id": "3.8.3",
4
+ "title": "Slopify 3.8.3",
5
+ "version": "3.8.3",
6
+ "date": "2026-10-04"
7
+ },
2
8
  {
3
9
  "id": "3.8.2",
4
10
  "title": "Slopify 3.8.2",
@@ -37,8 +37,16 @@ export function readRecovery(deps, projectId, input) {
37
37
  const row = rowSchema.parse(raw);
38
38
  if (row.request_hash !== requestHash("direct-recovery", input.baseRevisionId, input.action))
39
39
  return { ok: false, reason: "idempotency-conflict" };
40
- if (row.response_json !== null)
41
- return recoveryResultSchema.parse(JSON.parse(row.response_json));
40
+ if (row.response_json !== null) {
41
+ const saved = recoveryResultSchema.parse(JSON.parse(row.response_json));
42
+ // "Busy" was the moment's answer, not the request's outcome: one kept from before this was
43
+ // forgotten is dropped, so the same request asks again instead of replaying it forever.
44
+ if (busy(saved)) {
45
+ forget(deps, projectId, input);
46
+ return undefined;
47
+ }
48
+ return saved;
49
+ }
42
50
  return {
43
51
  pending: true,
44
52
  authority: row.authority_stamp,
@@ -57,11 +65,24 @@ export function reserveRecovery(deps, projectId, input, edit) {
57
65
  }
58
66
  export function rememberRecovery(deps, projectId, input, result) {
59
67
  const response = recoveryResultSchema.parse(result);
68
+ // Refused because the project was busy: nothing was done, so the same request may come again
69
+ // (an automatic narration retry keeps its request id) and must be tried again, not replayed.
70
+ if (busy(response)) {
71
+ forget(deps, projectId, input);
72
+ return response;
73
+ }
60
74
  deps.db
61
75
  .prepare("UPDATE project_recovery_requests SET response_json=? WHERE project_id=? AND idempotency_key=?")
62
76
  .run(JSON.stringify(response), projectId, input.idempotencyKey);
63
77
  return response;
64
78
  }
79
+ // Busy before anything was saved (a refusal after the revision was saved keeps its answer).
80
+ const busy = (result) => !result.ok && result.reason === "running" && result.intentRevisionId === undefined;
81
+ function forget(deps, projectId, input) {
82
+ deps.db
83
+ .prepare("DELETE FROM project_recovery_requests WHERE project_id=? AND idempotency_key=?")
84
+ .run(projectId, input.idempotencyKey);
85
+ }
65
86
  // The head has unfinished stages but nothing that will move them on its own: no call in
66
87
  // flight and no admitted, dispatchable work. A saved rerun or edit is admitted by no one
67
88
  // until Resume, and work held by a pause, a restart or a failed sibling of its admission
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gentbajko/slopify",
3
- "version": "3.8.2",
3
+ "version": "3.8.3",
4
4
  "description": "Turn a prompt into a finished video, locally, with your own provider keys",
5
5
  "license": "Apache-2.0",
6
6
  "author": "Gent Bajko",