feat: add page_data_dev code-run path and adm policy design docs
Introduce Page Data dev repair intent routing, page_data_dev taskType autodetect, and help-code02 memindadm runtime policy specification. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -35,6 +35,7 @@ H5_PUBLIC_BASE_URL=http://127.0.0.1:5173
|
|||||||
# MEMIND_AGENT_CODE_RUNS_ENABLED=0
|
# MEMIND_AGENT_CODE_RUNS_ENABLED=0
|
||||||
# VITE_AGENT_CODE_RUNS_ENABLED=0
|
# VITE_AGENT_CODE_RUNS_ENABLED=0
|
||||||
# VITE_AGENT_CODE_RUNS_AUTODETECT=0
|
# VITE_AGENT_CODE_RUNS_AUTODETECT=0
|
||||||
|
# VITE_AGENT_PAGE_DATA_DEV_AUTODETECT=0
|
||||||
|
|
||||||
# Tool Gateway Queue v0(/agent/runs 后台任务队列,默认保守限流)。
|
# Tool Gateway Queue v0(/agent/runs 后台任务队列,默认保守限流)。
|
||||||
# MEMIND_AGENT_RUN_QUEUE_CONCURRENCY=1
|
# MEMIND_AGENT_RUN_QUEUE_CONCURRENCY=1
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import {
|
|||||||
buildWebNewsSkillPrompt,
|
buildWebNewsSkillPrompt,
|
||||||
extractSelectedChatSkillName,
|
extractSelectedChatSkillName,
|
||||||
hasExplicitChatSkillPrompt,
|
hasExplicitChatSkillPrompt,
|
||||||
|
isPageDataDevIntent,
|
||||||
isPageDataIntent,
|
isPageDataIntent,
|
||||||
isPageGenerationIntent,
|
isPageGenerationIntent,
|
||||||
isProductCampaignIntent,
|
isProductCampaignIntent,
|
||||||
@@ -930,6 +931,15 @@ export function classifyWithRules({
|
|||||||
reason,
|
reason,
|
||||||
}, { source: 'rule' }), decisionContext);
|
}, { source: 'rule' }), decisionContext);
|
||||||
}
|
}
|
||||||
|
if (includeIntentPatterns && normalized && isPageDataDevIntent(normalized)) {
|
||||||
|
return finalizeRouterClassification(normalizeClassification({
|
||||||
|
route: CHAT_INTENT_ROUTE.AGENT,
|
||||||
|
confidence: 0.94,
|
||||||
|
reason: 'Page Data 开发修复(修 bug / 测试回归)',
|
||||||
|
agent_brief:
|
||||||
|
'这是 Page Data 开发修复任务:优先根据 verify/报错信息修 public HTML 与 policy,不要重建问卷;建表/bind 缺失时说明需 Goose 或 repair 脚本。',
|
||||||
|
}, { source: 'rule' }), decisionContext);
|
||||||
|
}
|
||||||
if (
|
if (
|
||||||
includeIntentPatterns &&
|
includeIntentPatterns &&
|
||||||
normalized &&
|
normalized &&
|
||||||
|
|||||||
@@ -1335,3 +1335,14 @@ test('createChatIntentRouter uses agent fallback for unmatched general questions
|
|||||||
assert.equal(result.source, 'fallback');
|
assert.equal(result.source, 'fallback');
|
||||||
assert.equal(llmCalls.length, 0);
|
assert.equal(llmCalls.length, 0);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('classifyWithRules routes page data dev repair before new page-data collect', () => {
|
||||||
|
const result = classifyWithRules({
|
||||||
|
text: '修复问卷 insert 403,columns_not_allowed',
|
||||||
|
sessionId: null,
|
||||||
|
sessionMessageCount: 0,
|
||||||
|
});
|
||||||
|
assert.equal(result.route, CHAT_INTENT_ROUTE.AGENT);
|
||||||
|
assert.match(result.reason, /Page Data 开发修复/);
|
||||||
|
assert.equal(result.suggested_skill, undefined);
|
||||||
|
});
|
||||||
|
|||||||
@@ -71,9 +71,27 @@ function isInteractivePageDataIntent(text) {
|
|||||||
return INTERACTIVE_PAGE_DATA_SURFACE_PATTERN.test(text) || featureCount >= 2;
|
return INTERACTIVE_PAGE_DATA_SURFACE_PATTERN.test(text) || featureCount >= 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const PAGE_DATA_DEV_INTENT_PATTERNS = [
|
||||||
|
/columns_not_allowed/i,
|
||||||
|
/\bpage[\s-]?data[\s-]?dev\b/i,
|
||||||
|
/(?:insert|提交|写入).{0,16}(?:失败|403|报错|error)/iu,
|
||||||
|
/(?:修复|修|排查|debug|fix).{0,24}(?:问卷|page[\s-]?data|数据提交|插入|admin|后台|policy|绑定|bind)/iu,
|
||||||
|
/(?:问卷|page[\s-]?data|数据页|表单页).{0,24}(?:bug|坏了|不通|失败|有问题)/iu,
|
||||||
|
/(?:测试|verify).{0,16}(?:没过|失败|不通过)/iu,
|
||||||
|
/(?:repair|修复).{0,16}(?:bind|绑定|policy|策略)/iu,
|
||||||
|
];
|
||||||
|
|
||||||
|
/** Page Data 开发修复(Aider 兜底):修 bug / 测回归,不是新建问卷。 */
|
||||||
|
export function isPageDataDevIntent(text) {
|
||||||
|
const normalized = String(text ?? '').trim();
|
||||||
|
if (!normalized) return false;
|
||||||
|
return PAGE_DATA_DEV_INTENT_PATTERNS.some((pattern) => pattern.test(normalized));
|
||||||
|
}
|
||||||
|
|
||||||
export function isPageDataIntent(text) {
|
export function isPageDataIntent(text) {
|
||||||
const normalized = String(text ?? '').trim();
|
const normalized = String(text ?? '').trim();
|
||||||
if (!normalized) return false;
|
if (!normalized) return false;
|
||||||
|
if (isPageDataDevIntent(normalized)) return false;
|
||||||
return PAGE_DATA_INTENT_PATTERNS.some((pattern) => pattern.test(normalized))
|
return PAGE_DATA_INTENT_PATTERNS.some((pattern) => pattern.test(normalized))
|
||||||
|| isInteractivePageDataIntent(normalized);
|
|| isInteractivePageDataIntent(normalized);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import {
|
|||||||
CHAT_SKILL_DEFINITIONS,
|
CHAT_SKILL_DEFINITIONS,
|
||||||
filterChatSkills,
|
filterChatSkills,
|
||||||
isExcelAnalysisIntent,
|
isExcelAnalysisIntent,
|
||||||
|
isPageDataDevIntent,
|
||||||
isPageDataIntent,
|
isPageDataIntent,
|
||||||
isPageGenerationIntent,
|
isPageGenerationIntent,
|
||||||
} from './chat-skills.mjs';
|
} from './chat-skills.mjs';
|
||||||
@@ -168,3 +169,11 @@ test('buildAutoChatSkillPrefix enables publish for implicit page requests', () =
|
|||||||
assert.match(prefix, /static-page-publish/);
|
assert.match(prefix, /static-page-publish/);
|
||||||
assert.match(prefix, /禁止询问是否还要发布/);
|
assert.match(prefix, /禁止询问是否还要发布/);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('isPageDataDevIntent matches repair phrases but not new survey creation', () => {
|
||||||
|
assert.equal(isPageDataDevIntent('修复问卷 insert 403 columns_not_allowed'), true);
|
||||||
|
assert.equal(isPageDataDevIntent('page-data-dev 修 policy 白名单'), true);
|
||||||
|
assert.equal(isPageDataDevIntent('帮我做一个报名问卷'), false);
|
||||||
|
assert.equal(isPageDataIntent('帮我做一个报名问卷'), true);
|
||||||
|
assert.equal(isPageDataIntent('修复问卷 insert 403 columns_not_allowed'), false);
|
||||||
|
});
|
||||||
|
|||||||
+23
-6
@@ -177,17 +177,21 @@ MindSpace/{userId}/
|
|||||||
|
|
||||||
## 6. 开发闭环操作手册
|
## 6. 开发闭环操作手册
|
||||||
|
|
||||||
### 6.1 前置:本地开启 code run(仅 dev)
|
### 6.1 前置:开启 code run
|
||||||
|
|
||||||
|
> **Phase 1(当前):** env + VITE,适合本机 dev。
|
||||||
|
> **Phase 1.5(目标):** memindadm 配置 + `/auth/status` 下发,见 [help-code02.md](./help-code02.md)。
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# .env 示例(开发机)
|
# .env 示例(开发机,Phase 1)
|
||||||
MEMIND_AGENT_CODE_RUNS_ENABLED=1
|
MEMIND_AGENT_CODE_RUNS_ENABLED=1
|
||||||
MEMIND_TOOL_GATEWAY_ENABLED=1
|
MEMIND_TOOL_GATEWAY_ENABLED=1
|
||||||
MEMIND_TOOL_GATEWAY_DEFAULT_EXECUTOR=aider
|
MEMIND_TOOL_GATEWAY_DEFAULT_EXECUTOR=aider
|
||||||
|
|
||||||
# 前端 build 时需注入(若走 H5 触发)
|
|
||||||
VITE_AGENT_CODE_RUNS_ENABLED=1
|
VITE_AGENT_CODE_RUNS_ENABLED=1
|
||||||
|
VITE_AGENT_PAGE_DATA_DEV_AUTODETECT=1
|
||||||
VITE_AGENT_CODE_RUNS_USER_IDS=<dev-user-uuid> # 或留空表示全用户(仅 dev)
|
VITE_AGENT_CODE_RUNS_USER_IDS=<dev-user-uuid> # 或留空表示全用户(仅 dev)
|
||||||
|
# 可选:MEMIND_AGENT_CODE_RUN_TASK_TYPES=page_data_dev,h5_chat_code_task,...
|
||||||
```
|
```
|
||||||
|
|
||||||
还需:
|
还需:
|
||||||
@@ -317,9 +321,21 @@ OpenHands 任务类型保持现有 `repo_refactor,multi_file,complex_repo`;**
|
|||||||
|
|
||||||
### Phase 1 — 最小 Memind 改动
|
### Phase 1 — 最小 Memind 改动
|
||||||
|
|
||||||
- [ ] 新增 `page_data_dev` taskType
|
- [x] 新增 `page_data_dev` taskType
|
||||||
- [ ] `chat-intent-router` 增加 Page Data 修 bug 意图
|
- [x] `chat-intent-router` 增加 Page Data 修 bug 意图
|
||||||
- [ ] `agentRunMode.ts` 可选 autodetect 模式(dev only)
|
- [x] `agentRunMode.ts` 可选 autodetect 模式(dev only,`VITE_AGENT_PAGE_DATA_DEV_AUTODETECT`)
|
||||||
|
|
||||||
|
### Phase 1.5 — memindadm 运行时策略(推荐下一步)
|
||||||
|
|
||||||
|
> 详细设计见 **[help-code02.md](./help-code02.md)**。
|
||||||
|
|
||||||
|
- [ ] `h5_agent_code_run_config` 表 + `agent-code-run-admin-config.mjs`
|
||||||
|
- [ ] memindadm API/UI:code run 开关、白名单、taskType、`pageDataDev.autodetect`
|
||||||
|
- [ ] `/auth/status` 下发 `agentCodeRun`,H5 不再依赖 `VITE_*` rebuild
|
||||||
|
- [ ] `agent-run-routes.mjs` 从 DB 读策略(env 仅紧急 override)
|
||||||
|
- [ ] Worker 拓扑(`MEMIND_TOOL_GATEWAY_ENABLED`)继续留 env
|
||||||
|
|
||||||
|
**为何需要 Phase 1.5:** Phase 1 的 env/VITE 适合 dev;要解决「用户经常失败」的运营灰度,必须在 memindadm 按用户/任务类型动态开关,且 H5 需运行时生效。
|
||||||
|
|
||||||
### Phase 2 — 自动化 dev loop
|
### Phase 2 — 自动化 dev loop
|
||||||
|
|
||||||
@@ -337,6 +353,7 @@ OpenHands 任务类型保持现有 `repo_refactor,multi_file,complex_repo`;**
|
|||||||
|
|
||||||
| 资源 | 路径 / 命令 |
|
| 资源 | 路径 / 命令 |
|
||||||
|------|-------------|
|
|------|-------------|
|
||||||
|
| **adm 运行时策略(Phase 1.5)** | `docs/help-code02.md` |
|
||||||
| Page Data API 用法 | `docs/page-data-api-usage.md` |
|
| Page Data API 用法 | `docs/page-data-api-usage.md` |
|
||||||
| page-data-collect skill | `skills/page-data-collect/SKILL.md` |
|
| page-data-collect skill | `skills/page-data-collect/SKILL.md` |
|
||||||
| 问卷 verify skill | `.claude/skills/memind-page-data-survey-verify/SKILL.md` |
|
| 问卷 verify skill | `.claude/skills/memind-page-data-survey-verify/SKILL.md` |
|
||||||
|
|||||||
@@ -0,0 +1,381 @@
|
|||||||
|
# Help Code02:Code Run / Page Data Dev 的 memindadm 运行时策略
|
||||||
|
|
||||||
|
> 文档版本:2026-07-23
|
||||||
|
> 状态:方案设计(未实施)
|
||||||
|
> 前置:[help-code01.md](./help-code01.md)(Aider 补充 Page Data 开发兜底)
|
||||||
|
> 关联:[memindadm-goose-gateway-design.md](./memindadm-goose-gateway-design.md)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 1. 为什么要做 Phase 1.5
|
||||||
|
|
||||||
|
Phase 1 用 **env + VITE_ 构建期变量** 控制 code run 与 `page_data_dev` autodetect。这在 dev/staging 可用,但对「用户经常页面失败、插入失败」的运营场景不够:
|
||||||
|
|
||||||
|
| 问题 | env/VITE 的局限 |
|
||||||
|
|------|----------------|
|
||||||
|
| 改开关要 rebuild H5 | `VITE_*` 打进 bundle,memindadm 点了不生效 |
|
||||||
|
| 改开关要 SSH 改 `.env` | 103 Portal / worker 两套 env,易不一致 |
|
||||||
|
| 无法按用户即时灰度 | 白名单改 env 无审计、无 UI |
|
||||||
|
| 与现有 adm 能力分裂 | aider 能力已在 `h5_capability_grants`,策略却在 env |
|
||||||
|
|
||||||
|
**目标:** 把 **「谁、哪种 task、是否 page_data_dev autodetect」** 迁入 memindadm;**部署拓扑**(worker 是否 spawn Aider)继续留 env。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2. 配置分层(硬边界)
|
||||||
|
|
||||||
|
```mermaid
|
||||||
|
flowchart TB
|
||||||
|
subgraph adm ["memindadm 运行时策略(DB)"]
|
||||||
|
P1[codeRun.enabled]
|
||||||
|
P2[userAllowlist / roleAllowlist]
|
||||||
|
P3[taskTypeAllowlist 含 page_data_dev]
|
||||||
|
P4[pageDataDev.autodetect]
|
||||||
|
P5[requireValidation]
|
||||||
|
P6[generalCodeAutodetect]
|
||||||
|
end
|
||||||
|
|
||||||
|
subgraph env ["部署 env(不改或只读 override)"]
|
||||||
|
E1[MEMIND_TOOL_GATEWAY_ENABLED]
|
||||||
|
E2[AIDER_BIN / OPENHANDS_BIN]
|
||||||
|
E3[worker 并发 / 超时 / guard]
|
||||||
|
E4[MEMIND_AGENT_RUN_* 紧急 override]
|
||||||
|
end
|
||||||
|
|
||||||
|
subgraph cap ["已有用户能力(DB)"]
|
||||||
|
C1[h5_capability_grants: aider/openhands]
|
||||||
|
C2[h5_llm_executor_bindings]
|
||||||
|
end
|
||||||
|
|
||||||
|
adm --> Portal[agent-run-routes 校验]
|
||||||
|
adm --> Auth["/auth/status → H5"]
|
||||||
|
cap --> Portal
|
||||||
|
env --> Worker[agent-run-worker]
|
||||||
|
Portal --> Worker
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2.1 进 memindadm 的项
|
||||||
|
|
||||||
|
| 原 env / VITE | adm 字段 | 说明 |
|
||||||
|
|---------------|----------|------|
|
||||||
|
| `MEMIND_AGENT_CODE_RUNS_ENABLED` | `codeRun.enabled` | 后端是否接受 `tool_mode=code` |
|
||||||
|
| `MEMIND_AGENT_CODE_RUNS_USER_IDS` | `codeRun.userAllowlist` | 空 = 不限制(enabled 时) |
|
||||||
|
| `MEMIND_AGENT_CODE_RUN_TASK_TYPES` | `codeRun.taskTypeAllowlist` | 含 `page_data_dev`、`h5_chat_code_task` |
|
||||||
|
| `MEMIND_AGENT_CODE_RUNS_REQUIRE_VALIDATION` | `codeRun.requireValidation` | 是否强制 receipt |
|
||||||
|
| `VITE_AGENT_CODE_RUNS_ENABLED` | `codeRun.clientEnabled` | H5 是否展示/走 code 路径 |
|
||||||
|
| `VITE_AGENT_CODE_RUNS_AUTODETECT` | `codeRun.generalAutodetect` | 通用代码语义 autodetect |
|
||||||
|
| `VITE_AGENT_PAGE_DATA_DEV_AUTODETECT` | `pageDataDev.autodetect` | Page Data 修 bug 专用 autodetect |
|
||||||
|
|
||||||
|
### 2.2 继续留 env 的项
|
||||||
|
|
||||||
|
| env | 原因 |
|
||||||
|
|-----|------|
|
||||||
|
| `MEMIND_TOOL_GATEWAY_ENABLED` | worker 进程级;Portal 与 worker 故意不同值 |
|
||||||
|
| `MEMIND_TOOL_GATEWAY_DEFAULT_EXECUTOR` | 机器上装的是 aider 还是 openhands |
|
||||||
|
| `MEMIND_AGENT_RUN_QUEUE_CONCURRENCY` 等 | SLO / guard / LaunchAgent |
|
||||||
|
| `MEMIND_AGENT_CODE_RUNS_*`(可选) | **紧急 override**:`MEMIND_CODE_RUN_POLICY_SOURCE=env` 时强制 env 优先 |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 3. 数据模型
|
||||||
|
|
||||||
|
### 3.1 表:`h5_agent_code_run_config`
|
||||||
|
|
||||||
|
与 `h5_skill_runtime_config`、`h5_image_make_admin_config` 同模式:`config_scope='global'` 单行 JSON。
|
||||||
|
|
||||||
|
```sql
|
||||||
|
CREATE TABLE IF NOT EXISTS h5_agent_code_run_config (
|
||||||
|
config_scope VARCHAR(32) PRIMARY KEY,
|
||||||
|
config_json JSON NOT NULL,
|
||||||
|
updated_by CHAR(36) NULL,
|
||||||
|
updated_at BIGINT NOT NULL
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3.2 默认 JSON Schema
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"codeRun": {
|
||||||
|
"enabled": false,
|
||||||
|
"clientEnabled": false,
|
||||||
|
"generalAutodetect": false,
|
||||||
|
"requireValidation": true,
|
||||||
|
"userAllowlist": [],
|
||||||
|
"taskTypeAllowlist": [
|
||||||
|
"page_data_dev",
|
||||||
|
"h5_chat_code_task",
|
||||||
|
"page_edit_code_task"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"pageDataDev": {
|
||||||
|
"autodetect": false
|
||||||
|
},
|
||||||
|
"meta": {
|
||||||
|
"notes": ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3.3 生效优先级(与 Memory V2 adm 一致)
|
||||||
|
|
||||||
|
参考 `memory-v2-admin-config.mjs` 的 `FIELD_SPECS + env override` 模式:
|
||||||
|
|
||||||
|
```text
|
||||||
|
1. 若 MEMIND_CODE_RUN_POLICY_SOURCE=env → 仅读 env(紧急回滚)
|
||||||
|
2. 否则读 h5_agent_code_run_config(admin-db)
|
||||||
|
3. 若表为空且 MEMIND_AGENT_CODE_RUNS_ENABLED=1 → source=env-migration(兼容旧部署)
|
||||||
|
4. 否则 default(全 false,fail closed)
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3.4 用户级能力(不重复造表)
|
||||||
|
|
||||||
|
以下 **仍用现有表**,adm「Code Run 策略」页只读展示 + 链到用户能力编辑:
|
||||||
|
|
||||||
|
- `h5_capability_grants`:`aider` / `openhands`
|
||||||
|
- `h5_llm_executor_bindings`:Aider 模型绑定
|
||||||
|
- `h5_user_policies`:`code_delegate_executor`、`code_task_routing`
|
||||||
|
|
||||||
|
**完整放行条件(与 today 相同,只是策略来源改为 DB):**
|
||||||
|
|
||||||
|
```text
|
||||||
|
codeRun.enabled (adm)
|
||||||
|
AND user ∈ allowlist(若配置)
|
||||||
|
AND taskType ∈ taskTypeAllowlist
|
||||||
|
AND capabilities.aider(用户 grant)
|
||||||
|
AND executor binding 可用
|
||||||
|
AND(若 requireValidation)message 含 validation metadata
|
||||||
|
AND toolGateway.enabled(env,worker 侧)
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 4. 服务模块:`agent-code-run-admin-config.mjs`
|
||||||
|
|
||||||
|
建议新建,API 对齐 `skill-runtime-admin-config.mjs`:
|
||||||
|
|
||||||
|
| 方法 | 用途 |
|
||||||
|
|------|------|
|
||||||
|
| `getAdminConfig()` | memindadm 编辑页 |
|
||||||
|
| `updateAdminConfig(patch, { updatedBy })` | 保存 + 审计 |
|
||||||
|
| `getRuntimeState()` | adm 运行时预览 |
|
||||||
|
| `getEffectivePolicy({ userId })` | Portal 校验用 |
|
||||||
|
| `getPublicClientPolicy({ userId })` | `/auth/status` 下发 H5 |
|
||||||
|
|
||||||
|
### 4.1 `getEffectivePolicy` 返回示例
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"source": "admin-db",
|
||||||
|
"updatedAt": 1753276800000,
|
||||||
|
"enabled": true,
|
||||||
|
"userAllowed": true,
|
||||||
|
"taskTypes": ["page_data_dev", "h5_chat_code_task", "page_edit_code_task"],
|
||||||
|
"requireValidation": true,
|
||||||
|
"pageDataDevAutodetect": true,
|
||||||
|
"generalAutodetect": false
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4.2 `getPublicClientPolicy` 返回示例(按用户过滤后)
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"codeRun": {
|
||||||
|
"enabled": true,
|
||||||
|
"pageDataDevAutodetect": true,
|
||||||
|
"generalAutodetect": false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
未登录或用户不在 allowlist 时:`codeRun.enabled=false`(或不返回该块)。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 5. HTTP API
|
||||||
|
|
||||||
|
### 5.1 memindadm(`admin-routes.mjs`)
|
||||||
|
|
||||||
|
| 方法 | 路径 | 说明 |
|
||||||
|
|------|------|------|
|
||||||
|
| GET | `/api/admin/agent-code-run/config` | 读配置 + source + updatedBy |
|
||||||
|
| PUT/PATCH | `/api/admin/agent-code-run/config` | 更新 |
|
||||||
|
| GET | `/api/admin/agent-code-run/runtime` | 有效策略 + env 覆盖状态 + worker 只读摘要 |
|
||||||
|
|
||||||
|
Worker 摘要可代理现有 `/api/runtime/status` 的 `toolRuntime.codeRunPolicy` 与 `queue`(**只读**,不在 adm 改 worker env)。
|
||||||
|
|
||||||
|
### 5.2 Portal 用户面
|
||||||
|
|
||||||
|
**扩展 `GET /auth/status`**(已登录用户):
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"authenticated": true,
|
||||||
|
"user": { "...": "..." },
|
||||||
|
"capabilities": { "aider": true, "...": "..." },
|
||||||
|
"skillRuntime": { "...": "..." },
|
||||||
|
"agentCodeRun": {
|
||||||
|
"enabled": true,
|
||||||
|
"pageDataDevAutodetect": true,
|
||||||
|
"generalAutodetect": false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**扩展 `POST /api/agent/runs` 校验:** 从 `getEffectivePolicy(userId)` 读,不再只读 `process.env.MEMIND_AGENT_CODE_RUNS_*`。
|
||||||
|
|
||||||
|
### 5.3 只读运维
|
||||||
|
|
||||||
|
`GET /api/runtime/status` 增加:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"toolRuntime": {
|
||||||
|
"codeRunPolicy": {
|
||||||
|
"source": "admin-db",
|
||||||
|
"enabled": true,
|
||||||
|
"userAllowlist": [],
|
||||||
|
"taskTypeAllowlist": ["page_data_dev", "..."],
|
||||||
|
"requireValidation": true,
|
||||||
|
"pageDataDevAutodetect": true,
|
||||||
|
"envOverrideActive": false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 6. 前端改造(去掉 VITE_ 硬依赖)
|
||||||
|
|
||||||
|
### 6.1 `src/utils/agentRunMode.ts`
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// 优先级:runtime policy(/auth/status)> VITE_(dev fallback)
|
||||||
|
|
||||||
|
let runtimePolicy: AgentCodeRunClientPolicy | null = null;
|
||||||
|
|
||||||
|
export function applyAgentCodeRunClientPolicy(policy: AgentCodeRunClientPolicy | null) {
|
||||||
|
runtimePolicy = policy;
|
||||||
|
}
|
||||||
|
|
||||||
|
function clientCodeRunsEnabled(): boolean {
|
||||||
|
if (runtimePolicy?.codeRun?.enabled != null) return runtimePolicy.codeRun.enabled;
|
||||||
|
return agentCodeRunsEnabled; // VITE fallback
|
||||||
|
}
|
||||||
|
|
||||||
|
function clientPageDataDevAutodetect(): boolean {
|
||||||
|
if (runtimePolicy?.codeRun?.pageDataDevAutodetect != null) {
|
||||||
|
return runtimePolicy.codeRun.pageDataDevAutodetect;
|
||||||
|
}
|
||||||
|
return agentPageDataDevAutodetectEnabled;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 6.2 注入时机
|
||||||
|
|
||||||
|
在现有 `/auth/status` 加载处(如 `src/api/client.ts` 或 auth hook):
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
const status = await fetchAuthStatus();
|
||||||
|
applyAgentCodeRunClientPolicy(status.agentCodeRun ?? null);
|
||||||
|
```
|
||||||
|
|
||||||
|
**效果:** memindadm 打开 `pageDataDev.autodetect` 后,用户刷新页面即生效,**无需 rebuild H5**。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 7. memindadm UI(Ops 后台)
|
||||||
|
|
||||||
|
建议菜单位置:**系统 / Agent 运行时 → Code Run & Page Data Dev**
|
||||||
|
|
||||||
|
| 控件 | 类型 | 说明 |
|
||||||
|
|------|------|------|
|
||||||
|
| Code Run 总开关 | toggle | `codeRun.enabled` |
|
||||||
|
| H5 客户端启用 | toggle | `codeRun.clientEnabled` |
|
||||||
|
| Page Data Dev Autodetect | toggle | `pageDataDev.autodetect` |
|
||||||
|
| 通用 Code Autodetect | toggle | `codeRun.generalAutodetect`(默认关) |
|
||||||
|
| 强制 Validation | toggle | `codeRun.requireValidation` |
|
||||||
|
| 用户白名单 | multi-select UUID | 空 = 全部(enabled 时) |
|
||||||
|
| Task Types | checkbox list | 至少含 `page_data_dev` |
|
||||||
|
| 当前 Worker 状态 | read-only | 来自 `/runtime/status` |
|
||||||
|
| Env Override 警告 | banner | `MEMIND_CODE_RUN_POLICY_SOURCE=env` 时显示 |
|
||||||
|
|
||||||
|
**保存时:** 写 `updated_by` + `updated_at`;可选写 admin audit log(与 image-make 一致)。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 8. 迁移与兼容
|
||||||
|
|
||||||
|
### 8.1 首次上线
|
||||||
|
|
||||||
|
1. 建表 `h5_agent_code_run_config`,默认全 `false`
|
||||||
|
2. 部署 `agent-code-run-admin-config.mjs` + admin API
|
||||||
|
3. **迁移脚本**(可选):若 env 已开启,import 到 DB 并提示改 `POLICY_SOURCE=admin`
|
||||||
|
|
||||||
|
```bash
|
||||||
|
node scripts/migrate-agent-code-run-config-from-env.mjs --dry-run
|
||||||
|
node scripts/migrate-agent-code-run-config-from-env.mjs --apply
|
||||||
|
```
|
||||||
|
|
||||||
|
### 8.2 回滚
|
||||||
|
|
||||||
|
| 场景 | 操作 |
|
||||||
|
|------|------|
|
||||||
|
| adm 配错了 | memindadm 关 `codeRun.enabled` |
|
||||||
|
| 紧急全站关闭 | `MEMIND_CODE_RUN_POLICY_SOURCE=env` + unset `MEMIND_AGENT_CODE_RUNS_ENABLED` |
|
||||||
|
| worker 异常 | LaunchAgent 停 worker(现有 runbook),与 adm 无关 |
|
||||||
|
|
||||||
|
### 8.3 与 help-code01 Phase 的关系
|
||||||
|
|
||||||
|
| Phase | 内容 | 配置来源 |
|
||||||
|
|-------|------|----------|
|
||||||
|
| Phase 1 ✅ | `page_data_dev` 意图 + autodetect 逻辑 | env/VITE |
|
||||||
|
| **Phase 1.5** | adm 运行时策略 + `/auth/status` | **DB + adm UI** |
|
||||||
|
| Phase 2 | verify → Aider dev loop 脚本 | 脚本 + adm 开关 |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 9. 实施清单(Phase 1.5)
|
||||||
|
|
||||||
|
- [ ] `agent-code-run-admin-config.mjs` + 单测
|
||||||
|
- [ ] `admin-routes.mjs`:`/agent-code-run/config`、`/runtime`
|
||||||
|
- [ ] `server.mjs`:`/auth/status` 增加 `agentCodeRun`
|
||||||
|
- [ ] `agent-run-routes.mjs`:改用 `getEffectivePolicy(userId)`
|
||||||
|
- [ ] `agentRunMode.ts`:runtime policy 优先于 VITE_
|
||||||
|
- [ ] Ops UI 页面(可先做 JSON 编辑,后做表单)
|
||||||
|
- [ ] `migrate-agent-code-run-config-from-env.mjs`
|
||||||
|
- [ ] 更新 `docs/agent-run-worker-rollout-runbook.md` 与 `.env.example`
|
||||||
|
- [ ] verify:`agent-code-run-admin-config.test.mjs` + 扩展 `chat-agent-run-gate.test.mjs`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 10. 安全与审计
|
||||||
|
|
||||||
|
1. **仅 admin 可写** — 复用 `requireAdmin`
|
||||||
|
2. **默认 fail closed** — 新环境 adm 配置为空 = 全关
|
||||||
|
3. **双闸门保留** — adm 开 + 用户 `aider` grant + worker gateway env
|
||||||
|
4. **审计字段** — `updated_by`、`updated_at`;重要变更写 admin audit
|
||||||
|
5. **生产建议** — 先 `userAllowlist` 小范围开 `page_data_dev`,再扩 `generalAutodetect`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 11. 相关文件索引
|
||||||
|
|
||||||
|
| 类型 | 路径 |
|
||||||
|
|------|------|
|
||||||
|
| 方案总览 | `docs/help-code01.md` |
|
||||||
|
| Goose 网关规划 | `docs/memindadm-goose-gateway-design.md` |
|
||||||
|
| 可复用 adm 模式 | `skill-runtime-admin-config.mjs` |
|
||||||
|
| env↔adm 映射先例 | `memory-v2-admin-config.mjs` |
|
||||||
|
| 后端 code run 门禁 | `agent-run-routes.mjs` |
|
||||||
|
| 前端 autodetect | `src/utils/agentRunMode.ts` |
|
||||||
|
| 运行时只读 | `server.mjs` → `runtimeCodeRunPolicyStatus()` |
|
||||||
|
| Worker runbook | `docs/agent-run-worker-rollout-runbook.md` |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 12. 一句话总结
|
||||||
|
|
||||||
|
**memindadm 管「策略」(谁、哪种 task、是否 page_data_dev);env 管「部署」(worker 是否 spawn Aider);H5 从 `/auth/status` 读运行时策略,不再依赖 rebuild VITE_。**
|
||||||
@@ -63,7 +63,7 @@ import {
|
|||||||
CHAT_IMAGE_UPLOAD_MAX_INPUT_BYTES,
|
CHAT_IMAGE_UPLOAD_MAX_INPUT_BYTES,
|
||||||
} from '../utils/imageUpload';
|
} from '../utils/imageUpload';
|
||||||
import { buildAbsoluteAssetDownloadUrl } from '../utils/mindspaceCards';
|
import { buildAbsoluteAssetDownloadUrl } from '../utils/mindspaceCards';
|
||||||
import { resolveAgentRunOptions } from '../utils/agentRunMode';
|
import { resolveAgentRunOptions, resolvePageDataDevTaskType } from '../utils/agentRunMode';
|
||||||
import {
|
import {
|
||||||
buildUserMessage,
|
buildUserMessage,
|
||||||
normalizeConversationMessages,
|
normalizeConversationMessages,
|
||||||
@@ -1472,8 +1472,9 @@ export function useTKMindChat(
|
|||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
const pageDataDevTaskType = resolvePageDataDevTaskType(trimmed);
|
||||||
const runOptions = resolveAgentRunOptions(trimmed, {
|
const runOptions = resolveAgentRunOptions(trimmed, {
|
||||||
taskType: 'h5_chat_code_task',
|
taskType: pageDataDevTaskType ?? 'h5_chat_code_task',
|
||||||
userId: userRef.current?.id ?? null,
|
userId: userRef.current?.id ?? null,
|
||||||
requestId,
|
requestId,
|
||||||
mindspaceContext: options?.mindspaceContext ?? null,
|
mindspaceContext: options?.mindspaceContext ?? null,
|
||||||
|
|||||||
@@ -28,6 +28,11 @@ export const agentCodeRunsEnabled = envFlag(import.meta.env.VITE_AGENT_CODE_RUNS
|
|||||||
export const agentCodeRunsAutodetectEnabled = envFlag(
|
export const agentCodeRunsAutodetectEnabled = envFlag(
|
||||||
import.meta.env.VITE_AGENT_CODE_RUNS_AUTODETECT,
|
import.meta.env.VITE_AGENT_CODE_RUNS_AUTODETECT,
|
||||||
);
|
);
|
||||||
|
export const agentPageDataDevAutodetectEnabled = envFlag(
|
||||||
|
import.meta.env.VITE_AGENT_PAGE_DATA_DEV_AUTODETECT,
|
||||||
|
);
|
||||||
|
|
||||||
|
export const PAGE_DATA_DEV_TASK_TYPE = 'page_data_dev';
|
||||||
|
|
||||||
function parseUserIdSet(value: unknown): Set<string> {
|
function parseUserIdSet(value: unknown): Set<string> {
|
||||||
return new Set(
|
return new Set(
|
||||||
@@ -46,6 +51,27 @@ export function agentCodeRunsEnabledForUser(userId?: string | null): boolean {
|
|||||||
return Boolean(userId && agentCodeRunUserIds.has(userId));
|
return Boolean(userId && agentCodeRunUserIds.has(userId));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const PAGE_DATA_DEV_TASK_PATTERNS = [
|
||||||
|
/columns_not_allowed/i,
|
||||||
|
/\bpage[\s-]?data[\s-]?dev\b/i,
|
||||||
|
/(?:insert|提交|写入).{0,16}(?:失败|403|报错|error)/iu,
|
||||||
|
/(?:修复|修|排查|debug|fix).{0,24}(?:问卷|page[\s-]?data|数据提交|插入|admin|后台|policy|绑定|bind)/iu,
|
||||||
|
/(?:问卷|page[\s-]?data|数据页|表单页).{0,24}(?:bug|坏了|不通|失败|有问题)/iu,
|
||||||
|
/(?:测试|verify).{0,16}(?:没过|失败|不通过)/iu,
|
||||||
|
/(?:repair|修复).{0,16}(?:bind|绑定|policy|策略)/iu,
|
||||||
|
];
|
||||||
|
|
||||||
|
export function isPageDataDevTaskText(text: string): boolean {
|
||||||
|
const normalized = String(text ?? '').trim();
|
||||||
|
if (!normalized) return false;
|
||||||
|
return PAGE_DATA_DEV_TASK_PATTERNS.some((pattern) => pattern.test(normalized));
|
||||||
|
}
|
||||||
|
|
||||||
|
export function resolvePageDataDevTaskType(text: string): string | null {
|
||||||
|
if (!agentPageDataDevAutodetectEnabled) return null;
|
||||||
|
return isPageDataDevTaskText(text) ? PAGE_DATA_DEV_TASK_TYPE : null;
|
||||||
|
}
|
||||||
|
|
||||||
const CODE_TASK_PATTERNS = [
|
const CODE_TASK_PATTERNS = [
|
||||||
/\b(repo|repository|branch|commit|pull request|pr|diff|patch)\b/i,
|
/\b(repo|repository|branch|commit|pull request|pr|diff|patch)\b/i,
|
||||||
/\b(aider|openhands|codex|codebase|workspace)\b/i,
|
/\b(aider|openhands|codex|codebase|workspace)\b/i,
|
||||||
@@ -185,6 +211,22 @@ export function buildAgentRunTaskValidation({
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (taskType === PAGE_DATA_DEV_TASK_TYPE) {
|
||||||
|
const taskReceiptPath = `.memind/agent-runs/${safeRequestId}-page-data-dev.json`;
|
||||||
|
expectedFiles.push({ path: taskReceiptPath, contains: PAGE_DATA_DEV_TASK_TYPE });
|
||||||
|
instructions.push(
|
||||||
|
'',
|
||||||
|
'[Memind page-data-dev validation]',
|
||||||
|
`Before finishing this Page Data dev repair task, create or update ${taskReceiptPath}.`,
|
||||||
|
'The file must be valid JSON and include:',
|
||||||
|
`- requestId: ${requestId}`,
|
||||||
|
`- taskType: ${PAGE_DATA_DEV_TASK_TYPE}`,
|
||||||
|
'- a brief summary of HTML/policy fixes or why no file change was needed.',
|
||||||
|
'Only modify public/*.html and .mindspace/page-data-policies/*.json unless explicitly told otherwise.',
|
||||||
|
'Do not recreate datasets or call private_data_execute unless the user pasted schema errors.',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
validation: expectedFiles.length ? { expectedFiles } : null,
|
validation: expectedFiles.length ? { expectedFiles } : null,
|
||||||
instruction: instructions.join('\n'),
|
instruction: instructions.join('\n'),
|
||||||
@@ -222,6 +264,7 @@ export function resolveAgentRunOptions(
|
|||||||
taskType = 'code_task',
|
taskType = 'code_task',
|
||||||
forceCode = false,
|
forceCode = false,
|
||||||
allowAutodetect = agentCodeRunsAutodetectEnabled,
|
allowAutodetect = agentCodeRunsAutodetectEnabled,
|
||||||
|
allowPageDataDevAutodetect = agentPageDataDevAutodetectEnabled,
|
||||||
userId = null,
|
userId = null,
|
||||||
requestId = null,
|
requestId = null,
|
||||||
mindspaceContext = null,
|
mindspaceContext = null,
|
||||||
@@ -230,6 +273,7 @@ export function resolveAgentRunOptions(
|
|||||||
taskType?: string;
|
taskType?: string;
|
||||||
forceCode?: boolean;
|
forceCode?: boolean;
|
||||||
allowAutodetect?: boolean;
|
allowAutodetect?: boolean;
|
||||||
|
allowPageDataDevAutodetect?: boolean;
|
||||||
userId?: string | null;
|
userId?: string | null;
|
||||||
requestId?: string | null;
|
requestId?: string | null;
|
||||||
mindspaceContext?: MindSpaceChatContext | null;
|
mindspaceContext?: MindSpaceChatContext | null;
|
||||||
@@ -237,29 +281,37 @@ export function resolveAgentRunOptions(
|
|||||||
} = {},
|
} = {},
|
||||||
): AgentRunCreateOptions {
|
): AgentRunCreateOptions {
|
||||||
const normalizedText = String(text ?? '').trim();
|
const normalizedText = String(text ?? '').trim();
|
||||||
|
const pageDataDevTaskType =
|
||||||
|
allowPageDataDevAutodetect && resolvePageDataDevTaskType(normalizedText);
|
||||||
|
const effectiveTaskType = pageDataDevTaskType ?? taskType;
|
||||||
const shouldUseDeepReasoning =
|
const shouldUseDeepReasoning =
|
||||||
forceCode || DEEP_REASONING_TASK_PATTERNS.some((pattern) => pattern.test(normalizedText));
|
forceCode ||
|
||||||
|
Boolean(pageDataDevTaskType) ||
|
||||||
|
DEEP_REASONING_TASK_PATTERNS.some((pattern) => pattern.test(normalizedText));
|
||||||
|
|
||||||
if (!agentCodeRunsEnabledForUser(userId)) {
|
if (!agentCodeRunsEnabledForUser(userId)) {
|
||||||
return shouldUseDeepReasoning ? { forceDeepReasoning: true, taskType } : {};
|
return shouldUseDeepReasoning ? { forceDeepReasoning: true, taskType: effectiveTaskType } : {};
|
||||||
}
|
}
|
||||||
|
|
||||||
const shouldUseCode = forceCode || (allowAutodetect && CODE_TASK_PATTERNS.some((pattern) => pattern.test(normalizedText)));
|
const shouldUseCode =
|
||||||
|
forceCode ||
|
||||||
|
Boolean(pageDataDevTaskType) ||
|
||||||
|
(allowAutodetect && CODE_TASK_PATTERNS.some((pattern) => pattern.test(normalizedText)));
|
||||||
if (!shouldUseCode) {
|
if (!shouldUseCode) {
|
||||||
return shouldUseDeepReasoning ? { forceDeepReasoning: true, taskType } : {};
|
return shouldUseDeepReasoning ? { forceDeepReasoning: true, taskType: effectiveTaskType } : {};
|
||||||
}
|
}
|
||||||
const normalizedRequestId = requestId ?? crypto.randomUUID();
|
const normalizedRequestId = requestId ?? crypto.randomUUID();
|
||||||
const receipt = buildAgentRunValidationReceipt(normalizedRequestId);
|
const receipt = buildAgentRunValidationReceipt(normalizedRequestId);
|
||||||
const taskValidation = buildAgentRunTaskValidation({
|
const taskValidation = buildAgentRunTaskValidation({
|
||||||
requestId: normalizedRequestId,
|
requestId: normalizedRequestId,
|
||||||
taskType,
|
taskType: effectiveTaskType,
|
||||||
text: normalizedText,
|
text: normalizedText,
|
||||||
mindspaceContext,
|
mindspaceContext,
|
||||||
pageEdit,
|
pageEdit,
|
||||||
});
|
});
|
||||||
return {
|
return {
|
||||||
toolMode: 'code',
|
toolMode: 'code',
|
||||||
taskType,
|
taskType: effectiveTaskType,
|
||||||
forceDeepReasoning: true,
|
forceDeepReasoning: true,
|
||||||
validation: mergeAgentRunValidationFiles(receipt.validation, taskValidation.validation),
|
validation: mergeAgentRunValidationFiles(receipt.validation, taskValidation.validation),
|
||||||
validationInstruction: `${receipt.instruction}${taskValidation.instruction}`,
|
validationInstruction: `${receipt.instruction}${taskValidation.instruction}`,
|
||||||
|
|||||||
Reference in New Issue
Block a user