refactor(agent): replace OMP with Claude Code kernel

This commit is contained in:
xiaohan.yuan committed 2026-10-20 14:47:56 +08:00
1 parent 0fb2226f8c
commit 9a1f182397
232 files changed
+2561 -25564

No files matched your search

+2 -3
View File
@@ -17,11 +17,10 @@ export function normalizeContextUsageSnapshot(value: unknown): ContextUsageSnaps
...(nonnegative(usage.cachedTokens) ? { cachedTokens: usage.cachedTokens } : {}) },
...(typeof sample.contextWindow === 'number' && Number.isFinite(sample.contextWindow) && sample.contextWindow > 0
? { contextWindow: sample.contextWindow } : {}),
...(['upstream','native-history','native-state'].includes(String(sample.source)) ? {source:sample.source} : {}),
...(sample.source === 'upstream' ? {source:sample.source} : {}),
...(nonnegative(sample.sampledAt) ? {sampledAt:sample.sampledAt} : {}),
...(typeof sample.requestId === 'string' ? {requestId:sample.requestId} : {}),
...(typeof sample.modelId === 'string' ? {modelId:sample.modelId} : {}),
...(typeof sample.nativeEntryId === 'string' ? {nativeEntryId:sample.nativeEntryId} : {})
...(typeof sample.modelId === 'string' ? {modelId:sample.modelId} : {})
}
}
+1 -1
View File
@@ -231,12 +231,12 @@ export interface YCodeApi {
chat: {
activeRun(sessionId: string): Ipc<ActiveChatRun | null>
onStarted(listener: (run: ActiveChatRun) => void): () => void
setSessionVisible(sessionId: string | null): Ipc<{ accepted: boolean }>
inspectEdit(sessionId: string, userMessageId: string): Ipc<EditInspection>
revert(sessionId: string, userMessageId: string, options?: RewindOptions): Ipc<{ messages: ChatMessage[] }>
feedback(sessionId: string, messageId: string, value: 'like' | 'dislike'): Ipc<void>
send(payload: SendPayload): Ipc<SendResult>
cancel(runId: string): Ipc<{ cancelled: boolean }>
cancelAgent(runId: string, agentId: string): Ipc<{ cancelled: boolean }>
respond(runId: string, interactionId: string, response: InteractionResponse): Ipc<{ accepted: boolean }>
/** 返回取消订阅函数 */
onEvent(listener: (event: ChatEvent) => void): () => void
+2 -8
View File
@@ -13,10 +13,9 @@ export function getMessageParts(message: ChatMessage): MessagePart[] {
/** 主进程持久化和渲染进程使用同一事件归约,避免重载前后内容顺序不同。 */
export function applyAssistantEvent(message: ChatMessage, event: ChatEvent): ChatMessage {
switch (event.type) {
case 'run_usage': return { ...message, runUsage: event.usage }
case 'usage': {
const contextUsage = normalizeContextUsageSnapshot({ usage: event.usage, contextWindow: event.contextWindow,
source:event.source,sampledAt:event.sampledAt,requestId:event.runId,modelId:event.modelId,nativeEntryId:event.nativeEntryId })
source:event.source,sampledAt:event.sampledAt,requestId:event.runId,modelId:event.modelId })
return contextUsage ? { ...message, contextUsage } : message
}
case 'context_breakdown': {
@@ -32,7 +31,6 @@ export function applyAssistantEvent(message: ChatMessage, event: ChatEvent): Cha
if (part.type === 'plan') return `plan:${part.plan.id}`
if (part.type === 'task') return `task:${part.task.id}`
if (part.type === 'artifact') return `artifact:${part.artifact.id}`
if (part.type === 'subagent') return `subagent:${part.agent.id}`
return undefined
}
const key = identity(event.part)
@@ -94,11 +92,7 @@ export function applyAssistantEvent(message: ChatMessage, event: ChatEvent): Cha
tool.status === 'running' ? { ...tool, status } : tool
)
const interactions = message.interactions?.map((item) => item.status === 'pending' || item.status === 'responding' ? { ...item, status: 'cancelled' as const } : item)
const parts = message.parts?.map((part): MessagePart => part.type === 'subagent' && ['queued', 'running'].includes(part.agent.status)
? { ...part, agent: { ...part.agent, status: 'cancelled', finishedAt: Date.now(),
tools: part.agent.tools.map(tool => tool.status === 'running' ? { ...tool, status: 'cancelled' } : tool),
events: part.agent.events.map(item => item.status === 'running' ? { ...item, status: 'cancelled' } : item) } } : part)
return { ...message, tools, interactions, parts, status, error: event.type === 'error' ? event.message : message.error, ...terminalDuration(event) }
return { ...message, tools, interactions, parts: message.parts, status, error: event.type === 'error' ? event.message : message.error, ...terminalDuration(event) }
}
default:
return message
+1 -1
View File
@@ -59,7 +59,7 @@ export function lookupModelMetadata(catalog: ModelCatalog, modelId: string, prot
const m = match.model, options = (m.reasoning_options ?? []).filter(option => ['effort', 'budget_tokens', 'toggle'].includes(option.type))
const values = options.flatMap(option => option.type === 'effort' ? option.values ?? [] : [])
const budget = options.find(option => option.type === 'budget_tokens')
// OMP's minimal native budget is 1024 tokens (verified for Anthropic/Gemini).
// The minimal native budget is 1024 tokens for Anthropic and Gemini models.
const efforts = values.length ? order.filter(value => values.includes(value)) : budget && (budget.min ?? 0) <= 1024 && (budget.max ?? Infinity) >= 1024 ? ['minimal'] : []
const canDisable = !m.reasoning || options.some(option => option.type === 'toggle' || option.type === 'budget_tokens' && option.min === 0) || values.includes('none')
const price = cheapestModelPrice(m)
+1 -1
View File
@@ -24,7 +24,7 @@ export function resolveModelTokenLimits(model: TokenSettings): ResolvedTokenSett
}
if (output >= context) return { error: '输出上限必须小于总上下文长度' }
const input = Math.min(context - output, positive(ceilings?.maxInputTokens) ?? Infinity)
// OMP 18.6.3 defaults to a 15% reserve with a 16,384-token floor, recovering
// Legacy runtime defaults use a 15% reserve with a 16,384-token floor, recovering
// proportional reserve on tiny windows. Also reserve the full output budget.
const proportional = Math.max(1, Math.floor(context * 0.15))
const normalReserve = Math.max(proportional, 16384)
-32
View File
@@ -1,32 +0,0 @@
import type { ChatMessage } from './types'
export interface RevertChange { path: string; before: string | null; after: string }
const readOnly = new Set(['read_file', 'list_dir', 'ask_user_question', 'ask_followup_question', 'todo_write', 'create_plan',
'spawn_agent', 'list_agents', 'wait_agent', 'send_agent_message', 'cancel_agent', 'ycode_load_skill', 'ycode_read_skill_resource', 'ycode_expert_resource'])
const writes = new Set(['write_file', 'edit_file'])
/** Only host writes with complete before/after records can offer a real rollback. */
export function getRevertChanges(messages: ChatMessage[], userMessageId: string): RevertChange[] {
const index = messages.findIndex((message) => message.id === userMessageId && message.role === 'user')
if (index < 0) throw new Error('未找到用户消息')
const changes = new Map<string, RevertChange>()
for (const message of messages.slice(index)) {
if (message.status === 'streaming') throw new Error('请先停止当前运行')
const children = (message.parts ?? []).flatMap(part => part.type === 'subagent' ? part.agent.tools : [])
const tools = [...(message.tools ?? []), ...children]
if (children.length) tools.sort((left, right) =>
Number(left.metadata?.completedAt ?? 0) - Number(right.metadata?.completedAt ?? 0)
|| Number(left.metadata?.completionOrder ?? 0) - Number(right.metadata?.completionOrder ?? 0))
for (const tool of tools) {
if (readOnly.has(tool.name)) continue
if (!writes.has(tool.name)) throw new Error('此版本包含无法完整回滚的工具操作')
const data = tool.result as Partial<{ path: string; oldContent: string | null; newContent: string }> | undefined
if (!data || typeof data.path !== 'string' || !(data.oldContent === null || typeof data.oldContent === 'string') || typeof data.newContent !== 'string') {
if (tool.status === 'error' || tool.status === 'cancelled') continue
throw new Error('此版本缺少文件修改记录')
}
const existing = changes.get(data.path)
if (existing && existing.after !== data.oldContent) throw new Error('文件在两次工具修改之间发生过外部编辑')
changes.set(data.path, { path: data.path, before: existing ? existing.before : data.oldContent, after: data.newContent })
}
}
return [...changes.values()]
}
-92
View File
@@ -1,92 +0,0 @@
export interface SandboxStatus {
backend: 'windows-appcontainer'; protocol: 1; state: 'installed' | 'unavailable'; reason?: string
network: 'deny'; filesystem: 'workspace-snapshot'; writeback: 'approval'; limits: { files: number; bytes: number; fileBytes: number; timeoutMs: number }
policy?: { revision: string; commandsEnabled: boolean; writeback: 'ask' | 'deny' }
capabilities?: SandboxCapability[]
selfTest?: SandboxSelfTestSummary
toolchains?: SandboxToolchainView[]
}
export interface SandboxLimits { files: number; bytes: number; fileBytes: number; timeoutMs: number }
export interface SandboxPolicyRules {
commandsEnabled: boolean
writeback: 'ask' | 'deny'
limits: SandboxLimits
deniedPaths: string[]
maxChanges: number
}
export type SandboxPolicyOverride = Partial<Omit<SandboxPolicyRules, 'limits'>> & { limits?: Partial<SandboxLimits> }
export interface SandboxPolicyDocument {
version: 1; revision: string; updatedAt: number
defaults: SandboxPolicyRules
workspaces: Record<string, { root: string; rules: SandboxPolicyOverride }>
}
export interface SandboxPolicyUpdate { expectedRevision: string; rules: SandboxPolicyOverride; workspaceRoot?: string }
export interface SandboxEffectivePolicy extends SandboxPolicyRules { revision: string; fingerprint: string; workspaceRoot: string }
export interface SandboxCapability {
id: 'command-isolation' | 'network-deny' | 'file-transactions' | 'persistent-workspace' | 'workspace-read-bridge' | 'node' | 'python' | 'git' | 'git-host-workflow' | 'git-remote-transport' | 'network-egress' | 'filesystem-broker' | 'registry-policy' | 'mcp-isolation' | 'terminal-isolation'
state: 'implemented' | 'unavailable'; detail: string
}
export interface SandboxSelfTestSummary { passed: boolean; checkedAt: number; binarySha256: string; policyRevision: string; policyFingerprint: string; runId: string }
export type SandboxToolchainKind = 'node' | 'python' | 'git'
export type SandboxDependencyKind = 'node' | 'python'
export interface SandboxToolchainIdentity { kind: SandboxToolchainKind; id: string; manifestSha256: string }
export interface SandboxToolchainView { kind: SandboxToolchainKind; state: 'registered' | 'missing' | 'invalid'; id?: string; files?: number; bytes?: number; features?: string[]; reason?: string; source?: 'bundled' | 'imported'; version?: string }
export interface SandboxDependencyIdentity { kind: SandboxDependencyKind; id: string; lockHash: string; manifestSha256: string; runtimeId: string; runtimeManifestSha256: string }
export interface SandboxGitState {
mode: 'private-snapshot' | 'host-mirror' | 'remote-clone' | 'new-repository'; baseline: 'filtered-workspace' | 'host-repository' | 'remote-repository' | 'empty-repository'; initialized: boolean
revision: string; manifestSha256: string
persistence: 'unchanged' | 'saved' | 'rejected' | 'not-applied' | 'failed'; error?: string
scope?: 'draft' | 'host-draft' | 'clone-draft' | 'new-draft'
}
export interface SandboxWorkspaceView {
mode: 'persistent'; state: 'missing' | 'ready' | 'conflict' | 'stale' | 'interrupted' | 'busy' | 'invalid'
sessionId: string; revision?: string; policyRevision?: string; files?: number; bytes?: number
changes: SandboxChangedFile[]; conflicts?: string[]; reason?: string; lastTransactionId?: string
}
export type SandboxWorkspaceAction = 'status' | 'changes' | 'commit' | 'discard' | 'recover'
export interface SandboxGitHostView {
mode: 'host-mirror' | 'remote-clone' | 'new-repository'; state: 'unimported' | 'ready' | 'busy' | 'stale' | 'interrupted'; sessionId: string
revision?: string; importedAt?: number; linkedWorktree?: boolean; pendingMetadata?: boolean
writeback?: 'committed' | 'unchanged' | 'rejected' | 'not-applied' | 'unknown'; groupId?: string; transactionId?: string; reason?: string
workspace?: SandboxWorkspaceView
}
export interface SandboxGitRemoteResult {
operationId: string; action: 'ls-remote' | 'clone' | 'fetch' | 'pull' | 'push'; state: 'denied' | 'completed' | 'failed' | 'unknown'
remote: string; stdout?: string; stderr?: string; revision?: string; needsCheckout?: boolean; needsMerge?: boolean
intendedRefs?: Record<string, string>; writeback: 'not-applied' | 'remote-committed' | 'unknown'; reason?: string; cleanupRequired?: boolean
}
export interface SandboxWorkspaceActionResult extends SandboxWorkspaceView {
git?: SandboxGitHostView
action: SandboxWorkspaceAction; writeback?: 'committed' | 'unchanged' | 'rejected' | 'not-applied' | 'unknown'
transactionId?: string; authorizationId?: string; operationId?: string
}
export interface SandboxOperationView {
id: string; workspaceRoot: string; createdAt: number; completedAt?: number
state: 'completed' | 'failed' | 'incomplete'; status: string
writeback: 'committed' | 'unchanged' | 'rejected' | 'not-applied' | 'deferred' | 'unknown'
transactionId?: string; identity: { runId?: string; toolCallId?: string; sessionId?: string; policyRevision?: string; policyFingerprint?: string }
git?: SandboxGitState
workspace?: SandboxWorkspaceView
}
export interface SandboxBrokerOperationView {
version: 1; requestId: string; operation: string; state: 'pending' | 'running' | 'succeeded' | 'denied' | 'failed' | 'cancelled' | 'unknown'
owner: { sandboxRunId: string; runId?: string; toolCallId?: string; sessionId?: string; policyRevision?: string; policyFingerprint?: string }
createdAt: number; completedAt?: number; result?: Record<string, unknown>; error?: { code: string; message: string }
}
export interface SandboxChangedFile { path: string; operation: 'create' | 'update' | 'delete' | 'mkdir' | 'rmdir'; beforeHash: string | null; afterHash: string | null }
export interface SandboxCommandData {
gitConflictState?: true
stdout: string; stderr: string; exitCode: number | null; sandbox: 'windows-appcontainer'; isolated: true
runId: string; status: 'completed' | 'timeout' | 'cancelled' | 'parent-exited' | 'error'
changes: SandboxChangedFile[]; writeback: 'unchanged' | 'committed' | 'rejected' | 'not-applied' | 'deferred'; transactionId?: string
excluded: number; outputTruncated: boolean; profileRemoved: boolean; driveRemoved: boolean; auditSaved: boolean
policyRevision?: string; authorizationId?: string
policyFingerprint?: string
toolchains?: SandboxToolchainIdentity[]
dependencies?: SandboxDependencyIdentity[]
outcomeSaved?: boolean
brokerTransport?: 'bound-stdio-v1'
workspaceReadBridge?: true
git?: SandboxGitState
workspace?: SandboxWorkspaceView
}
-41
View File
@@ -1,41 +0,0 @@
import type { TokenUsage, ToolCallRecord } from './types'
export type SubagentStatus = 'queued' | 'running' | 'completed' | 'error' | 'cancelled'
export interface SubagentEvent {
id: string
kind: 'text' | 'reasoning' | 'tool'
text: string
name?: string
status?: ToolCallRecord['status']
}
export interface SubagentSnapshot {
id: string
name: string
task: string
status: SubagentStatus
access: 'read-only' | 'write'
files: string[]
modelName: string
createdAt: number
startedAt?: number
finishedAt?: number
events: SubagentEvent[]
tools: ToolCallRecord[]
result?: string
error?: string
usage?: TokenUsage
truncated?: boolean
}
/** Sum of usage actually reported by upstream requests; separate from the latest context sample. */
export interface RunTokenUsage {
main: TokenUsage
subagents: TokenUsage
total: TokenUsage
reportedOnly: true
}
export function zeroUsage(): TokenUsage { return { inputTokens: 0, outputTokens: 0, totalTokens: 0 } }
export function addUsage(left: TokenUsage, right: TokenUsage): TokenUsage {
return { inputTokens: left.inputTokens + right.inputTokens, outputTokens: left.outputTokens + right.outputTokens,
totalTokens: left.totalTokens + right.totalTokens,
...(left.cachedTokens !== undefined || right.cachedTokens !== undefined ? { cachedTokens: (left.cachedTokens ?? 0) + (right.cachedTokens ?? 0) } : {}) }
}
+13 -17
View File
@@ -2,7 +2,7 @@
* 主进程 / 渲染进程共享的类型与 IPC 契约。
*
* 已存 API Key 不返回渲染进程;用户可在模型表单编辑地址和新密钥。
* 模型网络请求经由主进程网关或 OMP 子进程完成。
* 对话由主进程 Claude Code 内核完成;模型配置与连接测试仍在主进程。
*/
/** 会话内的一条消息(对外视图) */
@@ -23,9 +23,6 @@ export interface ChatMessage {
/** Ordered inline presentation, validated against content and its actual payload collections. */
userContent?: UserContentBlock[]
skills?: Array<{ id: string; name: string; uri: string }>
/** Native-history reconstruction; never includes connector credentials. */
capabilityContext?: string
expertId?: string
assistantDisplay?: import('./capabilities').ExpertBinding
/** The model selected when this assistant turn started; kept with history. */
modelId?: string
@@ -34,13 +31,11 @@ export interface ChatMessage {
contextUsage?: ContextUsageSnapshot
/** Independent local counts of one prepared provider input; never a split of billed usage. */
contextBreakdown?: ContextUsageBreakdown
/** Reported request totals, including separately measured child agents. */
runUsage?: import('./subagents').RunTokenUsage
interactions?: ChatInteraction[]
requestId?: string
/** Last top-level Claude chain entry for this assistant turn, used only as a fork point. */
claudeEntryId?: string
planId?: string
nativeEntryId?: string
nativeSessionPath?: string
feedback?: 'like' | 'dislike'
interrupted?: boolean
createdAt: number
@@ -82,7 +77,6 @@ export type MessagePart =
| { type: 'task'; task: TaskContent }
| { type: 'artifact'; artifact: ArtifactContent }
| { type: 'image'; id: string; url: string; alt?: string }
| { type: 'subagent'; agent: import('./subagents').SubagentSnapshot }
/** 面向模型暴露的工具描述(OpenAI function tool 格式) */
export interface ToolSpec {
@@ -227,9 +221,8 @@ export type ChatEvent = { sessionId?: string; turnDurationMs?: number; sequence?
| { runId: string; type: 'interaction'; interaction: ChatInteraction }
| { runId: string; type: 'tool_call'; call: ToolCallRecord }
| { runId: string; type: 'tool_result'; call: ToolCallRecord }
| { runId: string; type: 'usage'; usage: TokenUsage; contextWindow?: number; sampledAt?: number; modelId?: string; source?: ContextUsageSnapshot['source']; nativeEntryId?: string }
| { runId: string; type: 'usage'; usage: TokenUsage; contextWindow?: number; sampledAt?: number; modelId?: string; source?: ContextUsageSnapshot['source'] }
| { runId: string; type: 'context_breakdown'; breakdown: ContextUsageBreakdown }
| { runId: string; type: 'run_usage'; usage: import('./subagents').RunTokenUsage }
| { runId: string; type: 'done'; content: string; tools: ToolCallRecord[] }
| { runId: string; type: 'cancelled' }
| { runId: string; type: 'error'; message: string }
@@ -250,11 +243,10 @@ export interface ContextUsageSnapshot {
usage: TokenUsage
/** Missing when the selected profile never declared a real context window. */
contextWindow?: number
source?: 'upstream' | 'native-history' | 'native-state'
source?: 'upstream'
sampledAt?: number
requestId?: string
modelId?: string
nativeEntryId?: string
}
export type ContextUsageCategory = 'system' | 'tools' | 'conversation' | 'mcp' | 'skills' | 'other'
@@ -311,6 +303,10 @@ export interface SessionMeta {
expertId?: string | null
agentMode?: import('./conversation').AgentMode
thinkingLevel?: import('./workspaces').ThinkingLevel
/** Claude Code session used for this YCode conversation. */
claudeSessionId?: string
/** Pending Claude fork point for edit/regenerate. */
claudeForkAt?: string
/** 置顶时间(毫秒)。有值即视为已置顶,用于侧栏任务排序,不改变 createdAt/updatedAt 语义。 */
pinnedAt?: number
/** 归档时间(毫秒)。归档任务从默认任务列表移出,但不删除消息与工作目录。 */
@@ -336,11 +332,11 @@ export interface AppInfo {
chrome: string
node: string
configDir: string
/** OMP 内核状态(未探测到时 available 为 false) */
omp: {
/** Claude Code 内核状态(未探测到时 available 为 false) */
claude: {
available: boolean
exePath: string | null
profile: string
model: string | null
sessionDir: string
version?: string
}
}
+2 -2
View File
@@ -9,8 +9,8 @@ export interface WorkspaceRegistryView {
selectedWorkspaceId: string | null
}
/**
* Reasoning effort for one session. `off` disables thinking entirely; the rest mirror OMP's
* `--thinking` levels. `auto` leaves intensity to the model; `minimal` is also exposed for models declaring it in models.dev.
* Reasoning effort for one session. `off` disables thinking entirely; `auto` leaves intensity
* to the model, and `minimal` is exposed for models declaring it in models.dev.
*/
export type ThinkingLevel = 'off' | 'auto' | 'minimal' | 'low' | 'medium' | 'high' | 'xhigh' | 'max'