From 27236bd75ff76fe49bad4372a223d0eb97a1be8a Mon Sep 17 00:00:00 2001 From: _Kerman Date: Wed, 15 Jul 2026 16:03:19 +0800 Subject: [PATCH] refactor(agent-core-v2): drop the @moonshot-ai/protocol dependency (#1745) --- packages/agent-core-v2/package.json | 1 - .../scripts/check-domain-layers.mjs | 5 + .../agent-core-v2/src/_base/errors/codes.ts | 33 ++- .../agent-core-v2/src/_base/errors/errors.ts | 2 +- .../src/_base/errors/serialize.ts | 2 +- .../src/_base/utils/isoDateTime.ts | 26 ++ .../agent-core-v2/src/activity/activity.ts | 3 +- .../agent/contextMemory/messageProjection.ts | 2 +- .../src/agent/contextMemory/types.ts | 23 +- .../src/agent/contextMemory/wireMessage.ts | 100 ++++++++ .../externalHooks/externalHooksService.ts | 10 +- .../src/agent/fullCompaction/compactionOps.ts | 28 ++- .../src/agent/fullCompaction/types.ts | 25 +- .../src/agent/goal/goalService.ts | 2 +- .../src/agent/goal/tools/create-goal.ts | 2 +- .../src/agent/loop/loopService.ts | 27 +- .../src/agent/loop/turnEvents.ts | 101 ++++++++ .../agent-core-v2/src/agent/mcp/mcpService.ts | 31 ++- .../agent-core-v2/src/agent/mcp/tools/auth.ts | 15 +- .../permissionGate/permissionGateService.ts | 2 +- .../src/agent/permissionPolicy/types.ts | 2 +- .../agent/permissionRules/permissionRules.ts | 2 +- .../src/agent/plan/tools/exit-plan-mode.ts | 2 +- .../src/agent/profile/profile.ts | 2 +- .../src/agent/profile/profileService.ts | 7 +- .../src/agent/replayBuilder/types.ts | 2 +- .../agent-core-v2/src/agent/rpc/core-api.ts | 3 +- .../agent-core-v2/src/agent/rpc/rpcService.ts | 10 +- .../agent/shellCommand/shellCommandService.ts | 24 +- .../src/agent/stepRetry/stepRetryService.ts | 16 +- .../agent/toolExecutor/toolExecutorEvents.ts | 41 ++++ .../agent/toolExecutor/toolExecutorService.ts | 19 +- .../agent-core-v2/src/agent/usage/usage.ts | 2 +- .../agent-core-v2/src/agent/usage/usageOps.ts | 2 - packages/agent-core-v2/src/app/auth/auth.ts | 15 +- .../agent-core-v2/src/app/auth/authService.ts | 2 +- .../agent-core-v2/src/app/auth/oauthWire.ts | 109 +++++++++ .../src/app/authLegacy/authLegacy.ts | 24 +- .../src/app/authLegacy/authLegacyService.ts | 2 +- packages/agent-core-v2/src/app/cron/format.ts | 2 +- .../agent-core-v2/src/app/file/fileService.ts | 13 +- .../src/app/file/fileServiceImpl.ts | 2 +- packages/agent-core-v2/src/app/git/git.ts | 56 ++++- .../agent-core-v2/src/app/git/gitParsers.ts | 2 +- .../agent-core-v2/src/app/git/gitService.ts | 2 +- .../hostFolderBrowser/hostFolderBrowser.ts | 37 ++- .../hostFolderBrowserService.ts | 2 +- .../src/app/messageLegacy/messageLegacy.ts | 19 +- .../app/messageLegacy/messageLegacyService.ts | 4 +- .../src/app/modelCatalog/modelCatalog.ts | 69 +++++- .../app/modelCatalog/modelCatalogService.ts | 2 +- .../src/app/sessionLegacy/sessionLegacy.ts | 8 +- .../app/sessionLegacy/sessionLegacyService.ts | 8 +- .../src/app/sessionLegacy/sessionWire.ts | 102 ++++++++ .../sessionLifecycleService.ts | 2 +- .../src/app/workspaceRegistry/errors.ts | 9 + packages/agent-core-v2/src/errors.ts | 11 + .../src/os/interface/terminal.ts | 68 +++++- .../src/session/approval/approval.ts | 2 +- .../agent-core-v2/src/session/cron/cronOps.ts | 2 +- .../session/cron/sessionCronServiceImpl.ts | 2 +- .../agent-core-v2/src/session/sessionFs/fs.ts | 231 ++++++++++++++++-- .../src/session/sessionFs/fsSearch.ts | 2 +- .../src/session/sessionFs/fsService.ts | 30 ++- .../src/session/sessionFs/fsWatch.ts | 20 +- .../src/session/sessionFs/fsWatchService.ts | 2 +- .../src/session/subagent/mirrorAgentRun.ts | 38 ++- .../src/session/swarm/sessionSwarmService.ts | 7 +- .../agent-core-v2/src/tool/toolContract.ts | 2 +- .../src/tool/toolInputDisplay.ts | 87 +++++++ .../test/agent/goal/goal.test.ts | 2 +- .../test/agent/mcp/tools/auth.test.ts | 2 +- .../permissionGate/permissionGate.test.ts | 2 +- .../permissionPolicyService.test.ts | 2 +- .../exit-plan-mode-review-ask.test.ts | 3 +- .../policies/goal-start-review-ask.test.ts | 2 +- .../agent/toolExecutor/toolExecutor.test.ts | 17 +- .../test/session/approval/approval.test.ts | 2 +- .../test/session/cron/cron-tools.test.ts | 2 +- .../session/sessionFs/fsWatchService.test.ts | 2 +- .../ws/v1/sessionEventBroadcaster.ts | 6 +- pnpm-lock.yaml | 3 - 82 files changed, 1389 insertions(+), 225 deletions(-) create mode 100644 packages/agent-core-v2/src/_base/utils/isoDateTime.ts create mode 100644 packages/agent-core-v2/src/agent/contextMemory/wireMessage.ts create mode 100644 packages/agent-core-v2/src/agent/loop/turnEvents.ts create mode 100644 packages/agent-core-v2/src/agent/toolExecutor/toolExecutorEvents.ts create mode 100644 packages/agent-core-v2/src/app/auth/oauthWire.ts create mode 100644 packages/agent-core-v2/src/app/sessionLegacy/sessionWire.ts create mode 100644 packages/agent-core-v2/src/app/workspaceRegistry/errors.ts create mode 100644 packages/agent-core-v2/src/tool/toolInputDisplay.ts diff --git a/packages/agent-core-v2/package.json b/packages/agent-core-v2/package.json index 695377125..6c7000100 100644 --- a/packages/agent-core-v2/package.json +++ b/packages/agent-core-v2/package.json @@ -62,7 +62,6 @@ "@modelcontextprotocol/sdk": "^1.29.0", "@moonshot-ai/kimi-code-oauth": "workspace:^", "@moonshot-ai/minidb": "workspace:^", - "@moonshot-ai/protocol": "workspace:^", "@mozilla/readability": "^0.6.0", "ajv": "^8.18.0", "ajv-formats": "^3.0.1", diff --git a/packages/agent-core-v2/scripts/check-domain-layers.mjs b/packages/agent-core-v2/scripts/check-domain-layers.mjs index 8e558eb70..5f4a8c660 100644 --- a/packages/agent-core-v2/scripts/check-domain-layers.mjs +++ b/packages/agent-core-v2/scripts/check-domain-layers.mjs @@ -304,6 +304,11 @@ const ALLOWED_EXCEPTIONS = new Set([ 'auth>tool', 'auth>toolRegistry', 'permissionGate>approval', + // `permissionRules` (L3) persists the approval broker's `ApprovalResponse` + // (Session, L7) verbatim in its wire-logged `PermissionApprovalResultRecord` + // — a real cross-scope dependency, surfaced here rather than hidden behind a + // re-declared copy of the shape. + 'permissionRules>approval', 'userTool>interaction', 'permissionPolicy>plan', 'permissionPolicy>swarm', diff --git a/packages/agent-core-v2/src/_base/errors/codes.ts b/packages/agent-core-v2/src/_base/errors/codes.ts index 64c2d0f1f..7a1a40cdd 100644 --- a/packages/agent-core-v2/src/_base/errors/codes.ts +++ b/packages/agent-core-v2/src/_base/errors/codes.ts @@ -6,13 +6,12 @@ * codes, the registry (`registerErrorDomain` / `errorInfo` / `isErrorCode`) the * serializer reads, and the domain-independent core codes (`internal`, * `not_implemented`). Domain-owned codes live next to their owning domain and - * are aggregated into the public `ErrorCodes` const by `#/errors`. + * are aggregated into the public `ErrorCodes` const by `#/errors`, which also + * derives the `ErrorCode` union type from that aggregate — so each domain's + * `errors.ts` is the single source of truth and there is no central + * hand-maintained list to keep in sync. */ -import type { KimiErrorCode } from '@moonshot-ai/protocol'; - -export type ErrorCode = KimiErrorCode; - export interface ErrorInfo { readonly title: string; readonly retryable: boolean; @@ -21,18 +20,25 @@ export interface ErrorInfo { } export interface ErrorDomain { - readonly codes: { readonly [name: string]: ErrorCode }; - readonly retryable?: ReadonlyArray; + readonly codes: { readonly [name: string]: string }; + readonly retryable?: ReadonlyArray; readonly info?: { readonly [code: string]: ErrorInfo }; } -const registeredCodes = new Set(); -const retryableCodes = new Set(); +// Maps each registered code to the `codes` object that contributed it: a +// domain re-registering itself stays idempotent, while two different domains +// claiming the same code fail loudly at registration time. +const registeredCodes = new Map(); +const retryableCodes = new Set(); const infoOverrides: { [code: string]: ErrorInfo } = {}; export function registerErrorDomain(domain: ErrorDomain): void { for (const code of Object.values(domain.codes)) { - registeredCodes.add(code); + const owner = registeredCodes.get(code); + if (owner !== undefined && owner !== domain.codes) { + throw new Error(`error code '${code}' is registered by two different domains`); + } + registeredCodes.set(code, domain.codes); } for (const code of domain.retryable ?? []) { retryableCodes.add(code); @@ -42,11 +48,11 @@ export function registerErrorDomain(domain: ErrorDomain): void { } } -export function isErrorCode(code: unknown): code is ErrorCode { - return typeof code === 'string' && registeredCodes.has(code as ErrorCode); +export function isErrorCode(code: unknown): code is string { + return typeof code === 'string' && registeredCodes.has(code); } -export function errorInfo(code: ErrorCode): ErrorInfo { +export function errorInfo(code: string): ErrorInfo { const override = infoOverrides[code]; if (override !== undefined) return override; return { @@ -60,6 +66,7 @@ export const CoreErrors = { codes: { INTERNAL: 'internal', NOT_IMPLEMENTED: 'not_implemented', + VALIDATION_FAILED: 'validation.failed', }, info: { internal: { diff --git a/packages/agent-core-v2/src/_base/errors/errors.ts b/packages/agent-core-v2/src/_base/errors/errors.ts index 373db6344..d5cd7cebe 100644 --- a/packages/agent-core-v2/src/_base/errors/errors.ts +++ b/packages/agent-core-v2/src/_base/errors/errors.ts @@ -4,7 +4,7 @@ */ import { CoreErrors } from './codes'; -import type { ErrorCode } from './codes'; +import type { ErrorCode } from '#/errors'; export class ExpectedError extends Error { readonly isExpected = true; diff --git a/packages/agent-core-v2/src/_base/errors/serialize.ts b/packages/agent-core-v2/src/_base/errors/serialize.ts index 3d1b2a634..5b6ad0b40 100644 --- a/packages/agent-core-v2/src/_base/errors/serialize.ts +++ b/packages/agent-core-v2/src/_base/errors/serialize.ts @@ -9,7 +9,7 @@ */ import { CoreErrors, errorInfo, isErrorCode } from './codes'; -import type { ErrorCode } from './codes'; +import type { ErrorCode } from '#/errors'; import { Error2 } from './errors'; export interface ErrorPayload { diff --git a/packages/agent-core-v2/src/_base/utils/isoDateTime.ts b/packages/agent-core-v2/src/_base/utils/isoDateTime.ts new file mode 100644 index 000000000..f5346c4d2 --- /dev/null +++ b/packages/agent-core-v2/src/_base/utils/isoDateTime.ts @@ -0,0 +1,26 @@ +import { z } from 'zod'; + +const ISO_8601_REGEX = + /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d{1,9})?(?:Z|[+-]\d{2}(?::?\d{2})?)$/; + +/** + * Wire-schema primitive for ISO 8601 datetime strings: validates the shape and + * normalizes to `Date#toISOString()` output. Shared by the edge DTO schemas + * (`sessionFs`, `file`, `terminal`, `auth`, …) that expose timestamps. + */ +export const isoDateTimeSchema = z + .string() + .refine((value) => ISO_8601_REGEX.test(value), { + message: 'must be an ISO 8601 datetime string', + }) + .transform((value, ctx) => { + const ms = Date.parse(value); + if (Number.isNaN(ms)) { + ctx.addIssue({ + code: 'custom', + message: 'invalid ISO 8601 datetime', + }); + return z.NEVER; + } + return new Date(ms).toISOString(); + }); diff --git a/packages/agent-core-v2/src/activity/activity.ts b/packages/agent-core-v2/src/activity/activity.ts index b8e9d23c9..c5cfbec11 100644 --- a/packages/agent-core-v2/src/activity/activity.ts +++ b/packages/agent-core-v2/src/activity/activity.ts @@ -18,7 +18,8 @@ import { createDecorator, type ServiceIdentifier } from '#/_base/di/instantiation'; import type { IDisposable } from '#/_base/di/lifecycle'; import type { PromptOrigin } from '#/agent/contextMemory/types'; -import type { TurnEndReason } from '@moonshot-ai/protocol'; + +export type TurnEndReason = 'completed' | 'cancelled' | 'failed' | 'blocked'; export type AgentLifecycleState = 'initializing' | 'ready' | 'disposing' | 'disposed'; diff --git a/packages/agent-core-v2/src/agent/contextMemory/messageProjection.ts b/packages/agent-core-v2/src/agent/contextMemory/messageProjection.ts index 48ac6dddd..5431833b5 100644 --- a/packages/agent-core-v2/src/agent/contextMemory/messageProjection.ts +++ b/packages/agent-core-v2/src/agent/contextMemory/messageProjection.ts @@ -15,7 +15,7 @@ * so REST consumers can still render the media after reload/resume. */ -import type { Message, MessageContent, MessageRole, ToolUseContent } from '@moonshot-ai/protocol'; +import type { Message, MessageContent, MessageRole, ToolUseContent } from './wireMessage'; import type { ContextMessage } from './types'; diff --git a/packages/agent-core-v2/src/agent/contextMemory/types.ts b/packages/agent-core-v2/src/agent/contextMemory/types.ts index 10a1792b2..ebac0feb3 100644 --- a/packages/agent-core-v2/src/agent/contextMemory/types.ts +++ b/packages/agent-core-v2/src/agent/contextMemory/types.ts @@ -1,7 +1,6 @@ import type { ContentPart, Message } from '#/app/llmProtocol/message'; import type { AgentTaskStatus } from '#/agent/task/task'; -import type { CronJobOrigin, CronMissedOrigin, ShellCommandOrigin } from '@moonshot-ai/protocol'; export type SkillSource = 'project' | 'user' | 'extra' | 'builtin'; @@ -36,6 +35,14 @@ export interface InjectionOrigin { readonly variant: string; } +export interface ShellCommandOrigin { + readonly kind: 'shell_command'; + readonly phase: 'input' | 'output'; + /** Only present on `phase: 'output'` — whether the command failed, so replay + * can colour stderr red only for actual failures (not warnings). */ + readonly isError?: boolean; +} + export interface CompactionSummaryOrigin { readonly kind: 'compaction_summary'; } @@ -52,6 +59,20 @@ export interface TaskOrigin { readonly notificationId: string; } +export interface CronJobOrigin { + readonly kind: 'cron_job'; + readonly jobId: string; + readonly cron: string; + readonly recurring: boolean; + readonly coalescedCount: number; + readonly stale: boolean; +} + +export interface CronMissedOrigin { + readonly kind: 'cron_missed'; + readonly count: number; +} + export interface HookResultOrigin { readonly kind: 'hook_result'; readonly event: string; diff --git a/packages/agent-core-v2/src/agent/contextMemory/wireMessage.ts b/packages/agent-core-v2/src/agent/contextMemory/wireMessage.ts new file mode 100644 index 000000000..6db5d6572 --- /dev/null +++ b/packages/agent-core-v2/src/agent/contextMemory/wireMessage.ts @@ -0,0 +1,100 @@ +/** + * The wire `Message` shape — the legacy REST/streaming message format served + * on the `messages`, `snapshot`, and `sessions` (`:undo`) edge surfaces. + * Defined next to `messageProjection.ts`, which projects `ContextMessage` + * into this shape; consumed by the `messageLegacy` edge adapter and the + * transports. + */ + +import { z } from 'zod'; + +import { isoDateTimeSchema } from '#/_base/utils/isoDateTime'; + +export const messageRoleSchema = z.enum(['user', 'assistant', 'tool', 'system']); +export type MessageRole = z.infer; + +export const textContentSchema = z.object({ + type: z.literal('text'), + text: z.string(), +}); +export type TextContent = z.infer; + +export const toolUseContentSchema = z.object({ + type: z.literal('tool_use'), + tool_call_id: z.string().min(1), + tool_name: z.string().min(1), + input: z.unknown(), +}); +export type ToolUseContent = z.infer; + +export const toolResultContentSchema = z.object({ + type: z.literal('tool_result'), + tool_call_id: z.string().min(1), + output: z.unknown(), + is_error: z.boolean().optional(), +}); +export type ToolResultContent = z.infer; + +export const imageSourceSchema = z.discriminatedUnion('kind', [ + z.object({ kind: z.literal('url'), url: z.string().min(1) }), + z.object({ + kind: z.literal('base64'), + media_type: z.string().min(1), + data: z.string().min(1), + }), + z.object({ kind: z.literal('file'), file_id: z.string().min(1) }), +]); +export type ImageSource = z.infer; + +export const imageContentSchema = z.object({ + type: z.literal('image'), + source: imageSourceSchema, +}); +export type ImageContent = z.infer; + +// Video uses the same source shape as image (url / base64 / uploaded file id). +export const videoContentSchema = z.object({ + type: z.literal('video'), + source: imageSourceSchema, +}); +export type VideoContent = z.infer; + +export const fileContentSchema = z.object({ + type: z.literal('file'), + file_id: z.string().min(1), + name: z.string(), + media_type: z.string().min(1), + size: z.number().int().nonnegative(), +}); +export type FileContent = z.infer; + +export const thinkingContentSchema = z.object({ + type: z.literal('thinking'), + thinking: z.string(), + signature: z.string().optional(), +}); +export type ThinkingContent = z.infer; + +export const messageContentSchema = z.discriminatedUnion('type', [ + textContentSchema, + toolUseContentSchema, + toolResultContentSchema, + imageContentSchema, + videoContentSchema, + fileContentSchema, + thinkingContentSchema, +]); +export type MessageContent = z.infer; + +export const messageSchema = z.object({ + id: z.string().min(1), + session_id: z.string().min(1), + role: messageRoleSchema, + content: z.array(messageContentSchema), + created_at: isoDateTimeSchema, + prompt_id: z.string().min(1).optional(), + parent_message_id: z.string().min(1).optional(), + metadata: z.record(z.string(), z.unknown()).optional(), +}); + +export type Message = z.infer; diff --git a/packages/agent-core-v2/src/agent/externalHooks/externalHooksService.ts b/packages/agent-core-v2/src/agent/externalHooks/externalHooksService.ts index 02f843161..5b4ac69da 100644 --- a/packages/agent-core-v2/src/agent/externalHooks/externalHooksService.ts +++ b/packages/agent-core-v2/src/agent/externalHooks/externalHooksService.ts @@ -38,7 +38,7 @@ import { IAgentPromptService, type PromptSubmitContext, } from '#/agent/prompt/prompt'; -import type { HookResultEvent, TurnEndedEvent } from '@moonshot-ai/protocol'; +import type { TurnEndedEvent } from '#/agent/loop/turnEvents'; import { IEventBus } from '#/app/event/eventBus'; import type { ExecutableToolResult } from '#/tool/toolContract'; import type { ToolDidExecuteContext, ToolBeforeExecuteContext } from '#/agent/toolExecutor/toolHooks'; @@ -53,6 +53,14 @@ import { renderUserPromptHookResult, } from './user-prompt'; +export interface HookResultEvent { + readonly type: 'hook.result'; + readonly turnId?: number; + readonly hookEvent: string; + readonly content: string; + readonly blocked?: boolean; +} + declare module '#/app/event/eventBus' { interface DomainEventMap { 'hook.result': HookResultEvent; diff --git a/packages/agent-core-v2/src/agent/fullCompaction/compactionOps.ts b/packages/agent-core-v2/src/agent/fullCompaction/compactionOps.ts index 9162d71d8..858ec85c3 100644 --- a/packages/agent-core-v2/src/agent/fullCompaction/compactionOps.ts +++ b/packages/agent-core-v2/src/agent/fullCompaction/compactionOps.ts @@ -38,14 +38,28 @@ import { z } from 'zod'; import { defineModel } from '#/wire/model'; -import type { - CompactionBlockedEvent, - CompactionCancelledEvent, - CompactionCompletedEvent, - CompactionStartedEvent, -} from '@moonshot-ai/protocol'; -import type { CompactionBeginData } from './types'; +import type { CompactionBeginData, CompactionResult } from './types'; + +export interface CompactionStartedEvent { + readonly type: 'compaction.started'; + readonly trigger: 'manual' | 'auto'; + readonly instruction?: string; +} + +export interface CompactionBlockedEvent { + readonly type: 'compaction.blocked'; + readonly turnId?: number; +} + +export interface CompactionCancelledEvent { + readonly type: 'compaction.cancelled'; +} + +export interface CompactionCompletedEvent { + readonly type: 'compaction.completed'; + readonly result: CompactionResult; +} export type CompactionPhase = 'idle' | 'running' | 'cancelled' | 'completed'; diff --git a/packages/agent-core-v2/src/agent/fullCompaction/types.ts b/packages/agent-core-v2/src/agent/fullCompaction/types.ts index de13049c9..2d7f38f70 100644 --- a/packages/agent-core-v2/src/agent/fullCompaction/types.ts +++ b/packages/agent-core-v2/src/agent/fullCompaction/types.ts @@ -1,13 +1,32 @@ -import type { CompactionResult as ProtocolCompactionResult } from '@moonshot-ai/protocol'; - -export interface CompactionResult extends ProtocolCompactionResult { +export interface CompactionResult { summary: string; contextSummary?: string; compactedCount: number; tokensBefore: number; tokensAfter: number; + /** + * Number of real user messages kept verbatim ahead of the summary in the + * post-compaction live context. Recorded so the wire-transcript reducer can + * reproduce the live folded length without re-deriving it from the full + * transcript (which still holds the untruncated originals of messages the + * live context may have truncated, so the two would otherwise diverge). + * Optional for backward compatibility with older wire records. + */ keptUserMessageCount?: number; + /** + * Of `keptUserMessageCount`, how many messages form the head segment (the + * oldest user input kept when the pool overflowed the budget). Present iff + * the selection split into head + tail, in which case the live context also + * holds one elision-marker message between the segments. Optional for + * backward compatibility with older wire records. + */ keptHeadUserMessageCount?: number; + /** + * Oldest messages trimmed from the summarizer input when the compaction + * request overflowed the model window; not covered by the produced summary. + * Mirrors agent-core's `CompactionResult.droppedCount`; optional for backward + * compatibility. + */ droppedCount?: number; } diff --git a/packages/agent-core-v2/src/agent/goal/goalService.ts b/packages/agent-core-v2/src/agent/goal/goalService.ts index 33163d959..2a686a817 100644 --- a/packages/agent-core-v2/src/agent/goal/goalService.ts +++ b/packages/agent-core-v2/src/agent/goal/goalService.ts @@ -29,7 +29,7 @@ import { randomUUID } from 'node:crypto'; -import type { TurnEndedEvent, TurnStartedEvent } from '@moonshot-ai/protocol'; +import type { TurnEndedEvent, TurnStartedEvent } from '#/agent/loop/turnEvents'; import { Disposable, MutableDisposable, type IDisposable } from '#/_base/di/lifecycle'; import { InstantiationType } from '#/_base/di/extensions'; import { LifecycleScope, registerScopedService } from '#/_base/di/scope'; diff --git a/packages/agent-core-v2/src/agent/goal/tools/create-goal.ts b/packages/agent-core-v2/src/agent/goal/tools/create-goal.ts index d6379981d..5e5dd2fbc 100644 --- a/packages/agent-core-v2/src/agent/goal/tools/create-goal.ts +++ b/packages/agent-core-v2/src/agent/goal/tools/create-goal.ts @@ -7,7 +7,7 @@ import { z } from 'zod'; -import type { ToolInputDisplay } from '@moonshot-ai/protocol'; +import type { ToolInputDisplay } from '#/tool/toolInputDisplay'; import { toInputJsonSchema } from '#/tool/input-schema'; import { IAgentPermissionModeService } from '#/agent/permissionMode/permissionMode'; diff --git a/packages/agent-core-v2/src/agent/loop/loopService.ts b/packages/agent-core-v2/src/agent/loop/loopService.ts index 10792dec6..f15f148d4 100644 --- a/packages/agent-core-v2/src/agent/loop/loopService.ts +++ b/packages/agent-core-v2/src/agent/loop/loopService.ts @@ -36,16 +36,6 @@ import { Disposable, toDisposable, type IDisposable } from '#/_base/di/lifecycle import { LifecycleScope, registerScopedService } from '#/_base/di/scope'; import { abortError, isAbortError, isUserCancellation, userCancellationReason } from '#/_base/utils/abort'; import { toErrorMessage } from '#/_base/errors/errorMessage'; -import type { - AssistantDeltaEvent, - ThinkingDeltaEvent, - ToolCallDeltaEvent, - TurnEndedEvent, - TurnStartedEvent, - TurnStepCompletedEvent, - TurnStepInterruptedEvent, - TurnStepStartedEvent, -} from '@moonshot-ai/protocol'; import { IAgentLLMRequesterService, type LLMRequestFinish } from '#/agent/llmRequester/llmRequester'; import { IAgentToolExecutorService } from '#/agent/toolExecutor/toolExecutor'; import { IConfigService } from '#/app/config/config'; @@ -92,19 +82,10 @@ import { } from './stepRequest'; import { StepRequestQueue, type StepRequestBatch } from './stepRequestQueue'; import { cancelTurn, promptTurn, TurnModel } from './turnOps'; - -declare module '#/app/event/eventBus' { - interface DomainEventMap { - 'turn.started': TurnStartedEvent; - 'turn.ended': TurnEndedEvent; - 'turn.step.started': TurnStepStartedEvent; - 'turn.step.completed': TurnStepCompletedEvent; - 'turn.step.interrupted': TurnStepInterruptedEvent; - 'assistant.delta': AssistantDeltaEvent; - 'thinking.delta': ThinkingDeltaEvent; - 'tool.call.delta': ToolCallDeltaEvent; - } -} +// Loads the `DomainEventMap` augmentation for the `turn.*` / delta events this +// service publishes (the augmentation lives with the event definitions; +// without an import it would not enter every consumer's program). +import './turnEvents'; export type LoopInterruptReason = 'aborted' | 'max_steps' | 'error'; diff --git a/packages/agent-core-v2/src/agent/loop/turnEvents.ts b/packages/agent-core-v2/src/agent/loop/turnEvents.ts new file mode 100644 index 000000000..087d7bb3f --- /dev/null +++ b/packages/agent-core-v2/src/agent/loop/turnEvents.ts @@ -0,0 +1,101 @@ +/** + * `loop` domain — the `turn.*` / delta event payloads published through + * `IEventBus` as a turn runs. These are the loop's share of the agent event + * stream; consumers (transports, replay, telemetry) subscribe by `type`. + */ + +import type { KimiErrorPayload } from '#/_base/errors/serialize'; +import type { PromptOrigin } from '#/agent/contextMemory/types'; +import type { FinishReason } from '#/app/llmProtocol/finishReason'; +import type { TokenUsage } from '#/app/llmProtocol/usage'; +import type { TurnEndReason } from '#/activity/activity'; + +export interface TurnStartedEvent { + readonly type: 'turn.started'; + readonly turnId: number; + readonly origin: PromptOrigin; +} + +export interface TurnEndedEvent { + readonly type: 'turn.ended'; + readonly turnId: number; + readonly reason: TurnEndReason; + readonly error?: KimiErrorPayload; + readonly durationMs?: number; +} + +export interface TurnStepStartedEvent { + readonly type: 'turn.step.started'; + readonly turnId: number; + readonly step: number; + readonly stepId?: string; +} + +export interface TurnStepCompletedEvent { + readonly type: 'turn.step.completed'; + readonly turnId: number; + readonly step: number; + readonly stepId?: string; + readonly usage?: TokenUsage; + readonly finishReason?: string; + readonly llmFirstTokenLatencyMs?: number; + readonly llmStreamDurationMs?: number; + /** + * Split of `llmFirstTokenLatencyMs`: in-process request-building time on the + * client vs. network + API-server time to the first token. Both omitted when + * the provider does not report the client/server boundary. + */ + readonly llmRequestBuildMs?: number; + readonly llmServerFirstTokenMs?: number; + /** + * Split of `llmStreamDurationMs` (the decode window): time awaiting parts from + * the provider vs. time processing parts in-process. Both omitted when the + * provider stream did not report decode accounting. + */ + readonly llmServerDecodeMs?: number; + readonly llmClientConsumeMs?: number; + readonly providerFinishReason?: FinishReason; + readonly rawFinishReason?: string; +} + +export interface TurnStepInterruptedEvent { + readonly type: 'turn.step.interrupted'; + readonly turnId: number; + readonly step: number; + readonly stepId?: string; + readonly reason: string; + readonly message?: string; +} + +export interface AssistantDeltaEvent { + readonly type: 'assistant.delta'; + readonly turnId: number; + readonly delta: string; +} + +export interface ThinkingDeltaEvent { + readonly type: 'thinking.delta'; + readonly turnId: number; + readonly delta: string; +} + +export interface ToolCallDeltaEvent { + readonly type: 'tool.call.delta'; + readonly turnId: number; + readonly toolCallId: string; + readonly name?: string; + readonly argumentsPart?: string; +} + +declare module '#/app/event/eventBus' { + interface DomainEventMap { + 'turn.started': TurnStartedEvent; + 'turn.ended': TurnEndedEvent; + 'turn.step.started': TurnStepStartedEvent; + 'turn.step.completed': TurnStepCompletedEvent; + 'turn.step.interrupted': TurnStepInterruptedEvent; + 'assistant.delta': AssistantDeltaEvent; + 'thinking.delta': ThinkingDeltaEvent; + 'tool.call.delta': ToolCallDeltaEvent; + } +} diff --git a/packages/agent-core-v2/src/agent/mcp/mcpService.ts b/packages/agent-core-v2/src/agent/mcp/mcpService.ts index 065300505..b4d13c00d 100644 --- a/packages/agent-core-v2/src/agent/mcp/mcpService.ts +++ b/packages/agent-core-v2/src/agent/mcp/mcpService.ts @@ -5,12 +5,8 @@ import { LifecycleScope, registerScopedService } from '#/_base/di/scope'; import type { Tool as KosongTool } from '#/app/llmProtocol/tool'; import { Disposable, type IDisposable } from "#/_base/di/lifecycle"; +import type { KimiErrorPayload } from '#/_base/errors/serialize'; import { ErrorCodes, makeErrorPayload } from "#/errors"; -import type { - ErrorEvent, - McpServerStatusEvent, - ToolListUpdatedEvent, -} from '@moonshot-ai/protocol'; import { IEventBus } from '#/app/event/eventBus'; import { ITelemetryService } from '#/app/telemetry/telemetry'; import { sessionMediaOriginalsDir } from '#/agent/media/image-originals'; @@ -31,6 +27,31 @@ import { type McpToolCollision, } from './mcpDiscoveryOps'; +export interface ErrorEvent extends KimiErrorPayload { + readonly type: 'error'; +} + +export interface McpServerStatusPayload { + readonly name: string; + readonly transport: 'stdio' | 'http' | 'sse'; + readonly status: 'pending' | 'connected' | 'failed' | 'disabled' | 'needs-auth'; + readonly toolCount: number; + readonly error?: string; +} + +export interface McpServerStatusEvent { + readonly type: 'mcp.server.status'; + readonly server: McpServerStatusPayload; +} + +export type ToolListUpdatedReason = 'mcp.connected' | 'mcp.disconnected' | 'mcp.failed'; + +export interface ToolListUpdatedEvent { + readonly type: 'tool.list.updated'; + readonly reason: ToolListUpdatedReason; + readonly serverName: string; +} + declare module '#/app/event/eventBus' { interface DomainEventMap { 'mcp.server.status': McpServerStatusEvent; diff --git a/packages/agent-core-v2/src/agent/mcp/tools/auth.ts b/packages/agent-core-v2/src/agent/mcp/tools/auth.ts index f53d18f73..69df9c4e2 100644 --- a/packages/agent-core-v2/src/agent/mcp/tools/auth.ts +++ b/packages/agent-core-v2/src/agent/mcp/tools/auth.ts @@ -32,13 +32,20 @@ import { type ExecutableToolResult, } from '#/tool/toolContract'; import { toInputJsonSchema } from '#/tool/input-schema'; -import { - MCP_OAUTH_AUTHORIZATION_URL_TOOL_UPDATE, - type McpOAuthAuthorizationUrlUpdateData, -} from '@moonshot-ai/protocol'; import { AlreadyAuthorizedError, type McpOAuthService } from '#/agent/mcp/oauth/service'; import { qualifyMcpToolName } from '#/agent/mcp/tool-naming'; +/** + * `ToolUpdate.customKind` emitted by the MCP auth tool when the OAuth + * authorization URL is ready; clients render it as an actionable login link. + */ +export const MCP_OAUTH_AUTHORIZATION_URL_TOOL_UPDATE = 'mcp.oauth.authorization_url'; + +export interface McpOAuthAuthorizationUrlUpdateData { + readonly serverName: string; + readonly authorizationUrl: string; +} + const DEFAULT_AUTH_TIMEOUT_MS = 15 * 60 * 1000; const AUTH_TOOL_TOOL_NAME = 'authenticate'; diff --git a/packages/agent-core-v2/src/agent/permissionGate/permissionGateService.ts b/packages/agent-core-v2/src/agent/permissionGate/permissionGateService.ts index 59b1030b9..aae263aa8 100644 --- a/packages/agent-core-v2/src/agent/permissionGate/permissionGateService.ts +++ b/packages/agent-core-v2/src/agent/permissionGate/permissionGateService.ts @@ -23,7 +23,7 @@ import { IEventBus } from '#/app/event/eventBus'; import { ITelemetryService } from '#/app/telemetry/telemetry'; import { ISessionApprovalService } from "#/session/approval/approval"; import { ISessionContext } from '#/session/sessionContext/sessionContext'; -import type { ToolInputDisplay } from '@moonshot-ai/protocol'; +import type { ToolInputDisplay } from '#/tool/toolInputDisplay'; import { IAgentPermissionGate, } from './permissionGate'; diff --git a/packages/agent-core-v2/src/agent/permissionPolicy/types.ts b/packages/agent-core-v2/src/agent/permissionPolicy/types.ts index a1a8d6441..db2beb626 100644 --- a/packages/agent-core-v2/src/agent/permissionPolicy/types.ts +++ b/packages/agent-core-v2/src/agent/permissionPolicy/types.ts @@ -1,5 +1,5 @@ import type { PrepareToolExecutionResult, ResolvedToolExecutionHookContext } from '#/agent/toolExecutor/toolHooks'; -import type { ToolInputDisplay } from '@moonshot-ai/protocol'; +import type { ToolInputDisplay } from '#/tool/toolInputDisplay'; import type { PermissionRule } from '#/agent/permissionRules/permissionRules'; export type PermissionMode = 'manual' | 'yolo' | 'auto'; diff --git a/packages/agent-core-v2/src/agent/permissionRules/permissionRules.ts b/packages/agent-core-v2/src/agent/permissionRules/permissionRules.ts index 92ad3240a..fe640a14c 100644 --- a/packages/agent-core-v2/src/agent/permissionRules/permissionRules.ts +++ b/packages/agent-core-v2/src/agent/permissionRules/permissionRules.ts @@ -1,5 +1,5 @@ import { createDecorator } from "#/_base/di/instantiation"; -import type { ApprovalResponse } from "@moonshot-ai/protocol"; +import type { ApprovalResponse } from "#/session/approval/approval"; export interface PermissionApprovalResultRecord { readonly turnId: number; diff --git a/packages/agent-core-v2/src/agent/plan/tools/exit-plan-mode.ts b/packages/agent-core-v2/src/agent/plan/tools/exit-plan-mode.ts index a4484132b..34810c5fe 100644 --- a/packages/agent-core-v2/src/agent/plan/tools/exit-plan-mode.ts +++ b/packages/agent-core-v2/src/agent/plan/tools/exit-plan-mode.ts @@ -15,7 +15,7 @@ * keeps the user-approved output and the `approved` outcome. */ -import type { ToolInputDisplay } from '@moonshot-ai/protocol'; +import type { ToolInputDisplay } from '#/tool/toolInputDisplay'; import { z } from 'zod'; import type { BuiltinTool, ExecutableToolResult, ToolExecution } from '#/tool/toolContract'; diff --git a/packages/agent-core-v2/src/agent/profile/profile.ts b/packages/agent-core-v2/src/agent/profile/profile.ts index 9c6916b04..e8dbf7ad4 100644 --- a/packages/agent-core-v2/src/agent/profile/profile.ts +++ b/packages/agent-core-v2/src/agent/profile/profile.ts @@ -4,7 +4,7 @@ import type { ThinkingEffort } from '#/app/llmProtocol/thinkingEffort'; import type { Model } from '#/app/model/modelInstance'; import { createDecorator } from "#/_base/di/instantiation"; -import type { ErrorCode } from '#/_base/errors/codes'; +import type { ErrorCode } from '#/errors'; import { Error2 } from '#/_base/errors/errors'; import type { ToolSource } from '#/tool/toolContract'; diff --git a/packages/agent-core-v2/src/agent/profile/profileService.ts b/packages/agent-core-v2/src/agent/profile/profileService.ts index 3a949b05f..98fd70848 100644 --- a/packages/agent-core-v2/src/agent/profile/profileService.ts +++ b/packages/agent-core-v2/src/agent/profile/profileService.ts @@ -51,7 +51,6 @@ import { ISessionWorkspaceContext } from '#/session/workspaceContext/workspaceCo import { ISessionSkillCatalog } from '#/session/sessionSkillCatalog/skillCatalog'; import type { ResolvedAgentProfile, SystemPromptContext } from '#/agent/profile/profile'; -import type { WarningEvent } from '@moonshot-ai/protocol'; import { ITelemetryService } from '#/app/telemetry/telemetry'; import { IAgentTelemetryContextService } from '#/app/telemetry/agentTelemetryContext'; import { IWireService } from '#/wire/wire'; @@ -81,6 +80,12 @@ import { type ProfileModelState, } from './profileOps'; +export interface WarningEvent { + readonly type: 'warning'; + readonly message: string; + readonly code?: string; +} + declare module '#/app/event/eventBus' { interface DomainEventMap { warning: WarningEvent; diff --git a/packages/agent-core-v2/src/agent/replayBuilder/types.ts b/packages/agent-core-v2/src/agent/replayBuilder/types.ts index d662fa2bb..04f0bc1d0 100644 --- a/packages/agent-core-v2/src/agent/replayBuilder/types.ts +++ b/packages/agent-core-v2/src/agent/replayBuilder/types.ts @@ -8,7 +8,7 @@ import type { PermissionData, PermissionMode } from '#/agent/permissionPolicy/ty import type { PlanData } from '#/agent/plan/plan'; import type { ToolInfo } from '#/tool/toolContract'; import type { SessionSummary } from '#/agent/rpc/core-api'; -import type { UsageStatus } from '@moonshot-ai/protocol'; +import type { UsageStatus } from '#/agent/usage/usage'; import type { SessionMeta } from '#/session/sessionMetadata/sessionMetadata'; type AgentType = 'main' | 'sub'; diff --git a/packages/agent-core-v2/src/agent/rpc/core-api.ts b/packages/agent-core-v2/src/agent/rpc/core-api.ts index 853843565..f25193dd3 100644 --- a/packages/agent-core-v2/src/agent/rpc/core-api.ts +++ b/packages/agent-core-v2/src/agent/rpc/core-api.ts @@ -20,7 +20,8 @@ import type { ExperimentalFeatureState } from '#/app/flag/flag'; import type { ResumeSessionResult } from '#/agent/replayBuilder/types'; import type { SessionMeta } from '#/session/sessionMetadata/sessionMetadata'; import type { ContentPart } from '#/app/llmProtocol/message'; -import type { SessionWarning, UsageStatus } from '@moonshot-ai/protocol'; +import type { SessionWarning } from '#/app/sessionLegacy/sessionWire'; +import type { UsageStatus } from '#/agent/usage/usage'; import type { ExportSessionPayload, ExportSessionResult } from '#/app/sessionExport/sessionExport'; import type { PluginCommandDef, PluginInfo, PluginSummary, ReloadSummary } from '#/app/plugin/types'; diff --git a/packages/agent-core-v2/src/agent/rpc/rpcService.ts b/packages/agent-core-v2/src/agent/rpc/rpcService.ts index 4bba878b0..a993f9771 100644 --- a/packages/agent-core-v2/src/agent/rpc/rpcService.ts +++ b/packages/agent-core-v2/src/agent/rpc/rpcService.ts @@ -7,7 +7,6 @@ import { IAgentContextMemoryService } from '#/agent/contextMemory/contextMemory' import { IAgentContextSizeService } from '#/agent/contextSize/contextSize'; import { IAgentFullCompactionService } from '#/agent/fullCompaction/fullCompaction'; import { IAgentGoalService } from '#/agent/goal/goal'; -import type { PluginCommandActivatedEvent } from '@moonshot-ai/protocol'; import { IEventBus } from '#/app/event/eventBus'; import { IEventService } from '#/app/event/event'; import { ErrorCodes, Error2 } from '#/errors'; @@ -65,6 +64,15 @@ import { promptMetadataTextFromSkill, } from './prompt-metadata'; +export interface PluginCommandActivatedEvent { + readonly type: 'plugin_command.activated'; + readonly activationId: string; + readonly pluginId: string; + readonly commandName: string; + readonly commandArgs?: string; + readonly trigger: 'user-slash'; +} + declare module '#/app/event/eventBus' { interface DomainEventMap { 'plugin_command.activated': PluginCommandActivatedEvent; diff --git a/packages/agent-core-v2/src/agent/shellCommand/shellCommandService.ts b/packages/agent-core-v2/src/agent/shellCommand/shellCommandService.ts index fd27833ff..480cee0ca 100644 --- a/packages/agent-core-v2/src/agent/shellCommand/shellCommandService.ts +++ b/packages/agent-core-v2/src/agent/shellCommand/shellCommandService.ts @@ -9,8 +9,6 @@ * scope. */ -import type { ShellOutputEvent, ShellStartedEvent } from '@moonshot-ai/protocol'; - import { InstantiationType } from '#/_base/di/extensions'; import { LifecycleScope, registerScopedService } from '#/_base/di/scope'; import { userCancellationReason } from '#/_base/utils/abort'; @@ -27,6 +25,28 @@ import { type RunShellCommandResult, } from './shellCommand'; +/** + * Live stdout/stderr chunk from a user-initiated `!` shell command. Transient + * (never persisted, never replayed) — the final output is still recorded once + * via `context.append_message` on completion. `commandId` lets the TUI route + * chunks to the matching live entry and drop stale events from a prior run. + */ +export interface ShellOutputEvent { + readonly type: 'shell.output'; + readonly commandId: string; + readonly update: ToolUpdate; +} + +/** + * Fired once when a `!` shell command's foreground process task is registered, + * carrying the task id so the client can detach (ctrl+b) it. Transient. + */ +export interface ShellStartedEvent { + readonly type: 'shell.started'; + readonly commandId: string; + readonly taskId: string; +} + declare module '#/app/event/eventBus' { interface DomainEventMap { 'shell.output': ShellOutputEvent; diff --git a/packages/agent-core-v2/src/agent/stepRetry/stepRetryService.ts b/packages/agent-core-v2/src/agent/stepRetry/stepRetryService.ts index 47046b754..33e424d06 100644 --- a/packages/agent-core-v2/src/agent/stepRetry/stepRetryService.ts +++ b/packages/agent-core-v2/src/agent/stepRetry/stepRetryService.ts @@ -13,8 +13,6 @@ * before the first turn runs (same rationale as `fullCompaction`). */ -import type { TurnStepRetryingEvent } from '@moonshot-ai/protocol'; - import { Disposable } from '#/_base/di/lifecycle'; import { InstantiationType } from '#/_base/di/extensions'; import { LifecycleScope, registerScopedService } from '#/_base/di/scope'; @@ -37,6 +35,20 @@ import { LOOP_CONTROL_SECTION, type LoopControl } from '#/agent/loop/configSecti import { IAgentStepRetryService } from './stepRetry'; +export interface TurnStepRetryingEvent { + readonly type: 'turn.step.retrying'; + readonly turnId: number; + readonly step: number; + readonly stepId?: string; + readonly failedAttempt: number; + readonly nextAttempt: number; + readonly maxAttempts: number; + readonly delayMs: number; + readonly errorName: string; + readonly errorMessage: string; + readonly statusCode?: number; +} + declare module '#/app/event/eventBus' { interface DomainEventMap { 'turn.step.retrying': TurnStepRetryingEvent; diff --git a/packages/agent-core-v2/src/agent/toolExecutor/toolExecutorEvents.ts b/packages/agent-core-v2/src/agent/toolExecutor/toolExecutorEvents.ts new file mode 100644 index 000000000..e0c858c31 --- /dev/null +++ b/packages/agent-core-v2/src/agent/toolExecutor/toolExecutorEvents.ts @@ -0,0 +1,41 @@ +/** + * `toolExecutor` domain — the `tool.call.*` / `tool.progress` / `tool.result` + * event payloads published through `IEventBus` as tool calls execute. + */ + +import type { ToolUpdate } from '#/tool/toolContract'; +import type { ToolInputDisplay } from '#/tool/toolInputDisplay'; + +export interface ToolCallStartedEvent { + readonly type: 'tool.call.started'; + readonly turnId: number; + readonly toolCallId: string; + readonly name: string; + readonly args: unknown; + readonly description?: string; + readonly display?: ToolInputDisplay; +} + +export interface ToolProgressEvent { + readonly type: 'tool.progress'; + readonly turnId: number; + readonly toolCallId: string; + readonly update: ToolUpdate; +} + +export interface ToolResultEvent { + readonly type: 'tool.result'; + readonly turnId: number; + readonly toolCallId: string; + readonly output: unknown; + readonly isError?: boolean; + readonly synthetic?: boolean; +} + +declare module '#/app/event/eventBus' { + interface DomainEventMap { + 'tool.call.started': ToolCallStartedEvent; + 'tool.result': ToolResultEvent; + 'tool.progress': ToolProgressEvent; + } +} diff --git a/packages/agent-core-v2/src/agent/toolExecutor/toolExecutorService.ts b/packages/agent-core-v2/src/agent/toolExecutor/toolExecutorService.ts index 728948e66..e701e9572 100644 --- a/packages/agent-core-v2/src/agent/toolExecutor/toolExecutorService.ts +++ b/packages/agent-core-v2/src/agent/toolExecutor/toolExecutorService.ts @@ -11,12 +11,7 @@ import { InstantiationType } from '#/_base/di/extensions'; import { toDisposable } from '#/_base/di/lifecycle'; import { LifecycleScope, registerScopedService } from '#/_base/di/scope'; import type { ContentPart } from '#/app/llmProtocol/message'; -import type { - ToolCallStartedEvent, - ToolInputDisplay, - ToolProgressEvent, - ToolResultEvent, -} from '@moonshot-ai/protocol'; +import type { ToolInputDisplay } from '#/tool/toolInputDisplay'; import { compileToolArgsValidator, @@ -54,14 +49,10 @@ import { type UnavailableToolDescriber, } from './toolExecutor'; import { ToolScheduler } from './toolScheduler'; - -declare module '#/app/event/eventBus' { - interface DomainEventMap { - 'tool.call.started': ToolCallStartedEvent; - 'tool.result': ToolResultEvent; - 'tool.progress': ToolProgressEvent; - } -} +// Loads the `DomainEventMap` augmentation for the `tool.call.*` / `tool.result` +// events this service publishes (the augmentation lives with the event +// definitions; without an import it would not enter every consumer's program). +import './toolExecutorEvents'; const ABORT_GRACE_MS = 2_000; const TOOL_OUTPUT_EMPTY = 'Tool output is empty.'; diff --git a/packages/agent-core-v2/src/agent/usage/usage.ts b/packages/agent-core-v2/src/agent/usage/usage.ts index 833e5bd4e..692c61968 100644 --- a/packages/agent-core-v2/src/agent/usage/usage.ts +++ b/packages/agent-core-v2/src/agent/usage/usage.ts @@ -11,7 +11,7 @@ import type { TokenUsage } from '#/app/llmProtocol/usage'; import { createDecorator } from '#/_base/di/instantiation'; import type { Event } from '#/_base/event'; -import type { ErrorCode } from '#/_base/errors/codes'; +import type { ErrorCode } from '#/errors'; import { Error2 } from '#/_base/errors/errors'; import { UsageErrors } from './errors'; diff --git a/packages/agent-core-v2/src/agent/usage/usageOps.ts b/packages/agent-core-v2/src/agent/usage/usageOps.ts index fca26069e..8000d4b2e 100644 --- a/packages/agent-core-v2/src/agent/usage/usageOps.ts +++ b/packages/agent-core-v2/src/agent/usage/usageOps.ts @@ -15,7 +15,6 @@ */ import { z } from 'zod'; -import type { AgentPhase } from '@moonshot-ai/protocol'; import { addUsage, type TokenUsage } from '#/app/llmProtocol/usage'; import { defineModel } from '#/wire/model'; @@ -34,7 +33,6 @@ declare module '#/app/event/eventBus' { thinkingEffort?: string; maxContextTokens?: number; contextTokens?: number; - phase?: AgentPhase; }; } } diff --git a/packages/agent-core-v2/src/app/auth/auth.ts b/packages/agent-core-v2/src/app/auth/auth.ts index ebbc4da67..79648cf83 100644 --- a/packages/agent-core-v2/src/app/auth/auth.ts +++ b/packages/agent-core-v2/src/app/auth/auth.ts @@ -17,20 +17,19 @@ import type { KimiOAuthLogoutResult, KimiOAuthTokenRef, } from '@moonshot-ai/kimi-code-oauth'; -import type { - OAuthFlowSnapshot, - OAuthFlowStart, - OAuthLoginCancelResponse, - OAuthLogoutResponse, - RefreshOAuthProviderModelsResponse, -} from '@moonshot-ai/protocol'; - import { createDecorator, type ServiceIdentifier } from '#/_base/di/instantiation'; import { Error2 } from '#/_base/errors/errors'; import type { OAuthRef } from '#/app/provider/provider'; import { AuthErrors } from './errors'; +import type { + OAuthFlowSnapshot, + OAuthFlowStart, + OAuthLoginCancelResponse, + OAuthLogoutResponse, + RefreshOAuthProviderModelsResponse, +} from './oauthWire'; export interface AuthStatus { readonly loggedIn: boolean; diff --git a/packages/agent-core-v2/src/app/auth/authService.ts b/packages/agent-core-v2/src/app/auth/authService.ts index 7dbf538a0..62089fd3c 100644 --- a/packages/agent-core-v2/src/app/auth/authService.ts +++ b/packages/agent-core-v2/src/app/auth/authService.ts @@ -39,7 +39,7 @@ import type { OAuthLoginCancelResponse, OAuthLogoutResponse, RefreshOAuthProviderModelsResponse, -} from '@moonshot-ai/protocol'; +} from './oauthWire'; import { InstantiationType } from '#/_base/di/extensions'; import { Disposable } from '#/_base/di/lifecycle'; diff --git a/packages/agent-core-v2/src/app/auth/oauthWire.ts b/packages/agent-core-v2/src/app/auth/oauthWire.ts new file mode 100644 index 000000000..8d6cb90db --- /dev/null +++ b/packages/agent-core-v2/src/app/auth/oauthWire.ts @@ -0,0 +1,109 @@ +/** + * `auth` domain — the v1 OAuth wire DTO schemas. + * + * Request/response shapes of the v1 `/oauth/*` endpoints plus the managed + * OAuth provider model-refresh response, defined as zod schemas so the + * transports validate against the same contract the `IOAuthService` returns. + */ + +import { z } from 'zod'; + +import { isoDateTimeSchema } from '#/_base/utils/isoDateTime'; + +export const oauthFlowStatusEnum = z.enum([ + 'pending', + 'authenticated', + 'denied', + 'expired', + 'cancelled', +]); +export type OAuthFlowStatus = z.infer; + +/** + * Result of `POST /v1/oauth/login`. + * + * Two shapes, discriminated by `status`: + * - `pending`: a real device-code flow was started; the `verification_*`, + * `user_code`, `expires_*`, and `interval` fields are populated so the + * client can render the device-code step and start polling. + * - `authenticated`: the toolkit already had a usable token and short- + * circuited via its `ensureFresh` fast path, so no device code was + * issued. The client can skip the device-code step and treat the login + * as already complete. + */ +export const oauthFlowStartPendingSchema = z.object({ + flow_id: z.string().min(1), + provider: z.string().min(1), + status: z.literal('pending'), + verification_uri: z.string().url(), + verification_uri_complete: z.string().url(), + user_code: z.string().min(1), + expires_in: z.number().int().positive(), + interval: z.number().int().positive(), + expires_at: isoDateTimeSchema, +}); +export type OAuthFlowStartPending = z.infer; + +export const oauthFlowStartAuthenticatedSchema = z.object({ + flow_id: z.string().min(1), + provider: z.string().min(1), + status: z.literal('authenticated'), +}); +export type OAuthFlowStartAuthenticated = z.infer; + +export const oauthFlowStartSchema = z.discriminatedUnion('status', [ + oauthFlowStartPendingSchema, + oauthFlowStartAuthenticatedSchema, +]); +export type OAuthFlowStart = z.infer; + +export const oauthFlowSnapshotSchema = z.object({ + flow_id: z.string().min(1), + provider: z.string().min(1), + status: oauthFlowStatusEnum, + verification_uri: z.string().url(), + verification_uri_complete: z.string().url(), + user_code: z.string().min(1), + expires_in: z.number().int().positive(), + expires_at: isoDateTimeSchema, + interval: z.number().int().positive(), + resolved_at: isoDateTimeSchema.optional(), + error_message: z.string().optional(), +}); +export type OAuthFlowSnapshot = z.infer; + +export const oauthLoginCancelResponseSchema = z.object({ + cancelled: z.boolean(), + status: oauthFlowStatusEnum, +}); +export type OAuthLoginCancelResponse = z.infer; + +export const oauthLogoutResponseSchema = z.object({ + logged_out: z.literal(true), + provider: z.string().min(1), +}); +export type OAuthLogoutResponse = z.infer; + +const providerRefreshChangeSchema = z.object({ + provider_id: z.string().min(1), + provider_name: z.string().min(1), + added: z.number().int().min(0), + removed: z.number().int().min(0), +}); + +const providerRefreshFailureSchema = z.object({ + provider: z.string().min(1), + reason: z.string().min(1), +}); + +// Same response shape as the modelCatalog refresh endpoint; defined +// self-contained here because the two domains sit at different layers and the +// `auth` domain owns the OAuth-provider refresh operation. +export const refreshOAuthProviderModelsResponseSchema = z.object({ + changed: z.array(providerRefreshChangeSchema), + unchanged: z.array(z.string().min(1)), + failed: z.array(providerRefreshFailureSchema), +}); +export type RefreshOAuthProviderModelsResponse = z.infer< + typeof refreshOAuthProviderModelsResponseSchema +>; diff --git a/packages/agent-core-v2/src/app/authLegacy/authLegacy.ts b/packages/agent-core-v2/src/app/authLegacy/authLegacy.ts index ac9c959f5..88d004e49 100644 --- a/packages/agent-core-v2/src/app/authLegacy/authLegacy.ts +++ b/packages/agent-core-v2/src/app/authLegacy/authLegacy.ts @@ -9,10 +9,32 @@ * stateless projector over the global provider / model / credential state. */ -import type { AuthSummary } from '@moonshot-ai/protocol'; +import { z } from 'zod'; import { createDecorator, type ServiceIdentifier } from '#/_base/di/instantiation'; +export const managedProviderStatusSchema = z.enum([ + 'authenticated', + 'expired', + 'revoked', + 'unauthenticated', +]); +export type ManagedProviderStatus = z.infer; + +export const managedProviderSummarySchema = z.object({ + name: z.string().min(1), + status: managedProviderStatusSchema, +}); +export type ManagedProviderSummary = z.infer; + +export const authSummarySchema = z.object({ + ready: z.boolean(), + providers_count: z.number().int().nonnegative(), + default_model: z.string().nullable(), + managed_provider: managedProviderSummarySchema.nullable(), +}); +export type AuthSummary = z.infer; + export interface IAuthLegacyService { readonly _serviceBrand: undefined; diff --git a/packages/agent-core-v2/src/app/authLegacy/authLegacyService.ts b/packages/agent-core-v2/src/app/authLegacy/authLegacyService.ts index d50fe7ddf..083c29e02 100644 --- a/packages/agent-core-v2/src/app/authLegacy/authLegacyService.ts +++ b/packages/agent-core-v2/src/app/authLegacy/authLegacyService.ts @@ -11,7 +11,7 @@ */ import { KIMI_CODE_PROVIDER_NAME } from '@moonshot-ai/kimi-code-oauth'; -import type { AuthSummary } from '@moonshot-ai/protocol'; +import type { AuthSummary } from './authLegacy'; import { InstantiationType } from '#/_base/di/extensions'; import { LifecycleScope, registerScopedService } from '#/_base/di/scope'; diff --git a/packages/agent-core-v2/src/app/cron/format.ts b/packages/agent-core-v2/src/app/cron/format.ts index 86d990f26..49fb6a42a 100644 --- a/packages/agent-core-v2/src/app/cron/format.ts +++ b/packages/agent-core-v2/src/app/cron/format.ts @@ -7,7 +7,7 @@ * them without pulling in the rest of the cron stack. */ -import type { CronJobOrigin } from '@moonshot-ai/protocol'; +import type { CronJobOrigin } from '#/agent/contextMemory/types'; export function formatLocalIsoWithOffset(ms: number): string { const date = new Date(ms); diff --git a/packages/agent-core-v2/src/app/file/fileService.ts b/packages/agent-core-v2/src/app/file/fileService.ts index ed0b98dee..476b615e6 100644 --- a/packages/agent-core-v2/src/app/file/fileService.ts +++ b/packages/agent-core-v2/src/app/file/fileService.ts @@ -8,11 +8,22 @@ import type { Readable } from 'node:stream'; -import type { FileMeta } from '@moonshot-ai/protocol'; +import { z } from 'zod'; import { createDecorator, type ServiceIdentifier } from '#/_base/di/instantiation'; import { registerErrorDomain, type ErrorDomain } from '#/_base/errors/codes'; import { Error2 } from '#/_base/errors/errors'; +import { isoDateTimeSchema } from '#/_base/utils/isoDateTime'; + +export const fileMetaSchema = z.object({ + id: z.string().min(1), + name: z.string().min(1), + media_type: z.string().min(1), + size: z.number().int().nonnegative(), + created_at: isoDateTimeSchema, + expires_at: isoDateTimeSchema.optional(), +}); +export type FileMeta = z.infer; export const DEFAULT_MAX_UPLOAD_BYTES = 50 * 1024 * 1024; diff --git a/packages/agent-core-v2/src/app/file/fileServiceImpl.ts b/packages/agent-core-v2/src/app/file/fileServiceImpl.ts index bf15732ec..1b3f17dcb 100644 --- a/packages/agent-core-v2/src/app/file/fileServiceImpl.ts +++ b/packages/agent-core-v2/src/app/file/fileServiceImpl.ts @@ -11,7 +11,7 @@ import { randomUUID } from 'node:crypto'; import { Readable } from 'node:stream'; -import type { FileMeta } from '@moonshot-ai/protocol'; +import type { FileMeta } from './fileService'; import { InstantiationType } from '#/_base/di/extensions'; import { LifecycleScope, registerScopedService } from '#/_base/di/scope'; diff --git a/packages/agent-core-v2/src/app/git/git.ts b/packages/agent-core-v2/src/app/git/git.ts index 86565f25e..1063cef0f 100644 --- a/packages/agent-core-v2/src/app/git/git.ts +++ b/packages/agent-core-v2/src/app/git/git.ts @@ -9,8 +9,62 @@ * already-resolved absolute `cwd` and repo-relative paths. */ +import { z } from 'zod'; + import { createDecorator, type ServiceIdentifier } from '#/_base/di/instantiation'; -import type { FsDiffResponse, FsGitStatusResponse } from '@moonshot-ai/protocol'; + +export const fsGitStatusSchema = z.enum([ + 'clean', + 'modified', + 'added', + 'deleted', + 'renamed', + 'untracked', + 'ignored', + 'conflicted', +]); +export type FsGitStatus = z.infer; + +export const fsPullRequestSchema = z.object({ + number: z.number().int().positive(), + state: z.enum(['open', 'merged', 'closed', 'draft']), + url: z.string().url(), +}); +export type FsPullRequest = z.infer; + +export const fsGitStatusRequestSchema = z.object({ + paths: z.array(z.string().min(1)).optional(), +}); +export type FsGitStatusRequest = z.infer; + +export const fsGitStatusResponseSchema = z.object({ + branch: z.string(), + ahead: z.number().int().nonnegative(), + behind: z.number().int().nonnegative(), + entries: z.record(z.string(), fsGitStatusSchema), + // Aggregate working-tree diff against HEAD (`git diff --numstat HEAD`): + // summed added/deleted lines across all changed files. Binary files (numstat + // `-`) contribute 0. Both 0 for a clean tree or a repo with no commits yet. + additions: z.number().int().nonnegative(), + deletions: z.number().int().nonnegative(), + // GitHub pull request for the current branch, looked up via `gh pr view`. + // Null when not a GitHub repo, `gh` is unavailable/unauthenticated, the + // branch has no PR, or the lookup failed/timed out. Never fails the request. + pullRequest: fsPullRequestSchema.nullable(), +}); +export type FsGitStatusResponse = z.infer; + +export const fsDiffRequestSchema = z.object({ + path: z.string().min(1), +}); +export type FsDiffRequest = z.infer; + +export const fsDiffResponseSchema = z.object({ + path: z.string(), + diff: z.string(), + truncated: z.boolean(), +}); +export type FsDiffResponse = z.infer; export interface IGitService { readonly _serviceBrand: undefined; diff --git a/packages/agent-core-v2/src/app/git/gitParsers.ts b/packages/agent-core-v2/src/app/git/gitParsers.ts index b223bd569..613ed7b8a 100644 --- a/packages/agent-core-v2/src/app/git/gitParsers.ts +++ b/packages/agent-core-v2/src/app/git/gitParsers.ts @@ -8,7 +8,7 @@ * `services/fs/fsGit.ts`). */ -import type { FsGitStatus, FsGitStatusResponse, FsPullRequest } from '@moonshot-ai/protocol'; +import type { FsGitStatus, FsGitStatusResponse, FsPullRequest } from './git'; export function parsePorcelain( stdout: string, diff --git a/packages/agent-core-v2/src/app/git/gitService.ts b/packages/agent-core-v2/src/app/git/gitService.ts index a93a9f65b..73b7e4bc3 100644 --- a/packages/agent-core-v2/src/app/git/gitService.ts +++ b/packages/agent-core-v2/src/app/git/gitService.ts @@ -10,7 +10,7 @@ * paths. */ -import type { FsDiffResponse, FsGitStatusResponse, FsPullRequest } from '@moonshot-ai/protocol'; +import type { FsDiffResponse, FsGitStatusResponse, FsPullRequest } from './git'; import { InstantiationType } from '#/_base/di/extensions'; import { LifecycleScope, registerScopedService } from '#/_base/di/scope'; diff --git a/packages/agent-core-v2/src/app/hostFolderBrowser/hostFolderBrowser.ts b/packages/agent-core-v2/src/app/hostFolderBrowser/hostFolderBrowser.ts index 35d2026e1..5d1965f73 100644 --- a/packages/agent-core-v2/src/app/hostFolderBrowser/hostFolderBrowser.ts +++ b/packages/agent-core-v2/src/app/hostFolderBrowser/hostFolderBrowser.ts @@ -6,17 +6,42 @@ * folder. Distinct from the Session-side `sessionFs`, which is sandboxed and may * be remote. App-scoped. * - * The wire shapes (`FsBrowseResponse` / `FsHomeResponse`) are sourced from - * `@moonshot-ai/protocol` so the `/api/v1` and `/api/v2` transports share one - * contract. Domain errors (`HostFolder*Error`) carry the failing path and are - * translated to protocol error codes at the transport boundary. + * The wire shapes (`FsBrowseResponse` / `FsHomeResponse`) are defined here as + * zod schemas so the `/api/v1` and `/api/v2` transports share one contract. + * Domain errors (`HostFolder*Error`) carry the failing path and are + * translated to wire error codes at the transport boundary. */ +import { z } from 'zod'; + import { createDecorator, type ServiceIdentifier } from '#/_base/di/instantiation'; -import type { FsBrowseResponse, FsHomeResponse } from '@moonshot-ai/protocol'; +export const fsBrowseQuerySchema = z.object({ + path: z.string().min(1).optional(), +}); +export type FsBrowseQuery = z.infer; -export type { FsBrowseResponse, FsHomeResponse }; +export const fsBrowseEntrySchema = z.object({ + name: z.string().min(1), + path: z.string().min(1), + is_dir: z.literal(true), + is_git_repo: z.boolean(), + branch: z.string().optional(), +}); +export type FsBrowseEntry = z.infer; + +export const fsBrowseResponseSchema = z.object({ + path: z.string().min(1), + parent: z.string().min(1).nullable(), + entries: z.array(fsBrowseEntrySchema), +}); +export type FsBrowseResponse = z.infer; + +export const fsHomeResponseSchema = z.object({ + home: z.string().min(1), + recent_roots: z.array(z.string().min(1)), +}); +export type FsHomeResponse = z.infer; export class HostFolderNotAbsoluteError extends Error { readonly path: string; diff --git a/packages/agent-core-v2/src/app/hostFolderBrowser/hostFolderBrowserService.ts b/packages/agent-core-v2/src/app/hostFolderBrowser/hostFolderBrowserService.ts index fc4f2846e..b45707cf4 100644 --- a/packages/agent-core-v2/src/app/hostFolderBrowser/hostFolderBrowserService.ts +++ b/packages/agent-core-v2/src/app/hostFolderBrowser/hostFolderBrowserService.ts @@ -12,7 +12,7 @@ import { lstat, readFile, readdir, realpath } from 'node:fs/promises'; import { homedir } from 'node:os'; import { dirname, isAbsolute, join } from 'node:path'; -import type { FsBrowseEntry, FsBrowseResponse, FsHomeResponse } from '@moonshot-ai/protocol'; +import type { FsBrowseEntry, FsBrowseResponse, FsHomeResponse } from './hostFolderBrowser'; import { InstantiationType } from '#/_base/di/extensions'; import { LifecycleScope, registerScopedService } from '#/_base/di/scope'; diff --git a/packages/agent-core-v2/src/app/messageLegacy/messageLegacy.ts b/packages/agent-core-v2/src/app/messageLegacy/messageLegacy.ts index 5f70bd7e3..edeb48a42 100644 --- a/packages/agent-core-v2/src/app/messageLegacy/messageLegacy.ts +++ b/packages/agent-core-v2/src/app/messageLegacy/messageLegacy.ts @@ -19,15 +19,22 @@ * - `message.not_found` → 40403 */ -import type { - CursorQuery, - Message, - MessageRole, - PageResponse, -} from '@moonshot-ai/protocol'; +import type { Message, MessageRole } from '#/agent/contextMemory/wireMessage'; import { createDecorator, type ServiceIdentifier } from '#/_base/di/instantiation'; +/** Cursor pagination query shared by the v1 history/list endpoints. */ +export interface CursorQuery { + before_id?: string | undefined; + after_id?: string | undefined; + page_size?: number | undefined; +} + +export interface PageResponse { + items: T[]; + has_more: boolean; +} + export interface MessageListQuery extends CursorQuery { readonly role?: MessageRole; } diff --git a/packages/agent-core-v2/src/app/messageLegacy/messageLegacyService.ts b/packages/agent-core-v2/src/app/messageLegacy/messageLegacyService.ts index 168b8ef09..bda93c8fa 100644 --- a/packages/agent-core-v2/src/app/messageLegacy/messageLegacyService.ts +++ b/packages/agent-core-v2/src/app/messageLegacy/messageLegacyService.ts @@ -18,7 +18,9 @@ * (`packages/agent-core/src/services/message/messageService.ts`). */ -import type { Message, PageResponse } from '@moonshot-ai/protocol'; +import type { Message } from '#/agent/contextMemory/wireMessage'; + +import type { PageResponse } from './messageLegacy'; import { InstantiationType } from '#/_base/di/extensions'; import { type IAgentScopeHandle, LifecycleScope, registerScopedService } from '#/_base/di/scope'; diff --git a/packages/agent-core-v2/src/app/modelCatalog/modelCatalog.ts b/packages/agent-core-v2/src/app/modelCatalog/modelCatalog.ts index 66fc872f4..8789f8b64 100644 --- a/packages/agent-core-v2/src/app/modelCatalog/modelCatalog.ts +++ b/packages/agent-core-v2/src/app/modelCatalog/modelCatalog.ts @@ -3,20 +3,15 @@ * model aliases, plus the global default-model selection. * * Projects the `provider` / `model` configuration registries into the - * protocol `ProviderCatalogItem` / `ModelCatalogItem` wire shapes that the - * edge (`server-v2` `/api/v1` routes) serves. App-scoped — provider and + * `ProviderCatalogItem` / `ModelCatalogItem` wire shapes (defined below as zod + * schemas) that the edge (`server-v2` `/api/v1` routes) serves. App-scoped — provider and * model configuration is global and shared across sessions. This domain is a * thin facade over `provider`, `model`, `config`, and `auth`; it owns no * persistence of its own. The OAuth-provider model refresh lives in * The OAuth-provider model refresh lives in `auth` (`IOAuthService`), not here. */ -import type { - ModelCatalogItem, - ProviderCatalogItem, - RefreshProviderModelsResponse, - SetDefaultModelResponse, -} from '@moonshot-ai/protocol'; +import { z } from 'zod'; import { createDecorator, type ServiceIdentifier } from '#/_base/di/instantiation'; @@ -24,6 +19,64 @@ import type { ModelAlias } from '#/app/model/model'; import { effectiveModelConfig } from '#/app/model/modelAuth'; import type { ProviderConfig } from '#/app/provider/provider'; +export const modelCatalogItemSchema = z.object({ + provider: z.string().min(1), + model: z.string().min(1), + display_name: z.string().min(1).optional(), + max_context_size: z.number().int().min(1), + capabilities: z.array(z.string()).optional(), + support_efforts: z.array(z.string()).optional(), + default_effort: z.string().optional(), +}); +export type ModelCatalogItem = z.infer; + +export const providerCatalogStatusSchema = z.enum([ + 'connected', + 'error', + 'unconfigured', +]); +export type ProviderCatalogStatus = z.infer; + +export const providerCatalogItemSchema = z.object({ + id: z.string().min(1), + type: z.string().min(1), + base_url: z.string().min(1).optional(), + default_model: z.string().min(1).optional(), + has_api_key: z.boolean(), + status: providerCatalogStatusSchema, + models: z.array(z.string().min(1)).optional(), +}); +export type ProviderCatalogItem = z.infer; + +export const providerRefreshChangeSchema = z.object({ + provider_id: z.string().min(1), + provider_name: z.string().min(1), + added: z.number().int().min(0), + removed: z.number().int().min(0), +}); +export type ProviderRefreshChange = z.infer; + +export const providerRefreshFailureSchema = z.object({ + provider: z.string().min(1), + reason: z.string().min(1), +}); +export type ProviderRefreshFailure = z.infer; + +export const setDefaultModelResponseSchema = z.object({ + default_model: z.string().min(1), + model: modelCatalogItemSchema, +}); +export type SetDefaultModelResponse = z.infer; + +export const refreshProviderModelsResponseSchema = z.object({ + changed: z.array(providerRefreshChangeSchema), + unchanged: z.array(z.string().min(1)), + failed: z.array(providerRefreshFailureSchema), +}); +export type RefreshProviderModelsResponse = z.infer< + typeof refreshProviderModelsResponseSchema +>; + export type RefreshProviderModelsScope = 'all' | 'oauth'; export interface RefreshProviderModelsOptions { diff --git a/packages/agent-core-v2/src/app/modelCatalog/modelCatalogService.ts b/packages/agent-core-v2/src/app/modelCatalog/modelCatalogService.ts index e5d1b33ae..ae33a1ac6 100644 --- a/packages/agent-core-v2/src/app/modelCatalog/modelCatalogService.ts +++ b/packages/agent-core-v2/src/app/modelCatalog/modelCatalogService.ts @@ -23,7 +23,7 @@ import type { ProviderCatalogItem, RefreshProviderModelsResponse, SetDefaultModelResponse, -} from '@moonshot-ai/protocol'; +} from './modelCatalog'; import { InstantiationType } from '#/_base/di/extensions'; import { LifecycleScope, registerScopedService } from '#/_base/di/scope'; diff --git a/packages/agent-core-v2/src/app/sessionLegacy/sessionLegacy.ts b/packages/agent-core-v2/src/app/sessionLegacy/sessionLegacy.ts index d765aef05..86cce0a0e 100644 --- a/packages/agent-core-v2/src/app/sessionLegacy/sessionLegacy.ts +++ b/packages/agent-core-v2/src/app/sessionLegacy/sessionLegacy.ts @@ -22,14 +22,12 @@ * it is a stateless dispatcher that resolves the target session/agent per call. */ -import type { - GoalSnapshot, - SessionStatusResponse, - UpdateSessionProfileRequest, -} from '@moonshot-ai/protocol'; +import type { GoalSnapshot } from '#/agent/goal/types'; import { createDecorator, type ServiceIdentifier } from '#/_base/di/instantiation'; +import type { SessionStatusResponse, UpdateSessionProfileRequest } from './sessionWire'; + export interface SessionWireFields { readonly id: string; readonly workspaceId: string; diff --git a/packages/agent-core-v2/src/app/sessionLegacy/sessionLegacyService.ts b/packages/agent-core-v2/src/app/sessionLegacy/sessionLegacyService.ts index 65edda89c..c03a59a48 100644 --- a/packages/agent-core-v2/src/app/sessionLegacy/sessionLegacyService.ts +++ b/packages/agent-core-v2/src/app/sessionLegacy/sessionLegacyService.ts @@ -13,11 +13,9 @@ * the real work stays in the native services. */ -import type { - GoalSnapshot, - SessionStatusResponse, - UpdateSessionProfileRequest, -} from '@moonshot-ai/protocol'; +import type { GoalSnapshot } from '#/agent/goal/types'; + +import type { SessionStatusResponse, UpdateSessionProfileRequest } from './sessionWire'; import { InstantiationType } from '#/_base/di/extensions'; import { type IAgentScopeHandle, LifecycleScope, registerScopedService } from '#/_base/di/scope'; diff --git a/packages/agent-core-v2/src/app/sessionLegacy/sessionWire.ts b/packages/agent-core-v2/src/app/sessionLegacy/sessionWire.ts new file mode 100644 index 000000000..9a0e62e49 --- /dev/null +++ b/packages/agent-core-v2/src/app/sessionLegacy/sessionWire.ts @@ -0,0 +1,102 @@ +/** + * `sessionLegacy` domain — the v1 session wire DTO schemas. + * + * These zod schemas define the request/response shapes of the v1 session + * endpoints this adapter backs (`POST /sessions/{id}/profile`, + * `GET /sessions/{id}/status`, session warnings); the transports validate + * against them and the adapter's contract consumes the inferred types. + * Field-level changes here are wire breaks — see the schema-fidelity rule in + * `server-align.md`. + */ + +import { z } from 'zod'; + +import { isoDateTimeSchema } from '#/_base/utils/isoDateTime'; + +export const sessionStatusSchema = z.enum([ + 'idle', + 'running', + 'awaiting_approval', + 'awaiting_question', + 'aborted', +]); +export type SessionStatus = z.infer; + +export const sessionWarningSchema = z.object({ + code: z.string(), + message: z.string(), + severity: z.enum(['info', 'warning', 'error']), +}); +export type SessionWarning = z.infer; + +export const sessionWarningsResponseSchema = z.object({ + warnings: z.array(sessionWarningSchema), +}); +export type SessionWarningsResponse = z.infer; + +export const promptThinkingSchema = z.string().min(1); +export type PromptThinking = z.infer; + +export const promptPermissionModeSchema = z.enum(['manual', 'yolo', 'auto']); +export type PromptPermissionMode = z.infer; + +export const sessionMetadataSchema = z + .object({ + cwd: z.string().min(1), + }) + .catchall(z.unknown()); +export type SessionMetadata = z.infer; + +export const sessionAgentConfigSchema = z.object({ + model: z.string(), + system_prompt: z.string().optional(), + tools: z.array(z.string()).optional(), + mcp_servers: z.array(z.string()).optional(), + thinking: promptThinkingSchema.optional(), + permission_mode: promptPermissionModeSchema.optional(), + plan_mode: z.boolean().optional(), + swarm_mode: z.boolean().optional(), + goal_objective: z.string().optional(), + goal_control: z.enum(['pause', 'resume', 'cancel']).optional(), +}); +export type SessionAgentConfig = z.infer; + +export const sessionAgentConfigPartialSchema = sessionAgentConfigSchema.partial(); +export type SessionAgentConfigPartial = z.infer; + +export const permissionRuleMatcherSchema = z.object({ + kind: z.enum(['command_prefix', 'path_glob', 'exact_input', 'always']), + value: z.string().optional(), +}); +export type PermissionRuleMatcher = z.infer; + +export const permissionRuleSchema = z.object({ + id: z.string().min(1), + tool_name: z.string().min(1), + matcher: permissionRuleMatcherSchema.optional(), + decision: z.literal('approved'), + created_at: isoDateTimeSchema, + created_by: z.enum(['user', 'agent']), +}); +export type PermissionRule = z.infer; + +export const updateSessionProfileRequestSchema = z.object({ + title: z.string().min(1).optional(), + metadata: sessionMetadataSchema.partial().optional(), + agent_config: sessionAgentConfigPartialSchema.optional(), + permission_rules: z.array(permissionRuleSchema).optional(), +}); +export type UpdateSessionProfileRequest = z.infer; + +export const sessionStatusResponseSchema = z.object({ + status: sessionStatusSchema, + model: z.string().optional(), + thinking_level: z.string(), + permission: z.string(), + plan_mode: z.boolean(), + swarm_mode: z.boolean(), + context_tokens: z.number().int().nonnegative(), + max_context_tokens: z.number().int().nonnegative(), + context_usage: z.number().min(0).max(1), +}); +export type SessionStatusResponse = z.infer; diff --git a/packages/agent-core-v2/src/app/sessionLifecycle/sessionLifecycleService.ts b/packages/agent-core-v2/src/app/sessionLifecycle/sessionLifecycleService.ts index 5543c1733..3f0f68c1c 100644 --- a/packages/agent-core-v2/src/app/sessionLifecycle/sessionLifecycleService.ts +++ b/packages/agent-core-v2/src/app/sessionLifecycle/sessionLifecycleService.ts @@ -333,7 +333,7 @@ export class SessionLifecycleService extends Disposable implements ISessionLifec try { const workspace = await this.workspaceRegistry.get(workspaceId); if (workspace === undefined) { - throw new Error2('workspace.not_found', `workspace ${workspaceId} does not exist`); + throw new Error2(ErrorCodes.WORKSPACE_NOT_FOUND, `workspace ${workspaceId} does not exist`); } const sourceMeta = diff --git a/packages/agent-core-v2/src/app/workspaceRegistry/errors.ts b/packages/agent-core-v2/src/app/workspaceRegistry/errors.ts new file mode 100644 index 000000000..9c7752791 --- /dev/null +++ b/packages/agent-core-v2/src/app/workspaceRegistry/errors.ts @@ -0,0 +1,9 @@ +import { registerErrorDomain, type ErrorDomain } from '#/_base/errors/codes'; + +export const WorkspaceErrors = { + codes: { + WORKSPACE_NOT_FOUND: 'workspace.not_found', + }, +} as const satisfies ErrorDomain; + +registerErrorDomain(WorkspaceErrors); diff --git a/packages/agent-core-v2/src/errors.ts b/packages/agent-core-v2/src/errors.ts index 7bba21b30..a4fcbf67c 100644 --- a/packages/agent-core-v2/src/errors.ts +++ b/packages/agent-core-v2/src/errors.ts @@ -34,6 +34,7 @@ import { StorageErrors } from '#/persistence/interface/storage'; import { TerminalErrors } from '#/os/interface/terminalErrors'; import { UsageErrors } from '#/agent/usage/errors'; import { WireErrors } from '#/wire/errors'; +import { WorkspaceErrors } from '#/app/workspaceRegistry/errors'; export * from '#/_base/errors/codes'; export * from '#/_base/errors/errorMessage'; @@ -66,6 +67,7 @@ export { StorageErrors } from '#/persistence/interface/storage'; export { TerminalErrors } from '#/os/interface/terminalErrors'; export { UsageErrors } from '#/agent/usage/errors'; export { WireErrors } from '#/wire/errors'; +export { WorkspaceErrors } from '#/app/workspaceRegistry/errors'; export const ErrorCodes = { ...CoreErrors.codes, @@ -95,4 +97,13 @@ export const ErrorCodes = { ...TerminalErrors.codes, ...UsageErrors.codes, ...WireErrors.codes, + ...WorkspaceErrors.codes, } as const; + +/** + * The closed union of every error code a Kimi domain may throw — derived from + * the `ErrorCodes` aggregate rather than declared centrally, so each domain's + * `errors.ts` is the single source of truth: adding or renaming a code is a + * domain-local change with no central list to keep in sync. + */ +export type ErrorCode = (typeof ErrorCodes)[keyof typeof ErrorCodes]; diff --git a/packages/agent-core-v2/src/os/interface/terminal.ts b/packages/agent-core-v2/src/os/interface/terminal.ts index c17de551f..c3da910d3 100644 --- a/packages/agent-core-v2/src/os/interface/terminal.ts +++ b/packages/agent-core-v2/src/os/interface/terminal.ts @@ -8,24 +8,74 @@ * (`ISessionTerminalService`) lives in `src/session/terminal` and is the * surface most business code and the edge consume. * - * Wire types (`Terminal`, `CreateTerminalRequest`, frame messages) are sourced - * from `@moonshot-ai/protocol`. + * Wire types (`Terminal`, `CreateTerminalRequest`, frame messages) are defined + * here — the terminal REST schemas as zod, the attach-frame messages as plain + * types. */ -import type { - CreateTerminalRequest, - Terminal, - TerminalExitMessage, - TerminalOutputMessage, -} from '@moonshot-ai/protocol'; +import { z } from 'zod'; import { createDecorator, type ServiceIdentifier } from '#/_base/di/instantiation'; import type { Event } from '#/_base/event'; +import { isoDateTimeSchema } from '#/_base/utils/isoDateTime'; -export type { CreateTerminalRequest, Terminal, TerminalExitMessage, TerminalOutputMessage }; +const relativeCwdSchema = z + .string() + .min(1) + .refine((value) => !isAbsolutePath(value), 'cwd must be relative to the session workspace'); + +export const terminalStatusSchema = z.enum(['running', 'exited']); +export type TerminalStatus = z.infer; + +export const terminalSchema = z.object({ + id: z.string().min(1), + session_id: z.string().min(1), + cwd: z.string().min(1), + shell: z.string().min(1), + cols: z.number().int().positive(), + rows: z.number().int().positive(), + status: terminalStatusSchema, + created_at: isoDateTimeSchema, + exited_at: isoDateTimeSchema.optional(), + exit_code: z.number().int().nullable().optional(), +}); +export type Terminal = z.infer; + +export const createTerminalRequestSchema = z.object({ + cwd: relativeCwdSchema.optional(), + shell: z.string().min(1).optional(), + cols: z.number().int().positive().optional(), + rows: z.number().int().positive().optional(), +}); +export type CreateTerminalRequest = z.infer; + +export interface TerminalOutputMessage { + type: 'terminal_output'; + seq: number; + session_id: string; + terminal_id: string; + timestamp: string; + payload: { data: string }; +} + +export interface TerminalExitMessage { + type: 'terminal_exit'; + session_id: string; + terminal_id: string; + timestamp: string; + payload: { exit_code?: number | null | undefined }; +} export type TerminalFrame = TerminalOutputMessage | TerminalExitMessage; +function isAbsolutePath(value: string): boolean { + return ( + value.startsWith('/') || + value.startsWith('\\') || + /^[A-Za-z]:[\\/]/.test(value) + ); +} + export interface TerminalAttachSink { readonly id: string; send(frame: TerminalFrame): void; diff --git a/packages/agent-core-v2/src/session/approval/approval.ts b/packages/agent-core-v2/src/session/approval/approval.ts index 6bd2ee2f0..4f323265d 100644 --- a/packages/agent-core-v2/src/session/approval/approval.ts +++ b/packages/agent-core-v2/src/session/approval/approval.ts @@ -8,7 +8,7 @@ */ import { createDecorator, type ServiceIdentifier } from '#/_base/di/instantiation'; -import type { ToolInputDisplay } from '@moonshot-ai/protocol'; +import type { ToolInputDisplay } from '#/tool/toolInputDisplay'; export interface ApprovalRequest { readonly id?: string; diff --git a/packages/agent-core-v2/src/session/cron/cronOps.ts b/packages/agent-core-v2/src/session/cron/cronOps.ts index 9936798ff..271b6b8e0 100644 --- a/packages/agent-core-v2/src/session/cron/cronOps.ts +++ b/packages/agent-core-v2/src/session/cron/cronOps.ts @@ -17,7 +17,7 @@ * `OP_REGISTRY` at import time. */ -import type { CronJobOrigin } from '@moonshot-ai/protocol'; +import type { CronJobOrigin } from '#/agent/contextMemory/types'; import { z } from 'zod'; import { defineModel } from '#/wire/model'; diff --git a/packages/agent-core-v2/src/session/cron/sessionCronServiceImpl.ts b/packages/agent-core-v2/src/session/cron/sessionCronServiceImpl.ts index 52b4e3ce4..1bde48af2 100644 --- a/packages/agent-core-v2/src/session/cron/sessionCronServiceImpl.ts +++ b/packages/agent-core-v2/src/session/cron/sessionCronServiceImpl.ts @@ -17,7 +17,7 @@ import { ulid } from 'ulid'; import type { ContentPart } from '#/app/llmProtocol/message'; -import type { CronJobOrigin, CronMissedOrigin } from '@moonshot-ai/protocol'; +import type { CronJobOrigin, CronMissedOrigin } from '#/agent/contextMemory/types'; import { Disposable, toDisposable } from '#/_base/di/lifecycle'; import { InstantiationType } from '#/_base/di/extensions'; diff --git a/packages/agent-core-v2/src/session/sessionFs/fs.ts b/packages/agent-core-v2/src/session/sessionFs/fs.ts index fb6822461..f19320310 100644 --- a/packages/agent-core-v2/src/session/sessionFs/fs.ts +++ b/packages/agent-core-v2/src/session/sessionFs/fs.ts @@ -2,36 +2,219 @@ * `sessionFs` domain (L2) — wire-shaped filesystem operations. * * Defines the `ISessionFsService` that backs the fs REST surface: content search, - * content grep, and git status/diff. It orchestrates the os `IHostFileSystem` + * content grep, and git status/diff, together with the zod DTO schemas the + * transports validate against. It orchestrates the os `IHostFileSystem` * (file IO, resolved against the workspace root) plus `ISessionProcessRunner` - * (for `rg` / `git` / `gh`) and returns protocol-shaped responses. + * (for `rg` / `git` / `gh`). Git status/diff DTOs live in the `git` domain. * Session-scoped — the scope itself is the session, so no `sessionId` is * threaded through. */ +import { z } from 'zod'; + import { createDecorator, type ServiceIdentifier } from '#/_base/di/instantiation'; -import type { - FsDiffRequest, - FsDiffResponse, - FsGitStatusRequest, - FsGitStatusResponse, - FsGrepRequest, - FsGrepResponse, - FsListManyRequest, - FsListManyResponse, - FsListRequest, - FsListResponse, - FsMkdirRequest, - FsMkdirResponse, - FsReadRequest, - FsReadResponse, - FsSearchRequest, - FsSearchResponse, - FsStatManyRequest, - FsStatManyResponse, - FsStatRequest, - FsStatResponse, -} from '@moonshot-ai/protocol'; +import { isoDateTimeSchema } from '#/_base/utils/isoDateTime'; +import { + fsGitStatusSchema, + type FsDiffRequest, + type FsDiffResponse, + type FsGitStatusRequest, + type FsGitStatusResponse, +} from '#/app/git/git'; + +export { + fsDiffRequestSchema, + fsDiffResponseSchema, + fsGitStatusRequestSchema, + fsGitStatusResponseSchema, +} from '#/app/git/git'; +export type { FsDiffRequest, FsDiffResponse, FsGitStatusRequest, FsGitStatusResponse }; + +export const fsKindSchema = z.enum(['file', 'directory', 'symlink']); +export type FsKind = z.infer; + +export const fsEntrySchema = z.object({ + path: z.string(), + name: z.string(), + kind: fsKindSchema, + size: z.number().int().nonnegative().optional(), + modified_at: isoDateTimeSchema, + etag: z.string().optional(), + mime: z.string().optional(), + language_id: z.string().optional(), + is_binary: z.boolean().optional(), + is_symlink_to: z.string().optional(), + git_status: fsGitStatusSchema.optional(), + child_count: z.number().int().nonnegative().optional(), +}); +export type FsEntry = z.infer; + +export const fsSearchHitSchema = z.object({ + path: z.string(), + name: z.string(), + kind: fsKindSchema, + score: z.number().min(0).max(1), + match_positions: z.array(z.number().int().nonnegative()), +}); +export type FsSearchHit = z.infer; + +export const fsGrepMatchSchema = z.object({ + line: z.number().int().positive(), + col: z.number().int().positive(), + text: z.string(), + before: z.array(z.string()), + after: z.array(z.string()), +}); +export type FsGrepMatch = z.infer; + +export const fsGrepFileHitSchema = z.object({ + path: z.string(), + matches: z.array(fsGrepMatchSchema), +}); +export type FsGrepFileHit = z.infer; + +export const fsListSortSchema = z.enum([ + 'type_first', + 'name_asc', + 'name_desc', + 'mtime_desc', + 'size_desc', +]); +export type FsListSort = z.infer; + +export const fsListRequestSchema = z.object({ + path: z.string().default('.'), + depth: z.number().int().min(1).max(10).default(1), + limit: z.number().int().min(1).max(1000).default(200), + show_hidden: z.boolean().default(false), + follow_gitignore: z.boolean().default(true), + exclude_globs: z.array(z.string()).optional(), + sort: fsListSortSchema.default('type_first'), + include_git_status: z.boolean().default(false), +}); +export type FsListRequest = z.infer; + +export const fsListResponseSchema = z.object({ + items: z.array(fsEntrySchema), + children_by_path: z.record(z.string(), z.array(fsEntrySchema)).optional(), + truncated: z.boolean(), +}); +export type FsListResponse = z.infer; + +export const fsReadEncodingRequestSchema = z.enum(['auto', 'utf-8', 'base64']); +export const fsReadEncodingResponseSchema = z.enum(['utf-8', 'base64']); +export type FsReadEncoding = z.infer; + +export const fsReadRequestSchema = z.object({ + path: z.string().min(1), + offset: z.number().int().nonnegative().default(0), + length: z.number().int().min(1).max(10_485_760).default(1_048_576), + encoding: fsReadEncodingRequestSchema.default('auto'), +}); +export type FsReadRequest = z.infer; + +export const fsReadResponseSchema = z.object({ + path: z.string(), + content: z.string(), + encoding: fsReadEncodingResponseSchema, + size: z.number().int().nonnegative(), + truncated: z.boolean(), + etag: z.string(), + mime: z.string(), + language_id: z.string().optional(), + line_count: z.number().int().nonnegative().optional(), + is_binary: z.boolean(), +}); +export type FsReadResponse = z.infer; + +export const fsMkdirRequestSchema = z.object({ + path: z.string().min(1), + recursive: z.boolean().default(false), +}); +export type FsMkdirRequest = z.infer; + +export const fsMkdirResponseSchema = fsEntrySchema; +export type FsMkdirResponse = z.infer; + +export const fsListManyRequestSchema = z.object({ + paths: z.array(z.string().min(1)).min(1).max(100), + depth: z.number().int().min(1).max(10).default(1), + limit: z.number().int().min(1).max(1000).default(200), + show_hidden: z.boolean().default(false), + follow_gitignore: z.boolean().default(true), + exclude_globs: z.array(z.string()).optional(), + sort: fsListSortSchema.default('type_first'), + include_git_status: z.boolean().default(false), +}); +export type FsListManyRequest = z.infer; + +export const fsListManyPartialErrorSchema = z.object({ + code: z.number().int(), + msg: z.string(), +}); +export type FsListManyPartialError = z.infer; + +export const fsListManyResponseSchema = z.object({ + results: z.record(z.string(), z.array(fsEntrySchema)), + truncated_paths: z.array(z.string()).optional(), + partial_errors: z.record(z.string(), fsListManyPartialErrorSchema).optional(), +}); +export type FsListManyResponse = z.infer; + +export const fsStatRequestSchema = z.object({ + path: z.string().min(1), +}); +export type FsStatRequest = z.infer; + +export const fsStatResponseSchema = fsEntrySchema; +export type FsStatResponse = z.infer; + +export const fsStatManyRequestSchema = z.object({ + paths: z.array(z.string().min(1)).min(1).max(1000), +}); +export type FsStatManyRequest = z.infer; + +export const fsStatManyResponseSchema = z.object({ + entries: z.record(z.string(), fsEntrySchema.nullable()), +}); +export type FsStatManyResponse = z.infer; + +export const fsSearchRequestSchema = z.object({ + query: z.string().min(1), + limit: z.number().int().min(1).max(200).default(50), + include_globs: z.array(z.string()).optional(), + exclude_globs: z.array(z.string()).optional(), + follow_gitignore: z.boolean().default(true), +}); +export type FsSearchRequest = z.infer; + +export const fsSearchResponseSchema = z.object({ + items: z.array(fsSearchHitSchema), + truncated: z.boolean(), +}); +export type FsSearchResponse = z.infer; + +export const fsGrepRequestSchema = z.object({ + pattern: z.string().min(1), + regex: z.boolean().default(false), + case_sensitive: z.boolean().default(true), + include_globs: z.array(z.string()).optional(), + exclude_globs: z.array(z.string()).optional(), + follow_gitignore: z.boolean().default(true), + max_files: z.number().int().min(1).max(10_000).default(200), + max_matches_per_file: z.number().int().min(1).max(10_000).default(50), + max_total_matches: z.number().int().min(1).max(100_000).default(5000), + context_lines: z.number().int().min(0).max(10).default(2), +}); +export type FsGrepRequest = z.infer; + +export const fsGrepResponseSchema = z.object({ + files: z.array(fsGrepFileHitSchema), + files_scanned: z.number().int().nonnegative(), + truncated: z.boolean(), + elapsed_ms: z.number().int().nonnegative(), +}); +export type FsGrepResponse = z.infer; export interface FsPathResolved { readonly absolute: string; diff --git a/packages/agent-core-v2/src/session/sessionFs/fsSearch.ts b/packages/agent-core-v2/src/session/sessionFs/fsSearch.ts index efd2f37fd..0c4ed321f 100644 --- a/packages/agent-core-v2/src/session/sessionFs/fsSearch.ts +++ b/packages/agent-core-v2/src/session/sessionFs/fsSearch.ts @@ -6,7 +6,7 @@ * be unit-tested directly. Ported from v1 `services/fs/fsSearchService.ts`. */ -import type { FsGrepRequest } from '@moonshot-ai/protocol'; +import type { FsGrepRequest } from './fs'; export function computeFuzzyScore(name: string, queryLower: string): number { if (queryLower.length === 0) return 0; diff --git a/packages/agent-core-v2/src/session/sessionFs/fsService.ts b/packages/agent-core-v2/src/session/sessionFs/fsService.ts index 8eecf00dc..5804830da 100644 --- a/packages/agent-core-v2/src/session/sessionFs/fsService.ts +++ b/packages/agent-core-v2/src/session/sessionFs/fsService.ts @@ -16,7 +16,6 @@ import { basename, extname, isAbsolute, join, relative, sep } from 'node:path'; import { - ErrorCode, type FsDiffRequest, type FsDiffResponse, type FsEntry, @@ -41,7 +40,22 @@ import { type FsStatManyResponse, type FsStatRequest, type FsStatResponse, -} from '@moonshot-ai/protocol'; +} from './fs'; + +/** + * The v1 numeric wire codes this edge surface throws inside its + * `{ code, msg }` wire errors (`toWireError`). Mirrors the envelope error + * table owned by the transport (kap-server); kept as local literals because + * they are part of this service's v1 edge contract. + */ +const FsWireErrorCode = { + FS_PATH_NOT_FOUND: 40409, + FS_IS_DIRECTORY: 40906, + FS_IS_BINARY: 40907, + FS_TOO_LARGE: 41302, + FS_TOO_MANY_RESULTS: 41303, + INTERNAL_ERROR: 50001, +} as const; import ignore, { type Ignore } from 'ignore'; import { InstantiationType } from '#/_base/di/extensions'; @@ -919,19 +933,19 @@ function toWireError(err: unknown): { code: number; msg: string } { if (err instanceof Error2) { switch (err.code) { case ErrorCodes.FS_PATH_NOT_FOUND: - return { code: ErrorCode.FS_PATH_NOT_FOUND, msg: err.message }; + return { code: FsWireErrorCode.FS_PATH_NOT_FOUND, msg: err.message }; case ErrorCodes.FS_IS_DIRECTORY: - return { code: ErrorCode.FS_IS_DIRECTORY, msg: err.message }; + return { code: FsWireErrorCode.FS_IS_DIRECTORY, msg: err.message }; case ErrorCodes.FS_IS_BINARY: - return { code: ErrorCode.FS_IS_BINARY, msg: err.message }; + return { code: FsWireErrorCode.FS_IS_BINARY, msg: err.message }; case ErrorCodes.FS_TOO_LARGE: - return { code: ErrorCode.FS_TOO_LARGE, msg: err.message }; + return { code: FsWireErrorCode.FS_TOO_LARGE, msg: err.message }; case ErrorCodes.FS_TOO_MANY_RESULTS: - return { code: ErrorCode.FS_TOO_MANY_RESULTS, msg: err.message }; + return { code: FsWireErrorCode.FS_TOO_MANY_RESULTS, msg: err.message }; } } return { - code: ErrorCode.INTERNAL_ERROR, + code: FsWireErrorCode.INTERNAL_ERROR, msg: err instanceof Error ? err.message : 'internal error', }; } diff --git a/packages/agent-core-v2/src/session/sessionFs/fsWatch.ts b/packages/agent-core-v2/src/session/sessionFs/fsWatch.ts index 4b3c123c6..6e44c4778 100644 --- a/packages/agent-core-v2/src/session/sessionFs/fsWatch.ts +++ b/packages/agent-core-v2/src/session/sessionFs/fsWatch.ts @@ -11,7 +11,25 @@ import { createDecorator, type ServiceIdentifier } from '#/_base/di/instantiation'; import type { Event } from '#/_base/event'; -import type { FsChangeEvent } from '@moonshot-ai/protocol'; + +export type FsChangeKind = 'file' | 'directory' | 'symlink'; + +export type FsChangeAction = 'created' | 'modified' | 'deleted'; + +export interface FsChangeEntry { + path: string; + change: FsChangeAction; + kind: FsChangeKind; + size_delta?: number | undefined; + etag?: string | undefined; +} + +export interface FsChangeEvent { + changes: FsChangeEntry[]; + coalesced_window_ms: number; + truncated?: boolean | undefined; + count?: number | undefined; +} export interface ISessionFsWatchService { readonly _serviceBrand: undefined; diff --git a/packages/agent-core-v2/src/session/sessionFs/fsWatchService.ts b/packages/agent-core-v2/src/session/sessionFs/fsWatchService.ts index 50129c0eb..a440595bd 100644 --- a/packages/agent-core-v2/src/session/sessionFs/fsWatchService.ts +++ b/packages/agent-core-v2/src/session/sessionFs/fsWatchService.ts @@ -26,7 +26,7 @@ import { IHostFsWatchService, } from '#/os/interface/hostFsWatch'; import { ISessionWorkspaceContext } from '#/session/workspaceContext/workspaceContext'; -import type { FsChangeEntry, FsChangeEvent } from '@moonshot-ai/protocol'; +import type { FsChangeEntry, FsChangeEvent } from './fsWatch'; import { ISessionFsWatchService } from './fsWatch'; diff --git a/packages/agent-core-v2/src/session/subagent/mirrorAgentRun.ts b/packages/agent-core-v2/src/session/subagent/mirrorAgentRun.ts index e38a35c51..1e1cdec24 100644 --- a/packages/agent-core-v2/src/session/subagent/mirrorAgentRun.ts +++ b/packages/agent-core-v2/src/session/subagent/mirrorAgentRun.ts @@ -27,18 +27,44 @@ import { IAgentContextSizeService } from '#/agent/contextSize/contextSize'; import { isProviderRateLimitError } from '#/app/llmProtocol/errors'; import { type TokenUsage } from '#/app/llmProtocol/usage'; import { ITelemetryService } from '#/app/telemetry/telemetry'; -import type { - SubagentCompletedEvent, - SubagentFailedEvent, - SubagentSpawnedEvent, - SubagentStartedEvent, -} from '@moonshot-ai/protocol'; import { IEventBus } from '#/app/event/eventBus'; import { isAbortError } from '#/_base/utils/abort'; import { IAgentLifecycleService } from '#/session/agentLifecycle/agentLifecycle'; import { type AgentRunHandle, ISessionSubagentService } from './subagent'; +export interface SubagentSpawnedEvent { + readonly type: 'subagent.spawned'; + readonly subagentId: string; + readonly subagentName: string; + readonly parentToolCallId: string; + readonly parentToolCallUuid?: string; + readonly parentAgentId?: string; + readonly callerAgentId?: string; + readonly description?: string; + readonly swarmIndex?: number; + readonly runInBackground: boolean; +} + +export interface SubagentStartedEvent { + readonly type: 'subagent.started'; + readonly subagentId: string; +} + +export interface SubagentCompletedEvent { + readonly type: 'subagent.completed'; + readonly subagentId: string; + readonly resultSummary: string; + readonly usage?: TokenUsage; + readonly contextTokens?: number; +} + +export interface SubagentFailedEvent { + readonly type: 'subagent.failed'; + readonly subagentId: string; + readonly error: string; +} + declare module '#/app/event/eventBus' { interface DomainEventMap { 'subagent.spawned': SubagentSpawnedEvent; diff --git a/packages/agent-core-v2/src/session/swarm/sessionSwarmService.ts b/packages/agent-core-v2/src/session/swarm/sessionSwarmService.ts index 8b3a529c9..33be825af 100644 --- a/packages/agent-core-v2/src/session/swarm/sessionSwarmService.ts +++ b/packages/agent-core-v2/src/session/swarm/sessionSwarmService.ts @@ -23,7 +23,6 @@ import { IAgentProfileService } from '#/agent/profile/profile'; import { IAgentPermissionModeService } from '#/agent/permissionMode/permissionMode'; import { IAgentLoopService } from '#/agent/loop/loop'; import { IAgentUserToolService } from '#/agent/userTool/userTool'; -import type { SubagentSuspendedEvent } from '@moonshot-ai/protocol'; import { IEventBus } from '#/app/event/eventBus'; import { IAgentProfileCatalogService } from '#/app/agentProfileCatalog/agentProfileCatalog'; import { applyProfilePromptPrefix } from '#/app/agentProfileCatalog/promptPrefix'; @@ -56,6 +55,12 @@ import { type AgentRunAttemptHandle, } from './agentRunBatch'; +export interface SubagentSuspendedEvent { + readonly type: 'subagent.suspended'; + readonly subagentId: string; + readonly reason: string; +} + declare module '#/app/event/eventBus' { interface DomainEventMap { 'subagent.suspended': SubagentSuspendedEvent; diff --git a/packages/agent-core-v2/src/tool/toolContract.ts b/packages/agent-core-v2/src/tool/toolContract.ts index 8d276dd4a..fedb66fb2 100644 --- a/packages/agent-core-v2/src/tool/toolContract.ts +++ b/packages/agent-core-v2/src/tool/toolContract.ts @@ -17,7 +17,7 @@ import type { ContentPart, ToolCall } from '#/app/llmProtocol/message'; import type { Tool } from '#/app/llmProtocol/tool'; -import type { ToolInputDisplay } from '@moonshot-ai/protocol'; +import type { ToolInputDisplay } from '#/tool/toolInputDisplay'; export type ExecutableToolOutput = string | ContentPart[]; diff --git a/packages/agent-core-v2/src/tool/toolInputDisplay.ts b/packages/agent-core-v2/src/tool/toolInputDisplay.ts new file mode 100644 index 000000000..1229c95fa --- /dev/null +++ b/packages/agent-core-v2/src/tool/toolInputDisplay.ts @@ -0,0 +1,87 @@ +/** + * `ToolInputDisplay` — structured UI hint describing a tool call's input, so + * approval panels and tool renderers can present it without re-deriving it + * from raw arguments. Carried by `RunnableToolExecution.display`, + * `ToolResult.display`, and the `tool.call.started` event. + */ +export type ToolInputDisplay = + | { + kind: 'command'; + command: string; + cwd?: string | undefined; + description?: string | undefined; + language?: 'bash' | undefined; + } + | { + kind: 'file_io'; + operation: 'read' | 'write' | 'edit' | 'glob' | 'grep'; + path: string; + detail?: string | undefined; + content?: string | undefined; + before?: string | undefined; + after?: string | undefined; + } + | { + kind: 'diff'; + path: string; + before: string; + after: string; + hunks?: number | undefined; + } + | { + kind: 'search'; + query: string; + scope?: string | undefined; + } + | { + kind: 'url_fetch'; + url: string; + method?: string | undefined; + } + | { + kind: 'agent_call'; + agent_name: string; + prompt: string; + background?: boolean | undefined; + } + | { + kind: 'skill_call'; + skill_name: string; + args?: string | undefined; + } + | { + kind: 'todo_list'; + items: { title: string; status: string }[]; + } + | { + kind: 'task'; + task_id: string; + status: string; + description: string; + task_kind?: string | undefined; + } + | { + kind: 'task_stop'; + task_id: string; + task_description: string; + } + | { + kind: 'plan_review'; + plan: string; + path?: string | undefined; + options?: readonly { label: string; description: string }[] | undefined; + } + | { + kind: 'goal_start'; + objective: string; + completionCriterion?: string | undefined; + // Current permission mode at approval time. The client uses it to pick the + // start menu (manual vs yolo); `auto` never reaches this display because it + // auto-approves the goal without a prompt. + mode: 'manual' | 'yolo'; + } + | { + kind: 'generic'; + summary: string; + detail?: unknown; + }; diff --git a/packages/agent-core-v2/test/agent/goal/goal.test.ts b/packages/agent-core-v2/test/agent/goal/goal.test.ts index d22c1520d..4978f056a 100644 --- a/packages/agent-core-v2/test/agent/goal/goal.test.ts +++ b/packages/agent-core-v2/test/agent/goal/goal.test.ts @@ -6,7 +6,7 @@ */ import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'; -import type { TurnEndedEvent } from '@moonshot-ai/protocol'; +import type { TurnEndedEvent } from '#/agent/loop/turnEvents'; import type { IDisposable } from '#/_base/di/lifecycle'; import { IAgentContextMemoryService } from '#/agent/contextMemory/contextMemory'; diff --git a/packages/agent-core-v2/test/agent/mcp/tools/auth.test.ts b/packages/agent-core-v2/test/agent/mcp/tools/auth.test.ts index 31f719858..bfa958df7 100644 --- a/packages/agent-core-v2/test/agent/mcp/tools/auth.test.ts +++ b/packages/agent-core-v2/test/agent/mcp/tools/auth.test.ts @@ -1,4 +1,4 @@ -import { MCP_OAUTH_AUTHORIZATION_URL_TOOL_UPDATE } from '@moonshot-ai/protocol'; +import { MCP_OAUTH_AUTHORIZATION_URL_TOOL_UPDATE } from '#/agent/mcp/tools/auth'; import { describe, expect, it } from 'vitest'; import { AlreadyAuthorizedError, type BeginAuthorizationResult, type McpOAuthService } from '#/agent/mcp/oauth/service'; diff --git a/packages/agent-core-v2/test/agent/permissionGate/permissionGate.test.ts b/packages/agent-core-v2/test/agent/permissionGate/permissionGate.test.ts index 0cfb64d2f..bd4245b84 100644 --- a/packages/agent-core-v2/test/agent/permissionGate/permissionGate.test.ts +++ b/packages/agent-core-v2/test/agent/permissionGate/permissionGate.test.ts @@ -34,7 +34,7 @@ import { ISessionWorkspaceContext } from '#/session/workspaceContext/workspaceCo import type { ToolCall } from '#/app/llmProtocol/message'; import { IEventBus } from '#/app/event/eventBus'; import { EventBusService } from '#/app/event/eventBusService'; -import type { ToolInputDisplay } from '@moonshot-ai/protocol'; +import type { ToolInputDisplay } from '#/tool/toolInputDisplay'; import { stubApprovalService } from '../../session/approval/stubs'; import { stubPermissionModeService } from '../permissionMode/stubs'; diff --git a/packages/agent-core-v2/test/agent/permissionPolicy/permissionPolicyService.test.ts b/packages/agent-core-v2/test/agent/permissionPolicy/permissionPolicyService.test.ts index c5743a3a3..c6d9bb7b6 100644 --- a/packages/agent-core-v2/test/agent/permissionPolicy/permissionPolicyService.test.ts +++ b/packages/agent-core-v2/test/agent/permissionPolicy/permissionPolicyService.test.ts @@ -3,7 +3,7 @@ import { tmpdir } from 'node:os'; import { join } from 'node:path'; import type { ToolCall } from '#/app/llmProtocol/message'; -import type { ToolInputDisplay } from '@moonshot-ai/protocol'; +import type { ToolInputDisplay } from '#/tool/toolInputDisplay'; import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'; import { DisposableStore } from '#/_base/di/lifecycle'; diff --git a/packages/agent-core-v2/test/agent/permissionPolicy/policies/exit-plan-mode-review-ask.test.ts b/packages/agent-core-v2/test/agent/permissionPolicy/policies/exit-plan-mode-review-ask.test.ts index a2196b584..7ad1627f4 100644 --- a/packages/agent-core-v2/test/agent/permissionPolicy/policies/exit-plan-mode-review-ask.test.ts +++ b/packages/agent-core-v2/test/agent/permissionPolicy/policies/exit-plan-mode-review-ask.test.ts @@ -1,5 +1,6 @@ import type { ToolCall } from '#/app/llmProtocol/message'; -import type { ApprovalResponse, ToolInputDisplay } from '@moonshot-ai/protocol'; +import type { ToolInputDisplay } from '#/tool/toolInputDisplay'; +import type { ApprovalResponse } from '#/session/approval/approval'; import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'; import { DisposableStore } from '#/_base/di/lifecycle'; diff --git a/packages/agent-core-v2/test/agent/permissionPolicy/policies/goal-start-review-ask.test.ts b/packages/agent-core-v2/test/agent/permissionPolicy/policies/goal-start-review-ask.test.ts index 8a4b8da26..81ff3cb57 100644 --- a/packages/agent-core-v2/test/agent/permissionPolicy/policies/goal-start-review-ask.test.ts +++ b/packages/agent-core-v2/test/agent/permissionPolicy/policies/goal-start-review-ask.test.ts @@ -1,5 +1,5 @@ import type { ToolCall } from '#/app/llmProtocol/message'; -import type { ToolInputDisplay } from '@moonshot-ai/protocol'; +import type { ToolInputDisplay } from '#/tool/toolInputDisplay'; import { describe, expect, it } from 'vitest'; import type { ResolvedToolExecutionHookContext } from '#/agent/toolExecutor/toolHooks'; diff --git a/packages/agent-core-v2/test/agent/toolExecutor/toolExecutor.test.ts b/packages/agent-core-v2/test/agent/toolExecutor/toolExecutor.test.ts index 9d0d52fe0..741abe5ab 100644 --- a/packages/agent-core-v2/test/agent/toolExecutor/toolExecutor.test.ts +++ b/packages/agent-core-v2/test/agent/toolExecutor/toolExecutor.test.ts @@ -1,5 +1,6 @@ import type { ToolCall } from '#/app/llmProtocol/message'; -import type { AgentEvent, ToolInputDisplay } from '@moonshot-ai/protocol'; +import type { DomainEvent } from '#/app/event/eventBus'; +import type { ToolInputDisplay } from '#/tool/toolInputDisplay'; import { afterEach, beforeEach, describe, expect, it } from 'vitest'; import { SyncDescriptor } from '#/_base/di/descriptors'; @@ -33,7 +34,7 @@ let ix: TestInstantiationService; let executor: IAgentToolExecutorService; let registry: IAgentToolRegistryService; let events: ToolExecutorEvent[]; -let protocolEvents: AgentEvent[]; +let protocolEvents: DomainEvent[]; let telemetryEvents: TelemetryRecord[]; let truncateForModel: IAgentToolResultTruncationService['truncateForModel']; @@ -56,7 +57,7 @@ beforeEach(() => { reg.defineInstance(IEventBus, { publish: (event: { type: string }) => { if (event.type.startsWith('tool.')) { - protocolEvents.push(event as unknown as AgentEvent); + protocolEvents.push(event as unknown as DomainEvent); } }, subscribe: (..._args: unknown[]) => ({ dispose: () => {} }), @@ -178,7 +179,7 @@ describe('AgentToolExecutorService', () => { note: 'Image compressed.', }); const protocolResult = protocolEvents.find( - (event): event is Extract => + (event): event is Extract => event.type === 'tool.result', ); expect(protocolResult).toMatchObject({ @@ -334,7 +335,7 @@ describe('AgentToolExecutorService', () => { }), ]); const toolCallEvent = protocolEvents.find( - (event): event is Extract => + (event): event is Extract => event.type === 'tool.call.started', ); expect(toolCallEvent?.args).toEqual({ x: 1 }); @@ -753,7 +754,7 @@ function eventTypes(): ToolExecutorEvent['type'][] { return events.map((event) => event.type); } -function protocolEventTypes(): AgentEvent['type'][] { +function protocolEventTypes(): DomainEvent['type'][] { return protocolEvents.map((event) => event.type); } @@ -761,13 +762,13 @@ function pairedToolCallIds(): { readonly calls: string[]; readonly results: stri return { calls: protocolEvents .filter( - (event): event is Extract => + (event): event is Extract => event.type === 'tool.call.started', ) .map((event) => event.toolCallId), results: protocolEvents .filter( - (event): event is Extract => + (event): event is Extract => event.type === 'tool.result', ) .map((event) => event.toolCallId), diff --git a/packages/agent-core-v2/test/session/approval/approval.test.ts b/packages/agent-core-v2/test/session/approval/approval.test.ts index 9b5e758bc..60fc01e30 100644 --- a/packages/agent-core-v2/test/session/approval/approval.test.ts +++ b/packages/agent-core-v2/test/session/approval/approval.test.ts @@ -1,4 +1,4 @@ -import type { ToolInputDisplay } from '@moonshot-ai/protocol'; +import type { ToolInputDisplay } from '#/tool/toolInputDisplay'; import { afterEach, beforeEach, describe, expect, it } from 'vitest'; import { SyncDescriptor } from '#/_base/di/descriptors'; diff --git a/packages/agent-core-v2/test/session/cron/cron-tools.test.ts b/packages/agent-core-v2/test/session/cron/cron-tools.test.ts index 8cc76fd64..e329ecfa3 100644 --- a/packages/agent-core-v2/test/session/cron/cron-tools.test.ts +++ b/packages/agent-core-v2/test/session/cron/cron-tools.test.ts @@ -1,6 +1,6 @@ import { describe, expect, it } from 'vitest'; -import type { CronJobOrigin } from '@moonshot-ai/protocol'; +import type { CronJobOrigin } from '#/agent/contextMemory/types'; import type { ExecutableTool, diff --git a/packages/agent-core-v2/test/session/sessionFs/fsWatchService.test.ts b/packages/agent-core-v2/test/session/sessionFs/fsWatchService.test.ts index 507764b21..08868dd71 100644 --- a/packages/agent-core-v2/test/session/sessionFs/fsWatchService.test.ts +++ b/packages/agent-core-v2/test/session/sessionFs/fsWatchService.test.ts @@ -17,7 +17,7 @@ import { IHostFsWatchService, } from '#/os/interface/hostFsWatch'; import { ISessionWorkspaceContext } from '#/session/workspaceContext/workspaceContext'; -import type { FsChangeEvent } from '@moonshot-ai/protocol'; +import type { FsChangeEvent } from '#/session/sessionFs/fsWatch'; import { ISessionFsWatchService } from '#/session/sessionFs/fsWatch'; import { SessionFsWatchService } from '#/session/sessionFs/fsWatchService'; diff --git a/packages/kap-server/src/transport/ws/v1/sessionEventBroadcaster.ts b/packages/kap-server/src/transport/ws/v1/sessionEventBroadcaster.ts index 14e44cdfb..053ca88e8 100644 --- a/packages/kap-server/src/transport/ws/v1/sessionEventBroadcaster.ts +++ b/packages/kap-server/src/transport/ws/v1/sessionEventBroadcaster.ts @@ -476,11 +476,7 @@ export class SessionEventBroadcaster { const disposables: IDisposable[] = [ eventBus.subscribe((event) => { let projected = event; - if ( - handle.id === MAIN_AGENT_ID && - event.type === 'agent.status.updated' && - event.phase === undefined - ) { + if (handle.id === MAIN_AGENT_ID && event.type === 'agent.status.updated') { const snapshot = readLegacyStatus(handle); if (snapshot !== undefined) { lastLegacyStatus = JSON.stringify(snapshot); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index cecd4b668..daf81b71c 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -508,9 +508,6 @@ importers: '@moonshot-ai/minidb': specifier: workspace:^ version: link:../minidb - '@moonshot-ai/protocol': - specifier: workspace:^ - version: link:../protocol '@mozilla/readability': specifier: ^0.6.0 version: 0.6.0