Add EXT-1 inproc command handler and owner-scoped host handle (minimal set) #207
@@ -0,0 +1,177 @@
|
||||
# feature/ext-1-inproc-handler 任务卡
|
||||
|
||||
任务主记录:https://git-home.u1680832.nyat.app:30319/weichen/Agent-System-Dev/pulls/207;状态:待审阅;owner:weichen
|
||||
|
||||
## 基线与授权
|
||||
|
||||
- 权威仓:ssh://git@git-ssh.u1680832.nyat.app:31411/weichen/Agent-System-Dev.git;目标分支:main;R0(initial_base_sha)= aeb8513e9c746294cfdb44c0fb55fa0fe26590d9
|
||||
- 工作区:/home/weichen/projects/Agent-System-Dev-wt-feature-ext-1-inproc-handler(worktree);分支:feature/ext-1-inproc-handler
|
||||
- 授权与交付终点:EXT-1 基座侧实现(backend 子仓)+ 下游示例 + documents 提案 + .tmp P0 材料(11/12/13,不入库);子仓按 documents→backend→Dev 顺序提交,Dev PR 合入为本任务交付终点;不改基座既有路径语义,高后果操作(tag/建仓)不在本卡范围。
|
||||
|
||||
## 目标与非目标
|
||||
|
||||
本卡即 **EXT-1 扩展点申请卡**(compat-policy §6 四要素)+ 基座侧实现任务:
|
||||
|
||||
- **场景**:Agent-System-RDM 的 `rdm.ingest` 等命令经四入口(button/card/slash/chat)分发到 `cmdbus.Dispatch` 时,被以 `no host handler bound` 拒绝——`Registry.AddCommand` 与 manifest `provides.commands` 只注册规格不绑处理器;进程内插件也没有 owner 凭证与基座服务端回调身份(H4)。
|
||||
- **希望符号**:`pkg/extension` 新增 `Request`/`Handler`/`Host`(owner 作用域宿主句柄最小集:kv、audit、llm、knowledge search/write(embed)、tasks 台账、artifacts 只读)与 `Registry.AddCommandHandler(spec, h)`;`cmdbus` 新增 `RegisterPluginCommand`。
|
||||
- **为何现有注册点组合不出来**:`RegisterPluginCommands` 仅登记 spec;`HostClient` 走 sidecar HTTP 需进程内不可得的 token;owner 作用域必须由基座在分发时构造,插件无法自造。
|
||||
- **兼容**:只加符号与方法,不改既有路径语义;`SurfaceVersion` 保持 `2.0.0-experimental`,实验窗口内调整以提案 2026-10-05-ext-1-inproc-command-handler.md 为记录。
|
||||
|
||||
非目标:artifacts 写入(完整集,R1-7 前再评估);sidecar/EXT-3;下游仓创建与五仓 tag(另行双钥匙执行);RDM 领域逻辑。
|
||||
|
||||
## 同步记录
|
||||
|
||||
| 节点 | sync_base_sha | tested_head_sha | checked_at |
|
||||
|---|---|---|---|
|
||||
| 启动 | aeb8513e9c746294cfdb44c0fb55fa0fe26590d9 | aeb8513e9c746294cfdb44c0fb55fa0fe26590d9 | 2026-10-05T11:27:44Z |
|
||||
|
||||
## 证据(Evidence)
|
||||
|
||||
<!-- CMD="<命令>" make evidence 写入;手抄/转写输出视为无效证据(AGENTS.md 证据与验证门禁)。 -->
|
||||
| 时间(UTC) | 命令 | 被测HEAD | exit | 来源 | sha256(16) |
|
||||
|---|---|---|---|---|---|
|
||||
|
||||
## 结果与验证(完成后填写)
|
||||
|
||||
- 结果:**EXT-1 最小集全部交付**——`pkg/extension` 新增 `Request`/`Handler`/`Host`(surface.txt 12→15)与 `Registry.AddCommandHandler`;`cmdbus.RegisterPluginCommand` 绑定处理器(高危确认卡拦截天然覆盖);`rag.IndexChunks` 抽出复用;`internal/app` 以 `hostcaps` 同源实现 hostapi 闭包与 `inprocHost`(owner 作用域:所有 store 调用 owner 第一参数、KnowledgeWrite 强制 Owner/Source);示例 `rdm.ping` 处理器 + go.mod core require/replace;documents 提案 + compat-policy §5 tag 名对齐 v0.2.0。artifacts **写入**未实现(完整集,按批注 1 R1-7 前再评估)。P0 材料 11/12/13 已落 `.tmp/智能体系统/further-objective/`(不入库)。.tmp 方案 V1.2 的 D3「单条 replace」已证伪并勘误(下游须复制 8 条 deps replace)。
|
||||
- 验证:make test 全链 **PASS**(提交前 786f852 树与最终 HEAD 517b897 各一轮,exit 0);make test-boundaries **PASS**;examples/downstream-rdm `go test ./...` **PASS**;backend `go test ./...` 29 包 **PASS**(含 cmdbus 三例、rag IndexChunks 两例、app 双 owner 作用域 + embed=hash 向量写入);final-HEAD 快检(boundaries+pins+examples)**PASS**。逐条证据见下方证据表;权威源为 CI 在 PR HEAD 的 `make test`+`make build`(GATE)。
|
||||
- 影响与剩余项:公开面依赖 0→1(+agent-system-core,声明式 go.mod 须按 compat-policy §4 加 core require+replace);既有路径语义不变、SurfaceVersion 保持 2.0.0-experimental。剩余:artifacts 写入(完整集);五仓 tag 与下游建仓(12/13 号材料备好,owner 双钥匙执行);MS0 评审材料待评审会使用(11 号)。
|
||||
| 2026-10-05T11:27:52Z | `make precheck` | 786f852def | 0 | SELF | 3c8da1b91e60945e |
|
||||
|
||||
<!-- evidence:run 2026-10-05T11:27:52Z -->
|
||||
```
|
||||
$ make precheck
|
||||
[exit 0 | HEAD 786f852def | sha256:3c8da1b91e60945e] 输出尾部 15 行:
|
||||
make[1]: Entering directory '/home/weichen/projects/Agent-System-Dev-wt-feature-ext-1-inproc-handler'
|
||||
[repo] branch=feature/ext-1-inproc-handler head=786f852def
|
||||
[remote] origin/main=aeb8513e9c ahead=1 behind=0
|
||||
[tree] clean submodules_drift=0 uninit=0
|
||||
[worktrees] linked=1
|
||||
[hooks] core.hooksPath=scripts/hooks(pre-commit/post-merge 实际接线)
|
||||
[facts] policy/facts.md 4 条事实均在有效期内
|
||||
[required-reading] core/baseline.md, workflows/delivery.md §1
|
||||
[R0] aeb8513e9c(完整:aeb8513e9c746294cfdb44c0fb55fa0fe26590d9)
|
||||
[verdict] READY — 可建任务工作区:make task-start NAME=feature/your-branch
|
||||
make[1]: Leaving directory '/home/weichen/projects/Agent-System-Dev-wt-feature-ext-1-inproc-handler'
|
||||
```
|
||||
| 2026-10-05T11:38:10Z | `make test-boundaries` | 786f852def | 0 | SELF | d12268928a5549a2 |
|
||||
|
||||
<!-- evidence:run 2026-10-05T11:38:10Z -->
|
||||
```
|
||||
$ make test-boundaries
|
||||
[exit 0 | HEAD 786f852def | sha256:d12268928a5549a2] 输出尾部 15 行:
|
||||
make[1]: Entering directory '/home/weichen/projects/Agent-System-Dev-wt-feature-ext-1-inproc-handler'
|
||||
bash scripts/lint-boundaries.sh
|
||||
architecture ok (68 edges)
|
||||
architecture negative ok
|
||||
boundaries ok
|
||||
make[1]: Leaving directory '/home/weichen/projects/Agent-System-Dev-wt-feature-ext-1-inproc-handler'
|
||||
```
|
||||
| 2026-10-05T11:38:10Z | `cd examples/downstream-rdm && go test -count=1 ./...` | 786f852def | 0 | SELF | 640877758fe3c88f |
|
||||
|
||||
<!-- evidence:run 2026-10-05T11:38:10Z -->
|
||||
```
|
||||
$ cd examples/downstream-rdm && go test -count=1 ./...
|
||||
[exit 0 | HEAD 786f852def | sha256:640877758fe3c88f] 输出尾部 15 行:
|
||||
ok example.com/agent-system-plugin-rdm 0.002s
|
||||
```
|
||||
| 2026-10-05T11:38:18Z | `make test` | 786f852def | 0 | SELF | fe92462ce20f1fe9 |
|
||||
|
||||
<!-- evidence:run 2026-10-05T11:38:18Z -->
|
||||
```
|
||||
$ make test
|
||||
[exit 0 | HEAD 786f852def | sha256:fe92462ce20f1fe9] 输出尾部 25 行:
|
||||
at recursivelyTraversePassiveMountEffects (/home/weichen/projects/Agent-System-Dev-wt-feature-ext-1-inproc-handler/modules/ui/node_modules/react-dom/cjs/react-dom-client.development.js:16851:13)
|
||||
at commitPassiveMountOnFiber (/home/weichen/projects/Agent-System-Dev-wt-feature-ext-1-inproc-handler/modules/ui/node_modules/react-dom/cjs/react-dom-client.development.js:17183:11)
|
||||
at recursivelyTraversePassiveMountEffects (/home/weichen/projects/Agent-System-Dev-wt-feature-ext-1-inproc-handler/modules/ui/node_modules/react-dom/cjs/react-dom-client.development.js:16851:13)
|
||||
|
||||
✓ test/toolblock.test.tsx (4 tests) 76ms
|
||||
✓ test/tabstrip.test.tsx (3 tests) 71ms
|
||||
✓ test/collapsible.test.tsx (5 tests) 79ms
|
||||
✓ test/ui.test.tsx (6 tests) 68ms
|
||||
✓ test/overlay-host.test.tsx (6 tests) 64ms
|
||||
✓ test/card-anchor.test.tsx (4 tests) 82ms
|
||||
✓ test/file-preview.test.tsx (9 tests) 85ms
|
||||
✓ test/inbox.test.tsx (13 tests) 87ms
|
||||
✓ test/inline-confirm.test.tsx (7 tests) 103ms
|
||||
✓ test/capability.test.tsx (14 tests) 135ms
|
||||
|
||||
Test Files 14 passed (14)
|
||||
Tests 103 passed (103)
|
||||
Start at 11:38:48
|
||||
Duration 620ms (transform 477ms, setup 0ms, import 1.50s, tests 918ms, environment 3.20s)
|
||||
|
||||
knip unused exports 55 <= 55
|
||||
cd deps/agent-command && go vet ./... && go test -count=1 ./...
|
||||
ok agent-command 0.002s
|
||||
ok agent-command/conformance 0.002s
|
||||
make[1]: Leaving directory '/home/weichen/projects/Agent-System-Dev-wt-feature-ext-1-inproc-handler'
|
||||
```
|
||||
| 2026-10-05T11:39:42Z | `bash scripts/lint-boundaries.sh && bash scripts/check-pins.sh && cd examples/downstream-rdm && go test -count=1 ./... && echo 'final-HEAD quick checks OK'` | 517b89760a | 0 | SELF | 26cb4cbcc3d68ec3 |
|
||||
|
||||
<!-- evidence:run 2026-10-05T11:39:42Z -->
|
||||
```
|
||||
$ bash scripts/lint-boundaries.sh && bash scripts/check-pins.sh && cd examples/downstream-rdm && go test -count=1 ./... && echo 'final-HEAD quick checks OK'
|
||||
[exit 0 | HEAD 517b89760a | sha256:26cb4cbcc3d68ec3] 输出尾部 15 行:
|
||||
ok deps/agent-native agent-intent
|
||||
warn: deps/agent-native deps/development-guidelines is 26b562d2e34f, Dev is af199eeaa23c
|
||||
ok deps/agent-native error-code-system
|
||||
ok deps/agent-native logging-system
|
||||
ok deps/agent-squad agent-intent
|
||||
ok deps/agent-squad agent-native
|
||||
warn: deps/agent-squad deps/development-guidelines is 26b562d2e34f, Dev is af199eeaa23c
|
||||
ok deps/agent-squad error-code-system
|
||||
ok deps/agent-squad logging-system
|
||||
warn: deps/agent-intent deps/development-guidelines is 26b562d2e34f, Dev is af199eeaa23c
|
||||
ok deps/agent-intent error-code-system
|
||||
ok deps/agent-intent logging-system
|
||||
pins ok
|
||||
ok example.com/agent-system-plugin-rdm 0.003s
|
||||
final-HEAD quick checks OK
|
||||
```
|
||||
| 2026-10-05T11:39:50Z | `make test` | 517b89760a | 0 | SELF | b55eef9dea426887 |
|
||||
|
||||
<!-- evidence:run 2026-10-05T11:39:50Z -->
|
||||
```
|
||||
$ make test
|
||||
[exit 0 | HEAD 517b89760a | sha256:b55eef9dea426887] 输出尾部 25 行:
|
||||
at runWithFiberInDEV (/home/weichen/projects/Agent-System-Dev-wt-feature-ext-1-inproc-handler/modules/ui/node_modules/react-dom/cjs/react-dom-client.development.js:1048:13)
|
||||
at commitHookEffectListMount (/home/weichen/projects/Agent-System-Dev-wt-feature-ext-1-inproc-handler/modules/ui/node_modules/react-dom/cjs/react-dom-client.development.js:13748:29)
|
||||
at commitHookPassiveMountEffects (/home/weichen/projects/Agent-System-Dev-wt-feature-ext-1-inproc-handler/modules/ui/node_modules/react-dom/cjs/react-dom-client.development.js:13835:11)
|
||||
at commitPassiveMountOnFiber (/home/weichen/projects/Agent-System-Dev-wt-feature-ext-1-inproc-handler/modules/ui/node_modules/react-dom/cjs/react-dom-client.development.js:16906:13)
|
||||
at recursivelyTraversePassiveMountEffects (/home/weichen/projects/Agent-System-Dev-wt-feature-ext-1-inproc-handler/modules/ui/node_modules/react-dom/cjs/react-dom-client.development.js:16851:13)
|
||||
at commitPassiveMountOnFiber (/home/weichen/projects/Agent-System-Dev-wt-feature-ext-1-inproc-handler/modules/ui/node_modules/react-dom/cjs/react-dom-client.development.js:17183:11)
|
||||
at recursivelyTraversePassiveMountEffects (/home/weichen/projects/Agent-System-Dev-wt-feature-ext-1-inproc-handler/modules/ui/node_modules/react-dom/cjs/react-dom-client.development.js:16851:13)
|
||||
|
||||
✓ test/overlay-host.test.tsx (6 tests) 62ms
|
||||
✓ test/inbox.test.tsx (13 tests) 84ms
|
||||
✓ test/card-anchor.test.tsx (4 tests) 90ms
|
||||
✓ test/file-preview.test.tsx (9 tests) 88ms
|
||||
✓ test/inline-confirm.test.tsx (7 tests) 106ms
|
||||
✓ test/capability.test.tsx (14 tests) 143ms
|
||||
|
||||
Test Files 14 passed (14)
|
||||
Tests 103 passed (103)
|
||||
Start at 11:40:18
|
||||
Duration 613ms (transform 393ms, setup 0ms, import 1.30s, tests 952ms, environment 3.29s)
|
||||
|
||||
knip unused exports 55 <= 55
|
||||
cd deps/agent-command && go vet ./... && go test -count=1 ./...
|
||||
ok agent-command 0.002s
|
||||
ok agent-command/conformance 0.002s
|
||||
make[1]: Leaving directory '/home/weichen/projects/Agent-System-Dev-wt-feature-ext-1-inproc-handler'
|
||||
```
|
||||
| 同步前验收 | aeb8513e9c746294cfdb44c0fb55fa0fe26590d9 | 425aff9f57678085fee2d629c2b09776603e4f9b | 2026-10-05T11:43:04Z |
|
||||
| 2026-10-05T11:43:03Z | `make task-sync` | 425aff9f57 | 0 | SELF | 5698c778f7840325 |
|
||||
|
||||
<!-- evidence:run 2026-10-05T11:43:03Z -->
|
||||
```
|
||||
$ make task-sync
|
||||
[exit 0 | HEAD 425aff9f57 | sha256:5698c778f7840325] 输出尾部 15 行:
|
||||
make[1]: Entering directory '/home/weichen/projects/Agent-System-Dev-wt-feature-ext-1-inproc-handler'
|
||||
Already up to date.
|
||||
[evidence] WARN 证据表被测 HEAD 落后且其后有非卡片变更:786f852def (当前 425aff9f57)——关键检查须重跑并以 make evidence 重新写入
|
||||
[synced] sync_base=aeb8513e9c tested_head=425aff9f57 checked_at=2026-10-05T11:43:04Z
|
||||
[next] 跑正式验收(make test 等),证据写入任务卡
|
||||
make[1]: Leaving directory '/home/weichen/projects/Agent-System-Dev-wt-feature-ext-1-inproc-handler'
|
||||
```
|
||||
@@ -2,6 +2,14 @@ module example.com/agent-system-plugin-rdm
|
||||
|
||||
go 1.27
|
||||
|
||||
require agent-system-backend v0.0.0
|
||||
require (
|
||||
agent-system-backend v0.0.0
|
||||
agent-system-core v0.0.0
|
||||
)
|
||||
|
||||
// 声明式路径(仅 pkg/extension 根包)只需下面两条 replace。
|
||||
// 空白导入 pkg/extension/link 拉起完整后端时,须复制基座 go.mod 的
|
||||
// 全部 8 条 deps replace——见 README 与 extension-compat-policy §4。
|
||||
replace agent-system-backend => ../../modules/backend
|
||||
|
||||
replace agent-system-core => ../../modules/backend/deps/agent-system-core
|
||||
|
||||
@@ -1,12 +1,50 @@
|
||||
package rdm
|
||||
|
||||
import "agent-system-backend/pkg/extension"
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
|
||||
"agent-system-backend/pkg/extension"
|
||||
)
|
||||
|
||||
func init() {
|
||||
extension.RegisterCompiled(extension.InProc{
|
||||
ID: "rdm",
|
||||
Register: func(reg extension.Registry) error {
|
||||
return reg.AddSkill("rdm-note", "# 研发记忆\n\n下游切片技能,不修改基座。\n")
|
||||
},
|
||||
ID: "rdm",
|
||||
Register: register,
|
||||
})
|
||||
}
|
||||
|
||||
// register 登记下游扩展:技能 + 进程内命令处理器(EXT-1 最小集示范)。
|
||||
func register(reg extension.Registry) error {
|
||||
if err := reg.AddSkill("rdm-note", "# 研发记忆\n\n下游切片技能,不修改基座。\n"); err != nil {
|
||||
return err
|
||||
}
|
||||
return reg.AddCommandHandler(json.RawMessage(`{
|
||||
"id": "rdm.ping",
|
||||
"title": "连通性自检",
|
||||
"description": "读工件元数据 + 写审计,验证 EXT-1 owner 作用域宿主句柄",
|
||||
"paramsSchema": {"type": "object", "properties": {"artifactId": {"type": "string"}}}
|
||||
}`), rdmPing)
|
||||
}
|
||||
|
||||
// rdmPing 经 owner 作用域宿主句柄:可选读工件元数据 → 写审计 → 返回回执。
|
||||
// 处理器自身不持有任何 owner 凭证,一切能力经 h 取用。
|
||||
func rdmPing(ctx context.Context, h extension.Host, req extension.Request) (any, error) {
|
||||
artifact := ""
|
||||
if id, _ := req.Params["artifactId"].(string); id != "" {
|
||||
a, err := h.ArtifactGet(id)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
artifact = a.Name
|
||||
}
|
||||
if err := h.Audit("rdm.ping", req.ID, "{}"); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return map[string]any{
|
||||
"pong": true,
|
||||
"owner": h.Owner(),
|
||||
"plugin": h.PluginID(),
|
||||
"artifact": artifact,
|
||||
}, nil
|
||||
}
|
||||
|
||||
@@ -1,9 +1,13 @@
|
||||
package rdm
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"testing"
|
||||
|
||||
"agent-system-backend/pkg/extension"
|
||||
core "agent-system-core"
|
||||
)
|
||||
|
||||
func TestCompiledSkillRegistration(t *testing.T) {
|
||||
@@ -17,3 +21,83 @@ func TestCompiledSkillRegistration(t *testing.T) {
|
||||
t.Fatal("surface version required")
|
||||
}
|
||||
}
|
||||
|
||||
// fakeRegistry 记录 register() 的登记动作,不拉起基座。
|
||||
type fakeRegistry struct {
|
||||
skills []string
|
||||
commands []string
|
||||
handlers int
|
||||
}
|
||||
|
||||
func (f *fakeRegistry) AddSkill(name, markdown string) error {
|
||||
f.skills = append(f.skills, name)
|
||||
return nil
|
||||
}
|
||||
func (f *fakeRegistry) AddCommand(spec json.RawMessage) error { return nil }
|
||||
func (f *fakeRegistry) AddCommandHandler(spec json.RawMessage, h extension.Handler) error {
|
||||
var s struct {
|
||||
ID string `json:"id"`
|
||||
}
|
||||
if err := json.Unmarshal(spec, &s); err != nil {
|
||||
return err
|
||||
}
|
||||
if h == nil {
|
||||
return errors.New("nil handler")
|
||||
}
|
||||
f.commands = append(f.commands, s.ID)
|
||||
f.handlers++
|
||||
return nil
|
||||
}
|
||||
func (f *fakeRegistry) Note(kind, name string, payload json.RawMessage) error { return nil }
|
||||
|
||||
func TestRegisterBindsPingHandler(t *testing.T) {
|
||||
reg := &fakeRegistry{}
|
||||
if err := register(reg); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if len(reg.skills) != 1 || reg.skills[0] != "rdm-note" {
|
||||
t.Fatalf("skills: %+v", reg.skills)
|
||||
}
|
||||
if len(reg.commands) != 1 || reg.commands[0] != "rdm.ping" || reg.handlers != 1 {
|
||||
t.Fatalf("commands: %+v handlers=%d", reg.commands, reg.handlers)
|
||||
}
|
||||
}
|
||||
|
||||
// fakeHost 是下游测试里的最小句柄替身:只实现 rdm.ping 用到的能力。
|
||||
type fakeHost struct{ owner string }
|
||||
|
||||
func (f fakeHost) PluginID() string { return "rdm" }
|
||||
func (f fakeHost) Owner() string { return f.owner }
|
||||
func (f fakeHost) KVPut(key, value string) error { return nil }
|
||||
func (f fakeHost) KVGet(key string) (string, bool, error) { return "", false, nil }
|
||||
func (f fakeHost) Audit(action, target, evidence string) error { return nil }
|
||||
func (f fakeHost) Complete(ctx context.Context, prompt string) (string, error) {
|
||||
return "", nil
|
||||
}
|
||||
func (f fakeHost) KnowledgeSearch(ctx context.Context, projectID core.ID, text string, topK int) ([]core.RetrievalHit, error) {
|
||||
return nil, nil
|
||||
}
|
||||
func (f fakeHost) KnowledgeWrite(ctx context.Context, docs []core.KnowledgeDoc, embed bool) error {
|
||||
return nil
|
||||
}
|
||||
func (f fakeHost) TaskCreate(kind, title, conversationID string) (string, error) {
|
||||
return "task_1", nil
|
||||
}
|
||||
func (f fakeHost) TaskUpdate(taskID, status string, progress int, errText string) error { return nil }
|
||||
func (f fakeHost) ArtifactGet(artifactID string) (core.Artifact, error) {
|
||||
return core.Artifact{}, errors.New("not found")
|
||||
}
|
||||
func (f fakeHost) ArtifactContent(artifactID string) ([]byte, error) { return nil, nil }
|
||||
|
||||
func TestRdmPingHandler(t *testing.T) {
|
||||
out, err := rdmPing(context.Background(), fakeHost{owner: "alice"}, extension.Request{
|
||||
ID: "rdm.ping", Owner: "alice", Params: map[string]any{},
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
m, _ := out.(map[string]any)
|
||||
if m["pong"] != true || m["owner"] != "alice" || m["plugin"] != "rdm" {
|
||||
t.Fatalf("payload: %+v", out)
|
||||
}
|
||||
}
|
||||
|
||||
+1
-1
Submodule modules/backend updated: 3f438b9e80...cb26e4c947
+1
-1
Submodule modules/documents updated: f3f823ef8d...a47754b494
Reference in New Issue
Block a user