@worca/ui 0.15.2 → 0.15.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@worca/ui",
3
- "version": "0.15.2",
3
+ "version": "0.15.3",
4
4
  "description": "Pipeline monitoring UI for worca-cc",
5
5
  "license": "MIT",
6
6
  "author": "Sinisha Djukic",
@@ -422,10 +422,39 @@ export class ProcessManager {
422
422
  if (opts.resume) {
423
423
  args.push('--resume');
424
424
  if (opts.runId) {
425
- const statusDir = resumeCtx
426
- ? resumeCtx.runDir
427
- : join(this.worcaDir, 'runs', opts.runId);
425
+ // The runner derives worca_dir from os.path.dirname(status_path) and
426
+ // builds the per-run dir as <worca_dir>/runs/<run_id>/. We must pass
427
+ // the worca root, not the per-run dir — passing <worca>/runs/<id>
428
+ // would make the runner compute a nested <worca>/runs/<id>/runs/<id>/
429
+ // and write status updates there while the UI keeps reading the
430
+ // original. _find_active_runs(worca_root) then locates the run.
431
+ const statusDir = resumeCtx ? resumeCtx.worcaDir : this.worcaDir;
428
432
  args.push('--status-dir', statusDir);
433
+
434
+ // _find_active_runs filters out runs whose pipeline_status is in
435
+ // {completed, interrupted}. To resume an interrupted/failed run, flip
436
+ // the top-level status to "resuming" so the runner can pick it up;
437
+ // it'll transition to "running" once it's processing.
438
+ const statusPath = resumeCtx
439
+ ? join(resumeCtx.runDir, 'status.json')
440
+ : join(this.worcaDir, 'runs', opts.runId, 'status.json');
441
+ try {
442
+ const s = JSON.parse(readFileSync(statusPath, 'utf8'));
443
+ if (
444
+ s.pipeline_status === 'interrupted' ||
445
+ s.pipeline_status === 'failed'
446
+ ) {
447
+ s.pipeline_status = 'resuming';
448
+ delete s.stop_reason;
449
+ writeFileSync(
450
+ statusPath,
451
+ `${JSON.stringify(s, null, 2)}\n`,
452
+ 'utf8',
453
+ );
454
+ }
455
+ } catch {
456
+ /* non-fatal — runner will surface a clearer error if the file is missing */
457
+ }
429
458
  }
430
459
  } else if (opts.sourceType !== undefined) {
431
460
  // New format: separate source and prompt args
@@ -935,7 +935,14 @@ export function createProjectScopedRoutes({
935
935
  }
936
936
  const { worcaDir, settingsPath } = req.project;
937
937
  try {
938
- const controlDir = join(worcaDir, 'runs', runId);
938
+ // Worktree runs read control.json from <worktree>/.worca/runs/<id>/.
939
+ // Writing it to the parent project's worcaDir leaves the runner
940
+ // unaware of the stop request — SIGTERM still works, but we lose
941
+ // graceful-shutdown semantics.
942
+ const overlay = readPipelineOverlay(worcaDir, runId);
943
+ const controlDir = overlay?.worktree_path
944
+ ? join(overlay.worktree_path, '.worca', 'runs', runId)
945
+ : join(worcaDir, 'runs', runId);
939
946
  mkdirSync(controlDir, { recursive: true });
940
947
  writeFileSync(
941
948
  join(controlDir, 'control.json'),
@@ -1141,7 +1148,12 @@ export function createProjectScopedRoutes({
1141
1148
  pipeline_status: st.pipeline_status,
1142
1149
  });
1143
1150
  }
1144
- const controlDir = join(worcaDir, 'runs', runId);
1151
+ // Worktree runs read control.json from <worktree>/.worca/runs/<id>/;
1152
+ // writing to the parent's worcaDir is invisible to the runner.
1153
+ const overlay = readPipelineOverlay(worcaDir, runId);
1154
+ const controlDir = overlay?.worktree_path
1155
+ ? join(overlay.worktree_path, '.worca', 'runs', runId)
1156
+ : join(worcaDir, 'runs', runId);
1145
1157
  mkdirSync(controlDir, { recursive: true });
1146
1158
  writeFileSync(
1147
1159
  join(controlDir, 'control.json'),