replace hardcoded download job with download-media script capability

move video/audio downloads off the dedicated ReClip download lane onto the
generic script-job path:

- delete execute-download.ts, reclip-client.ts and the POST /jobs/download
  endpoint; drop the 'download' mode from the pipeline_jobs enum (legacy rows
  tolerated)
- execute-script.ts: strip the @@officer:progress@@ sentinel from the log,
  emit progress events, and isolate viewer/log writes (safeEmit/safeLog) so a
  broadcast or log throw can't wedge the stdout pump
- pipeline-job-manager.ts: persist latest progress; guard sendToViewer sends
- ScriptJobDetail: render the two progress bars; DownloadJobDetail kept for
  legacy history rows
- TaskRunnerModal: ScriptRunner descends into the triggered directory
- VideoDownloadPanel: rewire startJob to the script-job path

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-07-29 01:10:32 +00:00
co-authored by Claude Opus 4.8
parent b523c7d408
commit b2fb6f148c
10 changed files with 212 additions and 428 deletions
@@ -1774,7 +1774,13 @@ export const TaskRunnerModal = ({
taskDirName={task.dirName}
autoInputs={autoInputs}
context={autofillContext}
cwd={cwd.path || undefined}
cwd={
entryType === 'directory' && entryName
? cwd.path
? `${cwd.path}/${entryName}`
: entryName
: cwd.path || undefined
}
entryType={effectiveEntryType}
filePath={
multi
@@ -245,16 +245,17 @@ export const VideoDownloadPanel = () => {
setFetching(false);
};
// Job path: hand the whole playlist to a server-side two-phase download job (own lane, survives the
// panel closing). The server re-expands + fetches metadata (phase 1) then downloads survivors (phase 2).
// Job path: hand the whole playlist to the `download-media` script capability as a background job (it
// survives the panel closing). We pass the exact expanded video URLs — already individual, no `list=`,
// so the task won't re-expand and a Mix/radio playlist can't drift to a different set. cwd is
// home-relative (no leading slash) — the capability writes into it.
const startJob = async () => {
try {
const res = await client.post<{ jobId: string; status: string }>('/jobs/download', {
urls: expandedUrls, // the exact list we counted — a Mix playlist won't re-expand to a different set
format: jobFormat,
dir: targetDir(),
root,
label: `${jobFormat === 'audio' ? 'Audio' : 'Video'} · ${expandedUrls.length} items`,
const res = await client.post<{ jobId: string; status: string }>('/jobs', {
taskDirName: 'download-media',
inputs: { url: expandedUrls.join('\n'), format: jobFormat },
cwd: targetDir().replace(/^\/+/, ''),
action: 'queue',
});
lastDlDone.current = 0;
setJobId(res.jobId);