refactor: adapt to WaterFlow framework - zero source changes required

- Remove duplicate platform layer and tool interface definitions
- Add BFS breadth-first search with depth annotation (sync from main)
- Use WaterFlow platform.fs for binary storage instead of custom storage
- Add installTrulyMEM() one-line registration function
- Update SKILL.md with 10 complete operations
- Add waterflow.d.ts type declarations
- Update bilingual README with simplified installation guide
- Build passes with 0 errors
This commit is contained in:
root
2026-04-16 11:48:58 +08:00
parent e8c275c8f8
commit 13e3c662ac
17 changed files with 2822 additions and 356 deletions

View File

@ -0,0 +1,38 @@
export interface TrulyMEMConfig {
dbPath: string;
autoSave: boolean;
debug: boolean;
}
export const DEFAULT_CONFIG: TrulyMEMConfig = {
dbPath: '.trulymem/graph_memory.db',
autoSave: true,
debug: false
};
let globalConfig: TrulyMEMConfig = { ...DEFAULT_CONFIG };
export function initConfig(config: Partial<TrulyMEMConfig> = {}): TrulyMEMConfig {
globalConfig = { ...DEFAULT_CONFIG, ...config };
return globalConfig;
}
export function getConfig(): TrulyMEMConfig {
return globalConfig;
}
export function setDbPath(dbPath: string): void {
globalConfig.dbPath = dbPath;
}
export function getDbPath(): string {
return globalConfig.dbPath;
}
export function setAutoSave(autoSave: boolean): void {
globalConfig.autoSave = autoSave;
}
export function isAutoSave(): boolean {
return globalConfig.autoSave;
}

View File

@ -1,53 +1,159 @@
import initSqlJs, { type Database as SqlJsDatabase } from 'sql.js';
import type { Entity, Relation, RecallParams, CommitParams, PurgeParams, RecallResult, CommitResult, PurgeResult, MemoryStats } from './types';
import { getConfig } from './config';
export class GraphDatabase {
private entities: Map<string, Entity> = new Map();
private relations: Map<string, Relation> = new Map();
private db: SqlJsDatabase | null = null;
private sessionId: string;
private initPromise: Promise<void> | null = null;
private _platform: any = null;
constructor(sessionId?: string) {
this.sessionId = sessionId || `session-${Date.now()}`;
this.initPromise = this.initDatabase();
}
private async initDatabase(): Promise<void> {
const SQL = await initSqlJs();
const { getPlatform } = await import('waterflow/platform');
this._platform = getPlatform();
try {
const dbPath = this._platform.path.join(this._platform.getCwd(), getConfig().dbPath);
const exists = await this._platform.fs.exists(dbPath);
if (exists) {
const data = await this._platform.fs.readFile(dbPath, { encoding: 'binary' });
this.db = new SQL.Database(data as Uint8Array);
} else {
this.db = new SQL.Database();
}
} catch {
this.db = new SQL.Database();
}
this.createTables();
}
async save(): Promise<void> {
if (!this.db || !this._platform) return;
try {
const platform = this._platform;
const dbPath = platform.path.join(platform.getCwd(), getConfig().dbPath);
const dir = platform.path.dirname(dbPath);
const dirExists = await platform.fs.exists(dir);
if (!dirExists) {
await platform.fs.mkdir(dir, true);
}
const data = this.db.export();
await platform.fs.writeFile(dbPath, new Uint8Array(data), { encoding: 'binary' });
} catch (error) {
this._platform.getLogger().error(`[GraphDatabase] Save failed: ${error}`);
}
}
private createTables(): void {
if (!this.db) return;
this.db.run(`
CREATE TABLE IF NOT EXISTS entities (
id TEXT PRIMARY KEY,
name TEXT NOT NULL,
type TEXT DEFAULT 'unknown',
mention_count INTEGER DEFAULT 1,
created_at TEXT NOT NULL,
updated_at TEXT NOT NULL
)
`);
this.db.run(`
CREATE TABLE IF NOT EXISTS relations (
id TEXT PRIMARY KEY,
source_id TEXT NOT NULL,
target_id TEXT NOT NULL,
relation_type TEXT NOT NULL,
confidence REAL DEFAULT 1.0,
status TEXT DEFAULT 'active',
session_id TEXT NOT NULL,
turn_id INTEGER DEFAULT 0,
created_at TEXT NOT NULL,
updated_at TEXT NOT NULL,
date_bucket TEXT NOT NULL,
FOREIGN KEY (source_id) REFERENCES entities(id),
FOREIGN KEY (target_id) REFERENCES entities(id)
)
`);
this.db.run(`CREATE INDEX IF NOT EXISTS idx_entity_name ON entities(name)`);
this.db.run(`CREATE INDEX IF NOT EXISTS idx_entity_type ON entities(type)`);
this.db.run(`CREATE INDEX IF NOT EXISTS idx_relation_source ON relations(source_id)`);
this.db.run(`CREATE INDEX IF NOT EXISTS idx_relation_target ON relations(target_id)`);
this.db.run(`CREATE INDEX IF NOT EXISTS idx_relation_type ON relations(relation_type)`);
this.db.run(`CREATE INDEX IF NOT EXISTS idx_relation_status ON relations(status)`);
}
private ensureInit(): void {
if (!this.db) {
throw new Error('Database not initialized');
}
}
async recall(params: RecallParams): Promise<RecallResult> {
const { queryIntent, seedEntities, sessionFilter } = params;
await this.initPromise;
this.ensureInit();
const { queryIntent, seedEntities, depth = 2, sessionFilter } = params;
const keywords = queryIntent.split(/[,\s]+/).filter(k => k.length > 0);
const entities: Entity[] = [];
const relations: Relation[] = [];
const entityIds = new Set<string>();
for (const keyword of keywords) {
const lowerKeyword = keyword.toLowerCase();
for (const [_, entity] of this.entities) {
if (entity.name.toLowerCase().includes(lowerKeyword)) {
if (!entityIds.has(entity.id)) {
entityIds.add(entity.id);
entities.push(entity);
if (!this.db) return { entities, relations, message: 'Database not ready' };
if (!keywords.length && !seedEntities?.length) {
const rows = this.db.exec('SELECT * FROM entities ORDER BY mention_count DESC LIMIT 50');
if (rows.length > 0) {
const columns = rows[0].columns;
for (const row of rows[0].values) {
const obj = this.rowToObject(columns, row);
const id = obj.id as string;
entityIds.add(id);
entities.push(this.rowToEntity(obj, 0));
}
}
} else {
for (const keyword of keywords) {
const stmt = this.db.prepare('SELECT * FROM entities WHERE LOWER(name) LIKE ? LIMIT 100');
stmt.bind([`%${keyword.toLowerCase()}%`]);
while (stmt.step()) {
const row = stmt.getAsObject();
const id = row.id as string;
if (!entityIds.has(id)) {
entityIds.add(id);
entities.push(this.rowToEntity(row, 0));
}
}
stmt.free();
}
}
if (seedEntities && seedEntities.length > 0) {
if (seedEntities?.length) {
for (const seedName of seedEntities) {
for (const [_, entity] of this.entities) {
if (entity.name.toLowerCase() === seedName.toLowerCase()) {
if (!entityIds.has(entity.id)) {
entityIds.add(entity.id);
entities.push(entity);
}
const stmt = this.db.prepare('SELECT * FROM entities WHERE LOWER(name) = ? LIMIT 1');
stmt.bind([seedName.toLowerCase()]);
if (stmt.step()) {
const row = stmt.getAsObject();
const id = row.id as string;
if (!entityIds.has(id)) {
entityIds.add(id);
entities.push(this.rowToEntity(row, 0));
}
}
stmt.free();
}
}
for (const [_, relation] of this.relations) {
if (entityIds.has(relation.sourceId) || entityIds.has(relation.targetId)) {
if (relation.status === 'active') {
if (!sessionFilter || relation.sessionId === sessionFilter) {
relations.push(relation);
}
}
this.bfsExpand(entityIds, entities, relations, depth, sessionFilter);
for (const entity of entities) {
if (entity.depth === undefined) {
entity.depth = 0;
}
}
@ -58,132 +164,268 @@ export class GraphDatabase {
};
}
private bfsExpand(
seedIds: Set<string>,
entities: Entity[],
relations: Relation[],
maxDepth: number,
sessionFilter?: string
): void {
if (!this.db) return;
const visited = new Set(seedIds);
let currentLayer = new Set(seedIds);
const entityDepths: Record<string, number> = {};
for (const id of seedIds) {
entityDepths[id] = 0;
}
for (let layer = 0; layer < maxDepth; layer++) {
if (!currentLayer.size) break;
const placeholders = Array(currentLayer.size).fill('?').join(',');
let sql = `
SELECT r.id, r.source_id, r.target_id,
e1.name as source_name, e2.name as target_name,
r.relation_type, r.confidence, r.session_id,
r.turn_id, r.created_at, r.updated_at, r.status, r.date_bucket
FROM relations r
JOIN entities e1 ON r.source_id = e1.id
JOIN entities e2 ON r.target_id = e2.id
WHERE (r.source_id IN (${placeholders}) OR r.target_id IN (${placeholders}))
AND r.status = 'active'
`;
const params: (string | number)[] = [];
for (const id of currentLayer) { params.push(id); }
for (const id of currentLayer) { params.push(id); }
if (sessionFilter) {
sql += ` AND r.session_id = ?`;
params.push(sessionFilter);
}
const stmt = this.db.prepare(sql);
stmt.bind(params);
const nextLayer = new Set<string>();
const layerRelations: Relation[] = [];
while (stmt.step()) {
const row = stmt.getAsObject();
const sourceId = row.source_id as string;
const targetId = row.target_id as string;
const sourceDepth = entityDepths[sourceId] ?? layer;
const targetDepth = entityDepths[targetId] ?? layer;
const relationDepth = Math.max(sourceDepth, targetDepth) + 1;
layerRelations.push({
id: row.id as string,
sourceId,
targetId,
relationType: row.relation_type as string,
confidence: row.confidence as number,
status: row.status as Relation['status'],
sessionId: row.session_id as string,
turnId: row.turn_id as number,
createdAt: new Date(row.created_at as string),
updatedAt: new Date(row.updated_at as string),
dateBucket: row.date_bucket as string,
depth: relationDepth
});
if (!visited.has(sourceId)) {
visited.add(sourceId);
nextLayer.add(sourceId);
entityDepths[sourceId] = layer + 1;
}
if (!visited.has(targetId)) {
visited.add(targetId);
nextLayer.add(targetId);
entityDepths[targetId] = layer + 1;
}
}
stmt.free();
relations.push(...layerRelations);
if (nextLayer.size) {
const placeholders = Array(nextLayer.size).fill('?').join(',');
const entityStmt = this.db.prepare(
`SELECT * FROM entities WHERE id IN (${placeholders})`
);
entityStmt.bind(Array.from(nextLayer));
while (entityStmt.step()) {
const row = entityStmt.getAsObject();
const id = row.id as string;
entities.push(this.rowToEntity(row, entityDepths[id] ?? layer + 1));
}
entityStmt.free();
}
currentLayer = nextLayer;
}
}
async commit(params: CommitParams): Promise<CommitResult> {
await this.initPromise;
this.ensureInit();
const { triplets, sessionId, turnId } = params;
let createdEntities = 0;
let createdRelations = 0;
if (!this.db) return { createdEntities: 0, createdRelations: 0 };
for (const triplet of triplets) {
const sourceId = this.upsertEntity(triplet.subject);
const targetId = this.upsertEntity(triplet.object);
const relationId = this.generateId();
const now = new Date();
const relation: Relation = {
id: relationId,
sourceId,
targetId,
relationType: triplet.relation,
confidence: triplet.confidence || 1.0,
status: 'active',
sessionId: sessionId || this.sessionId,
turnId: turnId || 0,
createdAt: now,
updatedAt: now,
dateBucket: this.getDateBucket(now)
};
const now = new Date().toISOString();
this.db.run(
`INSERT INTO relations (id, source_id, target_id, relation_type, confidence, status, session_id, turn_id, created_at, updated_at, date_bucket)
VALUES (?, ?, ?, ?, ?, 'active', ?, ?, ?, ?, ?)`,
[relationId, sourceId, targetId, triplet.relation, triplet.confidence ?? 1.0, sessionId ?? this.sessionId, turnId ?? 0, now, now, new Date().toISOString().split('T')[0]]
);
this.relations.set(relationId, relation);
createdEntities += 2;
createdRelations++;
}
if (getConfig().autoSave) {
await this.save();
}
return { createdEntities, createdRelations };
}
async purge(params: PurgeParams): Promise<PurgeResult> {
await this.initPromise;
this.ensureInit();
const { criteria, mode = 'soft' } = params;
let deleted = 0;
for (const [id, relation] of this.relations) {
if (relation.status !== 'active') continue;
if (!this.db) return { deleted: 0, mode };
if (!criteria) {
continue;
}
const conditions: string[] = [];
const queryParams: (string | number)[] = [];
let matches = true;
if (criteria.subject) {
const sourceEntity = this.entities.get(relation.sourceId);
matches = sourceEntity?.name.toLowerCase() === criteria.subject.toLowerCase();
}
if (matches && criteria.target) {
const targetEntity = this.entities.get(relation.targetId);
matches = targetEntity?.name.toLowerCase() === criteria.target.toLowerCase();
}
if (matches && criteria.relation) {
matches = relation.relationType.toLowerCase() === criteria.relation.toLowerCase();
}
if (matches && criteria.sessionId) {
matches = relation.sessionId === criteria.sessionId;
if (criteria?.subject) {
const stmt = this.db.prepare('SELECT id FROM entities WHERE LOWER(name) = ?');
stmt.bind([criteria.subject.toLowerCase()]);
if (stmt.step()) {
const row = stmt.getAsObject();
conditions.push(`source_id = ?`);
queryParams.push(row.id as string);
}
stmt.free();
}
if (matches) {
if (mode === 'hard') {
this.relations.delete(id);
} else {
relation.status = 'deleted';
relation.updatedAt = new Date();
}
deleted++;
if (criteria?.target) {
const stmt = this.db.prepare('SELECT id FROM entities WHERE LOWER(name) = ?');
stmt.bind([criteria.target.toLowerCase()]);
if (stmt.step()) {
const row = stmt.getAsObject();
conditions.push(`target_id = ?`);
queryParams.push(row.id as string);
}
stmt.free();
}
if (criteria?.relation) {
conditions.push(`relation_type = ?`);
queryParams.push(criteria.relation);
}
if (!conditions.length) {
return { deleted: 0, mode, message: '无删除条件' };
}
const whereClause = conditions.join(' AND ');
const countStmt = this.db.prepare(`SELECT COUNT(*) as cnt FROM relations WHERE ${whereClause} AND status = 'active'`);
countStmt.bind(queryParams);
if (countStmt.step()) {
const row = countStmt.getAsObject();
deleted = row.cnt as number;
}
countStmt.free();
if (mode === 'hard') {
this.db.run(`DELETE FROM relations WHERE ${whereClause} AND status = 'active'`, queryParams);
} else {
this.db.run(
`UPDATE relations SET status = 'deleted', updated_at = ? WHERE ${whereClause} AND status = 'active'`,
[new Date().toISOString(), ...queryParams]
);
}
if (getConfig().autoSave && deleted > 0) {
await this.save();
}
return { deleted, mode };
}
async introspect(): Promise<MemoryStats> {
let entityCount = 0;
for (const [_, entity] of this.entities) {
if (!this.isEntityDeleted(entity.id)) entityCount++;
}
await this.initPromise;
this.ensureInit();
let relationCount = 0;
for (const [_, relation] of this.relations) {
if (relation.status === 'active') relationCount++;
}
if (!this.db) return { entityCount: 0, relationCount: 0, sessionId: this.sessionId };
const entityCount = (this.db.exec('SELECT COUNT(*) FROM entities')[0]?.values[0]?.[0] as number) ?? 0;
const relationCount = (this.db.exec("SELECT COUNT(*) FROM relations WHERE status = 'active'")[0]?.values[0]?.[0] as number) ?? 0;
return { entityCount, relationCount, sessionId: this.sessionId };
}
private upsertEntity(name: string): string {
for (const [id, entity] of this.entities) {
if (entity.name === name && !this.isEntityDeleted(id)) {
entity.mentionCount++;
entity.updatedAt = new Date();
return id;
}
if (!this.db) return this.generateId();
const existingStmt = this.db.prepare('SELECT id, mention_count FROM entities WHERE LOWER(name) = ?');
existingStmt.bind([name.toLowerCase()]);
if (existingStmt.step()) {
const row = existingStmt.getAsObject();
const id = row.id as string;
this.db.run('UPDATE entities SET mention_count = ?, updated_at = ? WHERE id = ?', [(row.mention_count as number) + 1, new Date().toISOString(), id]);
existingStmt.free();
return id;
}
existingStmt.free();
const id = this.generateId();
const now = new Date();
const entity: Entity = {
id,
name,
type: 'unknown',
mentionCount: 1,
createdAt: now,
updatedAt: now
};
this.entities.set(id, entity);
const now = new Date().toISOString();
this.db.run(
'INSERT INTO entities (id, name, type, mention_count, created_at, updated_at) VALUES (?, ?, ?, 1, ?, ?)',
[id, name, 'unknown', now, now]
);
return id;
}
private isEntityDeleted(entityId: string): boolean {
for (const [_, relation] of this.relations) {
if ((relation.sourceId === entityId || relation.targetId === entityId) && relation.status === 'deleted') {
return true;
}
private rowToEntity(row: Record<string, unknown>, depth?: number): Entity {
const entity: Entity = {
id: row.id as string,
name: row.name as string,
type: (row.type as string) ?? 'unknown',
mentionCount: row.mention_count as number,
createdAt: new Date(row.created_at as string),
updatedAt: new Date(row.updated_at as string)
};
if (depth !== undefined) {
entity.depth = depth;
}
return false;
return entity;
}
private rowToObject(columns: string[], values: unknown[]): Record<string, unknown> {
const obj: Record<string, unknown> = {};
columns.forEach((col, i) => { obj[col] = values[i]; });
return obj;
}
private generateId(): string {
return `${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
}
private getDateBucket(date: Date): string {
return date.toISOString().split('T')[0] ?? '';
if (this._platform?.globals?.randomUUID) {
return this._platform.globals.randomUUID();
}
return `ent-${Date.now()}-${Math.random().toString(36).slice(2, 9)}`;
}
setSessionId(sessionId: string): void {
@ -193,4 +435,11 @@ export class GraphDatabase {
getSessionId(): string {
return this.sessionId;
}
close(): void {
if (this.db) {
this.db.close();
this.db = null;
}
}
}

View File

@ -1,3 +1,4 @@
export * from './types';
export * from './config';
export * from './graph_database';
export * from './memory_service';
export * from './memory_service';

View File

@ -5,6 +5,7 @@ export interface Entity {
mentionCount: number;
createdAt: Date;
updatedAt: Date;
depth?: number; // BFS 搜索深度标注
}
export type RelationStatus = 'active' | 'deleted' | 'archived' | 'superseded';
@ -21,6 +22,7 @@ export interface Relation {
createdAt: Date;
updatedAt: Date;
dateBucket: string;
depth?: number; // BFS 搜索深度标注
}
export interface Triplet {
@ -71,6 +73,7 @@ export interface CommitResult {
export interface PurgeResult {
deleted: number;
mode: string;
message?: string;
}
export type TaskState = '进行中' | '已完成' | '已暂停' | '已取消';

View File

@ -1,4 +1,4 @@
import type { Tool, ToolCategory, PermissionLevel, ToolInputSchema, ToolExecutionContext, ToolOutput } from '../tool_interface';
import type { Tool, ToolCategory, PermissionLevel, ToolInputSchema, ToolOutput, ToolInput, ToolExecutionContext } from 'waterflow/runtime/core/tools/tool_interface';
import { GraphDatabase } from '../../graph_memory/graph_database';
import { MemoryService } from '../../graph_memory/memory_service';
@ -8,7 +8,7 @@ const GRAPH_MEMORY_TOOL_DESCRIPTION = `图记忆工具 - 让 AI 拥有真正的
操作:
- recall: 检索记忆
- commit: 写入记忆
- commit: 写入记忆
- purge: 删除记忆
- introspect: 查看状态
- persona_update/clear: 人设管理
@ -20,6 +20,7 @@ export class GraphMemoryTool implements Tool {
readonly description = GRAPH_MEMORY_TOOL_DESCRIPTION;
readonly category: ToolCategory = 'analysis';
readonly permissionLevel: PermissionLevel = 'safe';
readonly alwaysLoad = true;
readonly inputSchema: ToolInputSchema = {
type: 'object',
@ -100,14 +101,18 @@ export class GraphMemoryTool implements Tool {
this.service = new MemoryService(this.db);
}
async handler(params: Record<string, unknown>, _context: ToolExecutionContext): Promise<ToolOutput> {
async handler(params: ToolInput, context: ToolExecutionContext): Promise<ToolOutput> {
const action = params.action as string;
const actionParams = params.params as Record<string, unknown>;
const logger = context?.logger;
try {
logger?.info(`[GraphMemoryTool] Executing action: ${action}`);
const result = await this.executeAction(action, actionParams);
logger?.info(`[GraphMemoryTool] Action ${action} completed successfully`);
return JSON.stringify({ success: true, data: result }, null, 2);
} catch (error) {
logger?.error(`[GraphMemoryTool] Action ${action} failed:`, error);
return JSON.stringify({
success: false,
error: {

View File

@ -0,0 +1,19 @@
import type { Tool } from 'waterflow/runtime/core/tools/tool_interface';
import type { Platform } from 'waterflow/platform/types';
import { GraphMemoryTool, createGraphMemoryTool } from './graph_memory_tool';
export { GraphMemoryTool, createGraphMemoryTool };
export function registerGraphMemoryTool(
registry: { register: (tool: Tool) => void },
sessionId?: string
): void {
registry.register(createGraphMemoryTool(sessionId));
}
export function installTrulyMEM(platform: Platform, sessionId?: string) {
const { initializeToolRegistry } = require('waterflow/runtime/core/tools/builtin');
const registry = initializeToolRegistry(platform);
registerGraphMemoryTool(registry, sessionId);
return registry;
}

View File

@ -1,59 +0,0 @@
export type ToolCategory = 'file' | 'code' | 'search' | 'execute' | 'network' | 'analysis' | 'generation' | 'communication' | 'mcp' | 'custom';
export type PermissionLevel = 'safe' | 'moderate' | 'dangerous' | 'restricted';
export type SchemaType = 'string' | 'number' | 'integer' | 'boolean' | 'array' | 'object';
export interface SchemaProperty {
type: SchemaType;
description: string;
enum?: string[];
minimum?: number;
maximum?: number;
minLength?: number;
maxLength?: number;
pattern?: string;
default?: unknown;
examples?: unknown[];
items?: SchemaProperty;
properties?: Record<string, SchemaProperty>;
}
export interface ToolInputSchema {
type: 'object';
properties: Record<string, SchemaProperty>;
required?: string[];
additionalProperties?: boolean;
}
export interface ToolOutputSchema {
type: 'object';
properties: Record<string, SchemaProperty>;
format?: 'json' | 'text' | 'markdown' | 'binary';
maxSize?: number;
maxLines?: number;
}
export type ToolInput = Record<string, unknown>;
export type ToolOutput = string | Record<string, unknown> | void;
export interface ToolExecutionContext {
toolCallId: string;
workingDirectory: string;
abortController: { signal: AbortSignal };
config: { timeout?: number };
logger: { info: (...args: unknown[]) => void; warn: (...args: unknown[]) => void; error: (...args: unknown[]) => void; debug: (...args: unknown[]) => void };
}
export type ToolHandler = (params: ToolInput, context: ToolExecutionContext) => Promise<ToolOutput>;
export interface Tool {
readonly id: string;
readonly name: string;
readonly description: string;
readonly category: ToolCategory;
readonly inputSchema: ToolInputSchema;
readonly outputSchema?: ToolOutputSchema;
readonly handler: ToolHandler;
readonly permissionLevel: PermissionLevel;
}

29
ts/src/types/sql.js.d.ts vendored Normal file
View File

@ -0,0 +1,29 @@
declare module 'sql.js' {
export interface Database {
run(sql: string, params?: (string | number | null | Uint8Array)[]): void;
exec(sql: string): QueryExecResult[];
prepare(sql: string): Statement;
export(): Uint8Array;
close(): void;
}
export interface Statement {
bind(params?: (string | number | null | Uint8Array)[]): boolean;
step(): boolean;
getAsObject(): Record<string, unknown>;
free(): boolean;
}
export interface QueryExecResult {
columns: string[];
values: (string | number | null | Uint8Array)[][];
}
export interface SqlJsStatic {
Database: new (data?: ArrayLike<number>) => Database;
}
export default function initSqlJs(config?: {
locateFile?: (file: string) => string;
}): Promise<SqlJsStatic>;
}

193
ts/src/types/waterflow.d.ts vendored Normal file
View File

@ -0,0 +1,193 @@
declare module 'waterflow/platform' {
import type { Platform } from 'waterflow/platform/types';
export function getPlatform(): Platform;
export function hasCapability(capability: string): boolean;
export function initPlatform(options?: any): Platform;
export function resetPlatform(): void;
}
declare module 'waterflow/platform/types' {
export interface PlatformAbortSignal {
readonly aborted: boolean;
readonly reason?: unknown;
addEventListener(type: 'abort', listener: () => void): void;
removeEventListener(type: 'abort', listener: () => void): void;
}
export interface PlatformAbortController {
readonly signal: PlatformAbortSignal;
abort(reason?: unknown): void;
}
export interface PathOperations {
join(...paths: string[]): string;
dirname(p: string): string;
basename(p: string, ext?: string): string;
extname(p: string): string;
normalize(p: string): string;
isAbsolute(p: string): boolean;
resolve(...paths: string[]): string;
relative(from: string, to: string): string;
}
export interface FileReadOptions {
encoding?: 'utf-8' | 'binary';
start?: number;
end?: number;
}
export interface FileWriteOptions {
encoding?: 'utf-8' | 'binary';
append?: boolean;
}
export interface FileInfo {
path: string;
name: string;
isFile: boolean;
isDirectory: boolean;
size: number;
modifiedTime: number;
createdTime: number;
}
export interface FileSystemOperations {
readFile(filePath: string, options?: FileReadOptions): Promise<string | ArrayBuffer>;
writeFile(filePath: string, data: string | ArrayBuffer, options?: FileWriteOptions): Promise<void>;
appendFile(filePath: string, data: string, options?: FileWriteOptions): Promise<void>;
deleteFile(filePath: string): Promise<void>;
exists(filePath: string): Promise<boolean>;
stat(filePath: string): Promise<FileInfo>;
readdir(dirPath: string): Promise<FileInfo[]>;
mkdir(dirPath: string, recursive?: boolean): Promise<void>;
rmdir(dirPath: string, recursive?: boolean): Promise<void>;
copy(src: string, dest: string): Promise<void>;
move(src: string, dest: string): Promise<void>;
}
export interface ProcessResult {
exitCode: number;
stdout: string;
stderr: string;
signal?: string;
}
export interface ProcessOptions {
cwd?: string;
env?: Record<string, string>;
timeout?: number;
maxBuffer?: number;
}
export interface ProcessOperations {
exec(command: string, options?: ProcessOptions): Promise<ProcessResult>;
execFile(file: string, args: string[], options?: ProcessOptions): Promise<ProcessResult>;
}
export interface StorageOperations {
get(key: string): Promise<string | null>;
set(key: string, value: string, ttl?: number): Promise<void>;
delete(key: string): Promise<void>;
clear(): Promise<void>;
keys(): Promise<string[]>;
}
export interface PlatformGlobals {
TextEncoder: typeof TextEncoder;
TextDecoder: typeof TextDecoder;
URL: typeof URL;
randomUUID(): string;
now(): number;
btoa(data: string): string;
atob(data: string): string;
}
export interface Logger {
info(msg: string, ...args: unknown[]): void;
warn(msg: string, ...args: unknown[]): void;
error(msg: string, ...args: unknown[]): void;
debug(msg: string, ...args: unknown[]): void;
}
export interface Platform {
readonly path: PathOperations;
readonly fs: FileSystemOperations;
readonly process: ProcessOperations;
readonly storage: StorageOperations;
readonly globals: PlatformGlobals;
getInfo(): { runtime: string; os: string; version: string; arch: string; hostname: string };
createAbortController(): PlatformAbortController;
getEnv(key: string): string | undefined;
getAllEnv(): Record<string, string>;
setEnv(key: string, value: string): void;
getCwd(): string;
setCwd(p: string): void;
exit(code: number): void;
getLogger(): Logger;
}
}
declare module 'waterflow/runtime/core/tools/tool_interface' {
import type { PlatformAbortController, Logger } from 'waterflow/platform/types';
export type ToolCategory = 'file' | 'code' | 'search' | 'execute' | 'network' | 'analysis' | 'generation' | 'communication' | 'mcp' | 'custom';
export type PermissionLevel = 'safe' | 'moderate' | 'dangerous' | 'restricted';
export type ToolInput = Record<string, unknown>;
export type ToolOutput = string | Record<string, unknown> | void;
export interface SchemaProperty {
type: string;
description: string;
enum?: string[];
minimum?: number;
maximum?: number;
minLength?: number;
maxLength?: number;
pattern?: string;
default?: unknown;
examples?: unknown[];
items?: SchemaProperty;
properties?: Record<string, SchemaProperty>;
}
export interface ToolInputSchema {
type: 'object';
properties: Record<string, SchemaProperty>;
required?: string[];
additionalProperties?: boolean;
}
export interface ToolOutputSchema {
type: 'object';
properties: Record<string, SchemaProperty>;
format?: 'json' | 'text' | 'markdown' | 'binary';
maxSize?: number;
maxLines?: number;
}
export interface ToolExecutionContext {
toolCallId: string;
workingDirectory: string;
abortController: PlatformAbortController;
config: { timeout?: number };
logger: Logger;
}
export interface Tool {
readonly id: string;
readonly name: string;
readonly description: string;
readonly category: ToolCategory;
readonly inputSchema: ToolInputSchema;
readonly outputSchema?: ToolOutputSchema;
readonly handler: (params: ToolInput, context: ToolExecutionContext) => Promise<ToolOutput>;
readonly permissionLevel: PermissionLevel;
readonly alwaysLoad?: boolean;
readonly shouldDefer?: boolean;
readonly isMcp?: boolean;
readonly prompt?: (options: any) => Promise<string>;
readonly features?: string[];
readonly metadata?: Record<string, unknown>;
readonly searchHint?: string;
}
}