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