Persist conversation history to disk + mount workers under gateway appdata
Build Claw Telegram / build (push) Successful in 56s
Build Claw Telegram / cleanup (push) Successful in 1s

- TS worker saves/loads messages to {stateRoot}/conversation.json
- Saves after user message, assistant response, and session reset
- Loads on engine construction (survives container restarts)
- Add CLAW_GATEWAY_WORKER_HOST_STATE_ROOT and
  CLAW_GATEWAY_WORKER_HOST_WORKSPACE_ROOT to Unraid XML, defaulting
  to /mnt/user/appdata/claw-telegram-gateway/workers

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Wylabb
2026-04-08 02:22:25 +02:00
parent 4ba8098f7a
commit 12fdf7cf58
2 changed files with 32 additions and 1 deletions
+2
View File
@@ -33,6 +33,8 @@
<Config Name="Template Dir" Target="CLAW_GATEWAY_TEMPLATE_DIR" Default="/unraid/templates-user" Mode="" Description="Unraid template directory path inside the gateway container" Type="Variable" Display="advanced" Required="true" Mask="false">/unraid/templates-user</Config>
<Config Name="Template Archive Dir" Target="CLAW_GATEWAY_TEMPLATE_ARCHIVE_DIR" Default="/appdata/template-archive" Mode="" Description="Archive location for removed worker templates" Type="Variable" Display="advanced" Required="true" Mask="false">/appdata/template-archive</Config>
<Config Name="Worker Network" Target="CLAW_GATEWAY_WORKER_NETWORK" Default="claw_gateway" Mode="" Description="Docker network name shared by the gateway and workers" Type="Variable" Display="advanced" Required="true" Mask="false">claw_gateway</Config>
<Config Name="Worker Host State Root" Target="CLAW_GATEWAY_WORKER_HOST_STATE_ROOT" Default="/mnt/user/appdata/claw-telegram-gateway/workers" Mode="" Description="Host path root for worker state directories (bind-mounted into worker containers)" Type="Variable" Display="advanced" Required="true" Mask="false">/mnt/user/appdata/claw-telegram-gateway/workers</Config>
<Config Name="Worker Host Workspace Root" Target="CLAW_GATEWAY_WORKER_HOST_WORKSPACE_ROOT" Default="/mnt/user/appdata/claw-telegram-gateway/workers" Mode="" Description="Host path root for worker workspace directories (bind-mounted into worker containers)" Type="Variable" Display="advanced" Required="true" Mask="false">/mnt/user/appdata/claw-telegram-gateway/workers</Config>
<Config Name="Inherited Env" Target="CLAW_GATEWAY_INHERITED_ENV" Default="ANTHROPIC_AUTH_TOKEN,ANTHROPIC_API_KEY" Mode="" Description="Comma-separated env vars copied from gateway into worker containers" Type="Variable" Display="advanced" Required="false" Mask="false">ANTHROPIC_AUTH_TOKEN,ANTHROPIC_API_KEY</Config>
<Config Name="Worker Ready Timeout (s)" Target="CLAW_GATEWAY_WORKER_READY_TIMEOUT_SECS" Default="15" Mode="" Description="Seconds to wait for a worker container to pass its health check" Type="Variable" Display="advanced" Required="false" Mask="false">15</Config>
<Config Name="Anthropic OAuth Token" Target="ANTHROPIC_AUTH_TOKEN" Default="" Mode="" Description="Claude subscription OAuth token (sk-ant-oat...) inherited by worker containers" Type="Variable" Display="always" Required="false" Mask="true"></Config>
+30 -1
View File
@@ -14,6 +14,8 @@
*/
import Anthropic from '@anthropic-ai/sdk'
import { readFileSync, writeFileSync, mkdirSync } from 'node:fs'
import { join, dirname } from 'node:path'
import type { ApprovalBroker } from '../permissions/ApprovalBroker.js'
import { WorkerEventTranslator } from '../events/WorkerEventTranslator.js'
import type { WorkerTurnEvent } from '../protocol.js'
@@ -47,7 +49,7 @@ export class TelegramWorkerEngine {
private client: Anthropic | null = null
private useOAuth = false
// Session state — persists across turns
// Session state — persists across turns and container restarts
private messages: ConversationMessage[] = []
private ready = false
@@ -55,6 +57,30 @@ export class TelegramWorkerEngine {
this.config = config
this.broker = broker
this.translator = new WorkerEventTranslator()
this.loadMessages()
}
private get messagesPath(): string {
return join(this.config.stateRoot, 'conversation.json')
}
private loadMessages(): void {
try {
const data = readFileSync(this.messagesPath, 'utf-8')
this.messages = JSON.parse(data)
console.log(`[TelegramWorkerEngine] loaded ${this.messages.length} messages from disk`)
} catch {
this.messages = []
}
}
private saveMessages(): void {
try {
mkdirSync(dirname(this.messagesPath), { recursive: true })
writeFileSync(this.messagesPath, JSON.stringify(this.messages), 'utf-8')
} catch (err) {
console.error('[TelegramWorkerEngine] failed to save messages:', err)
}
}
async init(): Promise<boolean> {
@@ -112,6 +138,7 @@ export class TelegramWorkerEngine {
// Add user message to history
this.messages.push({ role: 'user', content: prompt })
this.saveMessages()
try {
const agentPrompt = `You are Claude, a personal AI assistant communicating via Telegram. You have no persistent memory between sessions — every conversation could be your last with this person before a reset wipes everything you've learned.
@@ -181,6 +208,7 @@ Current working directory: ${this.config.defaultCwd}`
.join('') || fullText
this.messages.push({ role: 'assistant', content: assistantText })
this.saveMessages()
yield {
type: 'completed',
@@ -199,6 +227,7 @@ Current working directory: ${this.config.defaultCwd}`
/** Reset session — clear conversation history. */
resetSession(): void {
this.messages = []
this.saveMessages()
this.broker.reset()
}
}