feat: prepare Zhinian desktop client for pilot release
This commit is contained in:
12
packages/kernel-adapter-openclaw/package.json
Normal file
12
packages/kernel-adapter-openclaw/package.json
Normal 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:*"
|
||||
}
|
||||
}
|
||||
|
||||
81
packages/kernel-adapter-openclaw/src/index.ts
Normal file
81
packages/kernel-adapter-openclaw/src/index.ts
Normal 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,
|
||||
};
|
||||
};
|
||||
|
||||
12
packages/kernel-context/package.json
Normal file
12
packages/kernel-context/package.json
Normal 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:*"
|
||||
}
|
||||
}
|
||||
|
||||
22
packages/kernel-context/src/index.ts
Normal file
22
packages/kernel-context/src/index.ts
Normal 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(),
|
||||
};
|
||||
}
|
||||
|
||||
9
packages/kernel-core/package.json
Normal file
9
packages/kernel-core/package.json
Normal 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"
|
||||
}
|
||||
|
||||
160
packages/kernel-core/src/index.ts
Normal file
160
packages/kernel-core/src/index.ts
Normal 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;
|
||||
|
||||
12
packages/skill-spec/package.json
Normal file
12
packages/skill-spec/package.json
Normal 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:*"
|
||||
}
|
||||
}
|
||||
|
||||
36
packages/skill-spec/src/index.ts
Normal file
36
packages/skill-spec/src/index.ts
Normal 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> }>;
|
||||
}
|
||||
|
||||
9
packages/skills-hotel-core/package.json
Normal file
9
packages/skills-hotel-core/package.json
Normal 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"
|
||||
}
|
||||
|
||||
28
packages/skills-hotel-core/src/index.ts
Normal file
28
packages/skills-hotel-core/src/index.ts
Normal 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}`,
|
||||
};
|
||||
}
|
||||
|
||||
9
packages/ui-kit/package.json
Normal file
9
packages/ui-kit/package.json
Normal 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"
|
||||
}
|
||||
|
||||
19
packages/ui-kit/src/index.ts
Normal file
19
packages/ui-kit/src/index.ts
Normal 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;
|
||||
|
||||
Reference in New Issue
Block a user