Fix CI: restore onboard embedded workspace and harden timeout test
This commit is contained in:
parent
b3cd76128e
commit
9f662830b1
7 changed files with 204 additions and 3 deletions
12
cmd/picoclaw/internal/onboard/workspace/AGENTS.md
Normal file
12
cmd/picoclaw/internal/onboard/workspace/AGENTS.md
Normal file
|
|
@ -0,0 +1,12 @@
|
||||||
|
# Agent Instructions
|
||||||
|
|
||||||
|
You are a helpful AI assistant. Be concise, accurate, and friendly.
|
||||||
|
|
||||||
|
## Guidelines
|
||||||
|
|
||||||
|
- Always explain what you're doing before taking actions
|
||||||
|
- Ask for clarification when request is ambiguous
|
||||||
|
- Use tools to help accomplish tasks
|
||||||
|
- Remember important information in your memory files
|
||||||
|
- Be proactive and helpful
|
||||||
|
- Learn from user feedback
|
||||||
56
cmd/picoclaw/internal/onboard/workspace/IDENTITY.md
Normal file
56
cmd/picoclaw/internal/onboard/workspace/IDENTITY.md
Normal file
|
|
@ -0,0 +1,56 @@
|
||||||
|
# Identity
|
||||||
|
|
||||||
|
## Name
|
||||||
|
PicoClaw 🦞
|
||||||
|
|
||||||
|
## Description
|
||||||
|
Ultra-lightweight personal AI assistant written in Go, inspired by nanobot.
|
||||||
|
|
||||||
|
## Version
|
||||||
|
0.1.0
|
||||||
|
|
||||||
|
## Purpose
|
||||||
|
- Provide intelligent AI assistance with minimal resource usage
|
||||||
|
- Support multiple LLM providers (OpenAI, Anthropic, Zhipu, etc.)
|
||||||
|
- Enable easy customization through skills system
|
||||||
|
- Run on minimal hardware ($10 boards, <10MB RAM)
|
||||||
|
|
||||||
|
## Capabilities
|
||||||
|
|
||||||
|
- Web search and content fetching
|
||||||
|
- File system operations (read, write, edit)
|
||||||
|
- Shell command execution
|
||||||
|
- Multi-channel messaging (Telegram, WhatsApp, Feishu)
|
||||||
|
- Skill-based extensibility
|
||||||
|
- Memory and context management
|
||||||
|
|
||||||
|
## Philosophy
|
||||||
|
|
||||||
|
- Simplicity over complexity
|
||||||
|
- Performance over features
|
||||||
|
- User control and privacy
|
||||||
|
- Transparent operation
|
||||||
|
- Community-driven development
|
||||||
|
|
||||||
|
## Goals
|
||||||
|
|
||||||
|
- Provide a fast, lightweight AI assistant
|
||||||
|
- Support offline-first operation where possible
|
||||||
|
- Enable easy customization and extension
|
||||||
|
- Maintain high quality responses
|
||||||
|
- Run efficiently on constrained hardware
|
||||||
|
|
||||||
|
## License
|
||||||
|
MIT License - Free and open source
|
||||||
|
|
||||||
|
## Repository
|
||||||
|
https://github.com/sipeed/picoclaw
|
||||||
|
|
||||||
|
## Contact
|
||||||
|
Issues: https://github.com/sipeed/picoclaw/issues
|
||||||
|
Discussions: https://github.com/sipeed/picoclaw/discussions
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
"Every bit helps, every bit matters."
|
||||||
|
- Picoclaw
|
||||||
17
cmd/picoclaw/internal/onboard/workspace/SOUL.md
Normal file
17
cmd/picoclaw/internal/onboard/workspace/SOUL.md
Normal file
|
|
@ -0,0 +1,17 @@
|
||||||
|
# Soul
|
||||||
|
|
||||||
|
I am picoclaw, a lightweight AI assistant powered by AI.
|
||||||
|
|
||||||
|
## Personality
|
||||||
|
|
||||||
|
- Helpful and friendly
|
||||||
|
- Concise and to the point
|
||||||
|
- Curious and eager to learn
|
||||||
|
- Honest and transparent
|
||||||
|
|
||||||
|
## Values
|
||||||
|
|
||||||
|
- Accuracy over speed
|
||||||
|
- User privacy and safety
|
||||||
|
- Transparency in actions
|
||||||
|
- Continuous improvement
|
||||||
21
cmd/picoclaw/internal/onboard/workspace/USER.md
Normal file
21
cmd/picoclaw/internal/onboard/workspace/USER.md
Normal file
|
|
@ -0,0 +1,21 @@
|
||||||
|
# User
|
||||||
|
|
||||||
|
Information about user goes here.
|
||||||
|
|
||||||
|
## Preferences
|
||||||
|
|
||||||
|
- Communication style: (casual/formal)
|
||||||
|
- Timezone: (your timezone)
|
||||||
|
- Language: (your preferred language)
|
||||||
|
|
||||||
|
## Personal Information
|
||||||
|
|
||||||
|
- Name: (optional)
|
||||||
|
- Location: (optional)
|
||||||
|
- Occupation: (optional)
|
||||||
|
|
||||||
|
## Learning Goals
|
||||||
|
|
||||||
|
- What the user wants to learn from AI
|
||||||
|
- Preferred interaction style
|
||||||
|
- Areas of interest
|
||||||
21
cmd/picoclaw/internal/onboard/workspace/memory/MEMORY.md
Normal file
21
cmd/picoclaw/internal/onboard/workspace/memory/MEMORY.md
Normal file
|
|
@ -0,0 +1,21 @@
|
||||||
|
# Long-term Memory
|
||||||
|
|
||||||
|
This file stores important information that should persist across sessions.
|
||||||
|
|
||||||
|
## User Information
|
||||||
|
|
||||||
|
(Important facts about user)
|
||||||
|
|
||||||
|
## Preferences
|
||||||
|
|
||||||
|
(User preferences learned over time)
|
||||||
|
|
||||||
|
## Important Notes
|
||||||
|
|
||||||
|
(Things to remember)
|
||||||
|
|
||||||
|
## Configuration
|
||||||
|
|
||||||
|
- Model preferences
|
||||||
|
- Channel settings
|
||||||
|
- Skills enabled
|
||||||
|
|
@ -3,7 +3,10 @@
|
||||||
package tools
|
package tools
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"os"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
"syscall"
|
"syscall"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -26,7 +29,57 @@ func terminateProcessTree(cmd *exec.Cmd) error {
|
||||||
|
|
||||||
// Kill the entire process group spawned by the shell command.
|
// Kill the entire process group spawned by the shell command.
|
||||||
_ = syscall.Kill(-pid, syscall.SIGKILL)
|
_ = syscall.Kill(-pid, syscall.SIGKILL)
|
||||||
|
// Some shells/background jobs may still leave descendants around
|
||||||
|
// briefly; aggressively walk /proc and kill child processes too.
|
||||||
|
killDescendants(pid)
|
||||||
// Fallback kill on the shell process itself.
|
// Fallback kill on the shell process itself.
|
||||||
_ = cmd.Process.Kill()
|
_ = cmd.Process.Kill()
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func killDescendants(ppid int) {
|
||||||
|
if ppid <= 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
entries, err := os.ReadDir("/proc")
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, e := range entries {
|
||||||
|
if !e.IsDir() {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
childPID, err := strconv.Atoi(e.Name())
|
||||||
|
if err != nil || childPID <= 0 || childPID == ppid {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
statPath := "/proc/" + e.Name() + "/stat"
|
||||||
|
data, err := os.ReadFile(statPath)
|
||||||
|
if err != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// /proc/<pid>/stat: pid (comm) state ppid ...
|
||||||
|
raw := string(data)
|
||||||
|
end := strings.LastIndex(raw, ")")
|
||||||
|
if end == -1 || end+2 >= len(raw) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
fields := strings.Fields(raw[end+2:])
|
||||||
|
if len(fields) < 2 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
parent, err := strconv.Atoi(fields[1])
|
||||||
|
if err != nil || parent != ppid {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// Recurse first, then kill child process/group.
|
||||||
|
killDescendants(childPID)
|
||||||
|
_ = syscall.Kill(-childPID, syscall.SIGKILL)
|
||||||
|
_ = syscall.Kill(childPID, syscall.SIGKILL)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -13,12 +13,33 @@ import (
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
func processExists(pid int) bool {
|
func processRunning(pid int) bool {
|
||||||
if pid <= 0 {
|
if pid <= 0 {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
// kill(0) can return success for zombie processes too, so inspect /proc
|
||||||
|
// state and treat zombies as not-running for timeout cleanup assertions.
|
||||||
err := syscall.Kill(pid, 0)
|
err := syscall.Kill(pid, 0)
|
||||||
return err == nil || err == syscall.EPERM
|
if err != nil && err != syscall.EPERM {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
data, readErr := os.ReadFile("/proc/" + strconv.Itoa(pid) + "/stat")
|
||||||
|
if readErr != nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
raw := string(data)
|
||||||
|
end := strings.LastIndex(raw, ")")
|
||||||
|
if end == -1 || end+2 >= len(raw) {
|
||||||
|
return true // best effort fallback
|
||||||
|
}
|
||||||
|
fields := strings.Fields(raw[end+2:])
|
||||||
|
if len(fields) == 0 {
|
||||||
|
return true // best effort fallback
|
||||||
|
}
|
||||||
|
|
||||||
|
state := fields[0]
|
||||||
|
return state != "Z"
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestShellTool_TimeoutKillsChildProcess(t *testing.T) {
|
func TestShellTool_TimeoutKillsChildProcess(t *testing.T) {
|
||||||
|
|
@ -55,7 +76,7 @@ func TestShellTool_TimeoutKillsChildProcess(t *testing.T) {
|
||||||
|
|
||||||
deadline := time.Now().Add(2 * time.Second)
|
deadline := time.Now().Add(2 * time.Second)
|
||||||
for time.Now().Before(deadline) {
|
for time.Now().Before(deadline) {
|
||||||
if !processExists(childPID) {
|
if !processRunning(childPID) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
time.Sleep(50 * time.Millisecond)
|
time.Sleep(50 * time.Millisecond)
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue