Merge authoritative main into the task branch
ci / svc-realcombo (pull_request) Successful in 1m47s
ci / test (pull_request) Successful in 5m39s
ci / test-pg (pull_request) Successful in 9m41s

Why: include the current integration baseline before verification.
Evidence: fetched origin/main. The multi-instance account bootstrap conflict keeps main's identity ensure and activation flow.
Impact: integrate upstream changes without rewriting task history. Backend gitlink stays 80bb123.
Refs: task-sync
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-10-15 03:43:03 +00:00
co-authored by Cursor
7 changed files with 1542 additions and 28 deletions
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+7
View File
@@ -36,6 +36,13 @@ Agent-System-Dev 是桌面优先、Web 兼容的 AI 工作台。界面应**冷
| `--bg-hover` | `rgba(255,255,255,.05)` | `rgba(13,13,13,.05)` | 通用悬停 |
| `--bg-active` | `rgba(255,255,255,.10)` | `rgba(13,13,13,.08)` | 选中态 |
三栏工作台的顶栏与左栏使用独立导航骨架令牌:`--workbench-chrome`、
`--workbench-chrome-text`、`--workbench-chrome-dim`、`--workbench-chrome-faint`、
`--workbench-chrome-hover`、`--workbench-chrome-active`。浅色主题为近黑导航骨架,深色主题取面板阶梯,
墨绿主题取海军蓝侧栏;反色创建按钮的 hover/分隔线使用
`--workbench-chrome-inverse-hover` / `--workbench-chrome-inverse-border`。宿主组件只能消费这些令牌,
不得用 `color-mix()`、rgba 或局部变量再次派生导航颜色。
### 2.2 边框(发丝三档)
`--border-soft: rgba(255,255,255,.06)` / `--border: rgba(255,255,255,.10)` /
@@ -45,10 +45,11 @@ const (
)
type instance struct {
name string
base string
cmd *exec.Cmd
log *lockedBuffer
name string
base string
dataDir string // serve 的 cwd:本地实例身份(manifest)所在,账户 CLI 须以此身份运行
cmd *exec.Cmd
log *lockedBuffer
}
type lockedBuffer struct {
@@ -311,6 +312,20 @@ func (e *env) startPair() {
e.t.Helper()
e.a = e.startInstance(e.t, "miA", "127.0.0.1:"+freePort(e.t))
e.b = e.startInstance(e.t, "miB", "127.0.0.1:"+freePort(e.t))
// 库内服务身份是显式引导(svc init 同款),且为部署级单行:双进程共享
// 同一身份,只对 A ensure 一次。账户 CLI 的审计与核验都依赖它。
e.identityEnsure(e.t, e.a)
}
// identityEnsure 建立/对齐库内服务身份行(agent_system_identity)。
func (e *env) identityEnsure(t *testing.T, inst *instance) {
t.Helper()
cmd := exec.Command(e.bin, "identity", "ensure", "--instance-id", inst.name, "--data", inst.dataDir, "--dbref", "pg")
cmd.Dir = inst.dataDir
cmd.Env = append(os.Environ(), "AGENT_SYSTEM_DSN="+e.dbDSN)
if out, err := cmd.CombinedOutput(); err != nil {
t.Fatalf("identity ensure %s: %v\n%s", inst.name, err, out)
}
}
func (e *env) startInstance(t *testing.T, name, addr string) *instance {
@@ -331,11 +346,12 @@ func (e *env) startInstance(t *testing.T, name, addr string) *instance {
// 否则测不到带类型空 *S3 回落本地目录的路径。
cmd.Env = withoutBlobstoreS3(os.Environ())
// 独立 cwd:serve 会向 ./data 写 skills/SQLite 等,不能落在测试目录。
cmd.Dir = t.TempDir()
dataDir := t.TempDir()
cmd.Dir = dataDir
if err := cmd.Start(); err != nil {
t.Fatalf("start %s: %v", name, err)
}
inst := &instance{name: name, base: "http://" + addr, cmd: cmd, log: log}
inst := &instance{name: name, base: "http://" + addr, dataDir: dataDir, cmd: cmd, log: log}
t.Cleanup(func() { e.stopInstance(inst) })
e.waitHealthy(t, inst)
return inst
@@ -434,38 +450,35 @@ func (e *env) killNine(t *testing.T, inst *instance) {
// loginAlice 引导 alice 账号并发放 API key(CLI 直连测试库;key 存共享 PG,
// 对两实例同时有效——程序化验收的首选凭证,SSE 亦可用 ?key= 兜底)。
// 当前基座拒绝带密码的 user create:空库先 bootstrap 一位开库管理员,再为
// alice 发放激活凭据。凭据兑换后设置 alicePass,然后才能发 API Key。
// U3 后本地无密码创建:user create 只签发待激活账号的一次性激活 Key(凭据
// 独占落 JSON),redeem 会话进入 grant-setup 改密设 alicePass,完成后再发 Key。
func (e *env) loginAlice(t *testing.T) *userClient {
t.Helper()
// 账户事务会锁库内服务身份;serve 不补写这行,须先显式建立。
e.cli(t, "identity", "ensure", "--dsn", e.dbDSN, "--instance-id", "mi-acceptance", "--dbref", "pg")
e.cli(t, "user", "bootstrap-admin", "--dsn", e.dbDSN, "--owner", "mi_setup", "--name", "Setup",
"--out-file", filepath.Join(t.TempDir(), "setup-grant"))
grantFile := filepath.Join(t.TempDir(), "alice-grant")
e.cli(t, "user", "create", "--dsn", e.dbDSN, "--owner", aliceOwner, "--name", "Alice", "--out-file", grantFile)
grantRaw, err := os.ReadFile(grantFile)
if err != nil {
t.Fatal(err)
}
// 空库先引导首个管理员(本地 pending 创建要求库内已有用户),再建 alice。
// 两步都签发一次性凭据,独占落临时 JSON 后即弃。
rootCred := filepath.Join(t.TempDir(), "root-cred.json")
e.cliIn(t, e.a.dataDir, "user", "bootstrap-admin", "--dsn", e.dbDSN, "--owner", "mi-root", "--out-file", rootCred)
activation := filepath.Join(t.TempDir(), "activation.json")
e.cliIn(t, e.a.dataDir, "user", "create", "--dsn", e.dbDSN, "--owner", aliceOwner, "--name", "Alice", "--out", activation)
activateKey := activationSecret(t, activation)
jar, err := cookiejar.New(nil)
if err != nil {
t.Fatal(err)
}
sess := &http.Client{Timeout: httpWait, Jar: jar}
// e.uc 尚未赋值 → baseFor 走 A;激活会话 cookie 落 jar 后设置密码。
// e.uc 尚未赋值 → baseFor 走 A;redeem 会话(grant-setup)直接设 alicePass。
e.do(t, sess, "POST", "/api/auth/login-key/redeem", map[string]any{
"key": strings.TrimSpace(string(grantRaw)),
"key": activateKey,
}, nil)
e.do(t, sess, "POST", "/api/auth/password", map[string]any{
"next": alicePass,
}, nil)
key := e.cliOut(t, "apikey", "create", "--dsn", e.dbDSN, "--owner", aliceOwner, "--name", "mi-acceptance")
apiKey := e.cliOut(t, "apikey", "create", "--dsn", e.dbDSN, "--owner", aliceOwner, "--name", "mi-acceptance")
uc := &userClient{
api: &http.Client{Timeout: httpWait, Transport: bearerRT{key: key}},
apiB: &http.Client{Timeout: httpWait, Transport: bearerRT{key: key}},
streamB: &http.Client{Transport: bearerRT{key: key}}, // SSE 无整体超时;chan 背压控制
api: &http.Client{Timeout: httpWait, Transport: bearerRT{key: apiKey}},
apiB: &http.Client{Timeout: httpWait, Transport: bearerRT{key: apiKey}},
streamB: &http.Client{Transport: bearerRT{key: apiKey}}, // SSE 无整体超时;chan 背压控制
}
// runtime.mock 自系统受管设置体制(backend #188/#190)起由 --mock 播种进
// 系统层,经 Effective 链对所有租户生效(selectConversationRunner 按
@@ -482,6 +495,22 @@ func (b bearerRT) RoundTrip(r *http.Request) (*http.Response, error) {
return http.DefaultTransport.RoundTrip(r2)
}
// activationSecret 读 CLI 凭据独占文件里的激活 Key(JSON {"kind","owner","secret"})。
func activationSecret(t *testing.T, path string) string {
t.Helper()
b, err := os.ReadFile(path)
if err != nil {
t.Fatal(err)
}
var v struct {
Secret string `json:"secret"`
}
if err := json.Unmarshal(b, &v); err != nil || v.Secret == "" {
t.Fatalf("activation credential unreadable: %v", err)
}
return v.Secret
}
// cliOut 运行 CLI 并取 stdout 首行(apikey create 的明文仅此一次输出)。
func (e *env) cliOut(t *testing.T, args ...string) string {
t.Helper()
@@ -500,9 +529,16 @@ func (e *env) cliOut(t *testing.T, args ...string) string {
}
func (e *env) cli(t *testing.T, args ...string) {
t.Helper()
e.cliIn(t, t.TempDir(), args...)
}
// cliIn 以指定 cwd 运行 CLI:账户命令经 assertLocalAccountOwner 核验本地实例
// 身份,必须从持有该身份的 dataDir 运行。
func (e *env) cliIn(t *testing.T, dir string, args ...string) {
t.Helper()
cmd := exec.Command(e.bin, args...)
cmd.Dir = t.TempDir()
cmd.Dir = dir
out, err := cmd.CombinedOutput()
if err != nil {
t.Fatalf("cli %v…: %v\n%s", args[:2], err, out)
+4 -1
View File
@@ -24,7 +24,10 @@ class TaskLifecycleTests(unittest.TestCase):
self.env.update({"GIT_CONFIG_NOSYSTEM": "1", "GIT_CONFIG_GLOBAL": "/dev/null",
"GIT_ALLOW_PROTOCOL": "file", "GIT_TERMINAL_PROMPT": "0",
"GIT_AUTHOR_NAME": "Lifecycle Test", "GIT_AUTHOR_EMAIL": "test@example.invalid",
"GIT_COMMITTER_NAME": "Lifecycle Test", "GIT_COMMITTER_EMAIL": "test@example.invalid"})
"GIT_COMMITTER_NAME": "Lifecycle Test", "GIT_COMMITTER_EMAIL": "test@example.invalid",
"GIT_CONFIG_COUNT": "2", "GIT_CONFIG_KEY_0": "gc.auto",
"GIT_CONFIG_VALUE_0": "0", "GIT_CONFIG_KEY_1": "maintenance.auto",
"GIT_CONFIG_VALUE_1": "false"})
for key in ("GIT_DIR", "GIT_WORK_TREE", "GIT_INDEX_FILE", "GITEA_TOKEN", "PRECHECK_RECEIPT",
"TASK_PARENT_BRANCH", "TASK_PARENT_PR", "FORCE_ABANDON", "TASK_TARGET_BRANCH",
"TASK_WORK_MODE", "VALIDATE_RECEIPT_PURPOSE"):