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>
This commit is contained in:
gsxdsm
2026-07-20 13:40:50 -07:00
parent 1b8b7f617e
commit 1d4e8afa7b
36 changed files with 384 additions and 36 deletions

View File

@@ -0,0 +1,3 @@
-- FNXC:TaskTiming 2026-08-01-10:00: durable planning AI session accounting.
ALTER TABLE project.tasks ADD COLUMN IF NOT EXISTS cumulative_planning_ms bigint;
ALTER TABLE project.tasks ADD COLUMN IF NOT EXISTS planning_started_at text;

View File

@@ -37,7 +37,7 @@ FNXC:PostgresBigintCounters 2026-07-19-12:00:
SCHEMA_BASELINE_VERSION advances to 0026 for the bigint counters migration.
Per-migration identities above stay fixed; only this latest-version marker moves.
*/
export const SCHEMA_BASELINE_VERSION = "0028";
export const SCHEMA_BASELINE_VERSION = "0029";
/** FNXC:SymbolLock 2026-07-31-10:00: upgrades need durable task declarations before admission resolves symbols. */
export const TASK_DECLARED_SYMBOLS_VERSION = "0028";
const INITIAL_SCHEMA_VERSION = "0000";
@@ -124,6 +124,8 @@ export const TASK_VERIFICATION_REQUEST_VERSION = "0024";
export const SYMBOL_LOCKS_SCHEMA_VERSION = "0025";
/** FNXC:PostgresBigintCounters 2026-07-18-21:45: widen overflow-prone counters to bigint before SQLite migration. */
export const BIGINT_COUNTERS_VERSION = "0026";
/** FNXC:TaskTiming 2026-08-01-10:00: existing clusters need planning-session timing columns. */
export const PLANNING_ACTIVE_TIMING_VERSION = "0029";
/**
* Thrown when the database was migrated by a NEWER Fusion binary than the one now
@@ -312,6 +314,8 @@ const WORKFLOW_IR_PIN_AND_LEGACY_ADOPTION_MIGRATION_PATH = join(
MIGRATIONS_DIR,
"0027_workflow_ir_pin_and_legacy_adoption.sql",
);
const PLANNING_ACTIVE_TIMING_MIGRATION_PATH = join(MIGRATIONS_DIR, "0029_planning_active_timing.sql");
const TASK_DECLARED_SYMBOLS_MIGRATION_PATH = join(MIGRATIONS_DIR, "0028_task_declared_symbols.sql");
/**
@@ -409,6 +413,7 @@ export async function applySchemaBaseline(
const symbolLocksAlreadyApplied = applied.includes(SYMBOL_LOCKS_SCHEMA_VERSION);
const bigintCountersAlreadyApplied = applied.includes(BIGINT_COUNTERS_VERSION);
const workflowIrPinAndLegacyAdoptionAlreadyApplied = applied.includes(WORKFLOW_IR_PIN_AND_LEGACY_ADOPTION_VERSION);
const planningActiveTimingAlreadyApplied = applied.includes(PLANNING_ACTIVE_TIMING_VERSION);
assertBinaryNotOlderThanDatabase(applied);
let schemaChanged = false;
@@ -830,6 +835,13 @@ export async function applySchemaBaseline(
baseline so databases that already recorded 0000 gain the IR-pin and adoption columns;
without the forward migration those clusters crash on the first slim TaskStore SELECT.
*/
if (!planningActiveTimingAlreadyApplied) {
const migrationSql = await readFile(PLANNING_ACTIVE_TIMING_MIGRATION_PATH, "utf8");
await tx.execute(sql.raw(migrationSql));
await tx.execute(sql`INSERT INTO public.${sql.identifier(MIGRATION_BOOKKEEPING_TABLE)} (version) VALUES (${PLANNING_ACTIVE_TIMING_VERSION}) ON CONFLICT (version) DO NOTHING`);
schemaChanged = true;
}
if (!workflowIrPinAndLegacyAdoptionAlreadyApplied) {
const migrationSql = await readFile(WORKFLOW_IR_PIN_AND_LEGACY_ADOPTION_MIGRATION_PATH, "utf8");
await tx.execute(sql.raw(migrationSql));

View File

@@ -178,6 +178,8 @@ export const tasks = projectSchema.table("tasks", {
columnMovedAt: text("column_moved_at"),
firstExecutionAt: text("first_execution_at"),
cumulativeActiveMs: bigint("cumulative_active_ms", { mode: "number" }),
cumulativePlanningMs: bigint("cumulative_planning_ms", { mode: "number" }),
planningStartedAt: text("planning_started_at"),
/*
FNXC:PostgresMigrationColumnCoverage 2026-07-14-13:17:
Keep the task schema aligned with late SQLite lifecycle migrations. JSON lifecycle markers stay jsonb for native backend reads; retired board/question fields remain text so their legacy payloads round-trip byte-for-byte.