feat: prepare Zhinian desktop client for pilot release

This commit is contained in:
inman
2026-04-29 10:23:20 +08:00
parent f9361e686a
commit 47b83b79fc
149 changed files with 15341 additions and 3590 deletions

View File

@@ -0,0 +1,12 @@
{
"name": "@yinian/kernel-adapter-openclaw",
"version": "0.1.0",
"private": true,
"type": "module",
"main": "./src/index.ts",
"types": "./src/index.ts",
"dependencies": {
"@yinian/kernel-core": "workspace:*"
}
}

View File

@@ -0,0 +1,81 @@
import type {
Adapter,
AdapterFactory,
Conversation,
ConversationId,
ConversationPort,
ConversationStreamEvent,
CreateConversationInput,
Message,
SendMessageInput,
} from '@yinian/kernel-core';
export interface OpenClawAdapterConfig {
wsUrl: string;
token: string;
reconnect?: {
maxAttempts: number;
backoffMs: number;
};
}
class NotImplementedConversationPort implements ConversationPort {
async list(): Promise<Conversation[]> {
return [];
}
async get(_id: ConversationId): Promise<Conversation> {
throw new Error('OpenClaw conversation.get adapter is not implemented yet.');
}
async create(_input: CreateConversationInput): Promise<Conversation> {
throw new Error('OpenClaw conversation.create adapter is not implemented yet.');
}
async delete(_id: ConversationId): Promise<void> {
throw new Error('OpenClaw conversation.delete adapter is not implemented yet.');
}
async *send(_input: SendMessageInput): AsyncIterable<ConversationStreamEvent> {
yield {
type: 'error',
error: {
code: 'kernel_unavailable',
message: 'OpenClaw conversation.send adapter is not implemented yet.',
retryable: false,
},
};
}
async abort(_conversationId: ConversationId): Promise<void> {
throw new Error('OpenClaw conversation.abort adapter is not implemented yet.');
}
async history(_conversationId: ConversationId): Promise<Message[]> {
return [];
}
}
export const createOpenClawAdapter: AdapterFactory<OpenClawAdapterConfig> = (config): Adapter => {
const conversation = new NotImplementedConversationPort();
return {
info: { name: 'openclaw', kernelVersion: 'unknown' },
async connect() {
if (!config.wsUrl || !config.token) {
throw new Error('OpenClaw adapter requires wsUrl and token.');
}
},
async disconnect() {
// No persistent socket yet. The real implementation lands with the Gateway contract.
},
async health() {
return {
ready: false,
details: { wsUrl: config.wsUrl, reason: 'adapter_stub' },
};
},
conversation,
};
};

View File

@@ -0,0 +1,12 @@
{
"name": "@yinian/kernel-context",
"version": "0.1.0",
"private": true,
"type": "module",
"main": "./src/index.ts",
"types": "./src/index.ts",
"dependencies": {
"@yinian/kernel-core": "workspace:*"
}
}

View File

@@ -0,0 +1,22 @@
import type { Adapter } from '@yinian/kernel-core';
export interface KernelContext {
adapter: Adapter;
conversation: Adapter['conversation'];
connect(): Promise<void>;
disconnect(): Promise<void>;
health(): ReturnType<Adapter['health']>;
}
export function createKernelContext(input: { adapter: Adapter }): KernelContext {
const { adapter } = input;
return {
adapter,
conversation: adapter.conversation,
connect: () => adapter.connect(),
disconnect: () => adapter.disconnect(),
health: () => adapter.health(),
};
}

View File

@@ -0,0 +1,9 @@
{
"name": "@yinian/kernel-core",
"version": "0.1.0",
"private": true,
"type": "module",
"main": "./src/index.ts",
"types": "./src/index.ts"
}

View File

@@ -0,0 +1,160 @@
export type Brand<T, Name extends string> = T & { readonly __brand: Name };
export type UserId = Brand<string, 'UserId'>;
export type HotelId = Brand<string, 'HotelId'>;
export type ConversationId = Brand<string, 'ConversationId'>;
export type MessageId = Brand<string, 'MessageId'>;
export type SkillId = Brand<string, 'SkillId'>;
export type TaskId = Brand<string, 'TaskId'>;
export type ExecutionId = Brand<string, 'ExecutionId'>;
export interface User {
id: UserId;
name: string;
avatar?: string;
email?: string;
phone?: string;
hotels: HotelMembership[];
}
export interface HotelMembership {
hotelId: HotelId;
role: 'owner' | 'manager' | 'staff' | 'viewer';
}
export interface Hotel {
id: HotelId;
name: string;
brand?: string;
city: string;
ota: HotelOTABinding[];
}
export interface HotelOTABinding {
ota: 'ctrip' | 'meituan' | 'booking' | 'agoda' | 'qunar' | string;
externalId: string;
enabled: boolean;
}
export type MessageRole = 'user' | 'assistant' | 'system' | 'tool';
export interface Message {
id: MessageId;
conversationId: ConversationId;
role: MessageRole;
blocks: ContentBlock[];
createdAt: number;
}
export type ContentBlock =
| TextBlock
| ToolCallBlock
| ToolResultBlock
| ArtifactBlock
| ReasoningBlock;
export interface TextBlock {
type: 'text';
text: string;
}
export interface ReasoningBlock {
type: 'reasoning';
text: string;
}
export interface ToolCallBlock {
type: 'tool_call';
toolCallId: string;
name: string;
input: Record<string, unknown>;
}
export interface ToolResultBlock {
type: 'tool_result';
toolCallId: string;
status: 'success' | 'failed';
output: unknown;
error?: KernelError;
}
export interface ArtifactBlock {
type: 'artifact';
artifact: Artifact;
}
export type Artifact =
| { kind: 'markdown'; id: string; title?: string; content: string }
| { kind: 'table'; id: string; title?: string; columns: string[]; rows: unknown[][] }
| { kind: 'chart'; id: string; title?: string; spec: unknown }
| { kind: 'image'; id: string; title?: string; url: string }
| { kind: 'file'; id: string; title?: string; url: string; mime: string };
export interface KernelError {
code: KernelErrorCode;
message: string;
retryable: boolean;
cause?: unknown;
}
export type KernelErrorCode =
| 'kernel_unavailable'
| 'kernel_timeout'
| 'kernel_aborted'
| 'auth_required'
| 'permission_denied'
| 'skill_not_found'
| 'skill_execution_failed'
| 'invalid_input'
| 'rate_limited'
| 'unknown';
export interface Conversation {
id: ConversationId;
title: string;
hotelId: HotelId;
createdAt: number;
updatedAt: number;
messageCount: number;
}
export interface CreateConversationInput {
hotelId: HotelId;
title?: string;
}
export interface SendMessageInput {
conversationId: ConversationId;
content: TextBlock | { type: 'invoke_skill'; skillId: SkillId; input: Record<string, unknown> };
}
export type ConversationStreamEvent =
| { type: 'message_start'; message: Pick<Message, 'id' | 'role' | 'createdAt'> }
| { type: 'text_delta'; messageId: MessageId; delta: string }
| { type: 'reasoning_delta'; messageId: MessageId; delta: string }
| { type: 'tool_call'; messageId: MessageId; block: ToolCallBlock }
| { type: 'tool_result'; messageId: MessageId; block: ToolResultBlock }
| { type: 'artifact'; messageId: MessageId; artifact: Artifact }
| { type: 'message_complete'; messageId: MessageId; message: Message }
| { type: 'error'; error: KernelError };
export interface ConversationPort {
list(query?: { hotelId?: HotelId; limit?: number }): Promise<Conversation[]>;
get(id: ConversationId): Promise<Conversation>;
create(input: CreateConversationInput): Promise<Conversation>;
delete(id: ConversationId): Promise<void>;
send(input: SendMessageInput): AsyncIterable<ConversationStreamEvent>;
abort(conversationId: ConversationId): Promise<void>;
history(conversationId: ConversationId, opts?: { before?: MessageId; limit?: number }): Promise<Message[]>;
}
export interface Adapter {
readonly info: { name: string; kernelVersion: string };
connect(): Promise<void>;
disconnect(): Promise<void>;
health(): Promise<{ ready: boolean; details?: Record<string, unknown> }>;
readonly conversation: ConversationPort;
}
export type AdapterFactory<Config> = (config: Config) => Adapter;

View File

@@ -0,0 +1,12 @@
{
"name": "@yinian/skill-spec",
"version": "0.1.0",
"private": true,
"type": "module",
"main": "./src/index.ts",
"types": "./src/index.ts",
"dependencies": {
"@yinian/kernel-core": "workspace:*"
}
}

View File

@@ -0,0 +1,36 @@
import type { Artifact, Hotel, User } from '@yinian/kernel-core';
export interface SkillManifest {
spec_version: '0.1';
id: string;
name: string;
version: string;
author: string;
category: string;
description: string;
required_capabilities: string[];
permissions: SkillPermissions;
}
export interface SkillPermissions {
network?: string[];
filesystem?: 'none' | 'read' | 'read_write';
shell?: boolean;
}
export interface SkillRunContext {
hotel: Hotel;
user: User;
emit: {
progress(event: { phase: string; ratio?: number; message?: string }): void;
log(level: 'info' | 'warn' | 'error', message: string): void;
artifact(artifact: Artifact): void;
};
abortSignal: AbortSignal;
}
export interface SkillRunResult {
output: Record<string, unknown>;
notifications?: Array<{ template: string; vars: Record<string, unknown> }>;
}

View File

@@ -0,0 +1,9 @@
{
"name": "@yinian/skills-hotel-core",
"version": "0.1.0",
"private": true,
"type": "module",
"main": "./src/index.ts",
"types": "./src/index.ts"
}

View File

@@ -0,0 +1,28 @@
export interface PriceAnomalyRuleInput {
roomType: string;
date: string;
price: number;
priceFloor?: number;
}
export type PriceAnomalyKind = 'below_floor' | 'large_gap' | 'unavailable' | 'missing_room' | 'parse_suspect';
export interface PriceAnomaly {
kind: PriceAnomalyKind;
roomType: string;
date: string;
message: string;
}
export function detectPriceFloorAnomaly(input: PriceAnomalyRuleInput): PriceAnomaly | null {
if (typeof input.priceFloor !== 'number') return null;
if (input.price >= input.priceFloor) return null;
return {
kind: 'below_floor',
roomType: input.roomType,
date: input.date,
message: `价格 ${input.price} 低于底价 ${input.priceFloor}`,
};
}

View File

@@ -0,0 +1,9 @@
{
"name": "@yinian/ui-kit",
"version": "0.1.0",
"private": true,
"type": "module",
"main": "./src/index.ts",
"types": "./src/index.ts"
}

View File

@@ -0,0 +1,19 @@
export const yinianTokens = {
color: {
brand: '#1A56DB',
success: '#16A34A',
warning: '#F59E0B',
danger: '#DC2626',
surface: '#FFFFFF',
mutedSurface: '#F8FAFC',
border: '#E2E8F0',
text: '#0F172A',
mutedText: '#64748B',
},
radius: {
sm: '4px',
md: '6px',
lg: '8px',
},
} as const;