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
45 changes: 45 additions & 0 deletions .github/workflows/release-windows.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
name: Release Windows

on:
push:
tags:
- 'v*.*.*'

env:
NODE_VERSION: 20
PNPM_VERSION: 10.32.1

jobs:
release:
runs-on: windows-latest
permissions:
contents: write
steps:
- uses: actions/checkout@v4
- uses: pnpm/action-setup@v4
with:
version: ${{ env.PNPM_VERSION }}
- uses: actions/setup-node@v4
with:
node-version: ${{ env.NODE_VERSION }}
- name: Verify tag matches package version
shell: bash
run: |
VERSION="$(node -p "JSON.parse(require('node:fs').readFileSync('package.json', 'utf8')).version")"
if [[ "v${VERSION}" != "${GITHUB_REF_NAME}" ]]; then
echo "Tag ${GITHUB_REF_NAME} does not match package.json version ${VERSION}" >&2
exit 1
fi
- name: Install dependencies
run: pnpm install --frozen-lockfile
- name: Build app
run: pnpm build
- name: Build Windows release artifacts
run: pnpm exec node ./scripts/run-electron-builder.cjs --win nsis --x64 --arm64 --publish never
- name: Publish GitHub release
uses: softprops/action-gh-release@v2
with:
files: |
dist/*.exe
dist/*.exe.blockmap
dist/latest.yml
Comment on lines +43 to +45
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Enable Windows auto-update before publishing metadata

This workflow now publishes Windows updater artifacts (latest.yml and blockmaps), but src/main/update/update-service.ts still hard-codes supported = platform === "darwin" && isPackaged, and src/shared/update.ts still tells non-macOS users updates are unsupported. For every packaged Windows build produced here, the Settings update flow will still no-op and display the macOS-only message, so the new release metadata cannot actually be consumed in-app.

Useful? React with 👍 / 👎.

6 changes: 6 additions & 0 deletions electron-builder.yml
Original file line number Diff line number Diff line change
Expand Up @@ -34,8 +34,14 @@ dmg:
type: link
path: /Applications
win:
icon: resources/icon.ico
artifactName: Agent-Trace-${version}-${arch}.${ext}
target:
- nsis
nsis:
oneClick: false
allowToChangeInstallationDirectory: true
deleteAppDataOnUninstall: true
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Preserve user data on Windows uninstall

Setting nsis.deleteAppDataOnUninstall to true makes the Windows uninstaller remove the same userData directory that src/main/index.ts passes into AppBootstrap, where src/main/bootstrap/app-bootstrap.ts stores profiles.json and agent-trace.db. In practice, a routine uninstall/reinstall of a broken build will now wipe every saved profile and captured trace for Windows users, which is a destructive regression for the new release path.

Useful? React with 👍 / 👎.

linux:
target:
- AppImage
Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
"pack": "pnpm build && node ./scripts/run-electron-builder.cjs --dir",
"dist": "pnpm build && node ./scripts/run-electron-builder.cjs",
"dist:mac": "pnpm build && node ./scripts/run-electron-builder.cjs --mac",
"dist:win": "pnpm build && node ./scripts/run-electron-builder.cjs --win",
"release:mac": "bash ./scripts/release.sh",
"preview": "electron-vite preview",
"typecheck": "tsc --noEmit && tsc -p tsconfig.node.json --noEmit",
Expand Down
Binary file modified resources/icon.icns
Binary file not shown.
Binary file added resources/icon.ico
Binary file not shown.
Binary file modified resources/icon.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
41 changes: 4 additions & 37 deletions resources/icon.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions src/main/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ function createWindow(): void {
minHeight: 600,
titleBarStyle: "hiddenInset",
trafficLightPosition: { x: 12, y: 11 },
autoHideMenuBar: process.platform === "win32",
webPreferences: {
preload: join(__dirname, "../preload/index.js"),
contextIsolation: true,
Expand Down
2 changes: 1 addition & 1 deletion src/main/update/update-service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,7 @@ export function createUpdateService({
isPackaged = false,
}: CreateUpdateServiceOptions): UpdateService {
const listeners = new Set<(state: UpdateState) => void>();
const supported = platform === "darwin" && isPackaged;
const supported = (platform === "darwin" || platform === "win32") && isPackaged;
let state = createDefaultUpdateState(
currentVersion,
supported ? null : UNSUPPORTED_AUTO_UPDATE_MESSAGE,
Expand Down
4 changes: 2 additions & 2 deletions src/renderer/src/components/content-block.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ export function ContentBlock({ block }: ContentBlockProps) {
const text = block.text ?? "";

return (
<div className="text-sm whitespace-pre-wrap">
<div className="text-xs text-foreground/80 whitespace-pre-wrap leading-relaxed">
{text}
</div>
);
Expand All @@ -36,7 +36,7 @@ export function ContentBlock({ block }: ContentBlockProps) {
Thinking
</div>
{expanded && (
<div className="px-3 pb-2 text-sm whitespace-pre-wrap">
<div className="px-3 pb-2 text-xs text-foreground/80 whitespace-pre-wrap leading-relaxed">
{block.text}
</div>
)}
Expand Down
2 changes: 1 addition & 1 deletion src/renderer/src/components/context-chip.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,7 @@ export function ContextChip({
</div>
{expanded && (
<div className="px-3 pb-2 max-h-64 overflow-auto">
<pre className="text-[11px] font-mono whitespace-pre-wrap break-all text-muted-foreground">
<pre className="text-[11px] font-mono whitespace-pre-wrap break-all text-muted-foreground leading-relaxed">
{content}
</pre>
</div>
Expand Down
2 changes: 1 addition & 1 deletion src/renderer/src/components/message-block.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -173,7 +173,7 @@ export function MessageBlock({ message, rawMode }: MessageBlockProps) {
))}
</div>
) : (
<div className="text-sm text-muted-foreground pl-1 whitespace-pre-wrap line-clamp-3">
<div className="text-xs text-muted-foreground/70 pl-1 whitespace-pre-wrap line-clamp-3 leading-relaxed">
{previewText}
</div>
)}
Expand Down
2 changes: 1 addition & 1 deletion src/renderer/src/components/other-view.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ function extractInjectedItems(messages: NormalizedMessage[]): InjectedItem[] {
function SectionContent({ section }: { section: InspectorSection }) {
if (section.kind === "text") {
return (
<pre className="text-sm whitespace-pre-wrap break-words">
<pre className="text-xs text-foreground/75 whitespace-pre-wrap break-words leading-relaxed">
{section.text}
</pre>
);
Expand Down
2 changes: 1 addition & 1 deletion src/renderer/src/components/tools-view.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,7 @@ function ToolItem({ tool, rawMode }: { tool: NormalizedTool; rawMode: boolean })
<span className="text-xs font-medium text-muted-foreground">Description</span>
</div>
{descExpanded && (
<div className="text-xs text-foreground/80 mt-2 pl-5 whitespace-pre-wrap">
<div className="text-xs text-foreground/75 mt-2 pl-5 whitespace-pre-wrap leading-relaxed">
{tool.description}
</div>
)}
Expand Down
90 changes: 82 additions & 8 deletions src/renderer/src/components/ui/markdown-renderer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,73 @@ interface MarkdownRendererProps {
className?: string;
}

function normalizeXmlContent(text: string): string {
const lines = text.split('\n');
const result: string[] = [];
let xmlDepth = 0;
let inXmlBlock = false;

for (const line of lines) {
const trimmed = line.trim();
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Preserve markdown indentation in rendered instructions

MarkdownRenderer now calls trim() on every input line before parsing. Any captured prompt that uses leading whitespace for structure, such as nested bullets or indented code blocks in the AGENTS/INSTRUCTIONS payloads we already fixture in tests/fixtures/protocols/openai-responses/request.json, loses that hierarchy here. Because SystemView renders those prompts through this component, the app can show materially different instructions than the model actually received.

Useful? React with 👍 / 👎.


// Detect XML tags
const hasOpenTag = /<[\w-]+>/.test(trimmed);
const hasCloseTag = /<\/[\w-]+>/.test(trimmed);
const isStandaloneOpenTag = /^<[\w-]+>$/.test(trimmed);
const isStandaloneCloseTag = /^<\/[\w-]+>$/.test(trimmed);

// Track if we're inside an XML block
if (hasOpenTag || hasCloseTag) {
inXmlBlock = true;
}

// For lines inside XML blocks, normalize indentation
if (inXmlBlock) {
// Empty lines
if (!trimmed) {
result.push('');
continue;
}

// Standalone closing tag - decrease depth first
if (isStandaloneCloseTag) {
xmlDepth = Math.max(0, xmlDepth - 1);
const indent = '\u00A0\u00A0'.repeat(xmlDepth);
result.push(indent + trimmed);
continue;
}

// Standalone opening tag
if (isStandaloneOpenTag) {
const indent = '\u00A0\u00A0'.repeat(xmlDepth);
result.push(indent + trimmed);
xmlDepth++;
continue;
}

// Content inside XML tags - use current depth
const indent = '\u00A0\u00A0'.repeat(xmlDepth);
result.push(indent + trimmed);
} else {
// Outside XML blocks, preserve original line
result.push(line);
}
}

return result.join('\n');
}

export function MarkdownRenderer({ content, className }: MarkdownRendererProps) {
// Normalize XML content indentation, then escape tags
let processedContent = normalizeXmlContent(content);
processedContent = processedContent.replace(/</g, '&lt;').replace(/>/g, '&gt;');

const components: Components = {
// Custom component styles to match theme
h1: ({ node, ...props }) => <h1 className="text-lg font-semibold text-foreground mb-2" {...props} />,
h2: ({ node, ...props }) => <h2 className="text-base font-semibold text-foreground mb-2" {...props} />,
h3: ({ node, ...props }) => <h3 className="text-sm font-semibold text-foreground mb-1" {...props} />,
p: ({ node, ...props }) => <p className="text-sm text-foreground/90 mb-2 break-words whitespace-pre-wrap" {...props} />,
h1: ({ node, ...props }) => <h1 className="text-base font-semibold text-foreground mb-3" {...props} />,
h2: ({ node, ...props }) => <h2 className="text-sm font-semibold text-foreground mb-2.5" {...props} />,
h3: ({ node, ...props }) => <h3 className="text-xs font-semibold text-foreground mb-2" {...props} />,
p: ({ node, ...props }) => <p className="text-xs text-foreground/75 mb-3 break-words whitespace-pre-wrap leading-relaxed" {...props} />,
code: ({ node, className: codeClassName, children, ...props }) => {
const match = /language-(\w+)/.exec(codeClassName || '');
const isInline = !match;
Expand All @@ -24,14 +84,28 @@ export function MarkdownRenderer({ content, className }: MarkdownRendererProps)
return <code className="block bg-muted p-3 rounded overflow-auto text-xs font-mono whitespace-pre-wrap break-all" {...props}>{children}</code>;
},
a: ({ node, ...props }) => <a className="text-primary hover:underline break-words" {...props} />,
ul: ({ node, ...props }) => <ul className="list-disc list-inside mb-2 text-sm" {...props} />,
ol: ({ node, ...props }) => <ol className="list-decimal list-inside mb-2 text-sm" {...props} />,
ul: ({ node, ...props }) => <ul className="list-disc list-inside mb-3 text-xs text-foreground/75 space-y-1 leading-relaxed" {...props} />,
ol: ({ node, ...props }) => <ol className="list-decimal list-inside mb-3 text-xs text-foreground/75 space-y-1 leading-relaxed" {...props} />,
li: ({ node, ...props }) => <li className="text-xs text-foreground/75 leading-relaxed" {...props} />,
blockquote: ({ node, ...props }) => <blockquote className="border-l-2 border-muted-foreground/30 pl-3 my-3 text-xs text-foreground/70 italic leading-relaxed" {...props} />,
strong: ({ node, ...props }) => <strong className="font-semibold text-foreground" {...props} />,
em: ({ node, ...props }) => <em className="italic text-foreground/80" {...props} />,
hr: ({ node, ...props }) => <hr className="my-4 border-border" {...props} />,
table: ({ node, ...props }) => <table className="w-full text-xs border-collapse my-3" {...props} />,
thead: ({ node, ...props }) => <thead className="border-b border-border" {...props} />,
tbody: ({ node, ...props }) => <tbody {...props} />,
tr: ({ node, ...props }) => <tr className="border-b border-border/50" {...props} />,
th: ({ node, ...props }) => <th className="text-left p-2 font-semibold text-foreground" {...props} />,
td: ({ node, ...props }) => <td className="p-2 text-foreground/75" {...props} />,
};

return (
<div className={cn('prose prose-sm dark:prose-invert max-w-none break-words', className)}>
<ReactMarkdown remarkPlugins={[remarkGfm]} components={components}>
{content}
<ReactMarkdown
remarkPlugins={[remarkGfm]}
components={components}
>
{processedContent}
</ReactMarkdown>
</div>
);
Expand Down
5 changes: 5 additions & 0 deletions src/renderer/src/index.css
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,11 @@
@layer base {
* {
@apply border-border outline-ring/50;
scrollbar-width: none; /* Firefox */
-ms-overflow-style: none; /* IE and Edge */
}
*::-webkit-scrollbar {
display: none; /* Chrome, Safari, Edge */
}
body {
@apply bg-background text-foreground;
Expand Down
2 changes: 1 addition & 1 deletion src/shared/update.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ export interface UpdateState {
}

export const UNSUPPORTED_AUTO_UPDATE_MESSAGE =
"Automatic updates are only enabled for packaged macOS builds.";
"Automatic updates are only enabled for packaged macOS and Windows builds.";

export const createDefaultUpdateState = (
currentVersion = "",
Expand Down
2 changes: 1 addition & 1 deletion tests/unit/update-service.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -138,7 +138,7 @@ describe("createUpdateService", () => {
expect(service.getState()).toEqual(
expect.objectContaining({
status: "idle",
message: "Automatic updates are only enabled for packaged macOS builds.",
message: "Automatic updates are only enabled for packaged macOS and Windows builds.",
}),
);

Expand Down
Loading