Files
fusion/packages/dashboard/app/utils/__tests__/taskTiming.test.ts
gsxdsm 1d4e8afa7b FN-8444: include planning time in task metrics
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>
2026-07-20 13:40:50 -07:00

67 lines
2.2 KiB
TypeScript

import { describe, it, expect } from "vitest";
import { getActiveRuntimeMs, getTotalAgentActiveMs, getWallClockSinceFirstExecutionMs } from "../taskTiming";
describe("taskTiming helpers", () => {
it("returns persisted plus live segment for in-progress tasks", () => {
const nowMs = Date.parse("2026-05-15T13:16:00.000Z");
const runtime = getActiveRuntimeMs(
{
column: "in-progress",
cumulativeActiveMs: 240_000,
executionStartedAt: "2026-05-15T13:15:00.000Z",
columnMovedAt: "2026-05-15T13:15:00.000Z",
},
nowMs,
);
expect(runtime).toBe(300_000);
});
it("sums planning and execution segments without using idle dwell", () => {
expect(getTotalAgentActiveMs({
column: "done", cumulativeActiveMs: 120_000, executionStartedAt: undefined,
cumulativePlanningMs: 180_000, planningStartedAt: undefined,
}, Date.parse("2026-05-15T13:16:00.000Z"))).toBe(300_000);
});
it("returns null when there is no active-runtime signal", () => {
const runtime = getActiveRuntimeMs(
{
column: "todo",
cumulativeActiveMs: undefined,
executionStartedAt: undefined,
columnMovedAt: undefined,
},
Date.now(),
);
expect(runtime).toBeNull();
});
it("uses shifted executionStartedAt so the active badge excludes engine-down time", () => {
const t0 = Date.parse("2026-06-25T00:00:00.000Z");
const runtime = getActiveRuntimeMs(
{
column: "in-progress",
cumulativeActiveMs: undefined,
executionStartedAt: new Date(t0 + 60 * 60_000).toISOString(),
columnMovedAt: new Date(t0).toISOString(),
},
t0 + 65 * 60_000,
);
expect(runtime).toBe(5 * 60_000);
expect(getActiveRuntimeMs({ column: "in-progress", cumulativeActiveMs: undefined, executionStartedAt: undefined, columnMovedAt: undefined }, t0)).toBeNull();
});
it("returns wall-clock runtime since first execution", () => {
const wallClock = getWallClockSinceFirstExecutionMs(
"2026-05-15T08:42:00.000Z",
"2026-05-15T13:17:00.000Z",
Date.parse("2026-05-15T13:20:00.000Z"),
);
expect(wallClock).toBe(16_500_000);
});
});