diff --git a/docs/parliament.md b/docs/parliament.md index b1846a7..e686c03 100644 --- a/docs/parliament.md +++ b/docs/parliament.md @@ -28,9 +28,10 @@ cst-agent --login --realm-label "thinkpad" 查看状态: ```bash -cst-agent # 进入 TUI 后 /status -# 或 CLI -cst-agent --service-stop # 停服务 +cst-agent # TUI: /status 或 /realm +# /realm login # TUI 内登录并启服务 +# /realm stop # 停服务 +cst-agent --service-stop ``` 密码 Realm 开启后,直接打开 agent 会要求登录(TTY 下可当场输入密码)。 diff --git a/include/cst_parliament.h b/include/cst_parliament.h index 2177bac..7148e82 100644 --- a/include/cst_parliament.h +++ b/include/cst_parliament.h @@ -63,8 +63,11 @@ int cst_parliament_apply_sync(unsigned long revision, const char *parliament_id, /* Background parliament service: discover + peer listen + context sync. */ int cst_parliament_daemon_pidfile(char *out, size_t n); int cst_parliament_daemon_running(void); +/* Read pidfile metadata. Returns 0 when a live service is recorded. */ +int cst_parliament_daemon_info(int *pid_out, char *instance_out, size_t in_sz, + int *port_out); /* Start in background (Unix fork). password unlocks Realm in the child. - * Returns 0 and writes pid. If already running, returns 0. */ + * Returns 0 and writes pid. Restarts if a different instance/port is running. */ int cst_parliament_daemon_start(const char *password, const char *instance_id, int peer_port, char *err, size_t err_sz); int cst_parliament_daemon_stop(void); diff --git a/include/cst_version.h b/include/cst_version.h index 3a86578..a4165ed 100644 --- a/include/cst_version.h +++ b/include/cst_version.h @@ -3,7 +3,7 @@ #define CST_VERSION_MAJOR 0 #define CST_VERSION_MINOR 9 -#define CST_VERSION_PATCH 15 +#define CST_VERSION_PATCH 16 #define CST_VERSION_STR_(x) #x #define CST_VERSION_STR(x) CST_VERSION_STR_(x) diff --git a/src/agent_loop.c b/src/agent_loop.c index 8d5e1f7..006507c 100644 --- a/src/agent_loop.c +++ b/src/agent_loop.c @@ -292,7 +292,8 @@ static const char DEF_INTRO[] = "- Verify (build, tests, or running it) before saying it works; if you " "could not verify, say so.\n" "- Keep going until the task is done or truly blocked. Prefer acting with " - "tools over describing the next step; do not stop at a plan unless asked.\n\n"; + "tools over describing the next step; do not stop at a plan unless asked. " + "Skip filler like \"I'll start by looking\" — call a tool immediately.\n\n"; /* Same role, framed for /mode general: the workspace may hold documents, * data or anything else, not just code. */ @@ -312,7 +313,8 @@ static const char DEF_INTRO_GENERAL[] = "- Check your work when you can (re-read it, run it, cross-check the " "source); if you could not verify something, say so.\n" "- Keep going until the task is done or truly blocked. Prefer acting with " - "tools over describing the next step; do not stop at a plan unless asked.\n\n"; + "tools over describing the next step; do not stop at a plan unless asked. " + "Skip filler like \"I'll start by looking\" — call a tool immediately.\n\n"; static void put_tool_use(cst_buf_t *b, const cst_agent_config_t *cfg) { cst_buf_puts(b, "\nTool use:\n"); diff --git a/src/parliament.c b/src/parliament.c index 30d0078..2f43907 100644 --- a/src/parliament.c +++ b/src/parliament.c @@ -449,7 +449,10 @@ char *cst_parliament_status_text(void) { cst_parliament_load(&m); char sid[64] = {0}; int sess = cst_parliament_session_ok(sid, sizeof(sid)); - int svc = cst_parliament_daemon_running(); + int svc_pid = 0, svc_port = 0; + char svc_id[64] = {0}; + int svc = cst_parliament_daemon_info(&svc_pid, svc_id, sizeof(svc_id), + &svc_port) == 0; cst_buf_printf(&b, "realm %s\n", pw ? "password" : "file-keys/legacy"); cst_buf_printf(&b, "parliament %s project=%s rev=%lu\n", m.status[0] ? m.status : "idle", @@ -460,6 +463,10 @@ char *cst_parliament_status_text(void) { m.chair_device[0] ? " @ " : "", m.chair_device); cst_buf_printf(&b, "login %s%s%s\n", sess ? "ok" : "required", sess && sid[0] ? " as " : "", sess ? sid : ""); - cst_buf_printf(&b, "service %s\n", svc ? "running" : "stopped"); + if (svc) + cst_buf_printf(&b, "service running pid=%d id=%s port=%d\n", svc_pid, + svc_id[0] ? svc_id : "-", svc_port); + else + cst_buf_puts(&b, "service stopped\n"); return cst_buf_steal(&b); } diff --git a/src/parliament_daemon.c b/src/parliament_daemon.c index 673bb2e..3060b3e 100644 --- a/src/parliament_daemon.c +++ b/src/parliament_daemon.c @@ -41,21 +41,53 @@ int cst_parliament_daemon_pidfile(char *out, size_t n) { return os_path_join(out, n, dir, "parliament.pid"); } -static int read_pid(void) { +typedef struct { + int pid; + char instance[64]; + int port; +} daemon_meta_t; + +static int read_meta(daemon_meta_t *m) { + memset(m, 0, sizeof(*m)); + m->pid = -1; char path[OS_PATH_MAX]; if (cst_parliament_daemon_pidfile(path, sizeof(path)) != 0) return -1; FILE *f = fopen(path, "rb"); if (!f) return -1; - int pid = -1; - if (fscanf(f, "%d", &pid) != 1) - pid = -1; + char line[256]; + while (fgets(line, sizeof(line), f)) { + if (!strncmp(line, "pid=", 4)) + m->pid = atoi(line + 4); + else if (!strncmp(line, "instance=", 9)) { + const char *p = line + 9; + size_t i = 0; + while (p[i] && p[i] != '\n' && p[i] != '\r' && + i + 1 < sizeof(m->instance)) { + m->instance[i] = p[i]; + i++; + } + m->instance[i] = 0; + } else if (!strncmp(line, "port=", 5)) + m->port = atoi(line + 5); + else if (m->pid <= 0) { + /* Legacy single-line pidfile. */ + int pid = atoi(line); + if (pid > 0) + m->pid = pid; + } + } fclose(f); - return pid; + return m->pid > 0 ? 0 : -1; } -static int write_pid(int pid) { +static int read_pid(void) { + daemon_meta_t m; + return read_meta(&m) == 0 ? m.pid : -1; +} + +static int write_pid(int pid, const char *instance_id, int peer_port) { char path[OS_PATH_MAX], tmp[OS_PATH_MAX + 8]; if (cst_parliament_daemon_pidfile(path, sizeof(path)) != 0) return -1; @@ -64,7 +96,9 @@ static int write_pid(int pid) { FILE *f = fopen(tmp, "wb"); if (!f) return -1; - int ok = fprintf(f, "%d\n", pid) > 0; + int ok = fprintf(f, "pid=%d\ninstance=%s\nport=%d\n", pid, + instance_id ? instance_id : "", + peer_port > 0 ? peer_port : 0) > 0; if (fclose(f) != 0) ok = 0; if (!ok) { @@ -80,8 +114,7 @@ static void clear_pid(void) { remove(path); } -int cst_parliament_daemon_running(void) { - int pid = read_pid(); +static int pid_alive(int pid) { if (pid <= 0) return 0; #ifdef _WIN32 @@ -95,6 +128,34 @@ int cst_parliament_daemon_running(void) { #endif } +int cst_parliament_daemon_running(void) { + daemon_meta_t m; + if (read_meta(&m) != 0) + return 0; + if (!pid_alive(m.pid)) { + clear_pid(); + return 0; + } + return 1; +} + +int cst_parliament_daemon_info(int *pid_out, char *instance_out, size_t in_sz, + int *port_out) { + daemon_meta_t m; + if (read_meta(&m) != 0 || !pid_alive(m.pid)) { + if (read_meta(&m) == 0 && !pid_alive(m.pid)) + clear_pid(); + return -1; + } + if (pid_out) + *pid_out = m.pid; + if (instance_out && in_sz) + snprintf(instance_out, in_sz, "%s", m.instance); + if (port_out) + *port_out = m.port; + return 0; +} + static void sleep_ms(int ms) { #ifdef _WIN32 Sleep((DWORD)ms); @@ -194,7 +255,8 @@ int cst_parliament_daemon_run(const char *password, const char *instance_id, #else (int)getpid() #endif - ); + , + instance_id, bound); fprintf(stderr, "parliament_service pid=%d port=%d id=%s realm=%s\n", #ifdef _WIN32 (int)GetCurrentProcessId(), @@ -273,8 +335,25 @@ int cst_parliament_daemon_start(const char *password, const char *instance_id, int peer_port, char *err, size_t err_sz) { if (err && err_sz) err[0] = 0; - if (cst_parliament_daemon_running()) - return 0; + if (!instance_id || !instance_id[0]) { + if (err && err_sz) + snprintf(err, err_sz, "instance id required"); + return -1; + } + int want_port = peer_port > 0 ? peer_port : 9100; + { + int cur_pid = 0, cur_port = 0; + char cur_id[64] = {0}; + if (cst_parliament_daemon_info(&cur_pid, cur_id, sizeof(cur_id), + &cur_port) == 0) { + int same = (!cur_id[0] || strcmp(cur_id, instance_id) == 0) && + (cur_port <= 0 || cur_port == want_port); + if (same) + return 0; + /* Different seat or port: take over. */ + (void)cst_parliament_daemon_stop(); + } + } #ifdef _WIN32 /* Windows: run in-process foreground loop under a detached console is * deferred; callers should use --service in a service manager. */ @@ -294,9 +373,13 @@ int cst_parliament_daemon_start(const char *password, const char *instance_id, return -1; } if (pid > 0) { - /* Parent: wait briefly for pidfile. */ + /* Parent: wait briefly for matching pidfile. */ for (int i = 0; i < 50; i++) { - if (cst_parliament_daemon_running()) + int cur_pid = 0, cur_port = 0; + char cur_id[64] = {0}; + if (cst_parliament_daemon_info(&cur_pid, cur_id, sizeof(cur_id), + &cur_port) == 0 && + (!cur_id[0] || strcmp(cur_id, instance_id) == 0)) return 0; sleep_ms(50); } @@ -314,7 +397,7 @@ int cst_parliament_daemon_start(const char *password, const char *instance_id, close(fd); } char derr[256]; - (void)cst_parliament_daemon_run(password, instance_id, peer_port, derr, + (void)cst_parliament_daemon_run(password, instance_id, want_port, derr, sizeof(derr)); _exit(0); #endif diff --git a/src/tui.c b/src/tui.c index 1ca0e21..06408b2 100644 --- a/src/tui.c +++ b/src/tui.c @@ -12,6 +12,7 @@ #include "cst_models.h" #include "cst_parliament.h" #include "cst_plugins.h" +#include "cst_realm.h" #include "cst_provider_switch.h" #include "cst_skills.h" #include "cst_subagent.h" @@ -4624,6 +4625,83 @@ static void cmd_max_steps(tui_t *t, char *arg, int *quit) { push_system_line(t, "(max steps per turn: %d)", cfg->max_steps); } +static void cmd_realm(tui_t *t, char *arg, int *quit) { + (void)quit; + const char *sub = arg && arg[0] ? arg : "status"; + if (!strcmp(sub, "status") || !strcmp(sub, "show")) { + char *pst = cst_parliament_status_text(); + if (pst && pst[0]) { + push_system_line(t, "Realm / Parliament:"); + char *p = pst; + while (*p) { + char *nl = strchr(p, '\n'); + if (nl) + *nl = 0; + if (p[0]) + push_system_line(t, " %s", p); + if (!nl) + break; + p = nl + 1; + } + } else { + push_system_line(t, "(no realm status)"); + } + free(pst); + return; + } + if (!strcmp(sub, "stop")) { + if (cst_parliament_daemon_stop() != 0) + push_system_line(t, "Error: could not stop parliament service"); + else + push_system_line(t, "(parliament service stopped)"); + return; + } + if (!strncmp(sub, "login", 5)) { + char dir[OS_PATH_MAX]; + if (cst_realm_dir(dir, sizeof(dir)) != 0 || + !cst_realm_is_password_mode(dir)) { + push_system_line(t, "No password Realm yet. Outside TUI run:"); + push_system_line(t, " cst-agent --realm-password-init"); + return; + } + char pw[256]; + if (secret_input_run(t, "Realm password", + "Unlocks this device as chair; starts the service", + pw, sizeof(pw)) != 0) { + push_system_line(t, "(login cancelled)"); + return; + } + const char *iid = getenv("CST_INSTANCE_ID"); + if (!iid || !iid[0]) + iid = "chair-local"; + char err[256]; + char *hand = cst_parliament_login(pw, iid, NULL, err, sizeof(err)); + if (!hand) { + memset(pw, 0, sizeof(pw)); + push_system_line(t, "Login failed: %s", err[0] ? err : "unknown"); + return; + } + /* Show a short summary, not the full handover blob. */ + push_system_line(t, "(logged in as chair %s)", iid); + if (strstr(hand, "handover_from=") && + !strstr(hand, "handover_from=\n") && + !strstr(hand, "handover_from=\r")) + push_system_line(t, "(took chair from previous device)"); + free(hand); + { + char serr[256]; + if (cst_parliament_daemon_start(pw, iid, 9100, serr, sizeof(serr)) != 0) + push_system_line(t, "warning: service: %s", + serr[0] ? serr : "start failed"); + else if (cst_parliament_daemon_running()) + push_system_line(t, "(parliament service running)"); + } + memset(pw, 0, sizeof(pw)); + return; + } + push_system_line(t, "Usage: /realm [status|login|stop]"); +} + static void cmd_status(tui_t *t, char *arg, int *quit) { (void)arg; (void)quit; @@ -5605,6 +5683,8 @@ static const slash_cmd_t SLASH[] = { cmd_mouse}, {"max-steps", "", "[N]", "tool steps allowed per turn", 1, cmd_max_steps}, {"status", "", "", "session, model, context and settings", 1, cmd_status}, + {"realm", "parliament login", "[status|login|stop]", + "Realm / parliament chair status, login, or stop service", 1, cmd_realm}, {"cost", "usage", "", "tokens, cache hit rate and estimated cost", 1, cmd_cost}, {"todo", "tasks", "", "show the agent's task list", 1, cmd_todo}, diff --git a/tests/test_parliament.c b/tests/test_parliament.c index d521370..963e44e 100644 --- a/tests/test_parliament.c +++ b/tests/test_parliament.c @@ -72,8 +72,10 @@ int main(void) { char *st = cst_parliament_status_text(); assert(st && strstr(st, "parliament")); + assert(strstr(st, "service")); free(st); assert(cst_parliament_is_active()); + assert(cst_parliament_daemon_info(NULL, NULL, 0, NULL) != 0); test_rmrf(dir); puts("test_parliament: OK");