Add tests that reflect actual session data shape: history starts with
user messages (no system prompt), includes chained tool-call sequences,
reasoning content, and media items. Exercises the proactive budget check
path with BuildMessages-style assembled messages.
Add context_window to config.example.json, the web configuration page
(form model, input field, save handler), and i18n strings (en/zh).
The field is optional — leaving it empty falls back to the 4x max_tokens
heuristic.
estimateMessageTokens now counts ReasoningContent (extended thinking /
chain-of-thought) which can be substantial and is persisted in session
history. Media items get a fixed per-item overhead (256 tokens) since
actual cost depends on provider-specific image tokenization.
Session history (GetHistory) contains only user/assistant/tool messages.
The system prompt is built dynamically by BuildMessages and is never
stored in session. The previous code incorrectly treated history[0] as
a system prompt, skipping the first user message and appending a
compression note to it.
Fix: operate on the full history slice, and record the compression
note in the session summary (which BuildMessages already injects into
the system prompt) rather than modifying any history message.
Separate context_window from max_tokens — they serve different purposes
(input capacity vs output generation limit). The previous conflation caused
premature summarization or missed compression triggers.
Changes:
- Add context_window field to AgentDefaults config (default: 4x max_tokens)
- Extract boundary-safe truncation helpers (isSafeBoundary, findSafeBoundary)
into context_budget.go — pure functions with no AgentLoop dependency
- forceCompression: align split to safe boundary so tool-call sequences
(assistant+ToolCalls → tool results) are never torn apart
- summarizeSession: use findSafeBoundary instead of hardcoded keep-last-4
- estimateTokens: count ToolCalls arguments and ToolCallID metadata,
not just Content — fixes systematic undercounting in tool-heavy sessions
- Add proactive context budget check before LLM call in runAgentLoop,
preventing 400 context-length errors instead of reacting to them
- Add estimateToolDefsTokens for tool definition token cost
Closes#556, closes#665
Ref #1439
- Replace duplicate types (ToolResult/Session/Message) with real project types
- Implement ephemeralSessionStore satisfying session.SessionStore interface
- Connect runTurn to real AgentLoop via runAgentLoop + AgentInstance
- Fix subturn_test.go to match updated signatures and types
Co-Authored-By: Claude Sonnet 4 <noreply@anthropic.com>
* feat(credential): add AES-GCM encryption, SecureStore, and onboard keygen
- pkg/credential: new package with AES-256-GCM enc:// credential format,
HKDF-SHA256 key derivation (passphrase + optional SSH key binding),
ErrPassphraseRequired / ErrDecryptionFailed sentinel errors,
and PassphraseProvider hook for runtime passphrase injection
- pkg/credential/store: lock-free SecureStore via atomic.Pointer[string];
passphrase never written to disk or os.Environ
- pkg/credential/keygen: ed25519 SSH key generation helper used by onboard
- pkg/config: replace os.Getenv(PassphraseEnvVar) with
credential.PassphraseProvider() at all three call sites so that
LoadConfig and SaveConfig use whatever passphrase source is active
- cmd/picoclaw/onboard: prompt for passphrase with echo-off, generate
picoclaw-specific SSH key, re-encrypt existing config on re-onboard
- docs/credential_encryption.md: design doc for the enc:// format
* fix(credential): address Copilot review comments on PR #1521
- credential.go: decouple ErrPassphraseRequired from env var name;
message is now 'enc:// passphrase required' since PassphraseProvider
may come from any source, not just os.Environ
- credential.go: Resolver resolves symlinks via EvalSymlinks before the
isWithinDir containment check, preventing symlink-based path traversal
for file:// credential references
- store.go: tighten comment to describe only what SecureStore guarantees
(in-memory only); remove claims about how callers transport the value
- store_test.go: replace the meaningless GetReturnsCopy test (Go strings
are immutable, equality across two calls proves nothing) with
TestSecureStore_ConcurrentSetGet that exercises atomic.Pointer under
10-goroutine concurrent Set/Get load
- config_test.go: update error-message assertion to match new sentinel text
- docs/credential_encryption.md: remove reference to non-existent
'picoclaw encrypt' subcommand; describe the onboard flow instead
* fix(config): encryptPlaintextAPIKeys: struct-based encryption, fail-fast, remove raw []byte
* fix(credential): require SSH private key for encryption/decryption, remove passphrase-only mode
* lint: fix credential keygen lint, fix test keygen
* onboard: make encryption opt-in via --enc flag
Encryption (passphrase prompt + SSH key generation) is now only
triggered when the user passes --enc to 'picoclaw onboard'.
Without the flag, onboard skips the credential-encryption setup and
writes a plain config + workspace templates directly.
- Add --enc BoolFlag in NewOnboardCommand()
- Pass encrypt bool into onboard()
- Guard passphrase prompt, SSH key generation, and related env-var
setup behind the encrypt branch
- Adjust 'Next steps' output so the passphrase reminder only appears
when --enc was used
* fix: Use secure defaults for Pico channel setup and stop leaking the token in the URL
* fix: Derive default allow_origins from the setup request's Origin header instead of hardcoding localhost ports
* Add support for azure openai provider
* Add checks for deployment model name
* Apply suggestion from @Copilot
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
* Addressing @Copilot suggestion to remove the init() function which seemed redundant
* Fix readme
* Fix linting checks
---------
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
- centralize Pico chat connection and session state in a shared store
- move chat lifecycle control out of usePicoChat
- hydrate and restore the active session across the app