Track active planning time alongside execution time for costs, analytics, and task displays. - Persist planning timing state across task lifecycle transitions and recovery - Include planning activity in token cost, analytics, and dashboard timing displays - Add PostgreSQL migration support using the configured migration directory Files changed: .changeset/fn-8444-planning-time-cost.md | 7 +++ docs/dashboard-guide.md | 3 ++ docs/task-management.md | 5 ++ packages/core/src/index.ts | 1 + .../migrations/0029_planning_active_timing.sql | 3 ++ packages/core/src/postgres/schema-applier.ts | 14 ++++- packages/core/src/postgres/schema/project.ts | 2 + packages/core/src/productivity-analytics.ts | 29 +++++----- packages/core/src/store.ts | 2 +- .../core/src/task-store/archive-lifecycle-2.ts | 2 + packages/core/src/task-store/moves.ts | 7 +++ packages/core/src/task-store/persistence.ts | 4 ++ packages/core/src/task-store/remaining-ops-2.ts | 2 +- packages/core/src/task-store/serialization.ts | 7 +++ packages/core/src/task-store/task-row-mappers.ts | 2 +- packages/core/src/task-store/task-update.ts | 10 ++++ packages/core/src/task-timing.ts | 35 ++++++++++++ packages/core/src/types.ts | 12 +++++ packages/dashboard/app/components/TaskCard.tsx | 13 ++--- .../app/components/TaskTokenStatsPanel.tsx | 6 ++- .../app/components/__tests__/TaskCard.test.tsx | 17 ++++++ .../app/utils/__tests__/taskTiming.test.ts | 9 +++- packages/dashboard/app/utils/taskTiming.ts | 14 +++++ packages/dashboard/app/utils/taskTokenCost.ts | 2 + .../dashboard/src/task-planner-chat-metrics.ts | 14 ++++- packages/engine/src/__tests__/self-healing.test.ts | 61 +++++++++++++++++++++ packages/engine/src/executor.ts | 50 +++++++++++++++++ packages/engine/src/runtimes/in-process-runtime.ts | 3 ++ packages/engine/src/self-healing.ts | 62 ++++++++++++++++++++++ packages/engine/src/triage.ts | 10 ++++ packages/i18n/locales/en/app.json | 2 +- packages/i18n/locales/es/app.json | 2 +- packages/i18n/locales/fr/app.json | 2 +- packages/i18n/locales/ko/app.json | 2 +- packages/i18n/locales/zh-CN/app.json | 2 +- packages/i18n/locales/zh-TW/app.json | 2 +- 36 files changed, 384 insertions(+), 36 deletions(-) Fusion-Task-Id: FN-8444 Fusion-Task-Lineage: 0178e0a7-3018-4ef4-be9b-6de5f964fb58 Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
36 lines
1.9 KiB
TypeScript
36 lines
1.9 KiB
TypeScript
import type { Task } from "./types.js";
|
|
|
|
/**
|
|
* FNXC:TaskTiming 2026-08-01-10:00:
|
|
* Operators' active-time totals include live and persisted planning AI work as
|
|
* well as in-progress execution. Column dwell remains idle wall-clock data and
|
|
* must never be substituted for an agent session anchor.
|
|
*/
|
|
export function getTotalAgentActiveMs(
|
|
task: Pick<Task, "column" | "cumulativeActiveMs" | "executionStartedAt" | "cumulativePlanningMs" | "planningStartedAt">,
|
|
nowMs: number,
|
|
): number | null {
|
|
const executionBase = Math.max(0, task.cumulativeActiveMs ?? 0);
|
|
const executionStartMs = task.column === "in-progress" ? Date.parse(task.executionStartedAt ?? "") : NaN;
|
|
const execution = executionBase + (Number.isFinite(executionStartMs) ? Math.max(0, nowMs - executionStartMs) : 0);
|
|
const planningBase = Math.max(0, task.cumulativePlanningMs ?? 0);
|
|
const planningStartMs = Date.parse(task.planningStartedAt ?? "");
|
|
const planning = planningBase + (Number.isFinite(planningStartMs) ? Math.max(0, nowMs - planningStartMs) : 0);
|
|
return task.cumulativeActiveMs != null || task.cumulativePlanningMs != null || Number.isFinite(executionStartMs) || Number.isFinite(planningStartMs)
|
|
? execution + planning
|
|
: null;
|
|
}
|
|
|
|
export function startPlanningSegment<T extends Pick<Task, "planningStartedAt">>(task: T, nowMs = Date.now()): { planningStartedAt?: string } {
|
|
return task.planningStartedAt ? {} : { planningStartedAt: new Date(nowMs).toISOString() };
|
|
}
|
|
|
|
export function finalizePlanningSegment<T extends Pick<Task, "cumulativePlanningMs" | "planningStartedAt">>(task: T, endMs = Date.now()): { cumulativePlanningMs?: number; planningStartedAt?: null } {
|
|
const startedMs = Date.parse(task.planningStartedAt ?? "");
|
|
if (!Number.isFinite(startedMs)) return {};
|
|
return {
|
|
cumulativePlanningMs: Math.max(0, task.cumulativePlanningMs ?? 0) + Math.max(0, endMs - startedMs),
|
|
planningStartedAt: null,
|
|
};
|
|
}
|