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>
67 lines
2.2 KiB
TypeScript
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);
|
|
});
|
|
});
|