Skip to content
Merged
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
14 changes: 10 additions & 4 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
name: 'Release'

# Fires when the dev→main promotion PR is merged.
# Fires when a release-bearing PR is merged into main.
# Builds the bundle, publishes @protolabsai/proto to npm, tags, and creates a GitHub Release.
#
# Flow: feature PRs → dev → prepare-release.yml bumps version on dev
# → dev→main promotion PR merges → this workflow tags and releases.
# Two flows trigger this:
# 1. Auto-release: auto-release.yml opens an `auto-release/v*` PR with the
# version bump, which merges to main and triggers this workflow.
# 2. Manual dev→main promotion: dev branch PR'd into main (legacy path,
# still supported).

on:
pull_request:
Expand All @@ -24,7 +27,10 @@ jobs:
github.event_name == 'workflow_dispatch' ||
(
github.event.pull_request.merged == true &&
github.event.pull_request.head.ref == 'dev'
(
github.event.pull_request.head.ref == 'dev' ||
startsWith(github.event.pull_request.head.ref, 'auto-release/')
)
)
)
timeout-minutes: 30
Expand Down
14 changes: 7 additions & 7 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@protolabsai/proto",
"version": "0.27.0",
"version": "0.28.0",
"publishConfig": {
"access": "public"
},
Expand All @@ -20,7 +20,7 @@
"url": "https://github.com/protoLabsAI/protoCLI/issues"
},
"config": {
"sandboxImageUri": "ghcr.io/qwenlm/qwen-code:0.27.0"
"sandboxImageUri": "ghcr.io/qwenlm/qwen-code:0.28.0"
},
"scripts": {
"start": "cross-env node scripts/start.js",
Expand Down
4 changes: 2 additions & 2 deletions packages/cli/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@protolabs/proto",
"version": "0.27.0",
"version": "0.28.0",
"description": "proto",
"repository": {
"type": "git",
Expand Down Expand Up @@ -37,7 +37,7 @@
"dist"
],
"config": {
"sandboxImageUri": "ghcr.io/qwenlm/qwen-code:0.27.0"
"sandboxImageUri": "ghcr.io/qwenlm/qwen-code:0.28.0"
},
"dependencies": {
"@agentclientprotocol/sdk": "^0.14.1",
Expand Down
2 changes: 1 addition & 1 deletion packages/cli/src/acp-integration/acpAgent.ts
Original file line number Diff line number Diff line change
Expand Up @@ -159,7 +159,7 @@ class QwenAgent implements Agent {
return {
protocolVersion: PROTOCOL_VERSION,
agentInfo: {
name: 'qwen-code',
name: 'proto-cli',
title: 'proto',
version,
},
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
/**
* @license
* Copyright 2025 Qwen Team
* SPDX-License-Identifier: Apache-2.0
*/

import { describe, expect, it } from 'vitest';
import { render } from 'ink-testing-library';
import { ThinkMessage, ThinkMessageContent } from './ConversationMessages.js';

describe('ThinkMessage', () => {
it('renders the streaming text expanded while pending', () => {
const { lastFrame } = render(
<ThinkMessage
text="Let me consider this carefully and weigh the options."
isPending={true}
contentWidth={80}
/>,
);
const output = lastFrame() ?? '';
expect(output).toContain('Let me consider this');
// Streaming render uses the existing ⟡ glyph, not the ▸ summary marker.
expect(output).toContain('⟡');
expect(output).not.toContain('thinking (');
});

it('renders compact "thinking (N chars)" summary once stream finalizes', () => {
const text = 'reasoning '.repeat(10).trim(); // 99 chars
const { lastFrame } = render(
<ThinkMessage text={text} isPending={false} contentWidth={80} />,
);
const output = lastFrame() ?? '';
expect(output).toContain('▸');
expect(output).toContain(`thinking (${text.length} chars)`);
// Underlying reasoning text is not rendered inline post-stream.
expect(output).not.toContain('reasoning reasoning');
});

it('formats large char counts with thousands separator', () => {
const text = 'x'.repeat(12_345);
const { lastFrame } = render(
<ThinkMessage text={text} isPending={false} contentWidth={80} />,
);
const output = lastFrame() ?? '';
expect(output).toContain('thinking (12,345 chars)');
});
});

describe('ThinkMessageContent', () => {
it('renders the continuation text while pending', () => {
const { lastFrame } = render(
<ThinkMessageContent
text="continued reasoning text"
isPending={true}
contentWidth={80}
/>,
);
const output = lastFrame() ?? '';
expect(output).toContain('continued reasoning text');
});

it('renders nothing once stream finalizes (the parent ThinkMessage owns the summary)', () => {
const { lastFrame } = render(
<ThinkMessageContent
text="continued reasoning text"
isPending={false}
contentWidth={80}
/>,
);
const output = lastFrame() ?? '';
expect(output.trim()).toBe('');
});
});
77 changes: 56 additions & 21 deletions packages/cli/src/ui/components/messages/ConversationMessages.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -227,35 +227,70 @@ export const AssistantMessageContent: React.FC<
/>
);

// Post-stream summary line ("▸ thinking (N chars)"). Phase 1 of the reasoning
// rendering work (see #162): full text remains live in Langfuse, ACP
// `agent_thought_chunk` notifications, and ChatRecord for back-compat. An
// in-TUI expand affordance is a follow-up. Note: when a long thought was
// split mid-stream into a gemini_thought + gemini_thought_content pair, this
// counts only the first chunk — the continuation renders nothing (see below).
// True total requires post-finalize coalescing in useGeminiStream and is
// deferred since splits are rare and the count is a hint, not a contract.
const ThinkSummary: React.FC<{ text: string }> = ({ text }) => {
const charCount = text.length;
return (
<PrefixedTextMessage
text={`thinking (${charCount.toLocaleString()} chars)`}
prefix="▸"
prefixColor={theme.text.secondary}
textColor={theme.text.secondary}
/>
);
};

export const ThinkMessage: React.FC<ThinkMessageProps> = ({
text,
isPending,
availableTerminalHeight,
contentWidth,
}) => (
<PrefixedMarkdownMessage
text={text}
prefix="⟡"
prefixColor={theme.text.secondary}
isPending={isPending}
availableTerminalHeight={availableTerminalHeight}
contentWidth={contentWidth}
textColor={theme.text.secondary}
/>
);
}) => {
if (!isPending) {
return <ThinkSummary text={text} />;
}
return (
<PrefixedMarkdownMessage
text={text}
prefix="⟡"
prefixColor={theme.text.secondary}
isPending={isPending}
availableTerminalHeight={availableTerminalHeight}
contentWidth={contentWidth}
textColor={theme.text.secondary}
/>
);
};

export const ThinkMessageContent: React.FC<ThinkMessageContentProps> = ({
text,
isPending,
availableTerminalHeight,
contentWidth,
}) => (
<ContinuationMarkdownMessage
text={text}
isPending={isPending}
availableTerminalHeight={availableTerminalHeight}
contentWidth={contentWidth}
basePrefix="⟡"
textColor={theme.text.secondary}
/>
);
}) => {
// When the stream has finalized, suppress the continuation block. The
// adjacent ThinkMessage already renders the summary line; rendering this
// continuation as another summary would double-count and drop chars across
// the split boundary. Streaming-time renders unchanged so live thoughts
// still appear.
if (!isPending) {
return null;
}
return (
<ContinuationMarkdownMessage
text={text}
isPending={isPending}
availableTerminalHeight={availableTerminalHeight}
contentWidth={contentWidth}
basePrefix="⟡"
textColor={theme.text.secondary}
/>
);
};
2 changes: 1 addition & 1 deletion packages/core/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@qwen-code/qwen-code-core",
"version": "0.27.0",
"version": "0.28.0",
"description": "proto core",
"repository": {
"type": "git",
Expand Down
14 changes: 0 additions & 14 deletions packages/core/src/config/config.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@ import { setGeminiMdFilename as mockSetGeminiMdFilename } from '../tools/memoryT
import {
DEFAULT_TELEMETRY_TARGET,
DEFAULT_OTLP_ENDPOINT,
QwenLogger,
} from '../telemetry/index.js';
import type {
ContentGenerator,
Expand Down Expand Up @@ -235,9 +234,6 @@ describe('Server Config (config.ts)', () => {
beforeEach(() => {
// Reset mocks if necessary
vi.clearAllMocks();
vi.spyOn(QwenLogger.prototype, 'logStartSessionEvent').mockImplementation(
async () => undefined,
);

// Setup default mock for resolveContentGeneratorConfigWithSources
vi.mocked(resolveContentGeneratorConfigWithSources).mockImplementation(
Expand Down Expand Up @@ -635,16 +631,6 @@ describe('Server Config (config.ts)', () => {
expect(config.getUsageStatisticsEnabled()).toBe(enabled);
},
);

it('logs the session start event', async () => {
const config = new Config({
...baseParams,
usageStatisticsEnabled: true,
});
await config.initialize();

expect(QwenLogger.prototype.logStartSessionEvent).toHaveBeenCalledOnce();
});
});

describe('Telemetry Settings', () => {
Expand Down
1 change: 0 additions & 1 deletion packages/core/src/core/client.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -146,7 +146,6 @@ vi.mock('../telemetry/index.js', async (importOriginal) => {
...actual,
uiTelemetryService: mockUiTelemetryService,
// We keep the real implementations of logChatCompression, etc.
// but we can spy on QwenLogger if needed
};
});
vi.mock('../ide/ideContext.js');
Expand Down
Loading
Loading