feat: password Realm, single-chair login, shared parliament context (0.9.12)

Derive Realm keys from a user password (Argon2id, no private key on disk).
Login claims the sole chair with handover; peers share parliament context;
--new-project clears it. CLI peer/discover helpers and Docker alpine recipe
included; headless empty-answer retries raised for unattended runs.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
WHC
2026-10-28 23:06:51 +08:00
co-authored by Cursor
parent 08ca613a0e
commit de9ee28773
18 changed files with 1101 additions and 8 deletions
+2
View File
@@ -497,6 +497,8 @@ typedef struct {
char effort[16];
char mode[16]; /* coding | general, "" = not chosen yet */
char approval[8]; /* "" = not chosen yet */
/* legacy_workers | parliament — parliament replaces in-process subagents. */
char collaboration[24];
int show_thinking; /* TUI: Thought blocks start expanded */
/* workspaces whose .agent/plugins and .agent/mcp.json may run */
int ntrusted;
+51
View File
@@ -0,0 +1,51 @@
#ifndef CST_PARLIAMENT_H
#define CST_PARLIAMENT_H
#include "cst_realm.h"
#include <stddef.h>
#ifdef __cplusplus
extern "C" {
#endif
/* Parliament is one shared whole across agents. Password unlock claims the
* single chair on the logging-in device; peers sync the same context. A new
* project replaces all parliament context. */
typedef struct {
char parliament_id[64];
char project_id[64];
char chair_instance[64];
char chair_device[128];
unsigned long revision;
char status[32]; /* idle | open | dissolved */
} cst_parliament_meta_t;
int cst_parliament_path(char *out, size_t n);
int cst_parliament_load(cst_parliament_meta_t *m);
int cst_parliament_save(const cst_parliament_meta_t *m);
/* Login: unlock Realm with password, enroll/ensure instance, claim chair,
* return handover summary (malloc'd) or NULL. */
char *cst_parliament_login(const char *password, const char *instance_id,
const char *device_label, char *err, size_t err_sz);
/* Only one chair: previous chair is demoted; handover notes left in meta. */
int cst_parliament_claim_chair(const char *instance_id, const char *device_label);
/* Probe local environment as device-private context (malloc'd text). */
char *cst_parliament_device_context(void);
/* New project: wipe shared parliament context; keep Realm keys. */
int cst_parliament_new_project(const char *project_id);
/* Append/replace synchronized context blob (all agents share this file). */
int cst_parliament_context_put(const char *text);
char *cst_parliament_context_get(void);
#ifdef __cplusplus
}
#endif
#endif
+26
View File
@@ -0,0 +1,26 @@
#ifndef CST_PARLIAMENT_CLI_H
#define CST_PARLIAMENT_CLI_H
#ifdef __cplusplus
extern "C" {
#endif
/* Standalone parliament/Realm ops for Docker and unattended smoke tests.
* Returns a process exit code (0 = success). */
int cst_parliament_cli_realm_new(void);
int cst_parliament_cli_realm_password_init(const char *password);
int cst_parliament_cli_login(const char *password, const char *instance_id,
const char *device_label);
int cst_parliament_cli_new_project(const char *project_id);
int cst_parliament_cli_realm_enroll(const char *instance_id, const char *label);
int cst_parliament_cli_peer_listen(int port, const char *instance_id);
int cst_parliament_cli_peer_dial(const char *host, int port,
const char *instance_id);
int cst_parliament_cli_discover(const char *instance_id, int peer_port,
int wait_ms);
#ifdef __cplusplus
}
#endif
#endif
+15
View File
@@ -36,6 +36,15 @@ int cst_realm_load(const char *dir, cst_realm_keys_t *out);
/* 32 hex chars (blake2b-128 of pk). */
int cst_realm_fingerprint(const uint8_t pk[32], char out[33]);
/* Password-string Realm: Argon2id(password, salt) → seed. Private key is not
* stored; only salt + public.key live on disk. Unlock derives sk in memory. */
int cst_realm_derive_password(cst_realm_keys_t *out, const char *password,
const uint8_t salt[16]);
int cst_realm_init_password(const char *dir, const char *password);
int cst_realm_unlock_password(const char *dir, const char *password,
cst_realm_keys_t *out);
int cst_realm_is_password_mode(const char *dir);
int cst_realm_enroll(const cst_realm_keys_t *realm, const char *instance_id,
const char *label, int64_t not_after, cst_realm_cert_t *cert,
uint8_t instance_seed[32], uint8_t instance_sk[64]);
@@ -52,6 +61,12 @@ int cst_realm_challenge_check(const cst_realm_cert_t *cert, const void *chal,
int cst_realm_revoke_add(const char *dir, const char *instance_id);
int cst_realm_revoked(const char *dir, const char *instance_id);
/* Persist an enrolled instance under <realm>/instances/<id>/. */
int cst_realm_instance_save(const char *instance_id, const cst_realm_cert_t *cert,
const uint8_t instance_seed[32]);
int cst_realm_instance_load(const char *instance_id, cst_realm_cert_t *cert,
uint8_t instance_sk[64]);
#ifdef __cplusplus
}
#endif
+1 -1
View File
@@ -3,7 +3,7 @@
#define CST_VERSION_MAJOR 0
#define CST_VERSION_MINOR 9
#define CST_VERSION_PATCH 11
#define CST_VERSION_PATCH 12
#define CST_VERSION_STR_(x) #x
#define CST_VERSION_STR(x) CST_VERSION_STR_(x)