diff --git a/.gitignore b/.gitignore index 135867842..a18f3ba14 100644 --- a/.gitignore +++ b/.gitignore @@ -69,3 +69,4 @@ web/backend/dist/* docker/data .omc/ +/local_pi/ diff --git a/docs/custom/git-workflow.md b/docs/custom/git-workflow.md new file mode 100644 index 000000000..5351c59c1 --- /dev/null +++ b/docs/custom/git-workflow.md @@ -0,0 +1,51 @@ +# Git Workflow Guide + +This guide breaks down how to manage your custom `picoclaw` logic alongside upstream official releases. + +## Core Concepts +- **Forking** is for **Taking Ownership**: It creates an independent copy of an entire project repository on your GitHub account so you have full read/write admin controls. +- **Branching** is for **Making Changes**: It isolates a specific line of code history (e.g., `feature/wrapper`) so you can experiment without breaking your working `main` branch. + +## The "One Custom Branch" Strategy (Best & Lowest Effort) + +For maintaining a personal Raspberry Pi deployment, the cleanest, least confusing, and absolute lowest effort method is to literally never switch branches again. Instead of juggling dozens of feature branches, you maintain a single dedicated branch (e.g., `custom-pi`) that holds all your custom logic. + +**Initial Setup:** +1. **The Fork**: You forked `sipeed/picoclaw` to `TimZickenrott/picoclaw`. +2. **The Clone**: You downloaded your fork to your PC. +3. **The Branch**: You started on `feature/wrapper`. Let's rename this to be your permanent deploy branch: + ```bash + git branch -m feature/wrapper custom-pi + ``` + +From now on, you always work on and deploy from `custom-pi`. + +### How to Save Your Work (Pushing) +When you write new custom code or edit features, just commit and push directly to `custom-pi` to save it: +```bash +git add . +git commit -m "Added my new custom logic" +git push origin custom-pi +``` + +## How To Pull Official Updates (Merging) + +When the official Sipeed developers release a new version (e.g., bugfixes or new capabilities), you want to pull those into your `custom-pi` branch so you get the newest features without losing your wrapper code. + +Here is exactly how you do it, all from your `custom-pi` branch: +*(Assuming you already added the original repo as an 'upstream' remote)* +```bash +# 1. Download the newest official code +git fetch upstream + +# 2. Smoothly merge the newest official features directly into your custom code +git merge upstream/main +``` + +### How Git Resolves This +- **The Safe Merge:** If Sipeed changed completely different files than you did (e.g., they fixed a bug in `telegram.go`, but you modified `loop.go`), Git will automatically splice the files together perfectly silently. Your wrapper stays intact. +- **The Merge Conflict:** If the developers modified the *exact same* lines of code in `loop.go` that you modified, Git will pause the merge and declare a "Merge Conflict." + - You open `loop.go` in your IDE. You will see markers (`<<<<<<`) showing their new code alongside your custom code. + - You manually edit that block to make sure your wrapper surrounds their new logic, save it, and run `git merge --continue`. + +Since your custom wrapper is self-contained and small, adjusting the code during a conflict takes barely a minute! diff --git a/docs/custom/pi-build.md b/docs/custom/pi-build.md new file mode 100644 index 000000000..5bb17cdc9 --- /dev/null +++ b/docs/custom/pi-build.md @@ -0,0 +1,54 @@ +# Raspberry Pi Build Guide + +This guide details the step-by-step process for building a custom version of Picoclaw and transferring it to a Raspberry Pi Zero 2W running locally. + +## Prerequisite: On Vulcan (Windows) + +1. **Install Go**: Ensure Go is installed on your Windows machine (`https://go.dev/dl/`). Add it to your `PATH` or invoke it directly. +2. **Clone repo**: Clone your custom fork of Picoclaw: + ```bash + git clone https://github.com//picoclaw.git + ``` +3. **Modify code**: Make your custom logic changes (e.g., in `pkg/agent/metrics.go` and `pkg/agent/loop.go`). + +## Step A: Compile for Raspberry Pi (ARM v7) + +Since the Raspberry Pi Zero 2W is an ARM-based environment (running a 32-bit `armhf` OS usually), we use Go's powerful cross-compilation features natively on Windows. + +Run this single command in PowerShell from the repository root: +```powershell +$env:CGO_ENABLED="0"; $env:GOOS="linux"; $env:GOARCH="arm"; $env:GOARM="7"; go build -o picoclaw-custom ./cmd/picoclaw +``` + +This instructs the Go compiler to generate a standalone Linux executable tailored for the Pi's architecture. + +## Step B: Transfer to Pi + +Use `scp` (Secure Copy Protocol), which transfers files over an encrypted SSH connection. + +```powershell +scp picoclaw-custom tim@picoclaw.local:/tmp/ +``` + +*This copies your newly compiled `picoclaw-custom` file from Windows up to the `/tmp/` folder on your Pi.* + +## Step C: Update the Raspberry Pi Service + +Log into your Raspberry Pi terminal via SSH. + +1. **Stop the current running service**: + ```bash + sudo systemctl stop picoclaw + ``` +2. **Replace the old binary with the new custom one**: + ```bash + sudo mv /tmp/picoclaw-custom /usr/local/bin/picoclaw + ``` + *(Ensure the binary is executable: `sudo chmod +x /usr/local/bin/picoclaw`)* +3. **Restart the service to utilize the new brain**: + ```bash + sudo systemctl start picoclaw + ``` + +### Why Cross-Compile? +Cross-compiling on your powerful Windows desktop ("Vulcan") saves the Raspberry Pi Zero 2W from the massive heat, CPU stress, and time-consumption of downloading the Go SDK and compiling code with 512MB of RAM. diff --git a/pkg/agent/loop.go b/pkg/agent/loop.go index fc37ff8a0..01f10e2b3 100644 --- a/pkg/agent/loop.go +++ b/pkg/agent/loop.go @@ -1589,10 +1589,37 @@ func (al *AgentLoop) runAgentLoop( } if opts.SendResponse && result.finalContent != "" { + var totalTokens, promptTokens, compTokens int + if usage := ts.GetLastUsage(); usage != nil { + totalTokens = usage.TotalTokens + promptTokens = usage.PromptTokens + compTokens = usage.CompletionTokens + } + + version := al.GetConfig().BuildInfo.Version + if version == "" { + version = "0.2.5" + } + + providerName := al.GetConfig().Agents.Defaults.Provider + modelName := al.GetConfig().Agents.Defaults.GetModelName() + + m := Metrics{ + Version: version, + Agent: agent.ID, + Route: providerName, + Model: modelName, + Complexity: calculateComplexity(promptTokens, compTokens), + Tokens: totalTokens, + Processing: time.Since(ts.startedAt), + } + + finalWrappedContent := WrapResponse(result.finalContent, m) + al.bus.PublishOutbound(ctx, bus.OutboundMessage{ Channel: opts.Channel, ChatID: opts.ChatID, - Content: result.finalContent, + Content: finalWrappedContent, }) } diff --git a/pkg/agent/metrics.go b/pkg/agent/metrics.go new file mode 100644 index 000000000..b612c3be2 --- /dev/null +++ b/pkg/agent/metrics.go @@ -0,0 +1,28 @@ +package agent + +import ( + "fmt" + "time" +) + +type Metrics struct { + Version string + Agent string + Route string + Model string + Complexity int + Tokens int + Processing time.Duration +} + +func WrapResponse(rawOutput string, m Metrics) string { + header := fmt.Sprintf( + "System: picoclaw-%s\nAgent: %s\nRoute: %s\nModel: %s\nComplexity: %d\nTokens: %d\nProcessing: %.1fs\n\n", + m.Version, m.Agent, m.Route, m.Model, m.Complexity, m.Tokens, m.Processing.Seconds(), + ) + return header + rawOutput +} + +func calculateComplexity(promptTokens, completionTokens int) int { + return int(float64(promptTokens)*0.1 + float64(completionTokens)*0.5) +}