Compare commits

..
Author SHA1 Message Date
weichen 6cc0a86615 Pin the endpoint-migration compat intent branch
Why: recursive checkout reads pinned dep .gitmodules; the compat
branch carries migrated URLs without API movement.

Evidence: host-verified; see Dev PR #706

Impact: pin update only.

Refs: Agent-System-Dev PR #706
2026-10-22 15:05:00 +00:00
weichen cdaf1579df Update Gitea endpoints on the legacy API line (compat branch)
Why: superprojects pinned to this commit need a migrated .gitmodules
without moving dependency APIs; created from the exact pinned commit.

Evidence: host-verified; see Dev PR #706

Impact: configuration only.

Refs: Agent-System-Dev PR #706
2026-10-22 15:04:40 +00:00
6 changed files with 4 additions and 220 deletions
+1 -1
View File
@@ -33,7 +33,7 @@
(baseline 硬边界 2:只记录来源、字段与可用状态)。
- **SSH key**:`~/.ssh/home_gitea`(ED25519,公钥指纹
`SHA256:GYXaOXIMeloQ1fKSfdJ/ndgPQzs/Ix7Hk62ekfkfQ20`,Gitea 侧密钥名 GMKtec);
`~/.ssh/config` 已有 `gitea.naitangbaobao.cn(端口 52222)` 主机条目,克隆/推送直接可用。
`~/.ssh/config` 已有 `git-ssh.u1680832.nyat.app` 主机条目,克隆/推送直接可用。
## Agent 执行与新项目准入(强制)
+2 -6
View File
@@ -212,9 +212,8 @@ func (b *Backend) Execute(ctx context.Context, req exec.Request) (*exec.Result,
}
writable := writableFrom(ctx)
// F02: a non-empty Writable cannot be enforced for exec/MCP/host tools or
// subagent delegation — reject in preflight rather than pretend the
// write-set holds.
// F02: a non-empty Writable cannot be enforced for exec/MCP/host tools —
// reject in preflight rather than pretend the write-set holds.
if len(writable) > 0 && profile == exec.ToolProfileWorkspace {
if b.opts.Exec != nil {
return preflightFail("writable write-set cannot be enforced when exec tools are enabled"), nil
@@ -225,9 +224,6 @@ func (b *Backend) Execute(ctx context.Context, req exec.Request) (*exec.Result,
if len(b.opts.HostTools) > 0 {
return preflightFail("writable write-set cannot be enforced when host tools are enabled"), nil
}
if b.opts.Subagent != nil {
return preflightFail("writable write-set cannot be enforced when subagent delegation is enabled"), nil
}
}
var reg *tools.Registry
if profile == exec.ToolProfileWorkspace {
-14
View File
@@ -5,7 +5,6 @@ import (
"crypto/sha256"
"encoding/hex"
"path/filepath"
"strconv"
"strings"
"agent-native/exec"
@@ -58,14 +57,6 @@ func captureText(ctx context.Context, text string) {
// scopeIdentity returns a stable non-empty identity string for a non-zero
// SessionScope, or "" for the legacy one-shot / empty-scope path.
//
// Encoding (collision-free when components contain "/"):
// - Legacy (no component contains "/"): tenant + "/" + project + "/" + session
// so existing workspaces keep the same hashed transcript filenames.
// - Otherwise: length-prefixed concatenation of each trimmed component as
// strconv.Itoa(len(part)) + ":" + part (no extra separators). Distinct
// scopes such as {tenant:"a/b", project:"c"} vs {tenant:"a", project:"b/c"}
// therefore produce different identity strings and transcript paths.
func scopeIdentity(s exec.SessionScope) string {
t := strings.TrimSpace(s.Tenant)
p := strings.TrimSpace(s.Project)
@@ -73,11 +64,6 @@ func scopeIdentity(s exec.SessionScope) string {
if t == "" && p == "" && sess == "" {
return ""
}
if strings.Contains(t, "/") || strings.Contains(p, "/") || strings.Contains(sess, "/") {
return strconv.Itoa(len(t)) + ":" + t +
strconv.Itoa(len(p)) + ":" + p +
strconv.Itoa(len(sess)) + ":" + sess
}
return t + "/" + p + "/" + sess
}
@@ -172,92 +172,6 @@ func TestContractOldStoreWithoutIdentityNotAdopted(t *testing.T) {
}
}
// TestScopeIdentitySlashCollisionFree: tenant/project values that collide under
// naive "/" joins must yield distinct identities and transcript paths (M1).
func TestScopeIdentitySlashCollisionFree(t *testing.T) {
scopeAB := exec.SessionScope{Tenant: "a/b", Project: "c", Session: "s"}
scopeAC := exec.SessionScope{Tenant: "a", Project: "b/c", Session: "s"}
idAB := scopeIdentity(scopeAB)
idAC := scopeIdentity(scopeAC)
if idAB == "" || idAC == "" {
t.Fatal("non-empty scopes must produce non-empty identities")
}
if idAB == idAC {
t.Fatalf("slash-bearing scopes must not collide: both %q", idAB)
}
dir := t.TempDir()
pathAB := transcriptPathForScope(dir, idAB)
pathAC := transcriptPathForScope(dir, idAC)
if pathAB == pathAC {
t.Fatalf("distinct identities must hash to distinct transcript paths: %s", pathAB)
}
}
// TestScopeIdentityLegacySlashFreeUnchanged keeps product workspaces on the
// historical identity string (and thus transcript filename) when no component
// contains "/".
func TestScopeIdentityLegacySlashFreeUnchanged(t *testing.T) {
scope := exec.SessionScope{Tenant: "t1", Project: "p1", Session: "s-a"}
const want = "t1/p1/s-a"
if got := scopeIdentity(scope); got != want {
t.Fatalf("slash-free identity: got %q want %q", got, want)
}
}
// TestBindSessionScopeRejectsMismatchedSlashScope: exact-match check must
// refuse when a store's recorded identity differs from the request, including
// when both identities use the slash-safe encoding.
func TestBindSessionScopeRejectsMismatchedSlashScope(t *testing.T) {
dir := t.TempDir()
scopeA := exec.SessionScope{Tenant: "a/b", Project: "c", Session: "sess"}
scopeB := exec.SessionScope{Tenant: "a", Project: "b/c", Session: "sess"}
idA := scopeIdentity(scopeA)
idB := scopeIdentity(scopeB)
if idA == idB {
t.Fatal("precondition: slash scopes must differ")
}
path := transcriptPathForScope(dir, idA)
if err := os.MkdirAll(filepath.Dir(path), 0o755); err != nil {
t.Fatal(err)
}
store, err := session.Open(path)
if err != nil {
t.Fatal(err)
}
if err := store.SaveMeta(map[string]string{"scope": idB}); err != nil {
t.Fatal(err)
}
b := New(Options{
ModelOptions: ModelOptions{
Model: "fake",
Stream: script(llm.Message{Role: llm.RoleAssistant, Content: "x", StopReason: llm.StopEnd}),
},
})
if err := b.bindSessionScope(store, idA, "fake", "e-mismatch"); err == nil {
t.Fatal("expected scope identity mismatch")
} else if !strings.Contains(err.Error(), "scope identity mismatch") {
t.Fatalf("want mismatch error, got %v", err)
}
// Contract path must also fail-close when meta disagrees with the request scope.
res, err := b.Contract().Execute(context.Background(), exec.ContractRequest{
ExecutionID: "e-mismatch",
Prompt: "hi",
SessionScope: scopeA,
Workspace: exec.WorkspaceHandle{Root: dir},
})
if err != nil {
t.Fatalf("transport: %v", err)
}
if res.Outcome == exec.OutcomeSucceeded {
t.Fatal("mismatched slash scope must not succeed")
}
if res.Err == nil || !strings.Contains(res.Err.Reason, "scope identity mismatch") {
t.Fatalf("want mismatch refusal, got %+v", res.Err)
}
}
func TestContractWritableFileToolHonorsAllowlist(t *testing.T) {
dir := t.TempDir()
b := New(Options{
-112
View File
@@ -1,112 +0,0 @@
package native
import (
"context"
"os"
"path/filepath"
"testing"
"agent-native/exec"
"agent-native/runtime/llm"
"agent-native/runtime/subagent"
)
func TestContractWritableRejectsSubagentInPreflight(t *testing.T) {
dir := t.TempDir()
streamCalls := 0
inner := script(llm.Message{Role: llm.RoleAssistant, Content: "x", StopReason: llm.StopEnd})
factoryCalls := 0
b := New(Options{
ModelOptions: ModelOptions{
Model: "fake",
Stream: func(ctx context.Context, model string, messages []llm.Message, decls []llm.ToolDecl, opts llm.StreamOptions) <-chan llm.Event {
streamCalls++
return inner(ctx, model, messages, decls, opts)
},
},
ToolOptions: ToolOptions{
Subagent: func(ctx context.Context, req subagent.Request) (subagent.Result, error) {
factoryCalls++
return subagent.Result{Success: true}, nil
},
},
}).Contract()
res, err := b.Execute(context.Background(), exec.ContractRequest{
ExecutionID: "e-sub",
Prompt: "x",
Workspace: exec.WorkspaceHandle{
Root: dir,
Writable: []string{"a.md"},
},
ToolPolicy: exec.ToolPolicy{Profile: exec.ToolProfileWorkspace},
Targets: []string{"a.md"},
})
if err != nil {
t.Fatal(err)
}
if res.Outcome == exec.OutcomeSucceeded {
t.Fatal("Writable + subagent delegation enabled must be rejected in preflight")
}
const want = "writable write-set cannot be enforced when subagent delegation is enabled"
if res.Err == nil || res.Err.Reason != want {
t.Fatalf("want reason %q, got %+v", want, res.Err)
}
if streamCalls != 0 || factoryCalls != 0 {
t.Fatalf("preflight must stop before model/factory: stream=%d factory=%d", streamCalls, factoryCalls)
}
if _, err := os.Stat(filepath.Join(dir, "a.md")); !os.IsNotExist(err) {
t.Fatal("deliverable a.md must not be created when preflight rejects")
}
if _, err := os.Stat(filepath.Join(dir, ".agentx")); !os.IsNotExist(err) {
t.Fatal("no session state must be written when preflight rejects")
}
}
func TestContractSubagentWithoutWritableStillDelegates(t *testing.T) {
dir := t.TempDir()
var delegated *subagent.Request
stream, c := captureStream(
llm.Message{Role: llm.RoleAssistant, StopReason: llm.StopToolCalls, ToolCalls: []llm.ToolCall{{
ID: "c1", Name: "delegate_task",
Arguments: `{"goal":"查证数据","write_scope":"research/","done_criteria":"三来源"}`,
}}},
llm.Message{Role: llm.RoleAssistant, StopReason: llm.StopToolCalls, ToolCalls: []llm.ToolCall{{
ID: "c2", Name: "write_file",
Arguments: `{"path":"output.md","content":"done"}`,
}}},
llm.Message{Role: llm.RoleAssistant, Content: "ok", StopReason: llm.StopEnd},
)
b := New(Options{
ModelOptions: ModelOptions{Model: "fake", Stream: stream},
ToolOptions: ToolOptions{
Subagent: func(ctx context.Context, req subagent.Request) (subagent.Result, error) {
delegated = &req
return subagent.Result{Success: true, Text: "结论已查明"}, nil
},
},
}).Contract()
res, err := b.Execute(context.Background(), exec.ContractRequest{
ExecutionID: "e-sub-ok",
Prompt: "分工",
Workspace: exec.WorkspaceHandle{Root: dir},
ToolPolicy: exec.ToolPolicy{Profile: exec.ToolProfileWorkspace},
Targets: []string{"output.md"},
})
if err != nil || res.Outcome != exec.OutcomeSucceeded {
t.Fatalf("execute: err=%v res=%+v", err, res)
}
if delegated == nil || delegated.Goal != "查证数据" {
t.Fatalf("factory not invoked with fields: %+v", delegated)
}
sawDelegate := false
for _, names := range c.toolDecls {
for _, n := range names {
if n == "delegate_task" {
sawDelegate = true
}
}
}
if !sawDelegate {
t.Fatal("delegate_task must be declared when Subagent factory is set and Writable is empty")
}
}