Ctrl K
ComunidadGuías…

lib/background-task-timing.js

Rama main · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
// Background task list shaping shared by the server-side consumers:
// start-time tracking, plus the ambient/user split every activity indicator
// needs.
//
// Vendors report background tasks as a full replacement list on every change
// and none of them carries a reliable start time today (see
// normalizeBackgroundTasks in yoke/adapters/claude.js and mapTerminals in
// yoke/codex-background-tasks.js). The session's previous list is the only
// place that knows when a task was first seen, so the merge belongs here,
// where that list is maintained, rather than in an adapter that rebuilds the
// array from scratch each time.
//
// A vendor-supplied timestamp always wins when present; otherwise a task is
// stamped the first time it appears and keeps that stamp for its whole life.

// Accepts epoch milliseconds, epoch seconds, or an ISO date string, since
// vendors are not consistent. Returns null for anything unusable so the
// caller falls back to first-seen stamping instead of rendering 1970.
function normalizeTimestamp(value) {
  if (typeof value === "number" && isFinite(value) && value > 0) {
    // Values this small are epoch seconds, not milliseconds.
    return value < 1e12 ? Math.round(value * 1000) : Math.round(value);
  }
  if (typeof value === "string" && value) {
    var parsed = Date.parse(value);
    if (!isNaN(parsed)) return parsed;
  }
  return null;
}

function vendorStartedAt(task) {
  if (!task) return null;
  return normalizeTimestamp(task.started_at)
    || normalizeTimestamp(task.startedAt)
    || normalizeTimestamp(task.createdAt)
    || normalizeTimestamp(task.created_at);
}

/**
 * Return `nextTasks` with a `started_at` (epoch ms) on every entry.
 * Tasks already present in `previousTasks` keep their original stamp so the
 * elapsed time does not reset every time the list is re-emitted.
 */
function mergeStartTimes(previousTasks, nextTasks, now) {
  if (!Array.isArray(nextTasks)) return [];
  var stampedAt = typeof now === "number" ? now : Date.now();
  // Stamps we wrote on a previous pass are already epoch milliseconds, so they
  // are read as-is. Only vendor-supplied values go through normalizeTimestamp,
  // whose seconds-vs-milliseconds heuristic would otherwise be re-applied to
  // our own output.
  var known = {};
  var previous = Array.isArray(previousTasks) ? previousTasks : [];
  for (var p = 0; p < previous.length; p++) {
    var seen = previous[p];
    if (!seen || !seen.task_id) continue;
    if (typeof seen.started_at === "number" && isFinite(seen.started_at) && seen.started_at > 0) {
      known[seen.task_id] = seen.started_at;
    }
  }

  var merged = [];
  for (var i = 0; i < nextTasks.length; i++) {
    var task = nextTasks[i];
    if (!task) continue;
    var startedAt = vendorStartedAt(task) || known[task.task_id] || stampedAt;
    merged.push(Object.assign({}, task, { started_at: startedAt }));
  }
  return merged;
}

// Ambient tasks are the CLI's own housekeeping (every skip_transcript task
// plus auto-started live-update watchers). The SDK asks hosts to keep them out
// of activity indicators, so every count that drives a badge, a summary, or a
// change notification must go through here rather than reading `.length`.
// Absent means false: vendors without an ambient concept (Codex terminals)
// and older CLI builds report only real user work.
function isAmbientTask(task) {
  return !!(task && task.ambient === true);
}

function userTasks(tasks) {
  if (!Array.isArray(tasks)) return [];
  return tasks.filter(function (task) {
    return task && !isAmbientTask(task);
  });
}

function ambientTasks(tasks) {
  if (!Array.isArray(tasks)) return [];
  return tasks.filter(isAmbientTask);
}

// The count that represents "user-visible background work is running".
function countUserTasks(tasks) {
  return userTasks(tasks).length;
}

module.exports = {
  mergeStartTimes: mergeStartTimes,
  normalizeTimestamp: normalizeTimestamp,
  isAmbientTask: isAmbientTask,
  userTasks: userTasks,
  ambientTasks: ambientTasks,
  countUserTasks: countUserTasks,
};