Skip to content
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions packages/core/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,10 @@
"types": "./dist/src/utils/transcript-records.d.ts",
"import": "./dist/src/utils/transcript-records.js"
},
"./goalWire": {
"types": "./dist/src/goals/goal-wire.d.ts",
"import": "./dist/src/goals/goal-wire.js"
},
"./package.json": "./package.json",
"./dist/*": "./dist/*",
"./src/*": "./src/*"
Expand Down
125 changes: 125 additions & 0 deletions packages/core/src/goals/goal-legacy-projection.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
/**
* @license
* Copyright 2026 Qwen Team
* SPDX-License-Identifier: Apache-2.0
*/

import { describe, expect, it } from 'vitest';
import type {
GoalRecord,
GoalStateCause,
GoalStateRecordPayloadV2,
} from './goal-protocol.js';
import { projectGoalStateToLegacy } from './goal-legacy-projection.js';

const GOAL: GoalRecord = {
goalId: 'goal-1',
revision: 2,
objective: 'ship it',
status: 'active',
evidenceCursor: { recordId: 'state-1' },
turnCount: 4,
activeTimeMs: 2000,
createdAt: 100,
updatedAt: 200,
lastReason: 'continuing',
};

function payload(
cause: GoalStateCause,
status: GoalRecord['status'] = 'active',
goal: GoalRecord | null = { ...GOAL, status },
): GoalStateRecordPayloadV2 {
return {
v: 2,
cause,
snapshot: { v: 2, activity: 'idle', goal },
};
}

describe('projectGoalStateToLegacy', () => {
it.each(['create', 'replace', 'edit', 'resume', 'migrated'] as const)(
'projects %s as legacy set with an active projection',
(cause) => {
const projected = projectGoalStateToLegacy(payload(cause));

expect(projected.goalStatus.kind).toBe('set');
expect(projected.activeGoal).toMatchObject({
condition: 'ship it',
iterations: 4,
setAt: 100,
lastReason: 'continuing',
});
expect(projected.goalTerminal).toBeNull();
},
);

it('projects completion as achieved and stops active_goal', () => {
const projected = projectGoalStateToLegacy(payload('complete', 'complete'));

expect(projected.goalStatus.kind).toBe('achieved');
expect(projected.activeGoal).toBeNull();
expect(projected.goalTerminal).toMatchObject({
kind: 'achieved',
condition: 'ship it',
iterations: 4,
durationMs: 2000,
});
});

it('projects clear as cleared using the prior goal objective', () => {
const projected = projectGoalStateToLegacy(
payload('clear', 'active', null),
GOAL,
);

expect(projected.goalStatus).toMatchObject({
kind: 'cleared',
condition: 'ship it',
});
expect(projected.activeGoal).toBeNull();
expect(projected.goalTerminal).toBeNull();
});

it('projects pause as a non-terminal legacy paused state', () => {
const projected = projectGoalStateToLegacy(payload('pause', 'paused'));

expect(projected.goalStatus.kind).toBe('paused');
expect(projected.activeGoal).toBeNull();
expect(projected.goalTerminal).toBeNull();
});

it.each(['blocked', 'usage_limited'] as const)(
'projects %s as a legacy stopped state',
(status) => {
const projected = projectGoalStateToLegacy(payload(status, status));

expect(projected.goalStatus.kind).toBe('aborted');
expect(projected.activeGoal).toBeNull();
expect(projected.goalTerminal).toMatchObject({
kind: 'aborted',
condition: 'ship it',
});
},
);

it('uses checking for active runtime progress without widening the union', () => {
const projected = projectGoalStateToLegacy(
payload('turn_finished', 'active'),
);

expect(projected.goalStatus.kind).toBe('checking');
expect(projected.activeGoal).not.toBeNull();
expect(projected.goalTerminal).toBeNull();
});

it('does not repeat an aborted terminal after a paused turn finishes', () => {
const projected = projectGoalStateToLegacy(
payload('turn_finished', 'paused'),
);

expect(projected.goalStatus.kind).toBe('checking');
expect(projected.activeGoal).toBeNull();
expect(projected.goalTerminal).toBeNull();
});
});
137 changes: 137 additions & 0 deletions packages/core/src/goals/goal-legacy-projection.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
/**
* @license
* Copyright 2026 Qwen Team
* SPDX-License-Identifier: Apache-2.0
*/

import type { GoalRecord, GoalStateRecordPayloadV2 } from './goal-protocol.js';

export type LegacyGoalStatusKind =
| 'set'
| 'achieved'
| 'cleared'
| 'failed'
| 'aborted'
| 'paused'
| 'checking';

export interface LegacyGoalStatus {
type: 'goal_status';
kind: LegacyGoalStatusKind;
condition: string;
iterations?: number;
setAt?: number;
durationMs?: number;
lastReason?: string;
}

export interface LegacyActiveGoal {
readonly condition: string;
readonly iterations: number;
readonly setAt: number;
readonly tokensAtStart?: number;
readonly hookId?: string;
readonly lastReason?: string;
}

export interface LegacyGoalTerminal {
kind: 'achieved' | 'failed' | 'aborted';
condition: string;
iterations: number;
durationMs: number;
lastReason?: string;
}

export interface LegacyGoalProjection {
activeGoal: LegacyActiveGoal | null;
goalStatus: LegacyGoalStatus;
goalTerminal: LegacyGoalTerminal | null;
}

export function projectGoalStateToLegacy(
payload: GoalStateRecordPayloadV2,
previousGoal: GoalRecord | null = null,
): LegacyGoalProjection {
const snapshotGoal = payload.snapshot.goal;
const displayGoal = snapshotGoal ?? previousGoal;
const kind = legacyStatusKind(payload);
const goalStatus: LegacyGoalStatus = {
type: 'goal_status',
kind,
condition: displayGoal?.objective ?? '',
...(displayGoal ? { iterations: displayGoal.turnCount } : {}),
...(displayGoal ? { setAt: displayGoal.createdAt } : {}),
...(displayGoal ? { durationMs: displayGoal.activeTimeMs } : {}),
...(displayGoal?.lastReason === undefined
? {}
: { lastReason: displayGoal.lastReason }),
};
const terminalKind =
kind === 'achieved' || kind === 'failed' || kind === 'aborted'
? kind
: undefined;

return {
activeGoal:
snapshotGoal?.status === 'active'
? {
condition: snapshotGoal.objective,
iterations: snapshotGoal.turnCount,
setAt: snapshotGoal.createdAt,
...(snapshotGoal.lastReason === undefined
? {}
: { lastReason: snapshotGoal.lastReason }),
}
: null,
goalStatus,
goalTerminal:
terminalKind && displayGoal
? {
kind: terminalKind,
condition: displayGoal.objective,
iterations: displayGoal.turnCount,
durationMs: displayGoal.activeTimeMs,
...(displayGoal.lastReason === undefined
? {}
: { lastReason: displayGoal.lastReason }),
}
: null,
};
}

function legacyStatusKind(
payload: GoalStateRecordPayloadV2,
): LegacyGoalStatusKind {
switch (payload.cause) {
case 'create':
case 'replace':
case 'edit':
case 'resume':
case 'migrated':
return 'set';
case 'complete':
return 'achieved';
case 'clear':
return 'cleared';
case 'pause':
return 'paused';
case 'blocked':
case 'usage_limited':
return 'aborted';
case 'turn_finished':
case 'verifier_accept':
case 'verifier_reject':
return payload.snapshot.goal?.status === 'complete'
? 'achieved'
: payload.snapshot.goal?.status === 'blocked' ||
payload.snapshot.goal?.status === 'usage_limited'
? 'aborted'
: 'checking';
Comment on lines +121 to +129

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[Suggestion] The turn_finished / verifier_accept / verifier_reject case group's inner ternary returns 'achieved' for complete, 'aborted' for blocked/usage_limited, and 'checking' otherwise — but only the 'checking' branch is exercised by tests (via turn_finished with 'active' and 'paused' goals). — Failure scenario: if the ternary logic were inverted or a status comparison used the wrong string literal, no test would catch it. A regression in verifier result projection ships undetected.

// Add parameterized tests mirroring the existing turn_finished patterns:
// payload('turn_finished', 'complete') → goalStatus.kind === 'achieved'
// payload('turn_finished', 'blocked') → goalStatus.kind === 'aborted'
// payload('verifier_accept', 'complete') → goalStatus.kind === 'achieved'
// payload('verifier_reject', 'active') → goalStatus.kind === 'checking'

— qwen3.7-max via Qwen Code /review

default:
return assertNever(payload.cause);
}
}

function assertNever(value: never): never {
throw new Error(`Unsupported Goal state cause: ${String(value)}`);
}
Loading
Loading