mirror of
https://github.com/deepseek-ai/deepseek-harness.git
synced 2026-08-29 04:26:38 +00:00
feat(ui): refine desktop workbench navigation
This commit is contained in:
@@ -94,6 +94,7 @@ flowchart TD
|
||||
subgraph group_ui["packages/ui"]
|
||||
pkg_acp["acp"]
|
||||
pkg_app_boot["app-boot"]
|
||||
pkg_desktop["desktop"]
|
||||
pkg_jsonrpc["jsonrpc"]
|
||||
pkg_permission["permission"]
|
||||
pkg_stdio["stdio"]
|
||||
@@ -383,6 +384,7 @@ flowchart TD
|
||||
| [`acp-snapshot`](../packages/support/acp-snapshot) | `support` | — |
|
||||
| [`loader-smoke`](../packages/support/loader-smoke) | `support` | — |
|
||||
| [`app-boot`](../packages/ui/app-boot) | `ui` | — |
|
||||
| [`desktop`](../packages/ui/desktop) | `ui` | — |
|
||||
| [`code-runtime`](../packages/code-runtime/code-runtime) | `code-runtime` | — |
|
||||
| [`jsonrpc-demo`](../packages/examples/jsonrpc-demo) | `examples` | — |
|
||||
| [`llm`](../packages/llm/llm) | `llm` | [`brand`](../packages/util/brand) |
|
||||
|
||||
@@ -94,6 +94,11 @@
|
||||
"entry": ["tests/**/*.spec.ts"],
|
||||
"project": ["src/**/*.ts", "tests/**/*.ts"]
|
||||
},
|
||||
"packages/ui/desktop": {
|
||||
"entry": ["tests/**/*.spec.ts"],
|
||||
"project": ["src/**/*.ts", "tests/**/*.ts"],
|
||||
"ignoreDependencies": ["cordis"]
|
||||
},
|
||||
"packages/examples/jsonrpc-demo": {
|
||||
"project": ["src/**/*.ts"]
|
||||
},
|
||||
|
||||
@@ -171,14 +171,33 @@ Start with a desktop package that defines shared UI contracts, then build the El
|
||||
- Replay creates a separate candidate run with lineage metadata.
|
||||
- Compare operates over two runs, not "inside" one session.
|
||||
|
||||
## Known limitations and deferred work
|
||||
## Model Experience
|
||||
|
||||
This package now ships a usable Electron/Vite development app and a real ACP subprocess bridge. It is still a v1 workbench rather than a packaged distributable.
|
||||
### Composer prompt
|
||||
|
||||
The first runtime channel is ACP. Direct in-process embedding would make context queries and restarts richer, but it makes isolation, teardown, and hot reload harder and should wait until the ACP path is working.
|
||||
**What the model sees**: The desktop composer sends the user's text to the managed ACP runtime as the `session/prompt` content. This package adds no extra system prompt, steering prose, or tool definition of its own.
|
||||
|
||||
The first Dev panel is agent-assisted. A direct graphical plugin/config editor should come after the app can reliably run, replay, compare, and restart the runtime.
|
||||
**Token effect**: User-message tokens are data-dependent and then follow the active runtime's normal session-retention and compaction behavior. Electron shell chrome, inspector state, and the language toggle add zero model-context tokens.
|
||||
|
||||
The current trace/context surfaces read persisted JSONL after turns complete and use ACP live updates for streaming chat. A richer live raw-event side channel would make Trajectory/Context update at token-time rather than after the persistence flush.
|
||||
### Runtime context evidence
|
||||
|
||||
The first Compare view is structural and textual. Semantic evaluation and dataset-level analysis belong to a later evaluation product surface.
|
||||
**What the model sees**: The visible system prompt, steering messages, tool set, message prefix, and config come from the active `cordis.yml` composition and its plugins. The desktop app only reads those emitted facts from ACP updates and persisted JSONL for inspection.
|
||||
|
||||
**Token effect**: No additional tokens are introduced by viewing `Chat`, `Trajectory`, `Waterfall`, or `Develop`. The displayed `request/header`, `context/message`, and `steering/message` data reflect tokens the runtime already assembled for the model.
|
||||
|
||||
## Known Limitations and Deferred Work
|
||||
|
||||
- **Development build only** — this package ships a usable Electron/Vite app and
|
||||
a real ACP subprocess bridge, but it is not yet packaged as a signed
|
||||
distributable.
|
||||
- **ACP is the first runtime channel** — direct in-process embedding could make
|
||||
context queries and restarts richer, but would make isolation, teardown, and
|
||||
hot reload harder.
|
||||
- **Develop is read-first** — it exposes prompts, tools, plugins, config,
|
||||
runtime state, and the change loop as a source browser; direct graphical
|
||||
plugin/config editing is deferred.
|
||||
- **Trace refresh is mixed live/persisted** — chat streams from ACP live updates,
|
||||
while Trajectory and Waterfall currently read persisted JSONL after turns
|
||||
complete.
|
||||
- **Compare and replay remain skeletal** — the product contract is documented,
|
||||
but semantic evaluation and dataset-level analysis belong to later work.
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
"version": "0.0.1",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"main": "src/main.mjs",
|
||||
"main": "lib/index.js",
|
||||
"types": "lib/types/index.d.ts",
|
||||
"scripts": {
|
||||
"dev": "node scripts/dev.mjs",
|
||||
@@ -27,5 +27,18 @@
|
||||
"lib/types/**/*.d.ts.map",
|
||||
"src"
|
||||
],
|
||||
"license": "BSD-3-Clause"
|
||||
"license": "BSD-3-Clause",
|
||||
"dependencies": {
|
||||
"@agentclientprotocol/sdk": "0.25.1"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"cordis": "^4.0.0-rc.7"
|
||||
},
|
||||
"devDependencies": {
|
||||
"cordis": "^4.0.0-rc.7",
|
||||
"electron": "^43.1.1",
|
||||
"typescript": "^6.0.3",
|
||||
"vite": "^8.0.16",
|
||||
"vitest": "^4.1.8"
|
||||
}
|
||||
}
|
||||
|
||||
+101
-115
@@ -1,3 +1,4 @@
|
||||
/* eslint-disable @typescript-eslint/no-base-to-string, @typescript-eslint/restrict-template-expressions, @typescript-eslint/no-unnecessary-condition, @typescript-eslint/no-non-null-assertion, @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unnecessary-type-conversion, @stylistic/max-len */
|
||||
import {
|
||||
DEFAULT_FEEDBACK_AUTHOR,
|
||||
INSPECTOR_TABS,
|
||||
@@ -10,6 +11,11 @@ import {
|
||||
import { translate, type I18nKey, type Locale } from './i18n.ts'
|
||||
import './styles.css'
|
||||
|
||||
const PATH_SYSTEM_PROMPT = 'packages/core/system-prompt/src/index.ts'
|
||||
const PATH_TOOL_REGISTRY = 'packages/core/tools/src/index.ts'
|
||||
const PATH_TOOL_BASH = 'packages/bash/tool-bash/src/index.ts'
|
||||
const PATH_TOOL_SUBAGENT = 'packages/subagent/tool-subagent/src/index.ts'
|
||||
|
||||
interface SessionSummary {
|
||||
readonly id: string
|
||||
readonly title: string
|
||||
@@ -109,7 +115,7 @@ interface TargetPayload {
|
||||
}
|
||||
|
||||
interface RequestContextSnapshot {
|
||||
readonly event?: SessionEvent
|
||||
readonly event: SessionEvent | undefined
|
||||
readonly seq: number
|
||||
readonly header: Record<string, unknown>
|
||||
readonly delta: Record<string, unknown>
|
||||
@@ -141,7 +147,7 @@ interface DevArtifactGroup {
|
||||
type AppModule = 'sessions' | 'develop'
|
||||
|
||||
const SESSION_SURFACES: readonly DesktopSurface[] = ['chat', 'trajectory', 'waterfall']
|
||||
const initialLocale = (localStorage.getItem('dsh.locale') === 'en-US' ? 'en-US' : 'zh-CN') as Locale
|
||||
const initialLocale: Locale = localStorage.getItem('dsh.locale') === 'en-US' ? 'en-US' : 'zh-CN'
|
||||
|
||||
const state = {
|
||||
runtime: undefined as unknown,
|
||||
@@ -175,20 +181,20 @@ void boot()
|
||||
|
||||
async function boot(): Promise<void> {
|
||||
if (!hasDesktopApi()) {
|
||||
state.error = 'Desktop API is not available. Open the Electron window with: pnpm --dir packages/ui/desktop run dev.'
|
||||
state.error = `${t('error.noDesktopApi')} pnpm --dir packages/ui/desktop run dev`
|
||||
render()
|
||||
return
|
||||
}
|
||||
|
||||
window.dshDesktop.runtime.onStatus(payload => {
|
||||
window.dshDesktop.runtime.onStatus((payload) => {
|
||||
state.runtime = payload
|
||||
render()
|
||||
})
|
||||
window.dshDesktop.runtime.onStderr(payload => {
|
||||
window.dshDesktop.runtime.onStderr((payload) => {
|
||||
state.stderr = String(asRecord(payload).tail ?? asRecord(payload).text ?? '')
|
||||
render()
|
||||
})
|
||||
window.dshDesktop.sessions.onUpdate(payload => {
|
||||
window.dshDesktop.sessions.onUpdate((payload) => {
|
||||
handleSessionUpdate(asSessionUpdate(payload))
|
||||
})
|
||||
|
||||
@@ -259,7 +265,7 @@ function handleSessionUpdate(payload: SessionUpdatePayload): void {
|
||||
else rows.push(row)
|
||||
state.liveRows.set(payload.sessionId, rows)
|
||||
} else if (kind === 'user_message_chunk') {
|
||||
rows.push(makeSyntheticRow(payload.sessionId, 'user', 'User', contentText(update.content), `user-${rows.length}`))
|
||||
rows.push(makeSyntheticRow(payload.sessionId, 'user', t('chat.user'), contentText(update.content), `user-${rows.length}`))
|
||||
state.liveRows.set(payload.sessionId, rows)
|
||||
}
|
||||
|
||||
@@ -339,7 +345,7 @@ function renderSessionGroup(title: string, sessions: SessionSummary[]): string {
|
||||
<section class="session-group">
|
||||
<div class="group-title">${escapeHtml(title)}</div>
|
||||
<div class="session-list">
|
||||
${sessions.map(renderSessionItem).join('') || `<div class="empty-list">No ${escapeHtml(title.toLowerCase())} sessions</div>`}
|
||||
${sessions.map(renderSessionItem).join('') || `<div class="empty-list">${escapeHtml(t('app.emptySessions'))}</div>`}
|
||||
</div>
|
||||
</section>
|
||||
`
|
||||
@@ -404,7 +410,7 @@ function renderMainContent(session: SessionSummary | undefined): string {
|
||||
if (session === undefined) return state.draftChat ? renderDraftChat() : renderEmptySession()
|
||||
return `
|
||||
<section class="session-canvas">
|
||||
${state.error ? `<div class="notice"><strong>Error</strong><span>${escapeHtml(state.error)}</span></div>` : ''}
|
||||
${state.error ? `<div class="notice"><strong>${escapeHtml(t('app.errorTitle'))}</strong><span>${escapeHtml(state.error)}</span></div>` : ''}
|
||||
${state.activeSurface === 'chat' ? '' : renderSessionHeader(session)}
|
||||
${renderActiveSurface()}
|
||||
</section>
|
||||
@@ -653,33 +659,6 @@ function renderSpanRow(span: SpanRow, total: number): string {
|
||||
`
|
||||
}
|
||||
|
||||
function renderContextSurface(): string {
|
||||
const rows = contextRows()
|
||||
return `
|
||||
<section class="surface context-surface">
|
||||
<header class="surface-intro">
|
||||
<strong>Context</strong>
|
||||
<span>这是开发分析视图:它解释模型请求边界里真正进入上下文的内容,用来改 prompt、tool schema 和 config。</span>
|
||||
</header>
|
||||
<div class="context-grid">
|
||||
${rows.map(renderContextCard).join('') || renderEmptyTrace()}
|
||||
</div>
|
||||
</section>
|
||||
`
|
||||
}
|
||||
|
||||
function renderContextCard(row: ContextRow): string {
|
||||
return `
|
||||
<button class="context-card ${row.kind} ${selectedClass(row.target)}" data-target="${row.target.id}">
|
||||
<span class="context-card-head">
|
||||
<strong>${escapeHtml(row.title)}</strong>
|
||||
<em>${escapeHtml(row.subtitle)}${row.changed ? ' · changed' : ''}</em>
|
||||
</span>
|
||||
<span class="context-preview">${escapeHtml(row.preview)}</span>
|
||||
</button>
|
||||
`
|
||||
}
|
||||
|
||||
function renderDevelopModule(): string {
|
||||
const groups = developArtifactGroups()
|
||||
const artifacts = groups.flatMap(group => group.artifacts)
|
||||
@@ -703,8 +682,8 @@ function renderDevelopModule(): string {
|
||||
function renderEmptyDevBrowser(): string {
|
||||
return `
|
||||
<section class="dev-empty">
|
||||
<strong>No development artifacts found</strong>
|
||||
<span>Start the runtime or check the active cordis.yml config.</span>
|
||||
<strong>${escapeHtml(t('dev.emptyTitle'))}</strong>
|
||||
<span>${escapeHtml(t('dev.emptyBody'))}</span>
|
||||
</section>
|
||||
`
|
||||
}
|
||||
@@ -744,14 +723,14 @@ function renderDevArtifactDetail(artifact: DevArtifact): string {
|
||||
<em>${escapeHtml(artifact.status ?? artifact.kind)}</em>
|
||||
</header>
|
||||
<section class="dev-detail-grid">
|
||||
${renderDevFact('Source', artifact.source ?? 'unknown')}
|
||||
${renderDevFact('Owner', artifact.owner ?? 'unknown')}
|
||||
${renderDevFact('Recently used', artifact.recent ?? 'No recent evidence yet')}
|
||||
${renderDevFact('Reload', reloadLabelForArtifact(artifact))}
|
||||
${renderDevFact(t('dev.source'), artifact.source ?? t('dev.unknown'))}
|
||||
${renderDevFact(t('dev.owner'), artifact.owner ?? t('dev.unknown'))}
|
||||
${renderDevFact(t('dev.recentlyUsed'), artifact.recent ?? t('dev.noRecentEvidence'))}
|
||||
${renderDevFact(t('dev.reload'), reloadLabelForArtifact(artifact))}
|
||||
</section>
|
||||
${renderDevCodePanel(contentTitleForArtifact(artifact), contentMetaForArtifact(artifact), artifact.value ?? '')}
|
||||
${artifact.id === 'prompt:persona' ? renderDevRegistrySnapshot() : ''}
|
||||
${artifact.metadata === undefined ? '' : renderDevCodePanel('Metadata', 'Implementation, dependency, and last-seen evidence', artifact.metadata)}
|
||||
${artifact.metadata === undefined ? '' : renderDevCodePanel(t('dev.metadata'), t('dev.metadataSubtitle'), artifact.metadata)}
|
||||
${artifact.kind === 'runtime' ? renderRuntimePanel() : ''}
|
||||
${artifact.kind === 'change' ? renderChangeLoopPanel() : ''}
|
||||
</article>
|
||||
@@ -774,12 +753,12 @@ function renderDevRegistrySnapshot(): string {
|
||||
return `
|
||||
<section class="dev-registry-snapshot">
|
||||
<div>
|
||||
<h4>Registered plugins</h4>
|
||||
${renderDevPillList(plugins, 'No registered plugins found')}
|
||||
<h4>${escapeHtml(t('dev.registeredPlugins'))}</h4>
|
||||
${renderDevPillList(plugins, t('dev.noPlugins'))}
|
||||
</div>
|
||||
<div>
|
||||
<h4>Registered tool surfaces</h4>
|
||||
${renderDevPillList(tools, 'No registered tools found yet')}
|
||||
<h4>${escapeHtml(t('dev.registeredTools'))}</h4>
|
||||
${renderDevPillList(tools, t('dev.noTools'))}
|
||||
</div>
|
||||
</section>
|
||||
`
|
||||
@@ -800,39 +779,39 @@ function renderDevPillList(artifacts: DevArtifact[], empty: string): string {
|
||||
}
|
||||
|
||||
function contentTitleForArtifact(artifact: DevArtifact): string {
|
||||
if (artifact.kind === 'prompt') return 'Effective prompt content'
|
||||
if (artifact.kind === 'tool') return 'Tool schema'
|
||||
if (artifact.kind === 'plugin') return 'Plugin config / contribution'
|
||||
if (artifact.kind === 'config') return 'Active configuration'
|
||||
if (artifact.kind === 'runtime') return 'Runtime state'
|
||||
return 'Suggested verification loop'
|
||||
if (artifact.kind === 'prompt') return t('dev.effectivePromptContent')
|
||||
if (artifact.kind === 'tool') return t('dev.toolSchema')
|
||||
if (artifact.kind === 'plugin') return t('dev.pluginContribution')
|
||||
if (artifact.kind === 'config') return t('dev.activeConfiguration')
|
||||
if (artifact.kind === 'runtime') return t('dev.runtimeState')
|
||||
return t('dev.suggestedLoop')
|
||||
}
|
||||
|
||||
function contentMetaForArtifact(artifact: DevArtifact): string {
|
||||
if (artifact.kind === 'prompt') return 'Current source-level prompt text or prompt owner metadata'
|
||||
if (artifact.kind === 'tool') return 'Current registered schema when last seen by a model request'
|
||||
if (artifact.kind === 'plugin') return 'Who injects prompt/context or registers tools, based on active Cordis config'
|
||||
if (artifact.kind === 'config') return 'Model/runtime parameters and files whose edits require reload'
|
||||
if (artifact.kind === 'runtime') return 'Current Electron main process and ACP bridge state'
|
||||
return 'How to rerun and compare after editing the agent'
|
||||
if (artifact.kind === 'prompt') return t('dev.promptMeta')
|
||||
if (artifact.kind === 'tool') return t('dev.toolMeta')
|
||||
if (artifact.kind === 'plugin') return t('dev.pluginMeta')
|
||||
if (artifact.kind === 'config') return t('dev.configMeta')
|
||||
if (artifact.kind === 'runtime') return t('dev.runtimeMeta')
|
||||
return t('dev.loopMeta')
|
||||
}
|
||||
|
||||
function reloadLabelForArtifact(artifact: DevArtifact): string {
|
||||
if (artifact.kind === 'runtime') return 'Manual restart available here'
|
||||
if (artifact.kind === 'change') return String(asRecord(state.dev).restartNeeded ?? false) === 'true' ? 'Restart recommended' : 'No restart signal'
|
||||
if (artifact.kind === 'config' || artifact.kind === 'prompt' || artifact.kind === 'tool' || artifact.kind === 'plugin') return 'Restart ACP after editing'
|
||||
return 'unknown'
|
||||
if (artifact.kind === 'runtime') return t('dev.manualRestart')
|
||||
if (artifact.kind === 'change') return String(asRecord(state.dev).restartNeeded ?? false) === 'true' ? t('dev.restartRecommended') : t('dev.noRestartSignal')
|
||||
if (artifact.kind === 'config' || artifact.kind === 'prompt' || artifact.kind === 'tool' || artifact.kind === 'plugin') return t('dev.restartAfterEdit')
|
||||
return t('dev.unknown')
|
||||
}
|
||||
|
||||
function renderChangeLoopPanel(): string {
|
||||
return `
|
||||
<section class="dev-loop-card">
|
||||
<strong>Recommended loop</strong>
|
||||
<strong>${escapeHtml(t('dev.recommendedLoop'))}</strong>
|
||||
<ol>
|
||||
<li>Edit the prompt, tool, plugin, or config source.</li>
|
||||
<li>Restart ACP runtime if the changed file is loaded at process start.</li>
|
||||
<li>Return to Chat and rerun a previous task or start a new one.</li>
|
||||
<li>Use Trajectory / Waterfall to compare behavior and timing evidence.</li>
|
||||
<li>${escapeHtml(t('dev.loopStepEdit'))}</li>
|
||||
<li>${escapeHtml(t('dev.loopStepRestart'))}</li>
|
||||
<li>${escapeHtml(t('dev.loopStepRerun'))}</li>
|
||||
<li>${escapeHtml(t('dev.loopStepCompare'))}</li>
|
||||
</ol>
|
||||
</section>
|
||||
`
|
||||
@@ -860,7 +839,7 @@ function developArtifactGroups(): DevArtifactGroup[] {
|
||||
source: String(composition.configPath ?? 'examples/acp-agent/cordis.yml'),
|
||||
owner: '@deepseek-ai/dsh-acp-demo -> @deepseek-ai/dsh-system-prompt',
|
||||
value: persona || 'No persona block found in the active config.',
|
||||
metadata: promptOwner ?? { path: 'packages/system-prompt/system-prompt/src/index.ts' },
|
||||
metadata: promptOwner ?? { path: PATH_SYSTEM_PROMPT },
|
||||
recent: recentPromptSummary(recentPromptUses, request),
|
||||
},
|
||||
{
|
||||
@@ -870,9 +849,9 @@ function developArtifactGroups(): DevArtifactGroup[] {
|
||||
title: 'Prompt assembly service',
|
||||
subtitle: 'Owns persona, steering sections, and tool-order assembly',
|
||||
status: 'source',
|
||||
source: String(asRecord(promptOwner).path ?? 'packages/system-prompt/system-prompt/src/index.ts'),
|
||||
source: String(asRecord(promptOwner).path ?? PATH_SYSTEM_PROMPT),
|
||||
owner: '@deepseek-ai/dsh-system-prompt',
|
||||
value: promptOwner ?? { path: 'packages/system-prompt/system-prompt/src/index.ts' },
|
||||
value: promptOwner ?? { path: PATH_SYSTEM_PROMPT },
|
||||
metadata: {
|
||||
lastSeenSystemPromptChars: request.system.length,
|
||||
messagePrefix: request.messagePrefix,
|
||||
@@ -972,23 +951,23 @@ function buildToolArtifacts(request: RequestContextSnapshot, plugins: unknown[],
|
||||
metadata: {
|
||||
fullTool: tool,
|
||||
ownerPlugin: plugin ?? 'No matching plugin inferred from active config',
|
||||
registry: owner ?? { path: 'packages/tools/tools/src/index.ts' },
|
||||
registry: owner ?? { path: PATH_TOOL_REGISTRY },
|
||||
recentToolCall: recent ?? 'No persisted call evidence found',
|
||||
},
|
||||
recent: recentToolSummary(recent) || (request.event === undefined ? 'No request evidence yet' : `Schema last seen in request seq ${request.seq}`),
|
||||
} satisfies DevArtifact
|
||||
})
|
||||
}
|
||||
const toolPlugins = plugins.filter(plugin => {
|
||||
const toolPlugins = plugins.filter((plugin) => {
|
||||
const record = asRecord(plugin)
|
||||
const id = String(record.id ?? '')
|
||||
const name = String(record.name ?? '')
|
||||
return id.includes('tool') || name.includes('tool') || id === 'bash' || id.includes('subagent') || id.includes('workflow')
|
||||
})
|
||||
return toolPlugins.map(plugin => {
|
||||
return toolPlugins.map((plugin) => {
|
||||
const record = asRecord(plugin)
|
||||
const id = String(record.id ?? 'tool')
|
||||
const recent = recentToolCalls.find(call => {
|
||||
const recent = recentToolCalls.find((call) => {
|
||||
const name = String(asRecord(call).name)
|
||||
return toolLikelyOwnedByPlugin(name, record) || id.includes(name)
|
||||
})
|
||||
@@ -1004,7 +983,7 @@ function buildToolArtifacts(request: RequestContextSnapshot, plugins: unknown[],
|
||||
value: String(record.configPreview ?? '').trim() || 'Tool schema will appear here after the first model request captures the registered tool list.',
|
||||
metadata: {
|
||||
plugin,
|
||||
registry: owner ?? { path: 'packages/tools/tools/src/index.ts' },
|
||||
registry: owner ?? { path: PATH_TOOL_REGISTRY },
|
||||
recentToolCall: recent ?? 'No persisted call evidence found',
|
||||
},
|
||||
recent: recentToolSummary(recent) || 'No request schema loaded',
|
||||
@@ -1069,9 +1048,9 @@ function toolLikelyOwnedByPlugin(toolName: string, plugin: Record<string, unknow
|
||||
function toolImplementationSource(toolName: string, plugin: unknown): string {
|
||||
const packageName = String(asRecord(plugin).name ?? '')
|
||||
if (packageName.startsWith('@deepseek-ai/dsh-')) return `packages/${packageName.replace('@deepseek-ai/dsh-', '').replace(/^tool-/, 'tool-')}/`
|
||||
if (toolName === 'bash') return 'packages/bash/tool-bash/src/index.ts'
|
||||
if (toolName === 'subagent' || toolName === 'subagent_fork') return 'packages/agent/tool-subagent/src/index.ts'
|
||||
return 'packages/tools/tools/src/index.ts'
|
||||
if (toolName === 'bash') return PATH_TOOL_BASH
|
||||
if (toolName === 'subagent' || toolName === 'subagent_fork') return PATH_TOOL_SUBAGENT
|
||||
return PATH_TOOL_REGISTRY
|
||||
}
|
||||
|
||||
function modelFromConfigText(text: string): string {
|
||||
@@ -1082,7 +1061,7 @@ function modelFromConfigText(text: string): string {
|
||||
function extractPersonaFromConfig(text: string): string {
|
||||
const match = text.match(/persona:\s*\|\n([\s\S]*?)(?:\n\S|\n\s*#|$)/)
|
||||
if (match === null) return ''
|
||||
return match[1]
|
||||
return (match[1] ?? '')
|
||||
.split('\n')
|
||||
.map(line => line.replace(/^ {6}/, ''))
|
||||
.join('\n')
|
||||
@@ -1092,8 +1071,8 @@ function extractPersonaFromConfig(text: string): string {
|
||||
function renderRuntimePanel(): string {
|
||||
return `
|
||||
<section class="module-card dev-status">
|
||||
<dl>${renderKeyValue('Repo', shortPath(runtimeRepoRoot()))}${renderKeyValue('Branch', gitField('branch'))}${renderKeyValue('Commit', gitField('commit'))}${renderKeyValue('Dirty', gitField('dirty'))}${renderKeyValue('ACP', runtimeLabel())}${renderKeyValue('Restart needed', String(asRecord(state.dev).restartNeeded ?? false))}</dl>
|
||||
<button data-action="restart-runtime" ${hasDesktopApi() ? '' : 'disabled'}>Restart ACP runtime</button>
|
||||
<dl>${renderKeyValue(t('app.repo'), shortPath(runtimeRepoRoot()))}${renderKeyValue(t('app.branch'), gitField('branch'))}${renderKeyValue(t('app.commit'), gitField('commit'))}${renderKeyValue(t('app.dirty'), gitField('dirty'))}${renderKeyValue(t('app.acp'), runtimeLabel())}${renderKeyValue(t('app.restartNeeded'), String(asRecord(state.dev).restartNeeded ?? false))}</dl>
|
||||
<button data-action="restart-runtime" ${hasDesktopApi() ? '' : 'disabled'}>${escapeHtml(t('dev.restartRuntime'))}</button>
|
||||
</section>
|
||||
`
|
||||
}
|
||||
@@ -1120,7 +1099,7 @@ function renderBottomArea(session: SessionSummary | undefined): string {
|
||||
<form class="composer" data-prompt-form="true">
|
||||
<textarea name="prompt" placeholder="${escapeHtml(session === undefined ? t('composer.placeholderDraft') : t('composer.placeholderSession'))}"></textarea>
|
||||
<div class="composer-meta">
|
||||
<button type="submit" disabled aria-label="Send message">↑</button>
|
||||
<button type="submit" disabled aria-label="${escapeHtml(t('composer.send'))}">↑</button>
|
||||
</div>
|
||||
</form>
|
||||
`
|
||||
@@ -1152,7 +1131,7 @@ function renderInspector(): string {
|
||||
function renderInspectorTabs(feedbackCount = feedbackForTarget().length): string {
|
||||
return INSPECTOR_TABS.map(tab => `
|
||||
<button class="${tab === state.activeInspectorTab ? 'active' : ''}" data-inspector-tab="${tab}">
|
||||
${label(tab)}${tab === 'feedback' && feedbackCount > 0 ? ` ${feedbackCount}` : ''}
|
||||
${inspectorTabLabel(tab)}${tab === 'feedback' && feedbackCount > 0 ? ` ${feedbackCount}` : ''}
|
||||
</button>
|
||||
`).join('')
|
||||
}
|
||||
@@ -1286,23 +1265,23 @@ function rowsFromEvents(events: readonly SessionEvent[]): ChatRow[] {
|
||||
const seq = event.seq ?? rows.length
|
||||
const data = asRecord(event.data)
|
||||
if (event.type === 'user/message') {
|
||||
rows.push({ target: makeTarget('message', 'User message', seq, `seq ${seq}`), role: 'user', title: 'User', body: contentText(data.content), eventSeqs: [seq] })
|
||||
rows.push({ target: makeTarget('message', t('chat.userMessage'), seq, `seq ${seq}`), role: 'user', title: t('chat.user'), body: contentText(data.content), eventSeqs: [seq] })
|
||||
} else if (event.type === 'assistant/message') {
|
||||
const key = `${data.turn}:${data.step}`
|
||||
const reasoning = reasoningByStep.get(key)
|
||||
if (reasoning !== undefined && !seenReasoningStep.has(key)) {
|
||||
seenReasoningStep.add(key)
|
||||
rows.push({
|
||||
target: makeTarget('assistant-stream', 'Thinking', reasoning.seq, `turn ${String(data.turn)} step ${String(data.step)}`),
|
||||
target: makeTarget('assistant-stream', t('chat.thinking'), reasoning.seq, `turn ${String(data.turn)} step ${String(data.step)}`),
|
||||
role: 'thinking',
|
||||
title: 'Thinking',
|
||||
title: t('chat.thinking'),
|
||||
body: reasoning.text,
|
||||
eventSeqs: [reasoning.seq],
|
||||
collapsed: true,
|
||||
badge: 'folded',
|
||||
})
|
||||
}
|
||||
rows.push({ target: makeTarget('message', 'Assistant message', seq, `seq ${seq}`), role: 'assistant', title: 'Assistant', body: contentText(data.content), eventSeqs: [seq] })
|
||||
rows.push({ target: makeTarget('message', t('chat.assistantMessage'), seq, `seq ${seq}`), role: 'assistant', title: t('chat.assistant'), body: contentText(data.content), eventSeqs: [seq] })
|
||||
} else if (event.type === 'tool/call') {
|
||||
const callId = String(data.callId ?? '')
|
||||
const result = toolResults.get(callId)
|
||||
@@ -1310,7 +1289,7 @@ function rowsFromEvents(events: readonly SessionEvent[]): ChatRow[] {
|
||||
rows.push({
|
||||
target: makeTarget('tool-call', `Tool · ${String(data.name ?? 'tool')}`, seq, `seq ${seq}`),
|
||||
role: 'tool',
|
||||
title: `Tool use · ${String(data.name ?? 'tool')}`,
|
||||
title: `${t('chat.toolUse')} · ${String(data.name ?? 'tool')}`,
|
||||
body: renderValue({
|
||||
input: data.arguments ?? data.rawInput ?? data,
|
||||
output: resultData.content ?? resultData.output,
|
||||
@@ -1339,11 +1318,11 @@ function flushLiveDrafts(sessionId: string): ChatRow[] {
|
||||
const rows = [...(state.liveRows.get(sessionId) ?? [])]
|
||||
const thinking = state.pendingThinking.get(sessionId)
|
||||
if (thinking !== undefined && thinking.length > 0) {
|
||||
rows.push(makeSyntheticRow(sessionId, 'thinking', 'Thinking', thinking, 'thinking-live', true))
|
||||
rows.push(makeSyntheticRow(sessionId, 'thinking', t('chat.thinking'), thinking, 'thinking-live', true))
|
||||
}
|
||||
const assistant = state.pendingAssistant.get(sessionId)
|
||||
if (assistant !== undefined && assistant.length > 0) {
|
||||
rows.push(makeSyntheticRow(sessionId, 'assistant', 'Assistant', assistant, 'assistant-live'))
|
||||
rows.push(makeSyntheticRow(sessionId, 'assistant', t('chat.assistant'), assistant, 'assistant-live'))
|
||||
}
|
||||
return rows
|
||||
}
|
||||
@@ -1369,7 +1348,7 @@ function makeSyntheticRow(
|
||||
body,
|
||||
eventSeqs: [],
|
||||
collapsed,
|
||||
badge: collapsed ? 'live' : undefined,
|
||||
...(collapsed ? { badge: 'live' } : {}),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1419,7 +1398,7 @@ function treeRow(
|
||||
title,
|
||||
subtitle,
|
||||
meta: `seq ${seq}`,
|
||||
tone,
|
||||
...(tone === undefined ? {} : { tone }),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1456,7 +1435,7 @@ function timedRow(title: string, start: SessionEvent, end: SessionEvent, kind: I
|
||||
subtitle: `${eventTime(start)} → ${eventTime(end)}`,
|
||||
startMs: Math.max(0, (start.time ?? 0) - zero),
|
||||
durationMs,
|
||||
tone,
|
||||
...(tone === undefined ? {} : { tone }),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1484,14 +1463,14 @@ function contextRows(): ContextRow[] {
|
||||
const request = latestRequestContext()
|
||||
const rows: ContextRow[] = []
|
||||
if (request.event !== undefined) {
|
||||
rows.push(contextRow('System prompt', `${request.system.length} chars`, request.system || 'No system prompt found in latest request header.', 'system', 'request', request.seq, Boolean(request.delta.system)))
|
||||
rows.push(contextRow('Tool schemas', `${request.tools.length} tools`, request.tools.length > 0 ? request.tools.map(tool => String(asRecord(tool).name ?? 'tool')).join(', ') : 'No tool schemas found in latest request header.', 'tools', 'request', request.seq, Boolean(request.delta.tools)))
|
||||
rows.push(contextRow('Call config', request.config === undefined ? 'empty' : 'available', renderValue(request.config), 'config', 'request', request.seq, Boolean(request.delta.config)))
|
||||
rows.push(contextRow('Message prefix', Array.isArray(request.messagePrefix) ? `${request.messagePrefix.length} messages` : 'derived', renderValue(request.messagePrefix), 'messages', 'request', request.seq, Boolean(request.delta.messagePrefix)))
|
||||
rows.push(contextRow(t('context.systemPrompt'), `${request.system.length} chars`, request.system || t('context.noSystem'), 'system', 'request', request.seq, Boolean(request.delta.system)))
|
||||
rows.push(contextRow(t('context.toolSchemas'), `${request.tools.length} ${t('waterfall.tools')}`, request.tools.length > 0 ? request.tools.map(tool => String(asRecord(tool).name ?? 'tool')).join(', ') : t('context.noTools'), 'tools', 'request', request.seq, Boolean(request.delta.tools)))
|
||||
rows.push(contextRow(t('context.callConfig'), request.config === undefined ? t('context.empty') : t('context.available'), renderValue(request.config), 'config', 'request', request.seq, Boolean(request.delta.config)))
|
||||
rows.push(contextRow(t('context.messagePrefix'), Array.isArray(request.messagePrefix) ? `${request.messagePrefix.length} ${t('context.messages')}` : t('context.derived'), renderValue(request.messagePrefix), 'messages', 'request', request.seq, Boolean(request.delta.messagePrefix)))
|
||||
}
|
||||
const modelVisible = rowsFromEvents(events).filter(row => row.role === 'user' || row.role === 'assistant' || row.role === 'context')
|
||||
rows.push(contextRow('Derived history', `${modelVisible.length} visible rows`, modelVisible.map(row => `${row.title}: ${truncate(row.body, 80)}`).join('\n'), 'messages', 'context-section', 0))
|
||||
rows.push(contextRow('Raw JSONL', `${events.length} events`, state.trace?.rawText ?? '', 'raw', 'session', 0))
|
||||
rows.push(contextRow(t('context.derivedHistory'), `${modelVisible.length} ${t('context.visibleRows')}`, modelVisible.map(row => `${row.title}: ${truncate(row.body, 80)}`).join('\n'), 'messages', 'context-section', 0))
|
||||
rows.push(contextRow(t('context.rawJsonl'), `${events.length} ${t('context.events')}`, state.trace?.rawText ?? '', 'raw', 'session', 0))
|
||||
return rows
|
||||
}
|
||||
|
||||
@@ -1591,7 +1570,7 @@ function feedbackForTarget(): FeedbackRecord[] {
|
||||
return (state.trace?.feedback ?? []).filter(record => record.data?.targetId === selected.id)
|
||||
}
|
||||
|
||||
document.addEventListener('click', event => {
|
||||
document.addEventListener('click', (event) => {
|
||||
const element = event.target instanceof Element ? event.target : undefined
|
||||
|
||||
const module = element?.closest<HTMLButtonElement>('[data-module]')?.dataset.module as AppModule | undefined
|
||||
@@ -1657,7 +1636,7 @@ document.addEventListener('click', event => {
|
||||
void loadTrace(state.selectedSessionId).then(() => refreshSessions(state.selectedSessionId))
|
||||
} else if (action === 'restart-runtime') {
|
||||
if (!hasDesktopApi()) {
|
||||
state.error = 'Desktop API is not available. Use the Electron window, not the browser tab.'
|
||||
state.error = t('error.noDesktopApi')
|
||||
render()
|
||||
return
|
||||
}
|
||||
@@ -1665,7 +1644,7 @@ document.addEventListener('click', event => {
|
||||
}
|
||||
})
|
||||
|
||||
document.addEventListener('input', event => {
|
||||
document.addEventListener('input', (event) => {
|
||||
const input = event.target instanceof HTMLInputElement ? event.target : undefined
|
||||
if (input?.dataset.search === 'true') {
|
||||
state.query = input.value
|
||||
@@ -1679,7 +1658,7 @@ document.addEventListener('input', event => {
|
||||
}
|
||||
})
|
||||
|
||||
document.addEventListener('submit', event => {
|
||||
document.addEventListener('submit', (event) => {
|
||||
const form = event.target instanceof HTMLFormElement ? event.target : undefined
|
||||
if (form?.dataset.promptForm === 'true') {
|
||||
event.preventDefault()
|
||||
@@ -1692,7 +1671,7 @@ document.addEventListener('submit', event => {
|
||||
}
|
||||
})
|
||||
|
||||
document.addEventListener('keydown', event => {
|
||||
document.addEventListener('keydown', (event) => {
|
||||
const textarea = event.target instanceof HTMLTextAreaElement ? event.target : undefined
|
||||
if (textarea !== undefined && textarea.closest<HTMLFormElement>('[data-prompt-form="true"]') !== null) {
|
||||
if (event.key !== 'Enter' || event.shiftKey || event.metaKey || event.ctrlKey || event.altKey) return
|
||||
@@ -1731,7 +1710,7 @@ function startDraftChat(): void {
|
||||
|
||||
async function createBackendSession(): Promise<string | undefined> {
|
||||
if (!hasDesktopApi()) {
|
||||
state.error = 'Desktop API is not available. Use the Electron window, not the browser tab.'
|
||||
state.error = t('error.noDesktopApi')
|
||||
render()
|
||||
return undefined
|
||||
}
|
||||
@@ -1750,7 +1729,7 @@ async function createBackendSession(): Promise<string | undefined> {
|
||||
|
||||
async function sendPrompt(prompt: string, form: HTMLFormElement): Promise<void> {
|
||||
if (!hasDesktopApi()) {
|
||||
state.error = 'Desktop API is not available. Use the Electron window, not the browser tab.'
|
||||
state.error = t('error.noDesktopApi')
|
||||
render()
|
||||
return
|
||||
}
|
||||
@@ -1761,7 +1740,7 @@ async function sendPrompt(prompt: string, form: HTMLFormElement): Promise<void>
|
||||
const textarea = form.querySelector<HTMLTextAreaElement>('textarea[name="prompt"]')
|
||||
if (textarea !== null) autosizeComposer(textarea)
|
||||
const rows = state.liveRows.get(sessionId) ?? []
|
||||
rows.push(makeSyntheticRow(sessionId, 'user', 'User', prompt, `user-${Date.now()}`))
|
||||
rows.push(makeSyntheticRow(sessionId, 'user', t('chat.user'), prompt, `user-${Date.now()}`))
|
||||
state.liveRows.set(sessionId, rows)
|
||||
state.pendingAssistant.delete(sessionId)
|
||||
state.pendingThinking.delete(sessionId)
|
||||
@@ -1870,22 +1849,29 @@ function uniqueTurns(): number[] {
|
||||
}
|
||||
|
||||
function moduleTitle(): string {
|
||||
if (state.activeModule === 'sessions') return 'Sessions'
|
||||
return 'Develop'
|
||||
if (state.activeModule === 'sessions') return t('app.sessions')
|
||||
return t('app.develop')
|
||||
}
|
||||
|
||||
function topbarTitle(session: SessionSummary | undefined): string {
|
||||
if (state.activeModule !== 'sessions') return moduleTitle()
|
||||
return session?.title || 'Sessions'
|
||||
return session?.title || t('app.sessions')
|
||||
}
|
||||
|
||||
function surfaceLabel(surface: DesktopSurface): string {
|
||||
if (surface === 'chat') return t('surface.chat')
|
||||
if (surface === 'trajectory') return t('surface.trajectory')
|
||||
if (surface === 'waterfall') return t('surface.waterfall')
|
||||
if (surface === 'context') return 'Context'
|
||||
if (surface === 'compare') return 'Compare'
|
||||
return 'Dev'
|
||||
if (surface === 'context') return t('surface.context')
|
||||
if (surface === 'compare') return t('surface.compare')
|
||||
return t('surface.dev')
|
||||
}
|
||||
|
||||
function inspectorTabLabel(tab: InspectorTab): string {
|
||||
if (tab === 'input') return t('inspector.input')
|
||||
if (tab === 'output') return t('inspector.output')
|
||||
if (tab === 'metadata') return t('inspector.metadata')
|
||||
return t('inspector.feedback')
|
||||
}
|
||||
|
||||
function runtimeLabel(): string {
|
||||
@@ -1952,7 +1938,7 @@ function asRecord(value: unknown): Record<string, unknown> {
|
||||
|
||||
function contentText(value: unknown): string {
|
||||
if (!Array.isArray(value)) return ''
|
||||
return value.map(block => {
|
||||
return value.map((block) => {
|
||||
const record = asRecord(block)
|
||||
if (record.type === 'text' || record.type === 'reasoning') return String(record.text ?? '')
|
||||
if (record.type === 'resource_link') return `[resource ${String(record.name ?? '')}] ${String(record.uri ?? '')}`
|
||||
|
||||
@@ -1,18 +1,30 @@
|
||||
/** Renderer locales currently supported by the desktop shell. */
|
||||
export type Locale = 'zh-CN' | 'en-US'
|
||||
|
||||
const messages = {
|
||||
'zh-CN': {
|
||||
'app.newChat': '新对话',
|
||||
'app.sessions': 'Sessions',
|
||||
'app.sessionsSubtitle': 'Chat + trace in one处',
|
||||
'app.sessionsSubtitle': 'Chat + trace in one place',
|
||||
'app.develop': 'Develop',
|
||||
'app.developSubtitle': 'Prompt、工具、插件、运行时',
|
||||
'app.searchPlaceholder': '搜索标题、id、模型',
|
||||
'app.recentSessions': '最近 sessions',
|
||||
'app.emptySessions': '没有匹配的 sessions',
|
||||
'app.language': 'EN',
|
||||
'app.errorTitle': '错误',
|
||||
'app.repo': 'Repo',
|
||||
'app.branch': 'Branch',
|
||||
'app.commit': 'Commit',
|
||||
'app.dirty': 'Dirty',
|
||||
'app.acp': 'ACP',
|
||||
'app.restartNeeded': 'Restart needed',
|
||||
'surface.chat': 'Chat',
|
||||
'surface.trajectory': 'Trajectory',
|
||||
'surface.waterfall': 'Waterfall',
|
||||
'surface.context': 'Context',
|
||||
'surface.compare': 'Compare',
|
||||
'surface.dev': 'Dev',
|
||||
'chat.details': '详情',
|
||||
'chat.thinking': 'Thinking',
|
||||
'chat.toolUse': 'Tool use',
|
||||
@@ -27,6 +39,10 @@ const messages = {
|
||||
'chat.newBody': '先输入一句话。发送后才会创建真实 ACP session,并在左侧出现记录。',
|
||||
'chat.startTitle': '开始一个 Deepseek Harness session',
|
||||
'chat.startBody': '点击 New chat 只会打开草稿;真正发送第一句话后,才会创建后端 session。',
|
||||
'chat.user': 'User',
|
||||
'chat.assistant': 'Assistant',
|
||||
'chat.userMessage': 'User message',
|
||||
'chat.assistantMessage': 'Assistant message',
|
||||
'trace.title': 'Trajectory',
|
||||
'trace.body': '按 session / turn / step / request / tool 组织。展开节点可以直接看关键 prompt、schema、input/output。',
|
||||
'trace.systemPrompt': 'System prompt',
|
||||
@@ -42,15 +58,74 @@ const messages = {
|
||||
'waterfall.tools': 'tools',
|
||||
'waterfall.slowest': 'slowest',
|
||||
'waterfall.errors': 'errors',
|
||||
'context.title': 'Context',
|
||||
'context.body': '这是开发分析视图:它解释模型请求边界里真正进入上下文的内容,用来改 prompt、tool schema 和 config。',
|
||||
'context.systemPrompt': 'System prompt',
|
||||
'context.toolSchemas': 'Tool schemas',
|
||||
'context.callConfig': 'Call config',
|
||||
'context.messagePrefix': 'Message prefix',
|
||||
'context.derivedHistory': 'Derived history',
|
||||
'context.rawJsonl': 'Raw JSONL',
|
||||
'context.noSystem': 'No system prompt found in latest request header.',
|
||||
'context.noTools': 'No tool schemas found in latest request header.',
|
||||
'context.changed': 'changed',
|
||||
'context.available': 'available',
|
||||
'context.empty': 'empty',
|
||||
'context.derived': 'derived',
|
||||
'context.visibleRows': 'visible rows',
|
||||
'context.messages': 'messages',
|
||||
'context.events': 'events',
|
||||
'empty.traceTitle': '还没有 trace',
|
||||
'empty.traceBody': '先运行一条 prompt,这里会读取真实 JSONL trace。',
|
||||
'dev.emptyTitle': 'No development artifacts found',
|
||||
'dev.emptyBody': 'Start the runtime or check the active cordis.yml config.',
|
||||
'dev.source': 'Source',
|
||||
'dev.owner': 'Owner',
|
||||
'dev.recentlyUsed': 'Recently used',
|
||||
'dev.reload': 'Reload',
|
||||
'dev.unknown': 'unknown',
|
||||
'dev.noRecentEvidence': 'No recent evidence yet',
|
||||
'dev.registeredPlugins': 'Registered plugins',
|
||||
'dev.registeredTools': 'Registered tool surfaces',
|
||||
'dev.noPlugins': 'No registered plugins found',
|
||||
'dev.noTools': 'No registered tools found yet',
|
||||
'dev.metadata': 'Metadata',
|
||||
'dev.metadataSubtitle': 'Implementation, dependency, and last-seen evidence',
|
||||
'dev.effectivePromptContent': 'Effective prompt content',
|
||||
'dev.toolSchema': 'Tool schema',
|
||||
'dev.pluginContribution': 'Plugin config / contribution',
|
||||
'dev.activeConfiguration': 'Active configuration',
|
||||
'dev.runtimeState': 'Runtime state',
|
||||
'dev.suggestedLoop': 'Suggested verification loop',
|
||||
'dev.promptMeta': 'Current source-level prompt text or prompt owner metadata',
|
||||
'dev.toolMeta': 'Current registered schema when last seen by a model request',
|
||||
'dev.pluginMeta': 'Who injects prompt/context or registers tools, based on active Cordis config',
|
||||
'dev.configMeta': 'Model/runtime parameters and files whose edits require reload',
|
||||
'dev.runtimeMeta': 'Current Electron main process and ACP bridge state',
|
||||
'dev.loopMeta': 'How to rerun and compare after editing the agent',
|
||||
'dev.manualRestart': 'Manual restart available here',
|
||||
'dev.restartRecommended': 'Restart recommended',
|
||||
'dev.noRestartSignal': 'No restart signal',
|
||||
'dev.restartAfterEdit': 'Restart ACP after editing',
|
||||
'dev.recommendedLoop': 'Recommended loop',
|
||||
'dev.loopStepEdit': 'Edit the prompt, tool, plugin, or config source.',
|
||||
'dev.loopStepRestart': 'Restart ACP runtime if the changed file is loaded at process start.',
|
||||
'dev.loopStepRerun': 'Return to Chat and rerun a previous task or start a new one.',
|
||||
'dev.loopStepCompare': 'Use Trajectory / Waterfall to compare behavior and timing evidence.',
|
||||
'dev.restartRuntime': 'Restart ACP runtime',
|
||||
'inspector.close': '关闭',
|
||||
'inspector.input': 'Input',
|
||||
'inspector.output': 'Output',
|
||||
'inspector.metadata': 'Metadata',
|
||||
'inspector.feedback': 'Feedback',
|
||||
'feedback.empty': '这个对象还没有 feedback。',
|
||||
'feedback.author': 'Feedback author',
|
||||
'feedback.placeholder': '给这个对象写 feedback',
|
||||
'feedback.add': '添加 feedback',
|
||||
'composer.placeholderDraft': '先输入一句话创建 session',
|
||||
'composer.placeholderSession': 'Message Deepseek Harness',
|
||||
'composer.send': 'Send message',
|
||||
'error.noDesktopApi': 'Desktop API 不可用。请用 Electron 窗口打开,而不是浏览器 tab。',
|
||||
},
|
||||
'en-US': {
|
||||
'app.newChat': 'New chat',
|
||||
@@ -60,10 +135,21 @@ const messages = {
|
||||
'app.developSubtitle': 'Prompts, tools, plugins, runtime',
|
||||
'app.searchPlaceholder': 'Search title, id, model',
|
||||
'app.recentSessions': 'Recent sessions',
|
||||
'app.emptySessions': 'No matching sessions',
|
||||
'app.language': '中文',
|
||||
'app.errorTitle': 'Error',
|
||||
'app.repo': 'Repo',
|
||||
'app.branch': 'Branch',
|
||||
'app.commit': 'Commit',
|
||||
'app.dirty': 'Dirty',
|
||||
'app.acp': 'ACP',
|
||||
'app.restartNeeded': 'Restart needed',
|
||||
'surface.chat': 'Chat',
|
||||
'surface.trajectory': 'Trajectory',
|
||||
'surface.waterfall': 'Waterfall',
|
||||
'surface.context': 'Context',
|
||||
'surface.compare': 'Compare',
|
||||
'surface.dev': 'Dev',
|
||||
'chat.details': 'Details',
|
||||
'chat.thinking': 'Thinking',
|
||||
'chat.toolUse': 'Tool use',
|
||||
@@ -78,6 +164,10 @@ const messages = {
|
||||
'chat.newBody': 'Type a message first. A real ACP session is created only after sending.',
|
||||
'chat.startTitle': 'Start a Deepseek Harness session',
|
||||
'chat.startBody': 'New chat opens a draft only. The backend session is created after the first sent message.',
|
||||
'chat.user': 'User',
|
||||
'chat.assistant': 'Assistant',
|
||||
'chat.userMessage': 'User message',
|
||||
'chat.assistantMessage': 'Assistant message',
|
||||
'trace.title': 'Trajectory',
|
||||
'trace.body': 'Organized by session / turn / step / request / tool. Expand nodes to inspect prompts, schemas, input, and output inline.',
|
||||
'trace.systemPrompt': 'System prompt',
|
||||
@@ -93,20 +183,86 @@ const messages = {
|
||||
'waterfall.tools': 'tools',
|
||||
'waterfall.slowest': 'slowest',
|
||||
'waterfall.errors': 'errors',
|
||||
'context.title': 'Context',
|
||||
'context.body': 'This development analysis view explains what actually entered the model request boundary, so prompts, tool schemas, and config can be changed with evidence.',
|
||||
'context.systemPrompt': 'System prompt',
|
||||
'context.toolSchemas': 'Tool schemas',
|
||||
'context.callConfig': 'Call config',
|
||||
'context.messagePrefix': 'Message prefix',
|
||||
'context.derivedHistory': 'Derived history',
|
||||
'context.rawJsonl': 'Raw JSONL',
|
||||
'context.noSystem': 'No system prompt found in latest request header.',
|
||||
'context.noTools': 'No tool schemas found in latest request header.',
|
||||
'context.changed': 'changed',
|
||||
'context.available': 'available',
|
||||
'context.empty': 'empty',
|
||||
'context.derived': 'derived',
|
||||
'context.visibleRows': 'visible rows',
|
||||
'context.messages': 'messages',
|
||||
'context.events': 'events',
|
||||
'empty.traceTitle': 'No trace yet',
|
||||
'empty.traceBody': 'Run a prompt first; this surface will read the real JSONL trace.',
|
||||
'dev.emptyTitle': 'No development artifacts found',
|
||||
'dev.emptyBody': 'Start the runtime or check the active cordis.yml config.',
|
||||
'dev.source': 'Source',
|
||||
'dev.owner': 'Owner',
|
||||
'dev.recentlyUsed': 'Recently used',
|
||||
'dev.reload': 'Reload',
|
||||
'dev.unknown': 'unknown',
|
||||
'dev.noRecentEvidence': 'No recent evidence yet',
|
||||
'dev.registeredPlugins': 'Registered plugins',
|
||||
'dev.registeredTools': 'Registered tool surfaces',
|
||||
'dev.noPlugins': 'No registered plugins found',
|
||||
'dev.noTools': 'No registered tools found yet',
|
||||
'dev.metadata': 'Metadata',
|
||||
'dev.metadataSubtitle': 'Implementation, dependency, and last-seen evidence',
|
||||
'dev.effectivePromptContent': 'Effective prompt content',
|
||||
'dev.toolSchema': 'Tool schema',
|
||||
'dev.pluginContribution': 'Plugin config / contribution',
|
||||
'dev.activeConfiguration': 'Active configuration',
|
||||
'dev.runtimeState': 'Runtime state',
|
||||
'dev.suggestedLoop': 'Suggested verification loop',
|
||||
'dev.promptMeta': 'Current source-level prompt text or prompt owner metadata',
|
||||
'dev.toolMeta': 'Current registered schema when last seen by a model request',
|
||||
'dev.pluginMeta': 'Who injects prompt/context or registers tools, based on active Cordis config',
|
||||
'dev.configMeta': 'Model/runtime parameters and files whose edits require reload',
|
||||
'dev.runtimeMeta': 'Current Electron main process and ACP bridge state',
|
||||
'dev.loopMeta': 'How to rerun and compare after editing the agent',
|
||||
'dev.manualRestart': 'Manual restart available here',
|
||||
'dev.restartRecommended': 'Restart recommended',
|
||||
'dev.noRestartSignal': 'No restart signal',
|
||||
'dev.restartAfterEdit': 'Restart ACP after editing',
|
||||
'dev.recommendedLoop': 'Recommended loop',
|
||||
'dev.loopStepEdit': 'Edit the prompt, tool, plugin, or config source.',
|
||||
'dev.loopStepRestart': 'Restart ACP runtime if the changed file is loaded at process start.',
|
||||
'dev.loopStepRerun': 'Return to Chat and rerun a previous task or start a new one.',
|
||||
'dev.loopStepCompare': 'Use Trajectory / Waterfall to compare behavior and timing evidence.',
|
||||
'dev.restartRuntime': 'Restart ACP runtime',
|
||||
'inspector.close': 'Close',
|
||||
'inspector.input': 'Input',
|
||||
'inspector.output': 'Output',
|
||||
'inspector.metadata': 'Metadata',
|
||||
'inspector.feedback': 'Feedback',
|
||||
'feedback.empty': 'No feedback for this object yet.',
|
||||
'feedback.author': 'Feedback author',
|
||||
'feedback.placeholder': 'Write feedback for this exact object',
|
||||
'feedback.add': 'Add feedback',
|
||||
'composer.placeholderDraft': 'Type a message to create a session',
|
||||
'composer.placeholderSession': 'Message Deepseek Harness',
|
||||
'composer.send': 'Send message',
|
||||
'error.noDesktopApi': 'Desktop API is not available. Use the Electron window, not the browser tab.',
|
||||
},
|
||||
} as const
|
||||
|
||||
/** Translation key shared by both renderer dictionaries. */
|
||||
export type I18nKey = keyof typeof messages['en-US']
|
||||
|
||||
/**
|
||||
* Return a localized renderer string, falling back to English and then the key.
|
||||
* @param locale - The active renderer locale.
|
||||
* @param key - The dictionary key to resolve.
|
||||
* @returns The localized string for the requested key.
|
||||
*/
|
||||
export function translate(locale: Locale, key: I18nKey): string {
|
||||
return messages[locale][key] ?? messages['en-US'][key] ?? key
|
||||
return messages[locale][key]
|
||||
}
|
||||
|
||||
@@ -201,6 +201,7 @@ export interface DevPanelStatus {
|
||||
readonly suggestedPrompt?: string
|
||||
}
|
||||
|
||||
/** Default author filled into new feedback entries. */
|
||||
export const DEFAULT_FEEDBACK_AUTHOR = 'shentuni'
|
||||
|
||||
/** Default surface definitions for the first Electron implementation. */
|
||||
@@ -270,22 +271,39 @@ export const SURFACE_DEFINITIONS: Record<DesktopSurface, SurfaceDefinition> = {
|
||||
/** Backward-compatible alias for callers that only need policies. */
|
||||
export const SURFACE_POLICIES: Record<DesktopSurface, SurfacePolicy> = SURFACE_DEFINITIONS
|
||||
|
||||
/** Returns whether selecting from a middle surface should open the inspector. */
|
||||
/**
|
||||
* Returns whether selecting from a middle surface should open the inspector.
|
||||
* @param surface - The active middle surface.
|
||||
* @param target - The object the user selected, if any.
|
||||
* @returns True when this selection should open the inspector drawer.
|
||||
*/
|
||||
export function opensInspector(surface: DesktopSurface, target: InspectorTarget | undefined): boolean {
|
||||
return target !== undefined && SURFACE_POLICIES[surface].fullDetailInInspector
|
||||
}
|
||||
|
||||
/** Returns whether the surface is allowed to show the chat composer. */
|
||||
/**
|
||||
* Returns whether the surface is allowed to show the chat composer.
|
||||
* @param surface - The active middle surface.
|
||||
* @returns True only for the driving chat surface.
|
||||
*/
|
||||
export function ownsComposer(surface: DesktopSurface): boolean {
|
||||
return SURFACE_DEFINITIONS[surface].ownsComposer
|
||||
}
|
||||
|
||||
/** Full detail belongs in the inspector for trace-analysis surfaces, not the Develop artifact browser. */
|
||||
/**
|
||||
* Full detail belongs in the inspector for trace-analysis surfaces, not the Develop artifact browser.
|
||||
* @param surface - The active middle surface.
|
||||
* @returns True when raw payloads should live in the inspector for this surface.
|
||||
*/
|
||||
export function fullDetailBelongsInInspector(surface: DesktopSurface): boolean {
|
||||
return SURFACE_DEFINITIONS[surface].fullDetailInInspector
|
||||
}
|
||||
|
||||
/** Create a stable, view-independent inspector id. */
|
||||
/**
|
||||
* Create a stable, view-independent inspector id.
|
||||
* @param key - Stable fields that identify the selected object.
|
||||
* @returns A colon-delimited target id shared across surfaces.
|
||||
*/
|
||||
export function createInspectorTargetId(key: InspectorTargetKey): string {
|
||||
const parts = [`session:${key.sessionId}`]
|
||||
if (key.runId !== undefined) parts.push(`run:${key.runId}`)
|
||||
@@ -295,7 +313,11 @@ export function createInspectorTargetId(key: InspectorTargetKey): string {
|
||||
return parts.join(':')
|
||||
}
|
||||
|
||||
/** Pick a useful starting inspector tab for common target kinds. */
|
||||
/**
|
||||
* Pick a useful starting inspector tab for common target kinds.
|
||||
* @param target - The selected object that will be inspected.
|
||||
* @returns The inspector tab that best matches the target's primary payload.
|
||||
*/
|
||||
export function defaultInspectorTabForTarget(target: InspectorTarget): InspectorTab {
|
||||
switch (target.kind) {
|
||||
case 'assistant-stream':
|
||||
@@ -314,7 +336,10 @@ export function defaultInspectorTabForTarget(target: InspectorTarget): Inspector
|
||||
}
|
||||
}
|
||||
|
||||
/** Build closed drawer state when no node is selected. */
|
||||
/**
|
||||
* Build closed drawer state when no node is selected.
|
||||
* @returns An inspector state with no target and no visible tabs.
|
||||
*/
|
||||
export function closedInspectorState(): InspectorState {
|
||||
return {
|
||||
open: false,
|
||||
@@ -323,13 +348,17 @@ export function closedInspectorState(): InspectorState {
|
||||
}
|
||||
}
|
||||
|
||||
/** Build an open drawer state for a selected target. */
|
||||
/**
|
||||
* Build an open drawer state for a selected target.
|
||||
* @param target - The selected object that owns the inspector content.
|
||||
* @returns An open inspector state with the default tab selected.
|
||||
*/
|
||||
export function openInspectorState(target: InspectorTarget): InspectorState {
|
||||
return {
|
||||
open: true,
|
||||
target,
|
||||
activeTab: defaultInspectorTabForTarget(target),
|
||||
tabs: INSPECTOR_TABS.map((tab) => ({
|
||||
tabs: INSPECTOR_TABS.map(tab => ({
|
||||
tab,
|
||||
available: true,
|
||||
canCopy: tab !== 'feedback',
|
||||
|
||||
@@ -369,12 +369,12 @@ function devStatus() {
|
||||
},
|
||||
{
|
||||
label: 'System prompt service',
|
||||
path: 'packages/system-prompt/system-prompt/src/index.ts',
|
||||
path: 'packages/core/system-prompt/src/index.ts',
|
||||
purpose: 'Owns persona, tool order, and assembled model-facing prompt sections.',
|
||||
},
|
||||
{
|
||||
label: 'Tool registry',
|
||||
path: 'packages/tools/tools/src/index.ts',
|
||||
path: 'packages/core/tools/src/index.ts',
|
||||
purpose: 'Owns model-facing tool registration, schema validation, and tool presentation mode.',
|
||||
},
|
||||
],
|
||||
|
||||
@@ -48,7 +48,7 @@ describe('desktop ACP subprocess bridge', () => {
|
||||
|
||||
let stderr = ''
|
||||
child.stderr.setEncoding('utf8')
|
||||
child.stderr.on('data', chunk => { stderr += String(chunk) })
|
||||
child.stderr.on('data', (chunk) => { stderr += String(chunk) })
|
||||
|
||||
const stream: Stream = ndJsonStream(
|
||||
Writable.toWeb(child.stdin) as WritableStream<Uint8Array>,
|
||||
@@ -69,7 +69,7 @@ describe('desktop ACP subprocess bridge', () => {
|
||||
}), stream)
|
||||
|
||||
const init = await client.initialize({ protocolVersion: PROTOCOL_VERSION, clientCapabilities: {} })
|
||||
expect(init.agentInfo.name).toBe('deepseek-harness-acp')
|
||||
expect(init.agentInfo?.name).toBe('deepseek-harness-acp')
|
||||
|
||||
const session = await client.newSession({ cwd: process.cwd(), mcpServers: [] })
|
||||
expect(session.sessionId).toBeTruthy()
|
||||
|
||||
@@ -11,9 +11,9 @@ import {
|
||||
ownsComposer,
|
||||
SURFACE_DEFINITIONS,
|
||||
SURFACE_POLICIES,
|
||||
type DesktopSurface,
|
||||
type InspectorTarget,
|
||||
} from '../src/index.ts'
|
||||
import { translate } from '../src/i18n.ts'
|
||||
|
||||
const target: InspectorTarget = {
|
||||
id: 'session:one:event:1',
|
||||
@@ -89,7 +89,7 @@ describe('desktop inspector contracts', () => {
|
||||
|
||||
it('keeps inspector tabs ordered with feedback last', () => {
|
||||
expect(INSPECTOR_TABS).toEqual(['input', 'output', 'metadata', 'feedback'])
|
||||
expect(openInspectorState(target).tabs.map((tab) => tab.tab)).toEqual(INSPECTOR_TABS)
|
||||
expect(openInspectorState(target).tabs.map(tab => tab.tab)).toEqual(INSPECTOR_TABS)
|
||||
expect(closedInspectorState()).toEqual({
|
||||
open: false,
|
||||
activeTab: 'input',
|
||||
@@ -101,3 +101,12 @@ describe('desktop inspector contracts', () => {
|
||||
expect(DEFAULT_FEEDBACK_AUTHOR).toBe('shentuni')
|
||||
})
|
||||
})
|
||||
|
||||
describe('desktop i18n', () => {
|
||||
it('switches visible shell labels between Chinese and English', () => {
|
||||
expect(translate('zh-CN', 'app.newChat')).toBe('新对话')
|
||||
expect(translate('en-US', 'app.newChat')).toBe('New chat')
|
||||
expect(translate('zh-CN', 'app.language')).toBe('EN')
|
||||
expect(translate('en-US', 'app.language')).toBe('中文')
|
||||
})
|
||||
})
|
||||
|
||||
Generated
+97
@@ -1571,6 +1571,28 @@ importers:
|
||||
specifier: ^4.0.0-rc.7
|
||||
version: 4.0.0-rc.7(@cordisjs/plugin-include@vendor+include)(@cordisjs/plugin-loader@vendor+loader)
|
||||
|
||||
packages/ui/desktop:
|
||||
dependencies:
|
||||
'@agentclientprotocol/sdk':
|
||||
specifier: 0.25.1
|
||||
version: 0.25.1(zod@4.4.3)
|
||||
devDependencies:
|
||||
cordis:
|
||||
specifier: ^4.0.0-rc.7
|
||||
version: 4.0.0-rc.7(@cordisjs/plugin-include@1.0.4)(@cordisjs/plugin-loader@1.0.0-rc.5)
|
||||
electron:
|
||||
specifier: ^43.1.1
|
||||
version: 43.1.1
|
||||
typescript:
|
||||
specifier: ^6.0.3
|
||||
version: 6.0.3
|
||||
vite:
|
||||
specifier: ^8.0.16
|
||||
version: 8.0.16(@types/node@25.9.3)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.22.4)(yaml@2.9.0)
|
||||
vitest:
|
||||
specifier: ^4.1.8
|
||||
version: 4.1.8(@types/node@25.9.3)(@vitest/coverage-v8@4.1.8)(jsdom@29.1.1)(vite@8.0.16(@types/node@25.9.3)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.22.4)(yaml@2.9.0))
|
||||
|
||||
packages/ui/jsonrpc:
|
||||
dependencies:
|
||||
schemastery:
|
||||
@@ -2519,6 +2541,14 @@ packages:
|
||||
engines: {node: '>=22.19.0'}
|
||||
hasBin: true
|
||||
|
||||
'@electron-internal/extract-zip@1.0.4':
|
||||
resolution: {integrity: sha512-Zr1Vs7E9tpCNhZHDAbFVXc2gEVCG9RqPDjrno5+bdgB6LRAuvgyMHJut4NCVyYwtAieapMzc3fiQ3CSTi75ARg==}
|
||||
engines: {node: '>=22.12.0'}
|
||||
|
||||
'@electron/get@5.0.0':
|
||||
resolution: {integrity: sha512-pjoBpru1KdEtcExBnuHAP1cAc/5faoedw0hzJkL3o4/IJp7HNF1+fbrdxT3gMYRX2oJfvnA/WXeCTVQpYYxyJA==}
|
||||
engines: {node: '>=22.12.0'}
|
||||
|
||||
'@emnapi/core@1.10.0':
|
||||
resolution: {integrity: sha512-yq6OkJ4p82CAfPl0u9mQebQHKPJkY7WrIuk205cTYnYe+k2Z8YBh11FrbRG/H6ihirqcacOgl2BIO8oyMQLeXw==}
|
||||
|
||||
@@ -3473,6 +3503,9 @@ packages:
|
||||
'@types/node@22.20.0':
|
||||
resolution: {integrity: sha512-QWlFW2wf3nTjC13/DqRnBpR4ZO36VJH/JVBkA/vcnmbTBNQIlnObqyqZE1tUR7+Ni23Lda8R1BxMfbXRpCUx5g==}
|
||||
|
||||
'@types/node@24.13.3':
|
||||
resolution: {integrity: sha512-Dh8vAsV36ig5wa9OX4pXvMc9D3Veibfw2wix0CUwYODLD8nkj9UsLjASr49nPg+2eKzxhBV+v7L8pXvT4e639Q==}
|
||||
|
||||
'@types/node@25.9.3':
|
||||
resolution: {integrity: sha512-603BddQMv3pUcr4U2dhujk83N2tTDVr/34wII2B6bJy6g+8WD6yUb11jszNs0gdi4PesVWl7ABt8nYMVpnLUcg==}
|
||||
|
||||
@@ -4047,6 +4080,11 @@ packages:
|
||||
ee-first@1.1.1:
|
||||
resolution: {integrity: sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==}
|
||||
|
||||
electron@43.1.1:
|
||||
resolution: {integrity: sha512-I5c5vfuVvaXpWx3IZdwvXgxQW44+e7OP1wXGVQkogLeSFSkUZ6sLCcWV05AdEcs65AO5tAIJJwbp7ixw+LdarA==}
|
||||
engines: {node: '>= 22.12.0'}
|
||||
hasBin: true
|
||||
|
||||
emoji-regex@8.0.0:
|
||||
resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==}
|
||||
|
||||
@@ -4065,6 +4103,10 @@ packages:
|
||||
resolution: {integrity: sha512-zwfzJecQ/Uej6tusMqwAqU/6KL2XaB2VZ2Jg54Je6ahNBGNH6Ek6g3jjNCF0fG9EWQKGZNddNjU5F1ZQn/sBnA==}
|
||||
engines: {node: '>=20.19.0'}
|
||||
|
||||
env-paths@3.0.0:
|
||||
resolution: {integrity: sha512-dtJUTepzMW3Lm/NPxRf3wP4642UWhjL2sQxc+ym2YMj1m/H2zDNQOlezafzkHwn6sMstjHTwG6iQQsctDW/b1A==}
|
||||
engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0}
|
||||
|
||||
es-define-property@1.0.1:
|
||||
resolution: {integrity: sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==}
|
||||
engines: {node: '>= 0.4'}
|
||||
@@ -4336,6 +4378,9 @@ packages:
|
||||
resolution: {integrity: sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==}
|
||||
engines: {node: '>= 0.4'}
|
||||
|
||||
graceful-fs@4.2.11:
|
||||
resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==}
|
||||
|
||||
hachure-fill@0.5.2:
|
||||
resolution: {integrity: sha512-3GKBOn+m2LX9iq+JC1064cSFprJY4jL1jCXTcpnfER5HYE2l/4EfWSGzkPa/ZDBmYI0ZOEj5VHV/eKnPGkHuOg==}
|
||||
|
||||
@@ -5156,6 +5201,10 @@ packages:
|
||||
process-nextick-args@2.0.1:
|
||||
resolution: {integrity: sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==}
|
||||
|
||||
progress@2.0.3:
|
||||
resolution: {integrity: sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==}
|
||||
engines: {node: '>=0.4.0'}
|
||||
|
||||
protobufjs@7.6.4:
|
||||
resolution: {integrity: sha512-RJJPTTpvFfHcWLkIa2JFWK4XvtSzS0yEWDmunqHXli1h3JlkbcQZXDZdcWxv+JK3Xsl5/UFDPZ0iGm7DAengYw==}
|
||||
engines: {node: '>=12.0.0'}
|
||||
@@ -5387,6 +5436,10 @@ packages:
|
||||
stylis@4.4.0:
|
||||
resolution: {integrity: sha512-5Z9ZpRzfuH6l/UAvCPAPUo3665Nk2wLaZU3x+TLHKVzIz33+sbJqbtrYoC3KD4/uVOr2Zp+L0LySezP9OHV9yA==}
|
||||
|
||||
sumchecker@3.0.1:
|
||||
resolution: {integrity: sha512-MvjXzkz/BOfyVDkG0oFOtBxHX2u3gKbMHIF/dXblZsgD3BWOFLmHovIpZY7BykJdAjcqRCBi1WYBNdEC9yI7vg==}
|
||||
engines: {node: '>= 8.0'}
|
||||
|
||||
supports-color@7.2.0:
|
||||
resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==}
|
||||
engines: {node: '>=8'}
|
||||
@@ -5540,6 +5593,9 @@ packages:
|
||||
undici-types@6.21.0:
|
||||
resolution: {integrity: sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==}
|
||||
|
||||
undici-types@7.18.2:
|
||||
resolution: {integrity: sha512-AsuCzffGHJybSaRrmr5eHr81mwJU3kjw6M+uprWvCXiNeN9SOGwQ3Jn8jb8m3Z6izVgknn1R0FTCEAP2QrLY/w==}
|
||||
|
||||
undici-types@7.24.6:
|
||||
resolution: {integrity: sha512-WRNW+sJgj5OBN4/0JpHFqtqzhpbnV0GuB+OozA9gCL7a993SmU+1JBZCzLNxYsbMfIeDL+lTsphD5jN5N+n0zg==}
|
||||
|
||||
@@ -6157,6 +6213,21 @@ snapshots:
|
||||
- ws
|
||||
- zod
|
||||
|
||||
'@electron-internal/extract-zip@1.0.4': {}
|
||||
|
||||
'@electron/get@5.0.0':
|
||||
dependencies:
|
||||
debug: 4.4.3
|
||||
env-paths: 3.0.0
|
||||
graceful-fs: 4.2.11
|
||||
progress: 2.0.3
|
||||
semver: 7.8.4
|
||||
sumchecker: 3.0.1
|
||||
optionalDependencies:
|
||||
undici: 7.28.0
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
'@emnapi/core@1.10.0':
|
||||
dependencies:
|
||||
'@emnapi/wasi-threads': 1.2.1
|
||||
@@ -6919,6 +6990,10 @@ snapshots:
|
||||
dependencies:
|
||||
undici-types: 6.21.0
|
||||
|
||||
'@types/node@24.13.3':
|
||||
dependencies:
|
||||
undici-types: 7.18.2
|
||||
|
||||
'@types/node@25.9.3':
|
||||
dependencies:
|
||||
undici-types: 7.24.6
|
||||
@@ -7545,6 +7620,14 @@ snapshots:
|
||||
|
||||
ee-first@1.1.1: {}
|
||||
|
||||
electron@43.1.1:
|
||||
dependencies:
|
||||
'@electron-internal/extract-zip': 1.0.4
|
||||
'@electron/get': 5.0.0
|
||||
'@types/node': 24.13.3
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
emoji-regex@8.0.0: {}
|
||||
|
||||
emoji-regex@9.2.2: {}
|
||||
@@ -7555,6 +7638,8 @@ snapshots:
|
||||
|
||||
entities@8.0.0: {}
|
||||
|
||||
env-paths@3.0.0: {}
|
||||
|
||||
es-define-property@1.0.1: {}
|
||||
|
||||
es-errors@1.3.0: {}
|
||||
@@ -7919,6 +8004,8 @@ snapshots:
|
||||
|
||||
gopd@1.2.0: {}
|
||||
|
||||
graceful-fs@4.2.11: {}
|
||||
|
||||
hachure-fill@0.5.2: {}
|
||||
|
||||
handlebars@4.7.9:
|
||||
@@ -8873,6 +8960,8 @@ snapshots:
|
||||
|
||||
process-nextick-args@2.0.1: {}
|
||||
|
||||
progress@2.0.3: {}
|
||||
|
||||
protobufjs@7.6.4:
|
||||
dependencies:
|
||||
'@protobufjs/aspromise': 1.1.2
|
||||
@@ -9165,6 +9254,12 @@ snapshots:
|
||||
|
||||
stylis@4.4.0: {}
|
||||
|
||||
sumchecker@3.0.1:
|
||||
dependencies:
|
||||
debug: 4.4.3
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
supports-color@7.2.0:
|
||||
dependencies:
|
||||
has-flag: 4.0.0
|
||||
@@ -9286,6 +9381,8 @@ snapshots:
|
||||
|
||||
undici-types@6.21.0: {}
|
||||
|
||||
undici-types@7.18.2: {}
|
||||
|
||||
undici-types@7.24.6: {}
|
||||
|
||||
undici@7.28.0: {}
|
||||
|
||||
@@ -59,6 +59,7 @@
|
||||
{ "path": "./packages/ui/acp" },
|
||||
{ "path": "./packages/examples/acp-demo" },
|
||||
{ "path": "./packages/ui/app-boot" },
|
||||
{ "path": "./packages/ui/desktop" },
|
||||
{ "path": "./packages/ui/jsonrpc" },
|
||||
{ "path": "./packages/examples/jsonrpc-demo" },
|
||||
{ "path": "./packages/ui/stdio" },
|
||||
|
||||
@@ -70,6 +70,7 @@
|
||||
{ "path": "./packages/ui/acp" },
|
||||
{ "path": "./packages/examples/acp-demo" },
|
||||
{ "path": "./packages/ui/app-boot" },
|
||||
{ "path": "./packages/ui/desktop" },
|
||||
{ "path": "./packages/ui/jsonrpc" },
|
||||
{ "path": "./packages/examples/jsonrpc-demo" },
|
||||
{ "path": "./packages/ui/stdio" },
|
||||
|
||||
Reference in New Issue
Block a user