You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@maka.apache.org by as...@apache.org on 2026/08/23 15:13:41 UTC
(maka) branch main updated: fix: expose active model in stable prompts (#3613)
This is an automated email from the ASF dual-hosted git repository.
Astro-Han pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/maka.git
The following commit(s) were added to refs/heads/main by this push:
new cd0163a1e fix: expose active model in stable prompts (#3613)
cd0163a1e is described below
commit cd0163a1effc27aec61cc331f799959e30fd7446
Author: AstroHan <le...@outlook.com>
AuthorDate: Sun Aug 23 23:13:36 2026 +0800
fix: expose active model in stable prompts (#3613)
* fix: expose active model in stable prompts
Prepend the selected model at the Runtime Host execution boundary after final target resolution. This keeps interactive composers model-agnostic while ensuring main, child, and headless provider requests share one stable execution identity.
Generated-by: Maka
* refactor: collapse model switch transcript projection
Return the latest assistant model directly instead of maintaining an unused conversation-presence field and object wrapper. The renderer only needs the latest actual model for switch feedback.
Generated-by: Maka
* refactor: trim session change invalidation payload
Session change events invalidate renderer projections and correlate turn lifecycle changes. Remove the unused connection slug field and stop attaching model details to ordinary model-update invalidations; modelId remains only for rebound feedback.
Generated-by: Maka
---
apps/desktop/src/main/runtime-host-boot.ts | 6 ++----
.../src/main/runtime-host-desktop-candidate.ts | 4 ++--
.../main/runtime-host-session-catalog-ipc-main.ts | 12 +++--------
.../renderer/app-shell-session-settings-actions.ts | 6 +++---
packages/cli/src/pi-tui-runner.ts | 6 +++---
.../src/__tests__/model-switch-transcript.test.ts | 15 +++++---------
packages/core/src/session.ts | 24 ++++------------------
.../__tests__/execution-model-composition.test.ts | 20 +++++++++---------
.../src/server/execution-model-composition.ts | 7 ++++++-
9 files changed, 38 insertions(+), 62 deletions(-)
diff --git a/apps/desktop/src/main/runtime-host-boot.ts b/apps/desktop/src/main/runtime-host-boot.ts
index 333dbe303..a17900318 100644
--- a/apps/desktop/src/main/runtime-host-boot.ts
+++ b/apps/desktop/src/main/runtime-host-boot.ts
@@ -941,7 +941,7 @@ function registerHostClientIpc(
const emitTargetSessionsChanged = (
reason: SessionChangedReason,
sessionId?: string,
- extra?: Pick<SessionChangedEvent, "connectionSlug" | "modelId" | "turnId">,
+ extra?: Pick<SessionChangedEvent, "modelId" | "turnId">,
): void => {
if (isTargetActive()) emitSessionsChanged(scope, reason, sessionId, extra);
};
@@ -1461,14 +1461,12 @@ function emitSessionsChanged(
scope: DesktopTargetScope,
reason: SessionChangedReason,
sessionId?: string,
- extra?: Pick<SessionChangedEvent, "connectionSlug" | "modelId" | "turnId">,
+ extra?: Pick<SessionChangedEvent, "modelId" | "turnId">,
): void {
const event: SessionChangedEvent = {
- type: "sessions_changed",
reason,
ts: Date.now(),
...(sessionId ? { sessionId } : {}),
- ...(extra?.connectionSlug ? { connectionSlug: extra.connectionSlug } : {}),
...(extra?.modelId ? { modelId: extra.modelId } : {}),
...(extra?.turnId ? { turnId: extra.turnId } : {}),
};
diff --git a/apps/desktop/src/main/runtime-host-desktop-candidate.ts b/apps/desktop/src/main/runtime-host-desktop-candidate.ts
index c6e9164e2..7d8f2027d 100644
--- a/apps/desktop/src/main/runtime-host-desktop-candidate.ts
+++ b/apps/desktop/src/main/runtime-host-desktop-candidate.ts
@@ -103,7 +103,7 @@ export interface DesktopRuntimeHostCandidateDeps {
scope: DesktopTargetScope,
reason: SessionChangedReason,
sessionId?: string,
- extra?: Pick<SessionChangedEvent, "connectionSlug" | "modelId" | "turnId">,
+ extra?: Pick<SessionChangedEvent, "modelId" | "turnId">,
) => void;
readonly completeComputerUseTurn: (
sessionId: string,
@@ -366,7 +366,7 @@ export async function createDesktopRuntimeHostCandidate(
const emitSessionsChanged = (
reason: SessionChangedReason,
sessionId?: string,
- extra?: Pick<SessionChangedEvent, "connectionSlug" | "modelId" | "turnId">,
+ extra?: Pick<SessionChangedEvent, "modelId" | "turnId">,
): void => {
if (isTargetActive()) deps.emitSessionsChanged(scope, reason, sessionId, extra);
};
diff --git a/apps/desktop/src/main/runtime-host-session-catalog-ipc-main.ts b/apps/desktop/src/main/runtime-host-session-catalog-ipc-main.ts
index a146d86ec..cf5c147c9 100644
--- a/apps/desktop/src/main/runtime-host-session-catalog-ipc-main.ts
+++ b/apps/desktop/src/main/runtime-host-session-catalog-ipc-main.ts
@@ -70,7 +70,7 @@ export interface RuntimeHostSessionCatalogIpcDeps {
emitSessionsChanged: (
reason: SessionChangedReason,
sessionId?: string,
- extra?: Pick<SessionChangedEvent, 'connectionSlug' | 'modelId' | 'turnId'>,
+ extra?: Pick<SessionChangedEvent, 'modelId' | 'turnId'>,
) => void;
releaseSessionResources: (sessionId: string) => void | Promise<void>;
sessionCopyCleanup: SessionCopyCleanupAuthority;
@@ -199,13 +199,7 @@ export function registerRuntimeHostSessionCatalogIpc(
);
ipcMain.handle('sessions:setModel', async (_event, sessionId: string, input: unknown) => {
const modelTarget = normalizeExplicitModel(input);
- return updateConfiguration(
- deps,
- sessionId,
- { modelTarget, thinkingLevel: null },
- 'updated',
- { connectionSlug: modelTarget.connectionSlug, modelId: modelTarget.model },
- );
+ return updateConfiguration(deps, sessionId, { modelTarget, thinkingLevel: null }, 'updated');
});
ipcMain.handle('sessions:setThinkingLevel', async (_event, sessionId: string, level: unknown) => {
if (level !== undefined && level !== null && !isThinkingLevel(level)) {
@@ -263,7 +257,7 @@ async function updateConfiguration(
sessionId: string,
patch: DesktopSessionConfigurationPatch,
reason: SessionChangedReason,
- extra?: Pick<SessionChangedEvent, 'connectionSlug' | 'modelId' | 'turnId'>,
+ extra?: Pick<SessionChangedEvent, 'modelId' | 'turnId'>,
): Promise<DesktopHostSessionSummary> {
const session = await deps.client.updateSessionConfiguration(sessionId, patch);
deps.emitSessionsChanged(reason, sessionId, extra);
diff --git a/apps/desktop/src/renderer/app-shell-session-settings-actions.ts b/apps/desktop/src/renderer/app-shell-session-settings-actions.ts
index 0c4f37d78..2db4f149a 100644
--- a/apps/desktop/src/renderer/app-shell-session-settings-actions.ts
+++ b/apps/desktop/src/renderer/app-shell-session-settings-actions.ts
@@ -21,7 +21,7 @@ import type { ChatDefaultPermissionMode } from '@maka/core/settings';
import type { LlmConnection } from '@maka/core/llm-connections';
import type { PermissionMode } from '@maka/core/permission';
import {
- deriveModelSwitchTranscript,
+ latestAssistantModelId,
type StoredMessage,
} from '@maka/core/session';
import type { ThinkingLevel } from '@maka/core/model-thinking';
@@ -177,7 +177,7 @@ export function createAppShellSessionSettingsActions(deps: {
const sessionId = activeIdRef.current;
if (!sessionId) return;
const previous = sessionsRef.current.find((session) => session.id === sessionId);
- const transcript = deriveModelSwitchTranscript(messages);
+ const lastUsedModel = latestAssistantModelId(messages);
if (pendingSessionModelChangesRef.current.has(sessionId)) return;
pendingSessionModelChangesRef.current.add(sessionId);
setPendingSessionModelBySession((current) => ({
@@ -190,7 +190,7 @@ export function createAppShellSessionSettingsActions(deps: {
if (activeIdRef.current === sessionId) {
const connectionChanged = previous?.llmConnectionSlug !== next.llmConnectionSlug;
const to = modelEndpointLabel(next.llmConnectionSlug, next.model, connectionChanged);
- const previousModel = transcript.lastUsedModel ?? previous?.model;
+ const previousModel = lastUsedModel ?? previous?.model;
toastApi.success(
copy.modelSwitchedTitle,
previous && previousModel
diff --git a/packages/cli/src/pi-tui-runner.ts b/packages/cli/src/pi-tui-runner.ts
index 939c56b72..478c66411 100644
--- a/packages/cli/src/pi-tui-runner.ts
+++ b/packages/cli/src/pi-tui-runner.ts
@@ -48,7 +48,7 @@ import {
} from '@maka/core/slash-command-catalog';
import { type QueueEnqueueOutcome, type ShellRunUpdate } from '@maka/core/events';
import {
- deriveModelSwitchTranscript,
+ latestAssistantModelId,
type SessionSummary,
type StoredMessage,
} from '@maka/core/session';
@@ -1462,7 +1462,7 @@ export async function runMakaPiTui(input: MakaPiTuiInput): Promise<void> {
const setModel = async (nextModel: string) => {
if (nextModel === model) return;
- const previousModel = deriveModelSwitchTranscript(transcriptMessages).lastUsedModel ?? model;
+ const previousModel = latestAssistantModelId(transcriptMessages) ?? model;
await input.driver.setModel(nextModel);
model = nextModel;
// Same-connection switch: scope the choice lookup to the live connection
@@ -1488,7 +1488,7 @@ export async function runMakaPiTui(input: MakaPiTuiInput): Promise<void> {
// Updates the provider (and thus the thinking variants) and the status line.
const setModelChoice = async (choice: ModelChoice) => {
if (choice.model === model && choice.connectionSlug === connectionSlug) return;
- const previousModel = deriveModelSwitchTranscript(transcriptMessages).lastUsedModel ?? model;
+ const previousModel = latestAssistantModelId(transcriptMessages) ?? model;
const previousConnectionSlug = connectionSlug;
const previousChoice = modelChoices?.find(
(candidate) =>
diff --git a/packages/core/src/__tests__/model-switch-transcript.test.ts b/packages/core/src/__tests__/model-switch-transcript.test.ts
index 2a67f5e75..f7e861310 100644
--- a/packages/core/src/__tests__/model-switch-transcript.test.ts
+++ b/packages/core/src/__tests__/model-switch-transcript.test.ts
@@ -19,10 +19,10 @@
import { strict as assert } from 'node:assert';
import { describe, it } from 'node:test';
-import { deriveModelSwitchTranscript, type StoredMessage } from '../session.js';
+import { latestAssistantModelId, type StoredMessage } from '../session.js';
-describe('deriveModelSwitchTranscript', () => {
- it('treats any durable transcript as an existing conversation', () => {
+describe('latestAssistantModelId', () => {
+ it('returns undefined until an assistant response exists', () => {
const messages: StoredMessage[] = [
{
type: 'user',
@@ -33,9 +33,7 @@ describe('deriveModelSwitchTranscript', () => {
},
];
- assert.deepEqual(deriveModelSwitchTranscript(messages), {
- hasConversation: true,
- });
+ assert.equal(latestAssistantModelId(messages), undefined);
});
it('uses the latest assistant model as the actual baseline', () => {
@@ -58,9 +56,6 @@ describe('deriveModelSwitchTranscript', () => {
},
];
- assert.deepEqual(deriveModelSwitchTranscript(messages), {
- hasConversation: true,
- lastUsedModel: 'model-b',
- });
+ assert.equal(latestAssistantModelId(messages), 'model-b');
});
});
diff --git a/packages/core/src/session.ts b/packages/core/src/session.ts
index d9fc06599..218a447a7 100644
--- a/packages/core/src/session.ts
+++ b/packages/core/src/session.ts
@@ -685,10 +685,8 @@ export type SessionChangedReason =
| 'rebound';
export interface SessionChangedEvent {
- type: 'sessions_changed';
reason: SessionChangedReason;
sessionId?: string;
- connectionSlug?: string;
modelId?: string;
/**
* The turn this change is ABOUT, when the change has a turn to name.
@@ -1205,27 +1203,13 @@ function isToolActivityIdentity(value: Record<string, unknown>): boolean {
export const STEP_LIMIT_NOTICE_TEXT =
'Reached the configured step limit. The task may be incomplete. Send “continue” to resume.';
-/**
- * View-boundary facts for explaining a model selection without adding a
- * second persisted model authority. Assistant rows already record the actual
- * model used by each completed step, so the latest such row is the only
- * durable model fact the switcher needs.
- */
-export function deriveModelSwitchTranscript(messages: readonly StoredMessage[]): {
- hasConversation: boolean;
- lastUsedModel?: string;
-} {
- let lastUsedModel: string | undefined;
+/** Latest actual model recorded by a completed assistant step. */
+export function latestAssistantModelId(messages: readonly StoredMessage[]): string | undefined {
for (let index = messages.length - 1; index >= 0; index -= 1) {
const message = messages[index];
- if (message?.type !== 'assistant') continue;
- lastUsedModel = message.modelId;
- break;
+ if (message?.type === 'assistant') return message.modelId;
}
- return {
- hasConversation: messages.length > 0,
- ...(lastUsedModel ? { lastUsedModel } : {}),
- };
+ return undefined;
}
export function deriveTurnRecords(messages: readonly StoredMessage[]): TurnRecord[] {
diff --git a/packages/runtime-host/src/__tests__/execution-model-composition.test.ts b/packages/runtime-host/src/__tests__/execution-model-composition.test.ts
index af100b503..38ede3a6f 100644
--- a/packages/runtime-host/src/__tests__/execution-model-composition.test.ts
+++ b/packages/runtime-host/src/__tests__/execution-model-composition.test.ts
@@ -134,7 +134,7 @@ const MIN_IMPLEMENTATION_CHILD_REQUESTS = 6;
const MAX_IMPLEMENTATION_CHILD_REQUESTS =
MIN_IMPLEMENTATION_CHILD_REQUESTS + MAX_IMPLEMENTATION_CHILD_PTY_READS - 1;
const HEADLESS_CODING_V1_PROMPT_HASH =
- 'sha256:0e3389e330b8b8f0db1c7a8b8e2126325fe4c672d6eff279afcd3f9412e52271';
+ 'sha256:b2773282ac4755dc8d8a663eafdec68c3fa6f5680ec8557d261b5f723672b467';
const HEADLESS_CODING_V1_TOOLS_HASH =
'sha256:c062194603f93b568da5ca59b865b316156b5f218ba854c291aa9582859b3de4';
const execFileAsync = promisify(execFile);
@@ -920,6 +920,7 @@ test('hosted execution freezes the headless coding provider wire contract', asyn
const instructions = responsesDeveloperPrompt(request?.body);
const tools = request?.body.tools;
assert.equal(typeof instructions, 'string', JSON.stringify(request?.body));
+ assert.match(instructions ?? '', /^Active model: deepseek-v4-flash$/mu);
assert.ok(Array.isArray(tools));
assert.equal(stableHash(instructions), HEADLESS_CODING_V1_PROMPT_HASH);
assert.equal(stableHash(tools), HEADLESS_CODING_V1_TOOLS_HASH);
@@ -2831,15 +2832,14 @@ test('backend composition survives a moved saved Git Bash executable while Bash
const capturedBash = childComposer.tools.find((tool) => tool.name === 'Bash');
assert.match(capturedBash?.description ?? '', /captured child shell/);
assert.doesNotMatch(capturedBash?.description ?? '', /unavailable this turn/);
- assert.match(
- await childComposer.turnTailPrompt({
- sessionId: 'session',
- turnId: 'turn-child',
- cwd: '/workspace',
- workspaceRoot: '/workspace',
- }),
- /captured child shell/,
- );
+ const childContext = {
+ sessionId: 'session',
+ turnId: 'turn-child',
+ cwd: '/workspace',
+ workspaceRoot: '/workspace',
+ } as const;
+ const childTail = await childComposer.turnTailPrompt(childContext);
+ assert.match(childTail, /captured child shell/);
});
test('child execution Bash carries the configured shell guidance and spawn plan', async () => {
diff --git a/packages/runtime-host/src/server/execution-model-composition.ts b/packages/runtime-host/src/server/execution-model-composition.ts
index 015e9b644..a18e6f293 100644
--- a/packages/runtime-host/src/server/execution-model-composition.ts
+++ b/packages/runtime-host/src/server/execution-model-composition.ts
@@ -288,7 +288,7 @@ export async function createHostAiSdkBackend(input: HostAiSdkBackendInput): Prom
readonly runId?: string;
readonly emitSkillCatalogTrace?: (message: string, data?: Record<string, unknown>) => void;
}) => {
- return await modelComposition.resolveSystemPrompt({
+ const resolved = await modelComposition.resolveSystemPrompt({
sessionId: input.context.sessionId,
turnId: context.turnId,
...(context.runId ? { runId: context.runId } : {}),
@@ -298,6 +298,11 @@ export async function createHostAiSdkBackend(input: HostAiSdkBackendInput): Prom
? { emitSkillCatalogTrace: context.emitSkillCatalogTrace }
: {}),
});
+ const model = target.model.replace(/[\r\n\t]+/g, ' ').trim();
+ return Object.freeze({
+ ...resolved,
+ text: [`Active model: ${model}`, resolved.text].filter(Boolean).join('\n\n'),
+ });
};
const recordRunComposition = input.context.recordRunComposition;
const commitRunComposition = recordRunComposition
---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@maka.apache.org
For additional commands, e-mail: commits-help@maka.apache.org