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
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
/*
* 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 assert from 'node:assert/strict';
import { test } from 'node:test';
import { createElement } from 'react';
import { renderToStaticMarkup } from 'react-dom/server';
import { InspectorCompositionSection } from '../../renderer/features/workbar/testing.js';
import { getDesktopConversationCopy } from '../../renderer/locales/conversation-copy.js';

test('maps each request-composition category to the same colour in the chart and legend', () => {
const markup = renderToStaticMarkup(
createElement(InspectorCompositionSection, {
copy: getDesktopConversationCopy('en').inspector,
state: {
status: 'available',
composition: {
parts: [
{ kind: 'system_instructions', estimatedTokens: 10 },
{ kind: 'tool_definitions', estimatedTokens: 20 },
{ kind: 'messages', estimatedTokens: 30 },
{ kind: 'other', estimatedTokens: 40 },
],
tools: [],
},
},
formatNumber: (value: number) => String(value),
}),
);

for (const [kind, tokens] of [
['system_instructions', 10],
['tool_definitions', 20],
['messages', 30],
['other', 40],
] as const) {
assert.match(
markup,
new RegExp(`class="maka-inspector-composition-band"[^>]*data-segment="${kind}"[^>]*flex-grow:${tokens}`),
);
assert.match(
markup,
new RegExp(`class="maka-inspector-composition-swatch"[^>]*data-segment="${kind}"`),
);
}
});
1 change: 0 additions & 1 deletion apps/desktop/src/main/__tests__/use-session-trace.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -195,7 +195,6 @@ function createTraceHarness(
usageChangeHandlers.delete(handler);
};
},
getRecordFile: async () => '',
},
});
harness.services = services;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,9 +46,6 @@ function createBridgeRecorder(): {
get: (_target, property) => (...args: unknown[]) => {
const callName = `${name}.${String(property)}`;
calls.push({ name: callName, args });
if (callName === 'app.info') {
return Promise.resolve({ operationalStateDatabasePath: '/tmp/runtime.sqlite' });
}
if (syncMethods.has(callName)) return () => undefined;
return Promise.resolve(undefined);
},
Expand Down Expand Up @@ -122,7 +119,6 @@ describe('createDesktopWorkbarServices', () => {
await services.inspector.context('s');
services.inspector.subscribeSessionEvents('s', eventHandler)();
services.inspector.subscribeUsageChanges('s', eventHandler)();
assert.equal(await services.inspector.getRecordFile(), '/tmp/runtime.sqlite');

await services.attachments.pickFiles();
await services.attachments.previewApproval('approval');
Expand Down Expand Up @@ -192,7 +188,6 @@ describe('createDesktopWorkbarServices', () => {
'inspector.context',
'sessions.subscribeEvents',
'inspector.subscribeUsageChanges',
'app.info',
'attachments.pickFiles',
'attachments.previewApproval',
'sessions.list',
Expand Down
7 changes: 0 additions & 7 deletions apps/desktop/src/main/app-ipc-main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,6 @@
import { join } from 'node:path';
import { arch as osArch, homedir, release as osRelease } from 'node:os';
import { app, ipcMain, shell } from 'electron';
import { resolveOperationalStateDatabasePath } from '@maka/storage';
import { resolveProjectGitInfo } from '@maka/runtime/system-prompt/project-context';
import type { createMainWindowController } from './main-window.js';
import type { ProjectRootController } from './project-root-controller.js';
Expand Down Expand Up @@ -114,12 +113,6 @@ export function registerAppIpc(
// Lets the renderer collapse a home prefix to `~` in displayed paths;
// it has no other way to learn this.
homePath: homedir(),
// The exact on-disk path of the workspace's operational-state database,
// resolved in main (node:path) — the one authority the renderer's
// inspector row and the data-settings row both read. The renderer must
// not reconstruct this (it cannot import @maka/storage, and guessing a
// separator is wrong for POSIX paths containing a backslash).
operationalStateDatabasePath: resolveOperationalStateDatabasePath(workspaceRoot),
projectId: selection.projectId,
projectPath,
projectGit: allowLocalProjectPaths
Expand Down
2 changes: 0 additions & 2 deletions apps/desktop/src/preload/bridge-contract.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -483,8 +483,6 @@ export interface DesktopAppInfo {
readonly workspacePath: string;
/** The OS home directory, for collapsing displayed paths to `~`. */
readonly homePath: string;
/** Exact operational-state database path resolved by main. */
readonly operationalStateDatabasePath: string;
readonly projectId?: string | null;
readonly projectPath: string;
readonly projectGit: { readonly isGitRepo: boolean; readonly branch?: string };
Expand Down
1 change: 0 additions & 1 deletion apps/desktop/src/renderer/features/workbar/ports.ts
Original file line number Diff line number Diff line change
Expand Up @@ -174,7 +174,6 @@ export interface WorkbarInspectorService {
sessionId: string,
handler: () => void,
): WorkbarUnsubscribe;
getRecordFile(): Promise<string>;
}

export interface WorkbarAttachmentsService {
Expand Down
2 changes: 1 addition & 1 deletion apps/desktop/src/renderer/features/workbar/testing.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ export * from './model/workbar-tool-definitions.js';
export * from './tools/artifacts/artifact-list-keyboard.js';
export * from './tools/artifacts/artifact-visibility.js';
export * from './tools/inspector/session-inspector-panel-model.js';
export { InspectorCompositionSection } from './tools/inspector/session-inspector-panel.js';
export * from './tools/inspector/session-inspector-overview-model.js';
export * from './tools/side-chat/quote-companion-panel-state.js';
export * from './tools/side-chat/quote-companion-core.js';
Expand Down Expand Up @@ -109,7 +110,6 @@ export function createFakeWorkbarServices(
},
subscribeSessionEvents: noopSubscription,
subscribeUsageChanges: noopSubscription,
getRecordFile: async () => '',
},
attachments: {
pickFiles: async () => ({ ok: false, reason: 'cancelled' }),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,15 +17,14 @@
* under the License.
*/

import { type ReactNode, useEffect, useMemo, useState } from 'react';
import { type ReactNode, useMemo } from 'react';
import { Banner } from '@astryxdesign/core/Banner';
import { Button } from '@astryxdesign/core/Button';
import { EmptyState } from '@astryxdesign/core/EmptyState';
import { Heading } from '@astryxdesign/core/Heading';
import { HStack, VStack } from '@astryxdesign/core/Layout';
import { Section } from '@astryxdesign/core/Section';
import { Text } from '@astryxdesign/core/Text';
import { Tooltip } from '@astryxdesign/core/Tooltip';
import { uiLocaleToIntlLocale, type UiLocale } from '@maka/core/ui-locale';
import { traceTurnIdentityKey } from '@maka/core/session-trace';
import { useToast, useUiLocale } from '@maka/ui';
Expand All @@ -46,30 +45,6 @@ import {
type InspectorTurnRow,
} from './session-inspector-panel-model.js';
import { useSessionTrace } from './use-session-trace.js';
import { useWorkbarServices } from '../../services-context.js';

/**
* The record file is the workspace's operational-state database — the file
* both trace ledgers live in. Its exact path is resolved once, in main
* (`app:info.operationalStateDatabasePath` via @maka/storage's
* `resolveOperationalStateDatabasePath`); the renderer cannot import that
* package at runtime (it pulls node:sqlite into the browser bundle), and
* recomputing the path here would create a second authority. The row only
* displays and copies the value it receives.
*/

/**
* Split a record-file path for display only: `dir` is everything before the
* last separator (both platform separators, since the path came from main and
* may be POSIX or Windows), `name` is the filename. The name is never
* truncated by the row's ellipsis; the dir is. The full path is not rebuilt
* here — the row shows exactly the parts of the value it received.
*/
function splitRecordFileDisplayPath(path: string): { dir: string; name: string } {
const separatorIndex = Math.max(path.lastIndexOf('\\'), path.lastIndexOf('/'));
if (separatorIndex === -1) return { dir: '', name: path };
return { dir: path.slice(0, separatorIndex + 1), name: path.slice(separatorIndex + 1) };
}

/**
* Per-session trace (#1625), read top to bottom rather than through a
Expand All @@ -87,7 +62,6 @@ function splitRecordFileDisplayPath(path: string): { dir: string; name: string }
* paragraph.
*/
export function SessionInspectorPanel(props: { sessionId: string; active: boolean }) {
const { inspector } = useWorkbarServices();
const locale = useUiLocale();
const copy = getDesktopConversationCopy(locale).inspector;
const toast = useToast();
Expand All @@ -101,42 +75,6 @@ export function SessionInspectorPanel(props: { sessionId: string; active: boolea
[snapshot.context, snapshot.summary],
);

// The record file is a fact about the workspace, not about the session's
// activity: it exists whether the trace is empty or not, and it never
// changes while the app is running. `app:info` resolves the exact database
// path in main (the same value the data-settings row shows), so the row has
// no second authority and no separator guessing. Read once on mount —
// before the user can open the tab — so the row is already painted when the
// trace lands and the banner never shifts; a failure hides the row — it is
// auxiliary, and a path that will not load should not masquerade as a trace
// that failed to read.
const [recordFile, setRecordFile] = useState<string | undefined>();
useEffect(() => {
if (recordFile !== undefined) return;
let mounted = true;
void inspector
.getRecordFile()
.then((path) => {
if (mounted) {
setRecordFile(path);
}
})
.catch(() => {});
return () => {
mounted = false;
};
}, [inspector, recordFile]);

async function copyRecordFile() {
if (!recordFile) return;
try {
await navigator.clipboard.writeText(recordFile);
toast.success(copy.pathCopied);
} catch {
toast.error(copy.copyFailed, copy.copyFailedDetail);
}
}

async function copyPricingKey(key: string) {
try {
await navigator.clipboard.writeText(key);
Expand All @@ -146,11 +84,6 @@ export function SessionInspectorPanel(props: { sessionId: string; active: boolea
}
}

// The path is split for display only — the directory part truncates while
// the filename never does, so a narrow panel still says which file the row
// is about. The full path stays the tooltip and clipboard value, and the
// authoritative string is the one `app:info` returned.
const pathParts = recordFile ? splitRecordFileDisplayPath(recordFile) : undefined;
return (
<Section
variant="transparent"
Expand All @@ -165,47 +98,6 @@ export function SessionInspectorPanel(props: { sessionId: string; active: boolea
same 16px on "these are two parts of one block" and "this is a
different block". */}
<VStack gap={6} height="100%">
{recordFile && pathParts && (
<HStack
gap={2}
vAlign="center"
className="maka-inspector-record-file-row"
data-maka-contract="session-inspector-record-file"
data-full-path={recordFile}
>
<Text type="label" color="secondary" className="maka-inspector-record-file-label">
{copy.recordFile}
</Text>
<Tooltip content={recordFile}>
{/* Keyboard-reachable tooltip trigger: the row is one Tab stop,
the tooltip opens on focus-visible and dismisses on Escape,
and the full path never depends on the tooltip alone (it is
the visible filename and the clipboard value too). */}
<HStack
gap={1}
vAlign="center"
className="maka-inspector-record-file"
tabIndex={0}
>
<Text type="supporting" className="maka-inspector-record-file-dir">
{pathParts.dir}
</Text>
<Text type="supporting" className="maka-inspector-record-file-name">
{pathParts.name}
</Text>
</HStack>
</Tooltip>
<Button
variant="ghost"
size="sm"
icon={<Copy size={ICON_SIZE.control} aria-hidden="true" />}
label={copy.copyPath}
onClick={() => {
void copyRecordFile();
}}
/>
</HStack>
)}
{snapshot.error && (
<Banner
status="error"
Expand Down Expand Up @@ -525,7 +417,7 @@ function InspectorContextSection(props: {
* Tools are listed by name because that is the only row a reader can act on:
* "tool definitions ≈ 40%" names nothing to remove.
*/
function InspectorCompositionSection(props: {
export function InspectorCompositionSection(props: {
copy: InspectorCopy;
state: NonNullable<ReturnType<typeof deriveInspectorOverviewModel>['composition']>;
formatNumber: (value: number) => string;
Expand All @@ -550,10 +442,27 @@ function InspectorCompositionSection(props: {
<p className="maka-inspector-section-note">{labels.unrecorded}</p>
) : (
<>
<div className="maka-inspector-composition-track" aria-hidden="true">
{state.composition.parts.map((part) => (
<span
key={part.kind}
className="maka-inspector-composition-band"
data-segment={part.kind}
style={{ flexGrow: part.estimatedTokens }}
/>
))}
</div>
<dl className="maka-inspector-grid">
{state.composition.parts.map((part) => (
<FactRow
key={part.kind}
swatch={
<span
className="maka-inspector-composition-swatch"
data-segment={part.kind}
aria-hidden="true"
/>
}
label={labels.part[part.kind]}
value={estimate(part.estimatedTokens)}
/>
Expand Down
12 changes: 0 additions & 12 deletions apps/desktop/src/renderer/locales/conversation-copy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -162,12 +162,6 @@ export interface DesktopConversationCopy {
};
inspector: {
ariaLabel: string;
/** Label of the record-file row at the top of the panel. */
recordFile: string;
/** Copy-button accessible label; copies the record file path. */
copyPath: string;
/** Toast after a successful path copy. */
pathCopied: string;
/** Copy action and success copy for an unpriced model call's exact Pricing key. */
copyPricingKey: string;
pricingKeyCopied: string;
Expand Down Expand Up @@ -487,9 +481,6 @@ const COPY = {
},
inspector: {
ariaLabel: '任务追踪',
recordFile: '记录文件',
copyPath: '复制文件路径',
pathCopied: '已复制文件路径',
copyPricingKey: '复制定价键',
pricingKeyCopied: '已复制定价键',
unpricedPricingKey: '未计价的定价键',
Expand Down Expand Up @@ -690,9 +681,6 @@ const COPY = {
},
inspector: {
ariaLabel: 'Task trace',
recordFile: 'Record file',
copyPath: 'Copy file path',
pathCopied: 'File path copied',
copyPricingKey: 'Copy pricing key',
pricingKeyCopied: 'Pricing key copied',
unpricedPricingKey: 'Unpriced pricing key',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -102,8 +102,6 @@ export function createDesktopWorkbarServices(
bridge.sessions.subscribeEvents(sessionId, handler),
subscribeUsageChanges: (sessionId, handler) =>
bridge.inspector.subscribeUsageChanges(sessionId, handler),
getRecordFile: async () =>
(await bridge.app.info()).operationalStateDatabasePath,
},
attachments: {
pickFiles: () => bridge.attachments.pickFiles(),
Expand Down
Loading