@lalalic/markcut 3.1.1 → 3.2.1

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,250 @@
1
+ # ChatGPT Browser Harness as a Markcut Vision Backend
2
+
3
+ Date: 2026-09-19
4
+ PR: #3
5
+ Recommendation: **not currently viable**
6
+
7
+ ## Objective
8
+
9
+ Evaluate whether Markcut's configurable Vision CLI can use an authenticated ChatGPT web session, operated through the `chatgpt-browser-worker` skill and `browser-harness`, as an image/video understanding backend without using the ChatGPT API or the legacy MacDeveloperBridge Chrome/token path.
10
+
11
+ ## Existing Markcut seam
12
+
13
+ Markcut already has a narrow backend seam in `src/config.mjs`:
14
+
15
+ - `DEFAULT_ITT_CLI` for image-to-text
16
+ - `DEFAULT_VTT_CLI` for video-to-text
17
+ - `DEFAULT_AGENT_CLI` for downstream agent work
18
+
19
+ The Vision CLI substitutes `{prompt}` and `{input}` into these command templates, so a future browser-backed backend does not require rewriting the Vision pipeline. The correct integration shape would be a small executable command that accepts the same prompt/input contract and emits normalized text/JSON on stdout.
20
+
21
+ ```mermaid
22
+ flowchart LR
23
+ M[markcut vision] --> C[configured ITT/VTT command]
24
+ C -->|current| L[mlx-vlm]
25
+ C -->|experimental| B[chatgpt-browser-worker adapter]
26
+ B --> H[browser-harness]
27
+ H --> W[authenticated ChatGPT web UI]
28
+ W --> R[verified assistant result]
29
+ R --> N[normalize JSON/text]
30
+ N --> M
31
+ ```
32
+
33
+ ## Browser-worker path tested
34
+
35
+ The experiment used the `chatgpt-browser-worker` contract from the Agents Relay worktree. The worker uses `browser-harness` as the only browser interaction layer and preserves ChatGPT thread identity and Project identity.
36
+
37
+ Observed worker thread:
38
+
39
+ - Project: `agents-relay`
40
+ - Thread: `6aaf32d4-4164-83ea-860a-c2453ce57e51`
41
+ - Requested thinking: `high`
42
+ - Effective thinking: `unknown` (UI could not verify it; no inference was made)
43
+
44
+ A second experiment targeted the existing `markcut` ChatGPT Project. The helper selected the Project, but current ChatGPT UI drift prevented its create helper from reliably observing/sending the composer without manual self-healing.
45
+
46
+ ## Synthetic fixtures
47
+
48
+ To avoid private media and make correctness objectively measurable, the test generated:
49
+
50
+ 1. `image-test-unique.png`
51
+ - red rectangle
52
+ - blue circle
53
+ - green triangle
54
+ - visible text identifying all three shapes and a unique fixture label
55
+ 2. `video-test.mp4`
56
+ - 6 seconds total
57
+ - 0-2s red phase
58
+ - 2-4s blue phase
59
+ - 4-6s green phase
60
+
61
+ Fixtures were generated locally and kept out of Git.
62
+
63
+ ## Control: existing Markcut image backend
64
+
65
+ The current configured image backend was run directly with the same image fixture using the existing `mlx-vlm`/MiniCPM path.
66
+
67
+ Observed runtime: approximately 8 seconds.
68
+
69
+ Observed result correctly identified:
70
+
71
+ - red rectangle
72
+ - blue circle
73
+ - green triangle
74
+ - `RED RECTANGLE | BLUE CIRCLE | GREEN TRIANGLE`
75
+ - `MARKCUT VISION UNIQUE FIXTURE 2026-09-19`
76
+
77
+ This establishes that the fixture and current Markcut backend are functioning.
78
+
79
+ ## ChatGPT image experiment
80
+
81
+ The Browser Harness path successfully:
82
+
83
+ - selected an authenticated ChatGPT Project;
84
+ - created and preserved a real ChatGPT thread id;
85
+ - uploaded an image into the composer;
86
+ - submitted a media-analysis prompt through the browser UI;
87
+ - observed assistant message ids from the actual ChatGPT DOM.
88
+
89
+ However, the result path was not reliable enough for a backend:
90
+
91
+ 1. One submitted image turn produced a terminal-looking but truncated assistant message:
92
+
93
+ `{"description":"A`
94
+
95
+ 2. The existing `result` helper incorrectly considered that partial text stable/final because its generating-state detector did not match the current UI state.
96
+ 3. A later image retry entered a persistent `Stop answering` state while the attachment and prompt remained in the composer and no durable new user turn was observed.
97
+ 4. The current helper's composer logic also drifted from the current ChatGPT DOM. In the current UI, the visible composer is a `contenteditable` element while a separate textarea may coexist. The helper can fill the wrong semantic surface or fail to observe the send control.
98
+ 5. Direct self-healing through fresh DOM observation could send ordinary text turns, but media-turn behavior was still inconsistent.
99
+
100
+ ### Structured-output reliability
101
+
102
+ Result: **fail** for backend-grade use.
103
+
104
+ A Markcut backend needs deterministic success/failure signaling and complete output. A truncated JSON prefix that is misclassified as complete is unsafe because downstream parsing or cache persistence can accept corrupted evidence.
105
+
106
+ ## Video experiment
107
+
108
+ Direct video upload was attempted as the preferred path. The experiment reached a second failure mode before a trustworthy model result could be obtained: after opening the `markcut` Project for a fresh direct-video test, the current helper/UI combination failed to observe the expected composer surface consistently.
109
+
110
+ Because image media submission was already non-deterministic and the direct-video setup could not produce a verified, repeatable turn, the experiment did **not** treat a frame-sampling fallback as proof that direct video upload works.
111
+
112
+ A frame-sampling fallback remains technically possible because Markcut already owns ffmpeg normalization/sampling and can present sampled images to a backend. But the browser-backed image transport itself must first become reliable.
113
+
114
+ ```mermaid
115
+ flowchart TD
116
+ V[video input] --> D{direct web video upload reliable?}
117
+ D -->|current result: no| F[Markcut frame sampling]
118
+ F --> I[image batches]
119
+ I --> B[ChatGPT browser backend]
120
+ B --> Q{media turn + result verified?}
121
+ Q -->|current result: no| X[do not enable backend]
122
+ Q -->|future: yes| O[chronology / scene JSON]
123
+ ```
124
+
125
+ ## Failure modes observed
126
+
127
+ | Area | Observation | Impact |
128
+ | --- | --- | --- |
129
+ | Composer detection | Current UI exposes both contenteditable and textarea-like surfaces | helper may target the wrong composer |
130
+ | Send detection | helper could report no `Send prompt` even when manual re-observation later found a usable control | brittle create/continue lifecycle |
131
+ | Media upload | attachment preview can exist without a reliable submitted turn | no durable proof input reached model |
132
+ | Generating detection | partial result was considered final | corrupted JSON may be accepted |
133
+ | Result verification | stable DOM text alone is insufficient when streaming state is misdetected | false completion |
134
+ | Project page lifecycle | new Project chat can change composer state/URL after first send | helper assumptions stale |
135
+ | Video | direct video path was not repeatably driven to a verified result | no production evidence |
136
+
137
+ ## Minimum changes required before re-benchmarking
138
+
139
+ 1. **Fix semantic composer selection**
140
+ - prefer the visible `contenteditable` composer by accessibility role/name;
141
+ - reject ambiguous multiple composer surfaces;
142
+ - verify typed text appears in the same visible surface before sending.
143
+
144
+ 2. **Fix send verification**
145
+ - re-observe the current send control after typing/upload;
146
+ - after click, require a new user-turn node or other durable conversation evidence;
147
+ - do not return `prompt_sent: true` from a click alone.
148
+
149
+ 3. **Fix streaming/final-result verification**
150
+ - do not infer completion only from absence of one historical stop selector;
151
+ - require the assistant message text to remain stable across a longer interval and require the conversation to return to an input-ready state;
152
+ - for structured output, parse the requested JSON before returning success.
153
+
154
+ 4. **Add media-specific lifecycle states**
155
+ - upload started
156
+ - attachment observed
157
+ - attachment ready
158
+ - turn submitted
159
+ - assistant started
160
+ - assistant complete
161
+ - output validated
162
+
163
+ 5. **Add regression fixtures**
164
+ - one small PNG
165
+ - one short MP4
166
+ - exact expected JSON fields
167
+ - timeout/stuck-generation case
168
+ - truncated-JSON case
169
+
170
+ 6. **Only then add a Markcut adapter command**
171
+ - keep it experimental and opt-in;
172
+ - preserve the existing ITT/VTT CLI template boundary;
173
+ - do not change the default backend.
174
+
175
+ ## Proposed future adapter shape
176
+
177
+ If the browser worker is repaired, the narrowest Markcut integration remains a command adapter rather than changes throughout the Vision pipeline:
178
+
179
+ ```text
180
+ markcut vision
181
+ -> MARKCUT_ITT_CLI / MARKCUT_VTT_CLI
182
+ -> chatgpt-browser-vision executable
183
+ -> chatgpt-browser-worker typed lifecycle
184
+ -> browser-harness
185
+ -> verified JSON stdout
186
+ ```
187
+
188
+ The adapter should be responsible for media upload, schema validation, retry/resume of the same ChatGPT thread, and non-zero exit on unverified/truncated output.
189
+
190
+ ## Recommendation
191
+
192
+ **Not currently viable.**
193
+
194
+ The architectural seam is good and ChatGPT web Vision is reachable through the intended browser-harness stack, but the current `chatgpt-browser-worker` implementation does not yet provide backend-grade guarantees for media submission or final-result verification. The existing Markcut local Vision backend remains substantially more deterministic for this workflow.
195
+
196
+ Do not switch Markcut's default Vision backend. Repair and regression-test the browser worker first, then rerun this PR's image and video fixtures. Once image media turns are deterministic, evaluate video in this order:
197
+
198
+ 1. direct MP4 upload;
199
+ 2. Markcut-owned frame sampling if direct upload is unsupported or unstable;
200
+ 3. compare output quality, JSON validity, p50/p95 latency, and retry rate against the existing backend.
201
+
202
+ ## Commands/evidence
203
+
204
+ Control image command used:
205
+
206
+ ```sh
207
+ uvx --from mlx-vlm mlx_vlm.generate \
208
+ --model mlx-community/MiniCPM-V-4.6-bf16 \
209
+ --max-tokens 512 \
210
+ --prompt 'Describe this image precisely, including colored shapes and visible text. Return concise JSON.' \
211
+ --image .tmp/pr3-vision/image-test-unique.png \
212
+ --temperature 0.0 \
213
+ --thinking-mode disabled
214
+ ```
215
+
216
+ Browser path used the `chatgpt-browser-worker` skill plus `browser-harness`; no ChatGPT API call, copied cookie, or MacDeveloperBridge ChatGPT runtime was used.
217
+
218
+ ## Re-benchmark after browser-worker attachment and tab-lifecycle fixes
219
+
220
+ A second pass was run after Neo main added verified attachment submission (`defe80d`) and operation-owned tab cleanup (`3b4331e`). This changed the result in two useful ways:
221
+
222
+ - attachment submission itself is now materially safer: a prior smoke run observed the requested filename, prompt text, a new durable user turn, and a real user message ID before reporting success;
223
+ - `result --expect-json` now rejects malformed/truncated assistant output instead of treating it as completed. A deliberately observed truncated response failed with `assistant result is not valid JSON`, which closes the false-completion path seen in the first pass;
224
+ - operation-owned tab cleanup was verified by comparing page targets before and after an operation that opened a temporary thread tab; the target sets were identical after return.
225
+
226
+ The re-benchmark also exposed a new blocking failure at the durable-thread boundary. Threads that are visible and usable when reached through the existing ChatGPT sidebar can load as a shell-only page when opened directly by their durable conversation URL in a fresh tab. In the observed failures, the URL and Project sidebar loaded, but the conversation had **0 visible user messages, 0 visible assistant messages, and 0 composer surfaces**. `operate_bh.py` therefore correctly failed with `ChatGPT composer was not observed` rather than sending into an ambiguous page.
227
+
228
+ This matters because the current worker contract reopens a durable thread by URL for `send`, `status`, and `result`. A Vision backend cannot be considered reliable if durable thread recovery depends on whether the ChatGPT SPA was entered through sidebar navigation versus direct URL navigation.
229
+
230
+ ### Updated failure boundary
231
+
232
+ ```mermaid
233
+ flowchart TD
234
+ A[Markcut media input] --> B[chatgpt-browser-worker send]
235
+ B --> C{attachment + durable user turn verified?}
236
+ C -->|yes| D[assistant generation]
237
+ C -->|no| X[fail safely]
238
+ D --> E{result JSON valid?}
239
+ E -->|no| X
240
+ E -->|yes| F[backend result]
241
+ R[resume/reopen thread by durable URL] --> G{conversation UI + composer restored?}
242
+ G -->|current rerun: not reliably| X
243
+ G -->|future| B
244
+ ```
245
+
246
+ ### Updated recommendation
247
+
248
+ The browser-worker changes fix two important defects from the first pass—attachment-submit evidence and structured-result validation—but PR #3 should still remain research-only. The remaining blocker is now narrower and clearer: **reliable durable-thread reopening / SPA recovery**. Until the worker can reopen the exact `thread_id` and observe the same conversation state independent of navigation path, direct image/video benchmarking cannot be considered repeatable enough for a Markcut backend.
249
+
250
+ The next browser-worker fix should therefore target thread reopening semantics: prefer semantic sidebar navigation to the exact observed thread when direct URL hydration yields an empty shell, verify the requested `thread_id` after navigation, and only then expose the composer or result operations. After that, rerun the same PNG and MP4 fixtures without changing Markcut itself.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lalalic/markcut",
3
- "version": "3.1.1",
3
+ "version": "3.2.1",
4
4
  "description": "Markdown-to-video engine. Describe scenes in markdown, get a rendered video.",
5
5
  "bin": {
6
6
  "markcut": "bin/markcut"
@@ -65,5 +65,9 @@
65
65
  "@types/react-dom": "^19.0.0",
66
66
  "typescript": "^5.6.0",
67
67
  "vitest": "^2.1.0"
68
+ },
69
+ "repository": {
70
+ "type": "git",
71
+ "url": "git+https://github.com/lalalic/markcut.git"
68
72
  }
69
73
  }
@@ -18,6 +18,13 @@ Everything video is a **stream tree** described with markdown. see [docs/markdow
18
18
 
19
19
  - Use `scene` nodes to organize your video. Scenes can nest inside other scenes.
20
20
  - Use `description`, `scene.instruction`, `script`, `image|video.prompt` to structure your video content.
21
+ - **If a scene has a `script` (or `audio`) plus one primary visual, the visual MUST be `isBackground:true`.** Without it the visual plays only its own duration (3s default for images) while narration continues — the rest of the scene is a black screen. Scene duration follows the audio, not the image.
22
+
23
+ ```md
24
+ ## scene-1
25
+ - image prompt:"..." isBackground:true # fills the whole scene, narration decides duration
26
+ - script "..."
27
+ ```
21
28
 
22
29
  see [docs/markdown-descriptive.md](docs/markdown-descriptive.md) for full details.
23
30
 
@@ -71,15 +78,12 @@ npx @lalalic/markcut spots --waypoints "lat,lng;lat,lng" # discover POIs along a
71
78
 
72
79
  ---
73
80
 
74
- ## self verification
75
- some common issues (photo or video can't be displayed, audio missing), take below actions to verify
76
- ### preview
77
- - take screenshot for some key frames in player, and understand image to verify intent
78
-
79
- ### final video
80
- - screenshot some key frames, and understand image to verify intent
81
- - stt the final video audio, and verify if vtt result is correct
82
-
81
+ ## review
82
+ use the review contract defined in [./review.md](./review.md) to guide the review process.
83
+ review as early as possible in the video production process to catch issues before they propagate.
84
+ * review md file
85
+ * review compiled.json
86
+ * review the rendered video
83
87
 
84
88
  ## Reference
85
89
 
@@ -89,8 +93,7 @@ some common issues (photo or video can't be displayed, audio missing), take belo
89
93
  | Route / vlog map clips (effects, spots, examples) | [docs/map-dynamic-camera.md](docs/map-dynamic-camera.md) |
90
94
  | Built-in components & common npm packages | [docs/components.md](docs/components.md) |
91
95
  | Sound effects | [docs/sound-effects.md](docs/sound-effects.md) |
92
-
93
-
96
+ | Review contract | [./review.md](./review.md) |
94
97
 
95
98
  ## Built-in Components
96
99
 
@@ -98,12 +101,6 @@ Built-in components available via `@lalalic/markcut/components`. See [docs/compo
98
101
 
99
102
 
100
103
  ## Golden rule
101
- - always check stream start and duration to avoid
102
- - audio cut off
103
- - video cut off
104
- - subtitle mismatch
105
- - sync issues between audio, video, and subtitles
106
- according to the content change. rm `.markcut` will cause all content to be regenerated, which is time consuming and wasteful.
107
104
  - put all manual assets in `assets` folder, such as bgm, logo, watermark, etc. don't put them in `.markcut` folder, which is auto generated and will be deleted when `markcut clean` command is run.
108
105
 
109
106
  ### Don'ts
@@ -705,7 +705,7 @@ tween(#000, #FFF) — color interpolation
705
705
  # Best practices
706
706
  - Markcut engine will automatically determine the duration of background streams, video, script audio. Don't set duration for them if no special requirements (e.g., speed up/down on purpose).
707
707
  - global streams (BGM, Logo, Lip-sync video,...) should be set on root level, and set `isBackground:true` to let it loop to fill the whole video duration.
708
- - Set `isBackground:true` for static vision content, such as image, when audio is playing
708
+ - **Set `isBackground:true` for static vision content (image) whenever the scene has audio/script.** Scene duration is driven by the audio (which can be much longer than the image's default 3s); without `isBackground` the image ends early and the scene tail plays over a black screen while narration continues.
709
709
  - adjust audio start time with `start` property to avoid audio cut when transition effect is applied on vision scene
710
710
 
711
711