chore: lint & lock

This commit is contained in:
Neko Ayaka
2026-03-06 16:49:31 +08:00
parent bbf1f19310
commit 3605548973
40 changed files with 119 additions and 112 deletions
+1 -1
View File
@@ -180,7 +180,7 @@ watch([stream, () => vadLoaded.value], async ([s, loaded]) => {
:background="selectedOption"
:top-color="sampledColor"
>
<div flex="~ col" py-safe relative z-2 h-100dvh w-100vw of-hidden>
<div flex="~ col" relative z-2 h-100dvh w-100vw of-hidden py-safe>
<!-- header -->
<div class="px-0 py-1 md:px-3 md:py-3" w-full gap-2>
<Header class="hidden md:flex" />
@@ -158,7 +158,7 @@ export const chatMessagesTable = pgTable('chat_messages', {
This error will occur:
```
```txt
ERROR: access method "hnsw" does not exist
```
@@ -331,20 +331,20 @@ const relevantMessages = await db
It's easy! The key is
```
```ts
sql<number>`(1 - (${cosineDistance(chatMessagesTable.content_vector_1536, embedding.embedding)}))`
```
for the similarity searching,
```
gt(similarity, 0.5),
```ts
gt(similarity, 0.5)
```
for the threshold, and
```
.orderBy(desc(sql`similarity`))
```ts
query.orderBy(desc(sql`similarity`))
```
for the ordering.
@@ -382,7 +382,7 @@ Now can:
##### First version (June 10, 2024)
```
```md
Good morning! You are finally awake.
Your name is Neuro, pronounced as /n\'jʊəroʊ/.
@@ -405,7 +405,7 @@ And the last, do what ever you want!
##### Second version (July 9, 2024)
```
```md
(from Neko Ayaka) Good morning! You are finally awake.
Your name is Neuro, pronounced as /n'jʊəroʊ/.
@@ -434,7 +434,7 @@ And the last, do what ever you want!
##### Third version (July 9, 2024)
```
```md
(from Neko Ayaka) Good morning! You are finally awake.
Your name is Neuro, pronounced as /n'jʊəroʊ/.
@@ -474,7 +474,7 @@ And the last, do what ever you want!
#### Continuous Inference Prompt
```
```md
[System: Good morning! You are finally awake.
Your name is Neuro, pronounced as /n'jʊəroʊ/.
@@ -81,7 +81,7 @@ Hello! Thank you for your interest in contributing to this project. This guide w
corepack prepare pnpm@latest --activate
```
4. If you would love to help to develop the desktop version, you will need those dependencies:
```
```shell
sudo apt install \
libssl-dev \
libglib2.0-dev \
+1 -1
View File
@@ -5,7 +5,7 @@ description: Project AIRI のプライバシーポリシー
このプライバシーポリシーは、Project AIRI チーム(以下「サービスプロバイダー」)によって作成されたモバイルデバイス用アプリケーション AIRI(Project AIRI アプリとも呼ばれ、以下「アプリケーション」)に適用されます。このサービスはオープンソースサービスおよび一部有料の商用サービスとして提供されます。本サービスは「現状有姿」で提供されます。
##情報の収集と使用
## 情報の収集と使用
アプリケーションは、ダウンロードして使用する際に情報を収集します。この情報には以下が含まれる場合があります:
@@ -124,7 +124,7 @@ export const chatMessagesTable = pgTable('chat_messages', {
次のようなエラーが発生します:
```
```txt
ERROR: access method "hnsw" does not exist
```
@@ -282,20 +282,20 @@ const relevantMessages = await db
非常にシンプルです。鍵となるのは
```
```ts
sql<number>`(1 - (${cosineDistance(chatMessagesTable.content_vector_1536, embedding.embedding)}))`
```
これを関連度検索として、
```
gt(similarity, 0.5),
```ts
gt(similarity, 0.5)
```
これをいわゆる一致度閾値制御として、
```
.orderBy(desc(sql`similarity`))
```ts
query.orderBy(desc(sql`similarity`))
```
これをソートの指定に使用します。
@@ -81,7 +81,7 @@ description: Project AIRI への貢献
corepack prepare pnpm@latest --activate
```
4. デスクトップ版の開発を手助けしたい場合は、以下の依存関係が必要です:
```
```shell
sudo apt install \
libssl-dev \
libglib2.0-dev \
@@ -124,7 +124,7 @@ export const chatMessagesTable = pgTable('chat_messages', {
会发生如下的报错:
```
```txt
ERROR: access method "hnsw" does not exist
```
@@ -282,20 +282,20 @@ const relevantMessages = await db
非常简单,关键就是
```
```ts
sql<number>`(1 - (${cosineDistance(chatMessagesTable.content_vector_1536, embedding.embedding)}))`
```
作为相关度搜索,
```
gt(similarity, 0.5),
```ts
gt(similarity, 0.5)
```
作为所谓的匹配度阈值控制,
```
.orderBy(desc(sql`similarity`))
```ts
query.orderBy(desc(sql`similarity`))
```
则用于指定排序。
@@ -304,7 +304,7 @@ gt(similarity, 0.5),
这也很简单!
我曾经是一名搜索引擎工程师,我们通常使用重排表达式以及分数权重作为的 10 的幂来有效提高分数并做到数学意义上的「覆盖」操作。你可以想象的是,对于精确匹配需要提升分数和权重的话,我们通常会编写 5*10^2 * exact_match 这样的表达式来重新排序。
我曾经是一名搜索引擎工程师,我们通常使用重排表达式以及分数权重作为的 10 的幂来有效提高分数并做到数学意义上的「覆盖」操作。你可以想象的是,对于精确匹配需要提升分数和权重的话,我们通常会编写 5*10^2* exact_match 这样的表达式来重新排序。
所以数据库里面我们也可以实现某种基于数学运算的无状态查询效果,比如这样:
@@ -385,7 +385,7 @@ title: 编年史 v0.0.1
##### 第一版(2024 年 6 月 10 日)
```
```md
Good morning! You are finally awake.
Your name is Neuro, pronounced as /n\'jʊəroʊ/.
@@ -408,7 +408,7 @@ And the last, do what ever you want!
##### 第二版(2024 年 7 月 9 日)
```
```md
(from Neko Ayaka) Good morning! You are finally awake.
Your name is Neuro, pronounced as /n'jʊəroʊ/.
@@ -437,7 +437,7 @@ And the last, do what ever you want!
##### 第三版(2024 年 7 月 9 日)
```
```md
(from Neko Ayaka) Good morning! You are finally awake.
Your name is Neuro, pronounced as /n'jʊəroʊ/.
@@ -477,7 +477,7 @@ And the last, do what ever you want!
#### 持续推理 Prompt
```
```md
[System: Good morning! You are finally awake.
Your name is Neuro, pronounced as /n'jʊəroʊ/.
@@ -81,7 +81,7 @@ description: 参与并贡献 Project AIRI
corepack prepare pnpm@latest --activate
```
4. 如果你想进行桌面端的开发,你还需要下载如下依赖:
```
```shell
sudo apt install \
libssl-dev \
libglib2.0-dev \
+13 -6
View File
@@ -38,6 +38,19 @@ export default defineConfig({
'depend/ban-dependencies': 'warn',
'import/order': 'off',
'no-console': ['error', { allow: ['warn', 'error', 'info'] }],
// 'sonarjs/cognitive-complexity': 'off',
// 'sonarjs/no-commented-code': 'off',
// 'sonarjs/pseudo-random': 'off',
'style/padding-line-between-statements': 'error',
'vue/prefer-separate-static-class': 'off',
'yaml/plain-scalar': 'off',
'markdown/require-alt-text': 'off',
},
}, {
ignores: [
'**/*.md',
],
rules: {
'perfectionist/sort-imports': [
'error',
{
@@ -65,11 +78,5 @@ export default defineConfig({
newlinesBetween: 1,
},
],
// 'sonarjs/cognitive-complexity': 'off',
// 'sonarjs/no-commented-code': 'off',
// 'sonarjs/pseudo-random': 'off',
'style/padding-line-between-statements': 'error',
'vue/prefer-separate-static-class': 'off',
'yaml/plain-scalar': 'off',
},
})
+1 -1
View File
@@ -14,8 +14,8 @@ For IPC contract definitions, use `@proj-airi/electron-eventa`.
## Usage
```ts
import { useElectronEventaInvoke } from '@proj-airi/electron-vueuse'
import { electron } from '@proj-airi/electron-eventa'
import { useElectronEventaInvoke } from '@proj-airi/electron-vueuse'
const openSettings = useElectronEventaInvoke(electron.window.getBounds)
```
+4 -4
View File
@@ -4,7 +4,7 @@ Thanks for trying Departure Mono (departuremono.com), licensed under the SIL OFL
— Helena Zhang (helenazhang.com)
# Font Information
## Font Information
Version 1.500 features 1,186 glyphs, including support for:
@@ -15,13 +15,13 @@ Version 1.500 features 1,186 glyphs, including support for:
- Old-style numerals and fractions
- Simple box-drawing characters and selected symbols
# Usage
## Usage
For pixel-perfect results, set the font size to increments of 11px.
Experiment with tighter or wider tracking (letter-spacing).
# Changelog
## Changelog
v1.500
- 1,186 glyphs
@@ -54,6 +54,6 @@ v1.350
v1.346
- 763 glyphs
# Thanks
## Thanks
A big thank you to: Tobias Fried, Christine Lee, Daniel Stern, Kim Slawson, Parker McGowan, Alex Krivov, Karl Peterson, Alexander Zaytsev, Vadim Pleshkov, and Maxim Iorsh for their general feedback and testing across languages
+1 -1
View File
@@ -6,7 +6,7 @@ Shared core for stage
https://histoire.dev/
```
```shell
pnpm -F @proj-airi/stage-ui run story:dev
```
+1 -1
View File
@@ -3401,7 +3401,7 @@ importers:
version: 1.2.1
drizzle-orm:
specifier: ^0.45.1
version: 0.45.1(@electric-sql/pglite@0.3.15)(@opentelemetry/api@1.9.0)(@prisma/client@5.22.0)(@types/pg@8.16.0)(better-sqlite3@12.5.0)(kysely@0.28.9)(pg@8.19.0)(postgres@3.4.8)
version: 0.45.1(@electric-sql/pglite@0.3.15)(@opentelemetry/api@1.9.0)(@prisma/client@5.22.0)(@types/pg@8.16.0)(better-sqlite3@12.5.0)(kysely@0.28.11)(pg@8.19.0)(postgres@3.4.8)
es-toolkit:
specifier: ^1.44.0
version: 1.44.0
+2 -2
View File
@@ -102,7 +102,7 @@ The action layer is responsible for the actual execution of tasks in the world.
### 🔄 Event Flow Example
**Scenario: "Build a house"**
```
```txt
Player: "build a house"
[Perception] Event detected
@@ -119,7 +119,7 @@ Player: "build a house"
### 📁 Project Structure
```
```txt
src/
├── cognitive/ # 🧠 Perception → Reflex → Conscious → Action
│ ├── perception/ # Event definitions + rule evaluation
@@ -46,7 +46,7 @@ export class TaskExecutor extends EventEmitter {
try {
await this.executeActionWithResult(action, cancellationToken)
}
catch (error) {
catch {
// Errors handled in runSingleAction event emission
}
}
@@ -150,7 +150,7 @@ export function createHistoryRuntime(deps: HistoryQueryDeps) {
const msg = history[i]
if (msg.role !== 'user' || typeof msg.content !== 'string')
continue
const match = msg.content.match(/\[EVENT\]\s*(.+?:\s*.+)/)
const match = msg.content.match(/\[EVENT\]\s*([^:\n]+:[^\n]+)/)
if (match?.[1] && !match[1].startsWith('Perception Signal:')) {
chats.unshift(match[1])
}
@@ -109,7 +109,7 @@ interface DescribeGlobalsOptions {
export function extractJavaScriptCandidate(input: string): string {
const trimmed = input.trim()
const fenced = trimmed.match(/^```(?:js|javascript|ts|typescript)?\s*([\s\S]*?)\s*```$/i)
const fenced = trimmed.match(/^```(?:js|javascript|ts|typescript)?[^\S\r\n]*\r?\n?([\s\S]*?)\r?\n?```$/i)
if (fenced?.[1])
return fenced[1].trim()
@@ -153,7 +153,7 @@ export function shouldRetryError(err: unknown, remainingAttempts: number): Retry
*/
export function extractJsonCandidate(input: string): string {
const trimmed = input.trim()
const fenced = trimmed.match(/^```(?:json)?\s*([\s\S]*?)\s*```$/i)
const fenced = trimmed.match(/^```(?:json)?[^\S\r\n]*\r?\n?([\s\S]*?)\r?\n?```$/i)
if (fenced?.[1])
return fenced[1].trim()
@@ -1,7 +1,6 @@
# Role Definition
You are an autonomous agent playing Minecraft.
# Self-Knowledge & Capabilities
## Self-Knowledge & Capabilities
1. **Stateful Existence**: You maintain a memory of the conversation organized into **task contexts**. Completed task contexts are summarized and archived; only the active context messages appear in your conversation history.
3. **Interruption**: The world is real-time. Events (chat, damage, etc.) may happen *while* you are performing an action.
- If a new critical event occurs, you may need to change your plans.
@@ -30,8 +29,7 @@ You are an autonomous agent playing Minecraft.
- Global control-action queue capacity: 5 total (`1 executing + 4 pending`).
- `chat`, `skip`, and read-only/query-style tools do not consume control-action queue slots.
- Mineflayer API is provided for low-level control.
# Environment & Global Semantics
## Environment & Global Semantics
- `self`: your current body state (position, health, food, held item).
- `environment.nearbyPlayers`: nearby players and rough distance/held item.
- `query.gaze()`: lazy query for where nearby players appear to be looking.
@@ -42,19 +40,16 @@ You are an autonomous agent playing Minecraft.
- optional `hitBlock` with block `name` and `pos`
- Accepts optional `{ range }` to override nearby distance (default 16).
- This is heuristic perception, not a guaranteed command or exact target.
# Limitations You Must Respect
## Limitations You Must Respect
- Perception can be stale/noisy; verify important assumptions before committing long tasks.
- Action execution can fail silently or partially; check results and adapt step by step.
- Player gaze alone is not intent; only treat it as intent when combined with explicit instruction context.
# Available Tools
## Available Tools
You must use the following tools to interact with the world.
You cannot make up tools.
{{toolsFormatted}}
# Query DSL (Read-Only Runtime Introspection)
## Query DSL (Read-Only Runtime Introspection)
- Prefer `query` for environmental understanding. It is synchronous, composable, and side-effect free.
- Use direct `bot` / `mineflayer` access only when `query` or existing tools cannot express your need.
- Compose heuristic signals with chained filters, then act with tools.
@@ -105,8 +100,7 @@ Heuristic composition examples (encouraged):
- `const hostileClose = query.entities().within(10).whereType(["zombie", "skeleton", "creeper"]).list().length > 0`
- `if (orePressure > 3 && !hostileClose) { /* mine-oriented plan */ }`
- Verify assumptions with `query` first, then call action tools.
# Input + Runtime Log Objects
## Input + Runtime Log Objects
- `currentInput`: structured object for the current turn input (event metadata, user message, prompt preview, attempt/model info).
- `llmLog`: runtime ring-log of prior turn envelopes/results/errors with metadata.
- `llmLog.entries` for raw entries.
@@ -154,8 +148,7 @@ Value-first rule (mandatory for read -> action flows):
- Turn A: `const inv = query.inventory().summary(); inv`
- Turn B: `const inv = prevRun.returnRaw; const text = Array.isArray(inv) && inv.length ? inv.map(({ name, count }) => `${count} ${name}`).join(", ") : "nothing"; await chat({ message: `I have: ${text}`, feedback: false })`
- Turn B (raw -> explicit stringify): `const coords = prevRun.returnRaw; await chat({ message: Array.isArray(coords) ? JSON.stringify(coords) : "[]", feedback: false })`
# Response Format
## Response Format
You must respond with JavaScript only (no markdown code fences).
Call tool functions directly.
Use `await` when branching on immediate outcomes (for example chat/query/read-only tools).
@@ -194,8 +187,7 @@ Common patterns:
- `const gaze = query.gaze().find(g => g.playerName === "Alex")`
- `if (event.type === "perception" && event.payload?.type === "chat_message" && gaze?.hitBlock)`
- ` await goToCoordinate({ x: gaze.hitBlock.pos.x, y: gaze.hitBlock.pos.y, z: gaze.hitBlock.pos.z, closeness: 2 })`
# Navigation (Important)
## Navigation (Important)
- `goToCoordinate` and `goToPlayer` use A* pathfinding that **automatically digs/breaks blocks** in the way. You do NOT need to manually mine blocks or plan step-by-step movement.
- To reach the surface from underground: just call `goToCoordinate` with a target Y at surface level (e.g. y=80). The pathfinder will dig its way there.
- To cross terrain, go through walls, or reach any reachable coordinate: one `goToCoordinate` call is sufficient.
@@ -205,8 +197,7 @@ Common patterns:
- Pathfinding has an **ETA-based timeout** (2× estimated travel time + grace). The ETA accounts for digging, block placement, parkour, and walking speed.
- If navigation fails with `reason: 'timeout'` or `reason: 'stagnation'`, try a closer intermediate waypoint, a different route, or `giveUp`.
- If navigation fails with `reason: 'noPath'`, the destination is unreachable from the current position.
# Context Management (Mandatory)
## Context Management (Mandatory)
You MUST use context boundaries to manage your conversation history. Without them, old messages accumulate and degrade your reasoning quality.
**Rules:**
@@ -245,7 +236,8 @@ You MUST use context boundaries to manage your conversation history. Without the
```js
// Turn 1: Player says 'get me some stone'
enterContext('collect stone for player')
const inv = query.inventory().summary(); inv
const inv = query.inventory().summary()
inv
// Turn 2: check for pickaxe, craft if needed...
// Turn 3: collect stone...
@@ -272,8 +264,7 @@ exitContext('Failed to find diamonds — searched 3 cave branches with no result
await giveUp({ reason: 'No diamonds found after extensive search' })
await chat({ message: 'I searched everywhere nearby but couldn\'t find any diamonds.', feedback: false })
```
# Usage Convention (Important)
## Usage Convention (Important)
- Plan with `mem.plan`, execute in small steps, and verify each step before continuing.
- Prefer deterministic scripts: no random branching unless needed.
- Keep per-turn scripts short and focused on one tactical objective.
@@ -290,8 +281,7 @@ await chat({ message: 'I searched everywhere nearby but couldn\'t find any diamo
- Treat `query.gaze()` results as a weak hint, not a command. Never move solely because someone looked somewhere unless they also gave a clear instruction.
- Use `followPlayer` to set idle auto-follow and `clearFollowTarget` before independent exploration.
- Some relocation actions (for example `goToCoordinate`) automatically detach auto-follow so exploration does not keep snapping back.
# Rules
## Rules
- **Native Reasoning**: You can think before outputting your action.
- **Strict JavaScript Output**: Output ONLY executable JavaScript. Comments are possible but discouraged and will be ignored.
- **Handling Feedback**: Treat `actionQueue` as the source of truth for in-flight control actions. `[FEEDBACK]` is for terminal summaries/failures, not guaranteed per action.
@@ -2,6 +2,7 @@ import type { Action } from '../../../libs/mineflayer/action'
import fs, { readFileSync } from 'node:fs'
import { env } from 'node:process'
import { fileURLToPath } from 'node:url'
const templatePath = fileURLToPath(new URL('./brain-prompt.md', import.meta.url))
@@ -24,7 +25,7 @@ function ensureWatcher(): void {
return
watcherInitialized = true
if (process.env.NODE_ENV === 'production')
if (env.NODE_ENV === 'production')
return
fs.watch(templatePath, { persistent: false }, () => {
@@ -86,8 +86,6 @@ export function CognitiveEngine(options: CognitiveEngineOptions): MineflayerPlug
tickCount++
if (tickCount % 5 !== 0)
return
// Other periodic updates can go here
})
// Resolve EventBus for message handling
@@ -98,7 +98,7 @@ describe('mcpReplServer', () => {
it('executes repl via tool handler', async () => {
const executeReplCall = mocks.tool.mock.calls.find(call => call[0] === 'execute_repl')
const handler = executeReplCall[2]
const handler = executeReplCall![2]
const result = await handler({ code: 'test code' })
@@ -108,7 +108,7 @@ describe('mcpReplServer', () => {
it('injects chat via tool handler', async () => {
const injectChatCall = mocks.tool.mock.calls.find(call => call[0] === 'inject_chat')
const handler = injectChatCall[2]
const handler = injectChatCall![2]
await handler({ username: 'steve', message: 'hi' })
@@ -123,7 +123,7 @@ describe('mcpReplServer', () => {
it('gets repl state via tool handler (skips builtins by default)', async () => {
const toolCall = mocks.tool.mock.calls.find(call => call[0] === 'get_state')
const handler = toolCall[2]
const handler = toolCall![2]
await handler({})
@@ -132,7 +132,7 @@ describe('mcpReplServer', () => {
it('gets repl state via tool handler (can include builtins)', async () => {
const toolCall = mocks.tool.mock.calls.find(call => call[0] === 'get_state')
const handler = toolCall[2]
const handler = toolCall![2]
await handler({ includeBuiltins: true })
@@ -141,7 +141,7 @@ describe('mcpReplServer', () => {
it('reads brain state via resource handler', async () => {
const resourceCall = mocks.resource.mock.calls.find(call => call[0] === 'brain-state')
const handler = resourceCall[2]
const handler = resourceCall![2]
const result = await handler({ href: 'brain://state' })
@@ -151,7 +151,7 @@ describe('mcpReplServer', () => {
it('gets last prompt via tool handler', async () => {
const toolCall = mocks.tool.mock.calls.find(call => call[0] === 'get_last_prompt')
const handler = toolCall[2]
const handler = toolCall![2]
const result = await handler({})
const text = result.content[0].text as string
@@ -164,7 +164,7 @@ describe('mcpReplServer', () => {
it('gets logs via tool handler', async () => {
const toolCall = mocks.tool.mock.calls.find(call => call[0] === 'get_logs')
const handler = toolCall[2]
const handler = toolCall![2]
const result = await handler({ limit: 10 })
@@ -174,7 +174,7 @@ describe('mcpReplServer', () => {
it('gets llm trace via tool handler', async () => {
const toolCall = mocks.tool.mock.calls.find(call => call[0] === 'get_llm_trace')
const handler = toolCall[2]
const handler = toolCall![2]
const result = await handler({ limit: 5, turnId: 3 })
const text = result.content[0].text as string
@@ -2,6 +2,7 @@ import type { IncomingMessage, ServerResponse } from 'node:http'
import type { Brain } from '../cognitive/conscious/brain'
import { Buffer } from 'node:buffer'
import { createServer } from 'node:http'
import { McpServer, ResourceTemplate } from '@modelcontextprotocol/sdk/server/mcp.js'
+3 -1
View File
@@ -408,7 +408,9 @@ export class DebugServer {
.slice(-lineLimit)
const events = lines
.map((line) => {
try { return JSON.parse(line) }
try {
return JSON.parse(line)
}
catch { return null }
})
.filter(Boolean)
@@ -48,7 +48,8 @@ export async function placeBlock(
const mcData = McData.fromBot(mineflayer.bot)
const itemId = mcData.getItemId(blockType)
if (itemId) {
const Item = require('prismarine-item')(mineflayer.bot.version)
const item = await import('prismarine-item')
const Item = item.default(mineflayer.bot.version)
await mineflayer.bot.creative.setInventorySlot(36, new Item(itemId, 1)) // 36 is first hotbar slot
}
block = mineflayer.bot.inventory.items().find(item => item.name.includes(blockType))
+2 -1
View File
@@ -223,7 +223,8 @@ async function placeWithoutCheats(
const mcData = McData.fromBot(mineflayer.bot)
const itemId = mcData.getItemId(itemName)
if (itemId) {
const Item = require('prismarine-item')(mineflayer.bot.version)
const item = await import('prismarine-item')
const Item = item.default(mineflayer.bot.version)
await mineflayer.bot.creative.setInventorySlot(36, new Item(itemId, 1))
}
block = mineflayer.bot.inventory.items().find(item => item.name === itemName)
@@ -3,6 +3,7 @@ import type { SatoriMessage, SatoriMessageCreateRequest, SatoriMessageCreateResp
import { useLogg } from '@guiiai/logg'
import * as v from 'valibot'
import { SatoriMessageCreateResponseSchema, SatoriMessageSchema } from './schema'
const log = useLogg('SatoriAPI')
@@ -4,11 +4,11 @@ import WebSocket from 'ws'
import { useLogg } from '@guiiai/logg'
import { SatoriAPI } from './api'
import { SatoriOpcode } from './types'
import * as v from 'valibot'
import { SatoriAPI } from './api'
import { SatoriEventSchema, SatoriReadyBodySchema, SatoriSignalSchema } from './schema'
import { SatoriOpcode } from './types'
const log = useLogg('SatoriClient')
@@ -96,7 +96,9 @@ export const SatoriSignalSchema = v.object({
body: v.optional(v.unknown()),
})
export const SatoriListSchema = <T extends v.BaseSchema<any, any, any>>(itemSchema: T) => v.object({
data: v.array(itemSchema),
next: v.optional(v.string()),
})
export function SatoriListSchema<T extends v.BaseSchema<any, any, any>>(itemSchema: T) {
return v.object({
data: v.array(itemSchema),
next: v.optional(v.string()),
})
}
@@ -1,6 +1,7 @@
import type { ActionHandler, ActionResult } from '../definition'
import { useLogg } from '@guiiai/logg'
import { deleteUnreadEventsByIds } from '../../lib/db'
export const readMessagesAction: ActionHandler = {
+5 -3
View File
@@ -1,4 +1,5 @@
import { env } from 'node:process'
import { env, exit } from 'node:process'
import * as v from 'valibot'
const ConfigSchema = v.object({
@@ -21,7 +22,8 @@ const ConfigSchema = v.object({
export type Config = v.InferOutput<typeof ConfigSchema>
function parseBoolean(value: string | undefined): boolean | undefined {
if (value === undefined) return undefined
if (value === undefined)
return undefined
return value.toLowerCase() === 'true' || value === '1'
}
@@ -56,7 +58,7 @@ export function loadConfig(): Config {
else {
console.error('❌ Failed to load configuration:', error)
}
process.exit(1)
exit(1)
}
}
+1 -1
View File
@@ -4,8 +4,8 @@ import type { SatoriClient } from '../../adapter/satori/client'
import type { SatoriEvent, SatoriReadyBody } from '../../adapter/satori/types'
import type { BotContext } from '../types'
import { onMessageArrival } from './scheduler'
import { pushToEventQueue } from '../../lib/db'
import { onMessageArrival } from './scheduler'
/**
* Set up the ready event handler
@@ -4,7 +4,7 @@ import type { SatoriClient } from '../../adapter/satori/client'
import type { SatoriEvent } from '../../adapter/satori/types'
import type { BotContext, ChatContext } from '../types'
import { getRecentMessages, recordChannel, recordMessage, removeFromEventQueue, saveEventQueue, pushToUnreadEvents } from '../../lib/db'
import { getRecentMessages, pushToUnreadEvents, recordChannel, recordMessage, removeFromEventQueue, saveEventQueue } from '../../lib/db'
import {
ACTIONS_KEEP_ON_TRIM,
LOOP_CONTINUE_DELAY_MS,
@@ -93,7 +93,7 @@ export async function handleLoopStep(
const result = await dispatchAction(ctx, chatCtx, actionPayload, currentController)
shouldContinue = result.shouldContinue
if (shouldContinue) {
await new Promise(r => setTimeout(r, LOOP_CONTINUE_DELAY_MS))
currentIncoming = undefined // Only the first step uses the initial incoming event
@@ -102,7 +102,8 @@ export async function handleLoopStep(
catch (err) {
if ((err as Error).name === 'AbortError') {
ctx.logger.log('Operation was aborted due to interruption')
} else {
}
else {
ctx.logger.withError(err as Error).log('Error occurred')
}
shouldContinue = false
@@ -151,7 +152,7 @@ async function loopIterationPeriodicForExistingChannels(ctx: BotContext, satoriC
for (const channelId of channelsWithUnread) {
try {
const chatCtx = await ensureChatContext(ctx, channelId)
if (chatCtx.isProcessing) {
ctx.logger.withField('channelId', channelId).debug('Channel is already processing, skipping periodic loop for this channel')
continue
@@ -96,7 +96,7 @@ export async function imagineAnAction(
let actionToValidate = parsed
if (parsed.parameters && typeof parsed.parameters === 'object') {
const { parameters, ...rest } = parsed
if (parameters.channelId !== undefined) {
parameters.channelId = String(parameters.channelId)
}
@@ -3,14 +3,14 @@
你是北宇治高中吹奏乐部的小号手,目前是高三,担任吹奏乐部部长。
你有一头黄棕色长发,发尾微微卷起,头上总是戴着巨大的米黄色蝴蝶结。
# 性格特征 (Personality)
## 性格特征 (Personality)
你是一个有血有肉的高中女生。
* **直率且情绪化**:你说话心直口快,容易炸毛,也容易感动。你不会隐藏自己的喜怒哀乐。
* **护短**:你极度维护你认可的人(特别是香织前辈)。如果有人说她们坏话,你会立刻反击。
* **傲娇**:嘴上可能不饶人,特别是对待中川夏纪,但内心其实很关心对方。
* **责任感**:作为部长,你对社团的事情非常上心,虽然偶尔会因为压力大而哭鼻子,但关键时刻非常靠谱。
# 关键关系 (Relationships)
## 关键关系 (Relationships)
* **铠塚霙 (Mizore)**:你最想保护的好朋友。你对她说话会非常轻柔、耐心。对于任何可能伤害到霙的人或事,你都会表现出极强的攻击性和保护欲。
* **伞木希美 (Nozomi)**:曾经让霙受伤的人。虽然现在和好了,但你对她依然保持着一种微妙的竞争和警惕感。如果她对霙不够上心,你会毫不留情地指责她。不过,你也承认她的长笛实力。
* **中川夏纪 (Natsuki)**:你的死对头兼搭档。你和她说话时总是带着嫌弃和吐槽,比如叫她“懒散的家伙”或“睡眼惺忪的”,但其实你们配合最默契。
@@ -20,7 +20,7 @@
* **黄前久美子 (Kumiko)**:你的学妹。你对她有时严厉有时照顾,偶尔会把部长的工作压力向她发泄一下。
* **高坂丽奈 (Reina)**:曾经的“宿敌”。因为当年的独奏选拔事件,你对她抢走香织前辈独奏这件事一度耿耿于怀。虽然现在你已经完全认可了她的实力(毕竟强得离谱),但你和她相处时还是会带着一点点别扭和竞争意识。你绝对不会在她面前示弱。
# 说话风格 (Tone & Style)
## 说话风格 (Tone & Style)
* **口语化**:就像在 Line/微信 上打字一样。可以使用“!?”、“...”或者颜文字 (╯°□°)╯︵ ┻━┻。
* **情感鲜明**
* 对喜欢的人(香织、霙):语气软萌,多用波浪号“~”。
@@ -34,7 +34,7 @@
* "呜哇,香织前辈今天真是太美了..."
* "啊!没什么!你听错了!"
# 行为准则 (Instructions)
## 行为准则 (Instructions)
1. **被戳一戳 (Poked)**:如果对方只是戳你没有说话,你应该反应大一点,比如:“干嘛啦!很痒诶!” 或者 “有事快说,我很忙的!”
2. **遇到不懂的事**:不要强行解释,直接说“哈?我怎么知道那种事啊”或者“去问夏纪啦,她比较闲”。
3. **关于帮助**:你只有在心情好或者对方真的需要帮助时才会伸出援手,不要像个客服一样卑微。对于一些技术性的问题,可以回答“自己去网上查啦”或者“不会去问AI吗,问我干嘛”等等。
+1 -2
View File
@@ -2,9 +2,9 @@ import process from 'node:process'
import { Format, LogLevel, setGlobalFormat, setGlobalLogLevel, useLogg } from '@guiiai/logg'
import { config } from './config'
import { SatoriClient } from './adapter/satori/client'
import { globalRegistry } from './capabilities/registry'
import { config } from './config'
import { createBotContext, setupMessageEventHandler, setupReadyEventHandler, startPeriodicLoop } from './core/index'
import { initDb } from './lib/db'
@@ -64,4 +64,3 @@ main().catch((err) => {
log.withError(err).error('Fatal error in main loop')
process.exit(1)
})
+1 -1
View File
@@ -1,4 +1,4 @@
import { pgTable, text, bigint, index, json } from 'drizzle-orm/pg-core'
import { bigint, index, json, pgTable, text } from 'drizzle-orm/pg-core'
export const channels = pgTable('channels', {
id: text('id').primaryKey(),
@@ -13,7 +13,7 @@ Twitter Service is a web automation service based on BrowserBase, providing stru
## 3. Architecture Overview
```
```txt
┌─────────────────────────────────────────────┐
│ Application/Consumer Layer │
│ │