From fdeb9561bcb807f26b615c7e13fc906946e7954f Mon Sep 17 00:00:00 2001 From: Rin Date: Wed, 1 Apr 2026 23:33:59 +0800 Subject: [PATCH] feat(minecraft,stage-*): airi integration, isolated-vm plus misc updates (#1371) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat(minecraft): contextflow integration Add AiriBridge to cognitive engine to enable communication with Airi system. Expose `notifyAiri` and `updateAiriContext` functions in JS planner runtime globals for sending notifications and context updates from agent code. * refactor(minecraft): remove redundant destinations field from airi-bridge Remove hardcoded `destinations: ['minecraft']` from spark:command, spark:notify, and context:update events as the destination is already implied by the bridge context. * feat(minecraft): complete AIRI context flow integration Wire up bidirectional AIRI ↔ Minecraft communication via dedicated signal channels instead of reusing signal:chat_message. - Add airi_command and airi_context to PerceptionSignalType - Route spark:command intents: action/plan/proposal/reroute emit signal:airi_command; context intent emits signal:airi_context - Add inbound context:update listener so AIRI can push context to the Minecraft agent - airi_context signals are injected into conversation history directly without triggering a full cognitive cycle - airi_command events reset the no-action budget (like player chat) - enterContext/exitContext auto-emit context:update back to AIRI for task lifecycle visibility - Add sendEmit() helper for queued/working/done progress reporting Co-Authored-By: Claude Sonnet 4.6 * feat(minecraft): report bot startup state to AIRI Send initial context update to AIRI when brain comes online, including bot username, server address, position, health, game mode, and list of other online players. Tagged with 'startup' and bot username for filtering. * feat(minecraft): manage bot config through airi * feat(stage-ui): add minecraft service shell * fix(stage-ui): use applied minecraft config for chat context * fix(minecraft): fall back on invalid env port * fix(minecraft): support quoted env values * fix(minecraft): ignore empty env overrides * fix(stage-ui): keep minecraft draft during apply * fix(stage-ui): simplify minecraft heartbeat label * feat(stage-ui): simplify minecraft settings shell * fix(minecraft): use registry heartbeat for stage liveness * chore(minecraft): remove annoying detector decision logging * fix(minecraft): limit context flow to bot pushes * fix(minecraft): use workspace server sdk * fix(stage-ui): replay registry snapshots for late listeners * chore(minecraft): add todo for explicit stage deregistration * feat: improve context flow observability * fix(stage-ui): remove redundant header and callout from settings and context flow pages * feat(stage-ui): enhance context flow prompt projection UI with readiness indicator * docs(stage-ui): clarify context flow prompt projection terminology * fix(stage-ui): add test for clone-safe context snapshots with nested fields * todo * fix(minecraft): avoid blocking startup on airi connection * Remove LLM timeouts from the Minecraft brain * Remove context boundary system from Minecraft brain * feat(minecraft): use isolated-vm for JS planner sandbox * refactor(minecraft): harden planner sandbox defaults * Remove LLM attempt timeout guard * [autofix.ci] apply automated fixes * Fix server runtime shutdown signal handling * Remove misleading Minecraft integration toggle * contextflow destination to stage-* * chore(deps): updated * Add settings layout route for web devtools * chore: default to expand context panels * fix(server-*): should export more types, and for module:announced should ignore self * refactor(minecraft): better context orchestration, handle module announce, context sync, and more * feat(stage-ui): not spark command ready * [autofix.ci] apply automated fixes * clean up * refactor: use :class arrays per AGENTS.md styling guidelines * cleanup bloated tests * rm more tests * use errorMessageFrom * hardcode to reduce bloat * update setup instructions * timeout for brain * remove more bloat * fix --------- Co-authored-by-agent: Claude Sonnet 4.6 Co-authored-by-agent: Codex Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com> Co-authored-by: Neko Ayaka --- apps/stage-web/src/pages/devtools/index.vue | 13 + packages/i18n/src/locales/en/settings.yaml | 32 +- packages/plugin-protocol/src/types/events.ts | 2 +- packages/server-runtime/src/bin/run.ts | 6 +- .../stage-layouts/src/layouts/settings.vue | 10 +- .../context-flow-active-contexts.vue | 125 ++ .../components/context-flow-lifecycle.vue | 134 ++ .../context-flow-prompt-projection.vue | 122 ++ .../components/context-flow-runtime.vue | 107 ++ .../src/pages/devtools/context-flow/index.vue | 77 +- .../settings/modules/gaming-minecraft.vue | 71 +- .../components/modules/GamingMinecraft.vue | 127 +- .../src/composables/use-modules-list.ts | 2 + packages/stage-ui/src/stores/chat.ts | 87 +- .../src/stores/chat/context-prompt.ts | 30 + .../stores/chat/context-providers/index.ts | 1 + .../chat/context-providers/minecraft.ts | 39 + .../stage-ui/src/stores/chat/context-store.ts | 76 +- .../stores/devtools/context-observability.ts | 122 ++ packages/stage-ui/src/stores/llm.ts | 109 +- .../src/stores/mods/api/channel-server.ts | 105 +- .../src/stores/mods/api/context-bridge.ts | 125 +- .../src/stores/modules/gaming-minecraft.ts | 236 +++- packages/stage-ui/src/types/chat.ts | 2 +- packages/stage-ui/src/utils/relative-time.ts | 9 + pnpm-lock.yaml | 14 + pnpm-workspace.yaml | 4 +- services/minecraft/README.md | 67 +- services/minecraft/package.json | 7 +- services/minecraft/src/airi/airi-bridge.ts | 226 +++ .../src/airi/minecraft-context-service.ts | 169 +++ .../src/airi/start-background-client.ts | 58 + .../src/cognitive/conscious/brain.test.ts | 23 +- .../src/cognitive/conscious/brain.ts | 339 +---- .../cognitive/conscious/context-summary.ts | 182 --- .../src/cognitive/conscious/history-query.ts | 63 +- .../conscious/js-planner-sandbox-protocol.ts | 152 ++ .../conscious/js-planner-sandbox-runner.ts | 196 +++ .../cognitive/conscious/js-planner-worker.ts | 291 ++++ .../cognitive/conscious/js-planner.test.ts | 58 + .../src/cognitive/conscious/js-planner.ts | 1236 +++++++++++++---- .../conscious/prompts/brain-prompt.md | 110 +- .../conscious/repl-code-normalizer.ts | 2 +- services/minecraft/src/cognitive/container.ts | 28 +- services/minecraft/src/cognitive/index.ts | 16 +- .../src/cognitive/perception/rules/engine.ts | 9 +- .../src/cognitive/perception/types/signals.ts | 2 + .../src/composables/runtime-config.ts | 164 +++ services/minecraft/src/debug/types.ts | 22 - services/minecraft/src/debug/web/app.js | 70 +- services/minecraft/src/main.ts | 165 ++- .../minecraft/src/minecraft-bot-runtime.ts | 85 ++ 52 files changed, 4314 insertions(+), 1213 deletions(-) create mode 100644 apps/stage-web/src/pages/devtools/index.vue create mode 100644 packages/stage-pages/src/pages/devtools/context-flow/components/context-flow-active-contexts.vue create mode 100644 packages/stage-pages/src/pages/devtools/context-flow/components/context-flow-lifecycle.vue create mode 100644 packages/stage-pages/src/pages/devtools/context-flow/components/context-flow-prompt-projection.vue create mode 100644 packages/stage-pages/src/pages/devtools/context-flow/components/context-flow-runtime.vue create mode 100644 packages/stage-ui/src/stores/chat/context-prompt.ts create mode 100644 packages/stage-ui/src/stores/chat/context-providers/minecraft.ts create mode 100644 packages/stage-ui/src/stores/devtools/context-observability.ts create mode 100644 packages/stage-ui/src/utils/relative-time.ts create mode 100644 services/minecraft/src/airi/airi-bridge.ts create mode 100644 services/minecraft/src/airi/minecraft-context-service.ts create mode 100644 services/minecraft/src/airi/start-background-client.ts delete mode 100644 services/minecraft/src/cognitive/conscious/context-summary.ts create mode 100644 services/minecraft/src/cognitive/conscious/js-planner-sandbox-protocol.ts create mode 100644 services/minecraft/src/cognitive/conscious/js-planner-sandbox-runner.ts create mode 100644 services/minecraft/src/cognitive/conscious/js-planner-worker.ts create mode 100644 services/minecraft/src/composables/runtime-config.ts create mode 100644 services/minecraft/src/minecraft-bot-runtime.ts diff --git a/apps/stage-web/src/pages/devtools/index.vue b/apps/stage-web/src/pages/devtools/index.vue new file mode 100644 index 000000000..0b749bbf7 --- /dev/null +++ b/apps/stage-web/src/pages/devtools/index.vue @@ -0,0 +1,13 @@ + + + + + +meta: + layout: settings + titleKey: tamagotchi.settings.devtools.title + diff --git a/packages/i18n/src/locales/en/settings.yaml b/packages/i18n/src/locales/en/settings.yaml index e1e7612f4..c9a563dc4 100644 --- a/packages/i18n/src/locales/en/settings.yaml +++ b/packages/i18n/src/locales/en/settings.yaml @@ -391,16 +391,30 @@ pages: description: Playing Minecraft! title: Minecraft enable: Enable Minecraft Integration - enable-description: Allow AIRI to interact with Minecraft game - server-address: Server Address - server-address-description: The IP address or hostname of the Minecraft server - server-address-placeholder: Enter server address (e.g., localhost or play.example.com) - server-port: Server Port - server-port-description: The port number of the Minecraft server (default 25565) - username: Username - username-description: Your username in the Minecraft server - username-placeholder: Enter your Minecraft username + enable-description: Include Minecraft self-knowledge in Stage chat turns and keep passive runtime visibility enabled. configured: Minecraft is properly configured! + read-only: + title: Read-only shell + description: Stage currently exposes Minecraft as a passive shell. Runtime status and traffic are visible here, but service configuration stays in the Minecraft service files. + setup: + title: Setup + description: You can find the instructions for setting up the bot in the following file + runtime: + title: Observed runtime + connection: Connection + latest-context: Latest bot context + waiting: Waiting for a fresh bot-originated context push. + unknown: Unknown + debug: + title: Minecraft traffic + empty: No Minecraft traffic observed yet. + source: 'Source: {source}' + status: + service-online: Service online + service-offline: Service offline + no-runtime-context: 'No bot context observed yet' + last-context-seconds: 'Last bot context: {seconds}s ago' + last-context-stale: 'Last bot context: Stale' hearing: description: Configure how speech recognition works title: Hearing diff --git a/packages/plugin-protocol/src/types/events.ts b/packages/plugin-protocol/src/types/events.ts index 20e6cf85b..b150981ce 100644 --- a/packages/plugin-protocol/src/types/events.ts +++ b/packages/plugin-protocol/src/types/events.ts @@ -471,7 +471,7 @@ interface InputSource { interface OutputSource { 'gen-ai:chat': { message: UserMessage - contexts: Record, string | CommonContentPart[]>[]> + contexts: Record, unknown>[]> composedMessage: Array input?: InputEventEnvelope } diff --git a/packages/server-runtime/src/bin/run.ts b/packages/server-runtime/src/bin/run.ts index 3cde056ec..1d4d04a6d 100644 --- a/packages/server-runtime/src/bin/run.ts +++ b/packages/server-runtime/src/bin/run.ts @@ -1,6 +1,6 @@ #!/usr/bin/env tsx -import { env, exit, on } from 'node:process' +import process, { env, exit } from 'node:process' import { createServer } from '../server' @@ -18,7 +18,7 @@ async function shutdown() { exit(0) } -on('SIGINT', shutdown) -on('SIGTERM', shutdown) +process.on('SIGINT', shutdown) +process.on('SIGTERM', shutdown) server.start() diff --git a/packages/stage-layouts/src/layouts/settings.vue b/packages/stage-layouts/src/layouts/settings.vue index cbbb18f67..d8264c2f6 100644 --- a/packages/stage-layouts/src/layouts/settings.vue +++ b/packages/stage-layouts/src/layouts/settings.vue @@ -78,14 +78,12 @@ onMounted(() => updateThemeColor()) paddingRight: 'env(safe-area-inset-right, 0px)', paddingLeft: 'env(safe-area-inset-left, 0px)', }" - h-full w-full + :class="['h-full w-full', 'flex flex-col']" > @@ -94,7 +92,7 @@ onMounted(() => updateThemeColor()) :class="[ 'px-3 py-0 2xl:max-w-screen-2xl md:py-0 xl:px-4', isStageTamagotchi() ? 'sm:max-h-[calc(100%)] max-h-[calc(100%)]' : 'sm:max-h-[calc(100%-56px)] max-h-[calc(100%-40px)]', - 'flex flex-col mx-auto h-full min-h-0', + 'mx-auto flex min-h-0 w-full flex-1 flex-col', ]" > updateThemeColor()) :subtitle="routeHeaderMetadata?.subtitle" :disable-back-button="isStageTamagotchi() && route.path === '/settings'" /> -
+
diff --git a/packages/stage-pages/src/pages/devtools/context-flow/components/context-flow-active-contexts.vue b/packages/stage-pages/src/pages/devtools/context-flow/components/context-flow-active-contexts.vue new file mode 100644 index 000000000..e7bc8ec83 --- /dev/null +++ b/packages/stage-pages/src/pages/devtools/context-flow/components/context-flow-active-contexts.vue @@ -0,0 +1,125 @@ + + + diff --git a/packages/stage-pages/src/pages/devtools/context-flow/components/context-flow-lifecycle.vue b/packages/stage-pages/src/pages/devtools/context-flow/components/context-flow-lifecycle.vue new file mode 100644 index 000000000..15a0d88a1 --- /dev/null +++ b/packages/stage-pages/src/pages/devtools/context-flow/components/context-flow-lifecycle.vue @@ -0,0 +1,134 @@ + + + diff --git a/packages/stage-pages/src/pages/devtools/context-flow/components/context-flow-prompt-projection.vue b/packages/stage-pages/src/pages/devtools/context-flow/components/context-flow-prompt-projection.vue new file mode 100644 index 000000000..bc4beaba5 --- /dev/null +++ b/packages/stage-pages/src/pages/devtools/context-flow/components/context-flow-prompt-projection.vue @@ -0,0 +1,122 @@ + + + diff --git a/packages/stage-pages/src/pages/devtools/context-flow/components/context-flow-runtime.vue b/packages/stage-pages/src/pages/devtools/context-flow/components/context-flow-runtime.vue new file mode 100644 index 000000000..2b0430897 --- /dev/null +++ b/packages/stage-pages/src/pages/devtools/context-flow/components/context-flow-runtime.vue @@ -0,0 +1,107 @@ + + + diff --git a/packages/stage-pages/src/pages/devtools/context-flow/index.vue b/packages/stage-pages/src/pages/devtools/context-flow/index.vue index 7e7ef3928..86c60f118 100644 --- a/packages/stage-pages/src/pages/devtools/context-flow/index.vue +++ b/packages/stage-pages/src/pages/devtools/context-flow/index.vue @@ -6,19 +6,28 @@ import type { FlowDirection, FlowEntry, SparkNotifyEntryState } from './context- import { errorMessageFrom } from '@moeru/std' import { ContextUpdateStrategy } from '@proj-airi/server-sdk' +import { Section } from '@proj-airi/stage-ui/components' import { useCharacterOrchestratorStore, useCharacterStore } from '@proj-airi/stage-ui/stores/character' import { useChatOrchestratorStore } from '@proj-airi/stage-ui/stores/chat' import { CHAT_STREAM_CHANNEL_NAME, CONTEXT_CHANNEL_NAME } from '@proj-airi/stage-ui/stores/chat/constants' +import { formatContextPromptText } from '@proj-airi/stage-ui/stores/chat/context-prompt' +import { useChatContextStore } from '@proj-airi/stage-ui/stores/chat/context-store' +import { useChatSessionStore } from '@proj-airi/stage-ui/stores/chat/session-store' +import { useContextObservabilityStore } from '@proj-airi/stage-ui/stores/devtools/context-observability' import { useModsServerChannelStore } from '@proj-airi/stage-ui/stores/mods/api/channel-server' import { getEventSourceKey } from '@proj-airi/stage-ui/utils' -import { Callout } from '@proj-airi/ui' import { useBroadcastChannel } from '@vueuse/core' import { nanoid } from 'nanoid' +import { storeToRefs } from 'pinia' import { computed, onMounted, onUnmounted, ref, watch } from 'vue' import { toast } from 'vue-sonner' import ContextFlowActions from './components/context-flow-actions.vue' +import ContextFlowActiveContexts from './components/context-flow-active-contexts.vue' import ContextFlowFilters from './components/context-flow-filters.vue' +import ContextFlowLifecycle from './components/context-flow-lifecycle.vue' +import ContextFlowPromptProjection from './components/context-flow-prompt-projection.vue' +import ContextFlowRuntime from './components/context-flow-runtime.vue' import ContextFlowStream from './components/context-flow-stream.vue' import { useContextFlowFormatters } from './composables/use-context-flow-formatters' @@ -33,9 +42,15 @@ const { } = useContextFlowFormatters() const chatStore = useChatOrchestratorStore() +const chatContextStore = useChatContextStore() +const chatSessionStore = useChatSessionStore() const characterStore = useCharacterStore() const characterOrchestratorStore = useCharacterOrchestratorStore() +const contextObservabilityStore = useContextObservabilityStore() const serverChannelStore = useModsServerChannelStore() +const { activeSessionId } = storeToRefs(chatSessionStore) +const { connected, pendingSendCount } = storeToRefs(serverChannelStore) +const { lastPromptProjection, lastBroadcastPostedAt, lastBroadcastReceivedAt } = storeToRefs(contextObservabilityStore) const entries = ref([]) const showIncoming = ref(true) @@ -64,6 +79,8 @@ const testSparkNotifyPayload = ref(JSON.stringify({ const directionFilter = ref('all') const sparkNotifyStates = ref>(new Map()) +const now = ref(Date.now()) +let nowTimer: ReturnType | null = null const maxEntriesValue = computed(() => { const parsed = Number.parseInt(maxEntries.value, 10) @@ -96,6 +113,10 @@ const filteredEntries = computed(() => { return filtered.slice().reverse() }) +const activeContextBuckets = computed(() => chatContextStore.getContextBucketsSnapshot()) +const currentPromptText = computed(() => formatContextPromptText(chatContextStore.getContextsSnapshot())) +const pendingQueuedSends = computed(() => chatStore.getPendingQueuedSendSnapshot()) + function normalizePayload(payload: unknown) { try { return JSON.parse(JSON.stringify(payload)) as unknown @@ -205,6 +226,7 @@ function pushEntry(entry: Omit) { function clearEntries() { entries.value = [] + contextObservabilityStore.clearHistory() } function sendTestContextUpdate() { @@ -327,6 +349,10 @@ const { data: incomingStreamEvent } = useBroadcastChannel void> = [] onMounted(() => { + nowTimer = setInterval(() => { + now.value = Date.now() + }, 1000) + cleanupFns.push(serverChannelStore.onContextUpdate((event) => { pushEntry({ direction: 'incoming', @@ -537,16 +563,16 @@ watch(maxEntriesValue, () => { onUnmounted(() => { for (const cleanup of cleanupFns) cleanup() + + if (nowTimer) { + clearInterval(nowTimer) + nowTimer = null + } })