Skip to content

Commit 9ea4dd9

Browse files
committed
feat: add preview support for ask_user tool
1 parent 0992ac8 commit 9ea4dd9

6 files changed

Lines changed: 363 additions & 57 deletions

File tree

internal/cron/cron.go

Lines changed: 3 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,6 @@ import (
1313
"strconv"
1414
"strings"
1515
"sync"
16-
"syscall"
1716
"time"
1817
)
1918

@@ -35,7 +34,7 @@ type Job struct {
3534
Durable bool `json:"durable,omitempty"`
3635

3736
interval time.Duration // parsed interval (mutually exclusive with cronExpr)
38-
cronExpr *CronExpr // parsed cron expression
37+
cronExpr *CronExpr // parsed cron expression
3938
nextFire time.Time
4039
}
4140

@@ -67,8 +66,8 @@ type Store struct {
6766

6867
writeCh chan struct{} // capacity 1 — coalesces multiple write requests
6968
closeCh chan struct{} // signals writer goroutine to stop
70-
writeMu sync.Mutex // serializes StartWriter/StopWriter calls
71-
writing bool // true while writer goroutine is running
69+
writeMu sync.Mutex // serializes StartWriter/StopWriter calls
70+
writing bool // true while writer goroutine is running
7271
}
7372

7473
// NewStore creates an empty Store. Call SetConfigDir to enable durable storage.
@@ -609,14 +608,6 @@ func (s *Store) readLock(lockPath string) *lockData {
609608
return &ld
610609
}
611610

612-
// processAlive checks if a PID is alive by sending signal 0.
613-
func processAlive(pid int) bool {
614-
if pid <= 0 {
615-
return false
616-
}
617-
return syscall.Kill(pid, 0) == nil
618-
}
619-
620611
// ---------------------------------------------------------------------------
621612
// Jitter — deterministic per-ID offset to avoid thundering herd
622613
// ---------------------------------------------------------------------------
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
//go:build !windows
2+
3+
package cron
4+
5+
import "syscall"
6+
7+
// processAlive checks whether the PID still exists on Unix-like systems.
8+
func processAlive(pid int) bool {
9+
if pid <= 0 {
10+
return false
11+
}
12+
err := syscall.Kill(pid, 0)
13+
return err == nil || err == syscall.EPERM
14+
}
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
//go:build windows
2+
3+
package cron
4+
5+
import "syscall"
6+
7+
const processQueryLimitedInformation = 0x1000
8+
9+
// processAlive checks whether the PID still exists on Windows.
10+
func processAlive(pid int) bool {
11+
if pid <= 0 {
12+
return false
13+
}
14+
15+
handle, err := syscall.OpenProcess(processQueryLimitedInformation, false, uint32(pid))
16+
if err == nil {
17+
_ = syscall.CloseHandle(handle)
18+
return true
19+
}
20+
21+
// Access denied still means a process with that PID exists.
22+
return err == syscall.ERROR_ACCESS_DENIED
23+
}

internal/tools/ask_user.go

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,10 +11,10 @@ import (
1111
"github.com/voocel/agentcore/schema"
1212
)
1313

14-
// AskUserResponse carries answers and optional user notes (from custom input).
14+
// AskUserResponse carries answers and optional user notes.
1515
type AskUserResponse struct {
1616
Answers map[string]string // question text → answer
17-
Notes map[string]string // question text → custom note (only for "Type something")
17+
Notes map[string]string // question text → user note (from "Type something" or preview notes)
1818
}
1919

2020
// AskUserHandler blocks until the user answers all questions.
@@ -33,6 +33,7 @@ type Question struct {
3333
type Option struct {
3434
Label string `json:"label"`
3535
Description string `json:"description"`
36+
Preview string `json:"preview,omitempty"` // optional markdown preview content
3637
}
3738

3839
// AskUserTool lets the LLM ask the user structured questions.
@@ -63,6 +64,7 @@ func (t *AskUserTool) Schema() map[string]any {
6364
option := schema.Object(
6465
schema.Property("label", schema.String("Display text (1-5 words)")).Required(),
6566
schema.Property("description", schema.String("What this option means")).Required(),
67+
schema.Property("preview", schema.String("Optional preview content (markdown) shown in a side panel when this option is focused")),
6668
)
6769
question := schema.Object(
6870
schema.Property("question", schema.String("The complete question to ask")).Required(),
@@ -148,9 +150,16 @@ func formatAnswers(questions []Question, resp *AskUserResponse) string {
148150
continue
149151
}
150152
entry := fmt.Sprintf("%q=%q", q.Question, answer)
151-
if note, hasNote := resp.Notes[q.Question]; hasNote {
153+
if note, hasNote := resp.Notes[q.Question]; hasNote && note != "" {
152154
entry += " user notes: " + note
153155
}
156+
// Include preview content of the selected option for context.
157+
for _, opt := range q.Options {
158+
if opt.Label == answer && opt.Preview != "" {
159+
entry += " preview: " + opt.Preview
160+
break
161+
}
162+
}
154163
parts = append(parts, entry)
155164
}
156165
return fmt.Sprintf("User has answered your questions: %s. You can now continue with the user's answers in mind.", strings.Join(parts, ", "))

0 commit comments

Comments
 (0)