Skip to content
Open
Show file tree
Hide file tree
Changes from all 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
23 changes: 23 additions & 0 deletions apps/desktop/src/main/__tests__/goal-controller.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -247,6 +247,29 @@ describe('useGoalController', () => {
assert.equal(pauseCalls, 2);
});

it('shows an armed marker only while the first Turn is unbound', async () => {
const { root } = installReactRenderer();
const defaults = createFakeGoalServices();
const services = createFakeGoalServices({
goal: {
...defaults.goal,
get: async () => ({ ...goal('a'), armedAt: 150 }),
},
});

await act(async () => renderController(root, services, input('a')));
assert.equal(controller().selectors.indicator?.armedAt, 150);

const boundServices = createFakeGoalServices({
goal: {
...defaults.goal,
get: async () => ({ ...goal('a'), armedAt: 150, boundTurnId: 'turn-1' }),
},
});
await act(async () => renderController(root, boundServices, input('a')));
assert.equal(controller().selectors.indicator?.armedAt, undefined);
});

it('routes resume and clear controls for paused Goals', async () => {
const { root } = installReactRenderer();
const calls: string[] = [];
Expand Down
15 changes: 15 additions & 0 deletions apps/desktop/src/main/__tests__/goal-dialog.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,21 @@ test('closes only for armed and locks reconciled state until reopen', async () =
assert.equal(harness.closed, 1);
});

test('redacts secrets in a reconciled Goal condition', async () => {
const secret = 'sk-ant-api03-abc123def456ghi789jkl0mn1opq';
const harness = installGoalDialog(async () => ({
kind: 'reconciled',
currentGoal: { ...goalState(), condition: `Use Authorization: Bearer ${secret}` },
matchesRequestedState: true,
}));
await harness.render('session-1');
await setInputValue(harness.document, 'textarea', 'Finish session one');
await clickButton(harness.document, 'Start');

assert.equal(harness.document.body.textContent.includes(secret), false);
assert.match(harness.document.body.textContent, /Authorization: Bearer <redacted>/);
});

test('keeps the Goal form editable after a deterministic rejection', async () => {
const harness = installGoalDialog(async () => {
throw new Error('Goal already exists');
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -892,6 +892,8 @@ function goalProjection(revision: number) {
lastReason: null,
achievedAt: null,
pausedAt: null,
armedAt: null,
boundTurnId: null,
};
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,7 @@ test('goal:arm reconciles a lost dispatched response without dispatching again',
tokensAtStart: 0,
tokensNow: 120,
tokensBaselinePending: false,
armedAt: 7,
},
matchesRequestedState: true,
},
Expand Down Expand Up @@ -311,6 +312,7 @@ test('goal:arm reconciliation reports different, missing, and unavailable author
tokensAtStart: 0,
tokensNow: 120,
tokensBaselinePending: false,
armedAt: 7,
},
matchesRequestedState: false,
},
Expand Down Expand Up @@ -490,6 +492,7 @@ test('adapts Host Goal, Task, Deep Research, and Resource projections', async ()
tokensAtStart: 0,
tokensNow: 120,
tokensBaselinePending: false,
armedAt: 7,
});
await ipc.invoke('goal:clear', 'session-1');
await ipc.invoke('goal:pause', 'session-1');
Expand Down Expand Up @@ -1217,6 +1220,8 @@ function baseGoalProjection() {
lastReason: null,
achievedAt: null,
pausedAt: null,
armedAt: 7,
boundTurnId: null,
};
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2402,6 +2402,8 @@ test("publishes Host sidecar and graph invalidations without inventing Session s
lastReason: null,
achievedAt: null,
pausedAt: null,
armedAt: null,
boundTurnId: null,
},
}),
});
Expand Down Expand Up @@ -2510,6 +2512,8 @@ function activeGoal() {
lastReason: null,
achievedAt: null,
pausedAt: null,
armedAt: null,
boundTurnId: null,
};
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ import type {
AgentGraphClientSnapshotOptions,
AgentGraphOperatorInspection,
} from '@maka/runtime/stream-graph-read-model';
import { DEFAULT_MAX_ITERATIONS, type GoalState } from '@maka/runtime/goal-state';
import { DEFAULT_MAX_ITERATIONS } from '@maka/runtime/goal-state';
import type { ShellRunPtyDataEvent } from '@maka/runtime/shell-run-contract';
import type {
GoalProjection,
Expand All @@ -37,6 +37,7 @@ import {
GOAL_ARM_REQUEST_KEYS,
type GoalArmOutcome,
} from '../shared/goal-arm.js';
import type { DesktopGoalState } from '../shared/desktop-goal-state.js';
import { projectHostedDeepResearch } from './deep-research-desktop-projection.js';
import {
handleReconciledControl,
Expand Down Expand Up @@ -458,7 +459,7 @@ function optionalCount(value: unknown, label: string): number | null {
return value;
}

function toDesktopGoal(goal: GoalProjection): GoalState {
function toDesktopGoal(goal: GoalProjection): DesktopGoalState {
return {
id: goal.goalId,
revision: goal.revision,
Expand All @@ -477,6 +478,8 @@ function toDesktopGoal(goal: GoalProjection): GoalState {
...(goal.lastReason === null ? {} : { lastReason: goal.lastReason }),
...(goal.achievedAt === null ? {} : { achievedAt: goal.achievedAt }),
...(goal.pausedAt === null ? {} : { pausedAt: goal.pausedAt }),
...(goal.armedAt === null ? {} : { armedAt: goal.armedAt }),
...(goal.boundTurnId === null ? {} : { boundTurnId: goal.boundTurnId }),
};
}

Expand Down
2 changes: 1 addition & 1 deletion apps/desktop/src/preload/bridge-contract.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -914,7 +914,7 @@ export interface MakaBridge {
};
goal: {
/** The session's current goal (null when none is set). */
get(sessionId: string): Promise<import('@maka/runtime/goal-state').GoalState | null>;
get(sessionId: string): Promise<import('../shared/desktop-goal-state').DesktopGoalState | null>;
/**
* Arm a goal for this session. It drives the session from the next turn
* on; arming alone starts nothing. Rejects when the session already has an
Expand Down
4 changes: 2 additions & 2 deletions apps/desktop/src/preload/preload.ts
Original file line number Diff line number Diff line change
Expand Up @@ -181,7 +181,7 @@ import type {
} from '@maka/runtime/stream-graph-read-model';
import type { BotStatus, WechatBridgeQrCodeResult } from '@maka/runtime/bots';
import type { ShellRunPtyDataEvent, ShellRunPtySnapshot } from '@maka/runtime/shell-run-contract';
import type { GoalState } from '@maka/runtime/goal-state';
import type { DesktopGoalState } from '../shared/desktop-goal-state.js';
import type { BundledSkillCatalogEntry, ManagedSkillSourceEntry, ManagedSkillUpdatePreview, SkillEntry } from '@maka/ui';
import type { ConfigCategory } from '@maka/storage';
import {
Expand Down Expand Up @@ -2122,7 +2122,7 @@ const makaBridge = {
},
},
goal: {
get(sessionId: string): Promise<GoalState | null> {
get(sessionId: string): Promise<DesktopGoalState | null> {
return invokeProjectedSessionRuntimeHost('goal:get', sessionId);
},
arm(sessionId: string, goal: GoalArmRequest): Promise<GoalArmOutcome> {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import {
useState,
type ComponentProps,
} from 'react';
import { isGoalArmedAwaitingFirstTurn } from '@maka/core/goal';
import { useUiLocale, type ChatView } from '@maka/ui';
import {
getShellCopy,
Expand Down Expand Up @@ -144,6 +145,9 @@ export function useGoalController(
iterations: activeGoal.iterations,
maxIterations: activeGoal.maxIterations,
setAt: activeGoal.setAt,
...(isGoalArmedAwaitingFirstTurn(activeGoal)
? { armedAt: activeGoal.armedAt }
: {}),
tokensSpent: activeGoal.tokensNow,
...(activeGoal.tokenBudget !== undefined
? { tokenBudget: activeGoal.tokenBudget }
Expand Down
9 changes: 5 additions & 4 deletions apps/desktop/src/renderer/features/goals/model/live-goal.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,21 +17,22 @@
* under the License.
*/

import type { GoalState, GoalStatus } from '@maka/core/goal';
import type { GoalStatus } from '@maka/core/goal';
import type { DesktopGoalState } from '../../../../shared/desktop-goal-state.js';

type LiveGoalStatus = Extract<GoalStatus, 'active' | 'waiting'>;

export type LiveGoalState =
| (GoalState & { readonly status: LiveGoalStatus })
| (GoalState & { readonly status: 'paused'; readonly pausedAt: number });
| (DesktopGoalState & { readonly status: LiveGoalStatus })
| (DesktopGoalState & { readonly status: 'paused'; readonly pausedAt: number });

const LIVE_GOAL_STATUSES: ReadonlySet<GoalStatus> = new Set([
'active',
'waiting',
'paused',
]);

export function isLiveGoal(goal: GoalState): goal is LiveGoalState {
export function isLiveGoal(goal: DesktopGoalState): goal is LiveGoalState {
return (
LIVE_GOAL_STATUSES.has(goal.status) &&
(goal.status !== 'paused' ||
Expand Down
4 changes: 2 additions & 2 deletions apps/desktop/src/renderer/features/goals/ports.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
* under the License.
*/

import type { GoalState } from '@maka/core/goal';
import type { DesktopGoalState } from '../../../shared/desktop-goal-state.js';
import type { GoalArmOutcome } from '../../../shared/goal-arm.js';

export type { GoalArmOutcome } from '../../../shared/goal-arm.js';
Expand All @@ -32,7 +32,7 @@ export interface GoalArmInput {

/** The minimum environment capability needed by the Goals feature. */
export interface GoalService {
get(sessionId: string): Promise<GoalState | null>;
get(sessionId: string): Promise<DesktopGoalState | null>;
arm(sessionId: string, goal: GoalArmInput): Promise<GoalArmOutcome>;
clear(sessionId: string): Promise<void>;
pause(sessionId: string): Promise<void>;
Expand Down
6 changes: 3 additions & 3 deletions apps/desktop/src/renderer/features/goals/ui/goal-dialog.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ import {
GOAL_MAX_ITERATIONS_LIMIT,
GOAL_TOKEN_BUDGET_MINIMUM,
} from '@maka/core/goal';
import { useUiLocale } from '@maka/ui';
import { redactSecrets, useUiLocale } from '@maka/ui';
import {
getShellCopy,
localizedShellErrorMessage,
Expand Down Expand Up @@ -109,12 +109,12 @@ export function GoalDialog(props: GoalDialogProps) {
switch (reconciliation.kind) {
case 'matching_goal':
return copy.reconciledMatching(
reconciliation.goal.condition,
redactSecrets(reconciliation.goal.condition),
copy.statusLabels[reconciliation.goal.status],
);
case 'different_goal':
return copy.reconciledDifferent(
reconciliation.goal.condition,
redactSecrets(reconciliation.goal.condition),
copy.statusLabels[reconciliation.goal.status],
);
case 'no_goal':
Expand Down
25 changes: 25 additions & 0 deletions apps/desktop/src/shared/desktop-goal-state.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/

import type { GoalState } from '@maka/runtime/goal-state';

/** Desktop-only runtime detail; it is transient and never persisted with a Goal. */
export type DesktopGoalState = GoalState & {
readonly boundTurnId?: string;
};
6 changes: 3 additions & 3 deletions apps/desktop/src/shared/goal-arm.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
* under the License.
*/

import type { GoalState } from '@maka/runtime/goal-state';
import type { DesktopGoalState } from './desktop-goal-state.js';

/**
* What the renderer sends to arm a Goal.
Expand All @@ -34,10 +34,10 @@ export interface GoalArmRequest {
}

export type GoalArmOutcome =
| { readonly kind: 'armed'; readonly goal: GoalState }
| { readonly kind: 'armed'; readonly goal: DesktopGoalState }
| {
readonly kind: 'reconciled';
readonly currentGoal: GoalState | null;
readonly currentGoal: DesktopGoalState | null;
readonly matchesRequestedState: boolean;
}
| { readonly kind: 'reconciliation_unavailable' };
Expand Down
35 changes: 35 additions & 0 deletions packages/cli/src/__tests__/pi-goal.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ import {
goalStatusLineText,
goalSummaryLines,
isLiveGoalStatus,
shouldAnnounceGoalAttachment,
} from '../pi-goal.js';

function goal(overrides: Partial<GoalProjection> = {}): GoalProjection {
Expand All @@ -51,6 +52,8 @@ function goal(overrides: Partial<GoalProjection> = {}): GoalProjection {
lastReason: null,
achievedAt: null,
pausedAt: null,
armedAt: null,
boundTurnId: null,
...overrides,
};
}
Expand Down Expand Up @@ -99,6 +102,28 @@ describe('pi-goal display helpers', () => {
);
});

test('armed Goals remain set until their first bound Turn, without a running notice or elapsed time', () => {
const armed = goal({ armedAt: 1_000 });
assert.equal(goalStatusLineText(armed, 61_000), 'goal set 3/50');
assert.deepEqual(goalSummaryLines(armed, 61_000).slice(0, 2), [
'Goal: Ship the feature',
'Status: set · 3/50 iterations',
]);
assert.equal(
goalAttachedNoticeText(armed),
'Autonomous goal is set (3/50): Ship the feature — it takes hold on the next Turn.',
);
assert.equal(shouldAnnounceGoalAttachment(armed), false);
assert.equal(shouldAnnounceGoalAttachment(goal()), true);
});

test('a bound first Turn makes the same Goal running again', () => {
const running = goal({ armedAt: 1_000, boundTurnId: 'turn-1' });
assert.equal(goalStatusLineText(running, 61_000), 'goal 3/50 1m');
assert.equal(shouldAnnounceGoalAttachment(running), true);
assert.match(goalAttachedNoticeText(running), /Autonomous goal is running/);
});

test('summary lines include budget only when set and the evaluator note only when present', () => {
const plain = goalSummaryLines(goal(), 61_000);
assert.equal(plain.length, 2);
Expand Down Expand Up @@ -184,4 +209,14 @@ describe('pi-goal display helpers', () => {
const long = goalAttachedNoticeText(goal({ condition: 'x'.repeat(200) }));
assert.ok(long.includes('…') && long.length <= 210);
});

test('redacts secrets from condition text in CLI goal displays', () => {
const secret = 'sk-ant-api03-abc123def456ghi789jkl0mn1opq';
const current = goal({ condition: `Use Authorization: Bearer ${secret}` });

for (const text of [goalAttachedNoticeText(current), goalSummaryLines(current, 61_000)[0]!]) {
assert.equal(text.includes(secret), false);
assert.match(text, /<redacted>/);
}
});
});
2 changes: 2 additions & 0 deletions packages/cli/src/__tests__/pi-transcript.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -155,6 +155,8 @@ describe('Maka Pi TUI transcript', () => {
lastReason: null,
achievedAt: null,
pausedAt: null,
armedAt: null,
boundTurnId: null,
} as const;
const active = stripAnsi(
renderMakaPiStatusLine({ ...meta(), goal: { ...base, status: 'active' as const } }, 120),
Expand Down
2 changes: 2 additions & 0 deletions packages/cli/src/__tests__/pi-tui-runner.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5730,6 +5730,8 @@ describe('Maka Pi TUI runner', () => {
lastReason: 'tests still failing',
achievedAt: null,
pausedAt: null,
armedAt: null,
boundTurnId: null,
};

test('/goal prints the live goal summary and the status line carries the indicator', async () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1844,6 +1844,8 @@ function goalProjection(overrides: Partial<GoalProjection> = {}): GoalProjection
lastReason: null,
achievedAt: null,
pausedAt: null,
armedAt: null,
boundTurnId: null,
...overrides,
};
}
Expand Down
Loading