Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions c/agent.c
Original file line number Diff line number Diff line change
Expand Up @@ -2033,7 +2033,7 @@ char *agent_handle_sub_agent(Agent *agent, const char *prompt,
/* 显示启动通知(事件已手动写入,只推显示队列不重复写事件) */
DisplayMessage *dm = malloc(sizeof(DisplayMessage));
*dm = display_msg_sub_agent_start(sub_session_id, description);
if (!store_event_stream_json_enabled()) {
if (!store_event_stream_json_enabled() && agent->display_queue) {
mq_push(agent->display_queue, dm);
} else {
display_message_free(dm);
Expand Down Expand Up @@ -2160,7 +2160,7 @@ int agent_handle_sub_agent_result(Agent *agent, const char *session_id,
DisplayMessage *dm = malloc(sizeof(DisplayMessage));
*dm = display_msg_sub_agent_result(session_id, status, thinking,
text, in_tokens, out_tokens);
if (!store_event_stream_json_enabled()) {
if (!store_event_stream_json_enabled() && agent->display_queue) {
mq_push(agent->display_queue, dm);
} else {
display_message_free(dm);
Expand Down
26 changes: 26 additions & 0 deletions c/cagent.c
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,19 @@ static void image_paste_wrapper(char **out, size_t *outlen) {
agent_image_clipboard_paste(g_paste_session_dir, out, outlen);
}

/* 崩溃信号处理:恢复终端 raw mode 后重新发出信号以生成 core dump。
* 仅使用 async-signal-safe 函数。 */
static void crash_handler(int sig) {
linenoiseRestoreTerminal();
/* 输出换行,避免终端残留 prompt 行 */
const char msg[] = "\n\rcagent: fatal signal received, terminal restored.\n\r";
write(STDERR_FILENO, msg, sizeof(msg) - 1);
/* 恢复默认 handler 并重新发送信号,让 OS 生成 core dump */
signal(sig, SIG_DFL);
raise(sig);
_exit(128 + sig); /* 理论上不会到达 */
}

int main(int argc, char *argv[]) {
srand((unsigned int)time(NULL) ^ (unsigned int)getpid());
/* 默认值 */
Expand Down Expand Up @@ -235,6 +248,19 @@ int main(int argc, char *argv[]) {
* 可能触发 SIGPIPE,默认行为是终止整个进程 */
signal(SIGPIPE, SIG_IGN);

/* SIGSEGV / SIGABRT handler:进程崩溃时恢复终端 raw mode,避免
* linenoise 设置的 raw mode 残留导致终端排版错乱。
* handler 内仅调用 async-signal-safe 函数(tcsetattr / write / _exit)。 */
struct sigaction sa;
memset(&sa, 0, sizeof(sa));
sa.sa_handler = crash_handler;
sa.sa_flags = SA_RESETHAND; /* 恢复默认行为,允许 core dump */
sigemptyset(&sa.sa_mask);
sigaction(SIGSEGV, &sa, NULL);
sigaction(SIGABRT, &sa, NULL);
sigaction(SIGBUS, &sa, NULL);
sigaction(SIGFPE, &sa, NULL);

/* 初始化 curl */
curl_global_init(CURL_GLOBAL_DEFAULT);

Expand Down
1 change: 1 addition & 0 deletions c/json.c
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,7 @@ JsonParse json_parse(const char *src, size_t *pos) {
}

JsonParse json_parse_root(const char *src) {
if (!src) return make_err("null input");
size_t pos = 0;
JsonParse p = json_parse_internal(src, &pos);
if (p.error) return p;
Expand Down
3 changes: 3 additions & 0 deletions c/msgqueue.c
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ void mq_destroy(MsgQueue *q) {
}

int mq_push(MsgQueue *q, void *data) {
if (!q || !data) return -1;
MQNode *node = malloc(sizeof(MQNode));
if (!node) return -1;
node->data = data;
Expand All @@ -62,6 +63,7 @@ int mq_push(MsgQueue *q, void *data) {
}

int mq_pop(MsgQueue *q, void **data_out) {
if (!q || !data_out) return -1;
pthread_mutex_lock(&q->mutex);
while (!q->head && !q->closed) {
pthread_cond_wait(&q->cond, &q->mutex);
Expand All @@ -83,6 +85,7 @@ int mq_pop(MsgQueue *q, void **data_out) {
}

int mq_try_pop(MsgQueue *q, void **data_out) {
if (!q || !data_out) return -1;
pthread_mutex_lock(&q->mutex);
if (!q->head) {
pthread_mutex_unlock(&q->mutex);
Expand Down
25 changes: 25 additions & 0 deletions go/cmd/goagent/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,38 @@ import (
"flag"
"fmt"
"os"
"os/signal"
"path/filepath"
"strings"
"syscall"

agent "github.com/lloyd/claude-code/bash-agent/go2"
"github.com/lloyd/claude-code/bash-agent/go2/linenoise"
)

func main() {
// Crash recovery: linenoise readline runs the terminal in raw mode.
// A panic or fatal signal (e.g. NULL deref in cgo) would leave the
// terminal in raw mode — no echo, no newline processing. These handlers
// restore the terminal before the process dies.
defer func() {
if r := recover(); r != nil {
linenoise.RestoreTerminal()
fmt.Fprintf(os.Stderr, "\ngoagent panic: %v\n", r)
os.Exit(1)
}
}()
crashSigCh := make(chan os.Signal, 1)
signal.Notify(crashSigCh, syscall.SIGSEGV, syscall.SIGABRT, syscall.SIGBUS, syscall.SIGFPE)
go func() {
sig := <-crashSigCh
linenoise.RestoreTerminal()
signal.Reset(sig)
p, _ := os.FindProcess(os.Getpid())
_ = p.Signal(sig.(syscall.Signal))
os.Exit(1)
}()

// 参数定义
var (
provider string
Expand Down
7 changes: 7 additions & 0 deletions go/cmd/goagent/readline.go
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,13 @@ func (r *Readline) SetInterruptCallback(fn func()) {
const promptStr = "\x1b[32m> \x1b[0m"

func (r *Readline) loop() {
defer func() {
if rec := recover(); rec != nil {
linenoise.RestoreTerminal()
fmt.Fprintf(os.Stderr, "\nreadline panic: %v\n", rec)
os.Exit(1)
}
}()
buf := make([]byte, 65536)
stdinFd := int(os.Stdin.Fd())

Expand Down
7 changes: 7 additions & 0 deletions go/linenoise/linenoise.go
Original file line number Diff line number Diff line change
Expand Up @@ -189,6 +189,13 @@ func SetMultiLine(ml bool) {
}
}

// RestoreTerminal restores the terminal from raw mode. Safe to call from a
// signal handler or panic recovery — internally only uses async-signal-safe
// C functions (tcsetattr, write).
func RestoreTerminal() {
C.linenoiseRestoreTerminal()
}

// EditStart initializes a non-blocking line editing session.
// Returns a LinenoiseState pointer for subsequent EditFeed/EditStop/Hide/Show calls.
func EditStart(stdinFd, stdoutFd int, buf []byte, prompt string) (*LinenoiseState, error) {
Expand Down
7 changes: 7 additions & 0 deletions vendor/linenoise/linenoise.c
Original file line number Diff line number Diff line change
Expand Up @@ -2324,6 +2324,13 @@ static void linenoiseAtExit(void) {
freeHistory();
}

/* Public wrapper to restore terminal from a crash signal handler.
* disableRawMode only calls tcsetattr (async-signal-safe) and write
* (async-signal-safe), so this is safe to call from a signal handler. */
void linenoiseRestoreTerminal(void) {
disableRawMode(STDIN_FILENO);
}

/* This is the API call to add a new entry in the linenoise history.
* It uses a fixed array of char pointers that are shifted (memmoved)
* when the history max length is reached in order to remove the older
Expand Down
4 changes: 4 additions & 0 deletions vendor/linenoise/linenoise.h
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,10 @@ void linenoisePrintf(const char *fmt, ...);
void linenoiseRegisterState(struct linenoiseState *ls);
void linenoiseSetActive(int active);

/* Restore terminal to pre-raw-mode state. Async-signal-safe wrapper
* around disableRawMode for use in crash signal handlers. */
void linenoiseRestoreTerminal(void);

#ifdef __cplusplus
}
#endif
Expand Down
Loading