fix(tasktool): use agent session key, persist plan on create, add docs
- Add WithToolSessionKey/ToolSessionKey context helpers to pkg/tools/base.go - Inject opts.SessionKey into ctx in runAgentLoop so tasktool uses the same agent-scoped session key as conversation history instead of the bare channel:chatID composite (fixes plan cross-agent/scope collision) - Call Save inside CreatePlan so plans persist after restart even on channels that never return a message ID (fixes data loss on non-Telegram) - Add tasktool section to config/config.example.json and docs/tools_configuration.md (P3 documentation gap)
This commit is contained in:
parent
e7585e8185
commit
8f40b0099f
6 changed files with 78 additions and 7 deletions
|
|
@ -431,6 +431,15 @@
|
|||
},
|
||||
"write_file": {
|
||||
"enabled": true
|
||||
},
|
||||
"tasktool": {
|
||||
"enabled": true,
|
||||
"icons": {
|
||||
"pending": "🔘",
|
||||
"in_progress": "🟡",
|
||||
"completed": "🟢",
|
||||
"failed": "🔴"
|
||||
}
|
||||
}
|
||||
},
|
||||
"heartbeat": {
|
||||
|
|
|
|||
|
|
@ -206,6 +206,48 @@ The skills tool configures skill discovery and installation via registries like
|
|||
}
|
||||
```
|
||||
|
||||
## Task Tool
|
||||
|
||||
The task tool lets the agent create and track a step-by-step execution plan visible to the user in real time.
|
||||
|
||||
### Config
|
||||
|
||||
| Config | Type | Default | Description |
|
||||
| ------------------- | ------ | ------- | -------------------------------------- |
|
||||
| `enabled` | bool | true | Enable the task planning tool |
|
||||
| `icons.pending` | string | 🔘 | Icon shown for pending tasks |
|
||||
| `icons.in_progress` | string | 🟡 | Icon shown for tasks in progress |
|
||||
| `icons.completed` | string | 🟢 | Icon shown for completed tasks |
|
||||
| `icons.failed` | string | 🔴 | Icon shown for failed tasks |
|
||||
|
||||
### Configuration Example
|
||||
|
||||
```json
|
||||
{
|
||||
"tools": {
|
||||
"tasktool": {
|
||||
"enabled": true,
|
||||
"icons": {
|
||||
"pending": "🔘",
|
||||
"in_progress": "🟡",
|
||||
"completed": "🟢",
|
||||
"failed": "🔴"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Environment Variables
|
||||
|
||||
| Variable | Description |
|
||||
| -------------------------------------------- | ------------------------ |
|
||||
| `PICOCLAW_TOOLS_TASK_TOOL_ENABLED` | Enable/disable the tool |
|
||||
| `PICOCLAW_TOOLS_TASK_TOOL_ICONS_PENDING` | Pending task icon |
|
||||
| `PICOCLAW_TOOLS_TASK_TOOL_ICONS_IN_PROGRESS` | In-progress task icon |
|
||||
| `PICOCLAW_TOOLS_TASK_TOOL_ICONS_COMPLETED` | Completed task icon |
|
||||
| `PICOCLAW_TOOLS_TASK_TOOL_ICONS_FAILED` | Failed task icon |
|
||||
|
||||
## Environment Variables
|
||||
|
||||
All configuration options can be overridden via environment variables with the format `PICOCLAW_TOOLS_<SECTION>_<KEY>`:
|
||||
|
|
|
|||
|
|
@ -798,6 +798,8 @@ func (al *AgentLoop) runAgentLoop(
|
|||
agent.Sessions.AddMessage(opts.SessionKey, "user", opts.UserMessage)
|
||||
|
||||
// 3. Run LLM iteration loop
|
||||
// Inject session key so tools (e.g. tasktool) can look it up from context.
|
||||
ctx = tools.WithToolSessionKey(ctx, opts.SessionKey)
|
||||
finalContent, iteration, err := al.runLLMIteration(ctx, agent, messages, opts)
|
||||
if err != nil {
|
||||
return "", err
|
||||
|
|
|
|||
|
|
@ -97,6 +97,9 @@ func (tm *TaskManager) CreatePlan(sessionKey string, tasks []Task) *SessionTasks
|
|||
}
|
||||
copy(st.Tasks, tasks)
|
||||
tm.tasks[sessionKey] = st
|
||||
// Persist immediately so the plan survives a restart even when no
|
||||
// message ID is available (e.g. channels that don't return a message ID).
|
||||
go func() { _ = tm.Save(sessionKey) }()
|
||||
return st
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -21,8 +21,9 @@ type Tool interface {
|
|||
type toolCtxKey struct{ name string }
|
||||
|
||||
var (
|
||||
ctxKeyChannel = &toolCtxKey{"channel"}
|
||||
ctxKeyChatID = &toolCtxKey{"chatID"}
|
||||
ctxKeyChannel = &toolCtxKey{"channel"}
|
||||
ctxKeyChatID = &toolCtxKey{"chatID"}
|
||||
ctxKeySessionKey = &toolCtxKey{"sessionKey"}
|
||||
)
|
||||
|
||||
// WithToolContext returns a child context carrying channel and chatID.
|
||||
|
|
@ -32,6 +33,11 @@ func WithToolContext(ctx context.Context, channel, chatID string) context.Contex
|
|||
return ctx
|
||||
}
|
||||
|
||||
// WithToolSessionKey returns a child context carrying the session key.
|
||||
func WithToolSessionKey(ctx context.Context, sessionKey string) context.Context {
|
||||
return context.WithValue(ctx, ctxKeySessionKey, sessionKey)
|
||||
}
|
||||
|
||||
// ToolChannel extracts the channel from ctx, or "" if unset.
|
||||
func ToolChannel(ctx context.Context) string {
|
||||
v, _ := ctx.Value(ctxKeyChannel).(string)
|
||||
|
|
@ -44,6 +50,12 @@ func ToolChatID(ctx context.Context) string {
|
|||
return v
|
||||
}
|
||||
|
||||
// ToolSessionKey extracts the session key from ctx, or "" if unset.
|
||||
func ToolSessionKey(ctx context.Context) string {
|
||||
v, _ := ctx.Value(ctxKeySessionKey).(string)
|
||||
return v
|
||||
}
|
||||
|
||||
// AsyncCallback is a function type that async tools use to notify completion.
|
||||
// When an async tool finishes its work, it calls this callback with the result.
|
||||
//
|
||||
|
|
|
|||
|
|
@ -98,11 +98,14 @@ func (t *TaskTool) Execute(ctx context.Context, args map[string]any) *ToolResult
|
|||
|
||||
channel := ToolChannel(ctx)
|
||||
chatID := ToolChatID(ctx)
|
||||
// We use the same combination for task state as session manager might.
|
||||
// But note: AgentLoop uses scopes out of routes. We'll use channel:chatID as implicit for now
|
||||
// To be perfectly aligned with SessionKey, we'd need to extract SessionKey from context.
|
||||
// We'll add SessionKey to context later if needed, or just use channel:chatID for tasks since planning is chat-specific.
|
||||
sessionKey := fmt.Sprintf("%s:%s", channel, chatID)
|
||||
// Use the agent-scoped session key injected by the agent loop so that task
|
||||
// plans are stored under the same key used for conversation history.
|
||||
// Fall back to channel:chatID only when running outside of an agent loop
|
||||
// (e.g. unit tests or direct tool invocations).
|
||||
sessionKey := ToolSessionKey(ctx)
|
||||
if sessionKey == "" {
|
||||
sessionKey = fmt.Sprintf("%s:%s", channel, chatID)
|
||||
}
|
||||
|
||||
action, ok := args["action"].(string)
|
||||
if !ok {
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue