Files
memind/skills/page-data-collect/SKILL.md
john 371900bae5
Memind CI / Test, build, and release guards (pull_request) Failing after 18s
feat(mindspace): add page quota grace write and local delivery guardrails.
Centralize page HTML quota checks with grace-write semantics across page
services and workspace tools, keep localhost MindSpace links clickable in
chat display, and expand Page Data/static-page skill plus local dev docs
for quota, delivery URLs, and native Aider/OpenHands tooling.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-30 10:44:26 +08:00

19 KiB
Raw Permalink Blame History

name, description
name description
page-data-collect 在 MindSpace 页面中收集、存储并管理结构化数据(问卷、报名、台账、后台查看),基于 Page Data API 与用户隔离的 PostgreSQL 空间

页面数据收集(Page Data Collect

在 MindSpace 公开 HTML 页面中嵌入表单、问卷、报名等交互,并将提交持久化到当前用户隔离的 PostgreSQL schema,通过平台 Page Data API 受控读写。运行时禁止创建、读取或回退到用户 SQLite。

详细 API 说明见工作区外文档 docs/page-data-api-usage.mdMemind 仓库)。

何时使用

  • 用户要在页面里收集并保存数据:问卷、投票、报名、签到、意见反馈
  • 用户要后台查看提交记录,可能带口令/密码
  • 用户提到「数据交互」「sqlite」「存数据库」「提交记录」
  • 在已有页面上追加可提交、可统计的表单区块

不要用于:

  • 只在聊天里弹表单、不落库 → 用 form-builder
  • 纯静态展示页、无数据读写 → 用 static-page-publish

核心原则

Agent 可以建模 SQL 并注册 dataset
HTML 页面只能调用 Page Data APIpage-data-client.js);
禁止自建 Express / 独立端口 / 直接暴露数据库连接;禁止 SQLite / localStorage 数据回退。

方案选择:默认快车道 + 能力分支(必做)

平台配置不由 LLM 发明页面内容可由 LLM 自由生成

用户描述需求
  → 匹配能力分支(下表 A/B/C/D,默认 A)
  → 仅冲突或无法匹配时,用 1 题让用户选分支或改口令
  → 输出简短「方案摘要」;用户已明确要求直接创建/完成/发布时视为已确认并立即执行
  → 再 load_skill / 建表 / write_file / bind

默认方案 A(无特殊说明时直接采用,不必逐条追问)

默认值
访客 匿名提交(public,仅 insert
后台 独立 HTML 页(password,仅 read
后台口令 88888888(平台要求 8~128 位;用户可指定其它合法口令覆盖)
提交后修改 不支持(一次性)
页面 public/*-survey.html + public/*-admin.html(两文件各 bind 一次)

用户只说「问卷/报名/收集数据 + 后台查看」且未提登录、协作改单、同页后台时 → 用方案 A。若用户已说「直接做」「完整创建并发布」「不用询问」「持续推进」等终态指令,摘要后必须在同一轮立即调用工具开工,禁止停下来等待再次确认。

口令规则:

  • 禁止接受或使用 <8 位口令(如 888);若用户坚持短口令,说明平台限制并代用 88888888 或请其给出 ≥8 位。
  • bind 时 password 必须传入且会写入发布记录;禁止只改 access_mode 不写 password_hash
  • 交付说明写明后台口令;用户可在 MindSpace「页面数据」面板重置。

能力分支(非默认需求时切换,仍用结构化选项)

分支 适用用户表述 前台 后台/协作 页面数
A 匿名问卷+口令后台(默认) 匿名填、管理员看统计 public insert 独立页 password read 2
B 登录后各自提交/查看 要登录、只看自己的、销售上报 login_required insert+read 同页或独立;own_rows 12
C 共口令协作台账 团队共用一个密码、一起改 password insert+read+update 同页;先 authenticate 1
D 仅公开提交无后台 只要收集、不要后台 public insert only 1
E 登录提交+口令管理 登录填报 + 管理员口令看全量 login_required insert 独立页 password read 2

LLM 只做分支匹配:从用户原话判断 A~E;能确定则写入方案摘要,不要机械念 5 题问卷。

仅以下情况才问用户(每次最多 1~2 点)

  1. 表述同时命中两个互斥分支(如「匿名提交」+「同页内嵌口令看全量」)→ 给 A/E 选项说明须拆页
  2. 用户明确要的口令 <8 位 → 请改口令或确认用默认 88888888
  3. 需要 B/C 但表结构是否要 created_by_user_id 等列尚不清楚 → 确认登录隔离

禁止连续多轮只输出「我先检查工作区/加载技能」而不调用工具;第一轮工具应是 load_skilllist_dir / private_data_execute,不是空计划。

平台硬约束(分支菜单边界,不可绕过)

- public:可匿名 insert;服务端禁止 update / softDelete
- password:所有 API 须先 authenticate;口令 ≥8 位且须写入发布记录
- `public` 策略中的 `insert: true` 等价于任何访客都能直接调用 API 写入;前端口令、隐藏按钮和 JavaScript 判断都不是权限控制
- password 页面必须调用 `client.authenticate(password)` 获取服务端 token;禁止在 HTML 中硬编码口令或只做 `password === "..."` 比较
- 同一公开页若要求「所有人可评价、只有所有者可写正文」:正文 dataset 在公开页必须 `insert: false`,评价 dataset 才能 `insert: true`;所有者写正文必须使用独立 `password` 作者页或 `login_required` 页面
- 同页不能同时「匿名 insert」+「口令 read 全量」→ 须拆前台 + 后台(方案 A/E)
- password 无法区分访客身份;「只改自己的」须 login_required + own_rows(方案 B)
- 匿名提交后「凭链接改自己的」无内置能力,须改 B 或接受一次性提交

方案摘要模板(开工前展示;默认填 A,用户可改口令或换分支)

## 页面数据方案(请确认或只改口令)

- 分支:A 匿名提交 + 独立口令后台
- 访客:匿名提交(public,仅 insert
- 管理员:独立后台页(password,仅 read
- 后台口令:88888888(可改为你的 ≥8 位口令)
- 提交后修改:不支持
- 页面:public/xxx.html + public/xxx-admin.html
- 内容:(由你的描述生成题目/字段/报表)

若用户尚未授权创建,确认后开始建表与写页面;若用户已明确要求创建/完成/发布,此摘要仅用于告知,后面必须紧接工具调用,不得结束回复等待确认。

分支切换示例

用户:「销售要登录后才能上报,管理员用 88888888 看全部。」
方案 E:前台 login_required insert;后台独立页 password read;口令 88888888

用户:「小团队共用一个密码,一起维护台账。」
方案 C:单页 passwordbind 传 password;页内先 authenticate 再读写。

用户:「做个投票,不用后台。」
方案 D:单页 public insert only。

标准工作流

1. 加载本技能 + 选定分支

load_skill → page-data-collect
→ 匹配分支(默认 A)→ 方案摘要 → 用户确认,或从明确的创建/完成/发布指令判定已确认
→ 禁止未获创建授权就 bind;已获授权时禁止空转计划、重复确认或不调用工具

2. 数据层:建表 + 注册 dataset

每个独立交付任务必须独立建表、独立 dataset,禁止复用已有表。

场景 正确做法
用户要做一个新页面 / 新功能(即使主题相似,如第二个日记本、另一个问卷) 新建专用表 + 新 dataset 名,例如 safe_diary_entrieschildren_hobby_survey_20260729
用户明确说「在这个已有页面里加一块表单」 才可复用该页已 bind 的 dataset
Agent 在 registry 里看到同名/同主题旧表 不得直接拿来给新 HTML 用;必须新建

命名建议:{页面slug}_entries / {页面slug}_responses,表名与 dataset 名一致。两个页面即使业务相似(日记、台账、问卷),也必须是两套 {table, dataset, policy},避免字段约束、口令策略、软删除配置互相污染。

private_data_execute 建表(示例):

CREATE TABLE IF NOT EXISTS survey_responses (
  id BIGINT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
  q1_feature TEXT NOT NULL,
  q2_usage TEXT NOT NULL,
  q3_suggestion TEXT NOT NULL,
  created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP
);

private_data_register_dataset 注册(示例):

{
  "name": "survey_responses",
  "table": "survey_responses",
  "description": "TKMind 功能偏好问卷",
  "actions": ["read", "insert"],
  "columns": {
    "read": ["id", "q1_feature", "q2_usage", "q3_suggestion", "created_at"],
    "insert": ["q1_feature", "q2_usage", "q3_suggestion"]
  }
}

数据层失败必须立即停止(fail closed)

  • private_data_executeprivate_data_register_datasetprivate_data_schema/query 任一返回连接错误、权限错误或 isError: true 时,禁止继续写 HTML、bind 或发布。
  • 禁止声称“先准备 HTML,PG 恢复后会自动生效”;平台没有延迟补执行队列。数据库恢复后必须重新执行建表、注册、bind 和交付前自检。
  • PostgreSQL 连接错误应原样报告,不得把 /tmp/.s.PGSQL.*ECONNREFUSEDpermission denied 解释成“稍后会自动恢复”。
  • dataset 配置了 soft_delete 时,表必须包含 deleted_at TIMESTAMPTZ;配置了 own_rows 时,表必须包含 policy 指定的所有者字段。

用户空间配额(页面写入 grace 策略)

MindSpace 页面 HTMLpublic/*.htmldraft/*.html)写入采用 「本次写完、下次再拦」

状态 平台行为
尚未超配额,但剩余空间 < 本次页面大小 允许写完grace write;本次 bind 必须继续完成)
已经超配额(used ≥ quota 直接拦截 write_file / edit_file / bind / 发布
空间充足 正常写入

Agent 必须遵守:

  • grace 只保证当次页面 HTML 能落盘并完成 bind;不是可以跳过 bind 或只写 policy 文件。
  • bind 失败(含 quota_exceeded 且已超配额、dataset_schema_mismatch、PG 连接错误)时,禁止向用户交付「已就绪」链接;必须如实报告失败原因。
  • 用户空间已满时,提示其在 MindSpace「我的空间」清理旧页面/资产,或购买/扩容后再继续写新页面。
  • grace 不覆盖图片/附件等非页面 HTML 资产上传;那些仍按原有配额硬拦截。

禁止只写 .mindspace/page-data-policies/<slug>.json 或使用非 UUID 的 pageId 冒充 bind 成功;policy 的 pageId 必须来自 private_data_bind_workspace_page 返回的真实 UUID,且数据库中存在对应 h5_page_records

3. 页面层:写 HTML

  • write_file / edit_file 写入或更新 public/*.html
  • 页面视觉、封面、mindspace-cover、页脚等规范参照 static-page-publish
  • 必须引入平台脚本:
<script src="/assets/page-data-client.js"></script>

客户端只允许调用 page-data-client.js 已公开的方法:listRowsgetSchemagetStatsinsertRowupdateRowdeleteRowauthenticate。删除单行使用:

await client.deleteRow('dataset_name', rowId);

禁止发明 softDeleteRowsdeleteRows 等不存在的方法;服务端会根据 dataset 的 soft_delete 授权把 deleteRow 转换为软删除。

  • 第三方 JS 库Chart.js、ECharts 等)禁止写 CDN https://...;发布页 CSP 只允许同源脚本。优先用平台预置路径,或下载到 public/assets/ 后用相对路径引用:
<!-- 推荐:平台已预置 Chart.js -->
<script src="/assets/chart.umd.min.js"></script>
<!-- 或页面自有副本 -->
<script src="assets/chart.umd.min.js"></script>

平台会在发布/访问时自动把常见 Chart.js CDN 改写为 /assets/chart.umd.min.js;其它未知 CDN 会在发布检查中被拦截。

4. 发布并绑定页面(推荐)

private_data_bind_workspace_page 一步完成:创建/更新页面记录 → 发布 → 写入 Page Data 策略

问卷页(匿名提交)示例:

{
  "relativePath": "public/tkmind-survey.html",
  "accessMode": "public",
  "datasets": {
    "survey_responses": {
      "insert": true,
      "read": false,
      "columns": {
        "insert": ["q1_feature", "q2_usage", "q3_suggestion"]
      }
    }
  }
}

后台页(口令查看)示例:

{
  "relativePath": "public/tkmind-survey-admin.html",
  "accessMode": "password",
  "password": "88888888",
  "datasets": {
    "survey_responses": {
      "insert": false,
      "read": true,
      "columns": {
        "read": ["id", "q1_feature", "q2_usage", "q3_suggestion", "created_at"]
      }
    }
  }
}

返回 pageIdUUID)与 workspaceUrl / deliveryUrl/MindSpace/<用户ID>/public/xxx.html)。

交付时必须优先给用户真实可点击 URL

环境 链接前缀示例
本地开发 http://127.0.0.1:8081/MindSpace/<用户ID>/public/xxx.htmlPage Data API 走 Portal 80815173 仅 UI 预览)
生产 https://m.tkmind.cn/MindSpace/<用户ID>/public/xxx.html
  • 禁止交付占位 host(如 http://本地服务/...);聊天展示层不得把 127.0.0.1:8081 替换成不可点击文字。
  • /u/用户名/pages/... 仅作补充,用户可见主链接必须是 MindSpace /public/ 路径。
  • bind 会同步工作区 HTML 到发布快照,但禁止先发布占位内容再补文件。

dataset 名称必须与 HTML 一致private_data_register_datasetnameprivate_data_bind_workspace_pagedatasets 键名、以及 HTML 里 insertRow('...') / listRows('...') 的字符串必须完全相同(例如都用 tkmind_exp_survey)。若不一致,提交会报「dataset 未授权 insert/read」。

若问卷与后台是多个 HTML 文件,对每个 public/*.html 各调用一次 bind

  • 问卷页accessMode: "public" + dataset 仅 insert
  • 后台页accessMode: "password" + dataset 仅 read(发布口令至少 8 位)

顺序:先 write_file 完整 HTML → 再 bind;禁止只写「问卷页面」占位文字就发布。

也可手动发布后调用 private_data_set_page_policy(需已知 pageId)。

5. 配置 Page Access Policy(手动路径)

多页场景请按页分别配置,勿把 public 问卷与 password 后台混在同一 pageId 策略里。

6. 前端读写

<script src="/assets/page-data-client.js"></script>
<script>
  // pageId 可省略:平台访问时注入 __MINDSPACE_PAGE_DATA__
  const client = MindSpacePageData.createClient({ apiBase: '/api' });

  // 口令页后台:await client.authenticate('88888888');  // 口令 ≥8 位,与发布时一致
  // 提交:await client.insertRow('survey_responses', { q1_feature: '...', ... });
  // 列表:const { rows } = await client.listRows('survey_responses', { limit: 50 });
</script>

常见场景策略

分支 场景 发布模式 Page Data 能力
A(默认) 匿名问卷 + 口令后台 前台 public;后台 password insert / read(分页)
B 登录用户各自提交/查看/改自己的 login_required insert/read/update + own_rows
C 共口令协作台账 password insert/read/update(先 authenticate
D 仅公开收集 public insert
E 登录提交 + 口令管理全量 前台 login_required;后台 password insert / read(分页)

需要 update/deletepublic 不支持;匿名无「改自己的」→ 用 B 或 C。详见上文分支表。

严格禁止

  1. 禁止在尚未获得创建授权时建表 / bind / 发布;用户明确要求「创建、完成、发布、直接做、不用询问、持续推进」均视为确认,禁止再次停下来询问
  2. 禁止让 LLM 自行发明 accessMode/拆页/口令;必须落在分支 A~E 与默认口令规则内
  3. 禁止连续两轮仅输出计划、不调用 load_skill / list_dir / write_file / private_data_execute / bind
  4. 禁止接受 <8 位发布口令;用户未指定口令时后台默认 88888888
  5. 禁止创建 scripts/*-api.mjs、Express 服务、或监听独立端口(如 8899
  6. 禁止 HTML 中硬编码 http://127.0.0.1:端口 或自定义 /api/survey/*
  7. 禁止在 HTML 中使用 onclick / oninput 等内联事件属性(MindSpace 发布页 CSP 不允许);改用 addEventListener
  8. 禁止用 CDN https://... 引用 <script src>(发布页 CSP 仅允许同源;Chart.js 用 /assets/chart.umd.min.jspublic/assets/ 本地文件)
  9. 禁止在页面 JS 中直接使用 better-sqlite3 或读取 .sqlite 文件路径
  10. 禁止CREATE TABLE 而不 private_data_register_dataset
  11. 禁止未配置 private_data_set_page_policy 就让页面调用公开 API
  12. 禁止先发布占位页(如 <p>问卷页面</p>)再让用户访问 /u/.../pages/...
  13. 禁止在 Agent 生成的 HTML/JavaScript 中使用 localStorage / sessionStorage / IndexedDB 保存任何数据或做 API fallback;所有需持久化的数据必须进入当前用户专属 PostgreSQL schema,禁止 SQLite、静态 JSON/JS 文件和内存 fallback
  14. 禁止在空间已满(已超配额)时继续 write_file / bind 新页面;必须先提示清理或扩容
  15. 禁止bind 未完成或 pageId 非 UUID 时声称 Page Data 页面已就绪;控制台出现 pageId 未配置 说明 bind/发布链路未闭环
  16. 禁止为新的独立页面复用已有 dataset / 表(例如第二个日记页继续用 diary_entries);除非用户明确要求改同一页面上的表单

交付前自检

  1. 已选定分支 AE,方案摘要已确认;password 页口令 ≥8 位且 bind 已传入
  2. private_data_register_dataset 已注册;PostgreSQL 表字段与 policy 一致(含 deleted_at 等)
  3. private_data_bind_workspace_page 已成功,返回 UUID 形式 pageIddeliveryUrl
  4. .mindspace/page-data-policies/<pageId>.json 已写入(文件名必须是 UUID,不是 slug)
  5. 通过 Portal 打开 deliveryUrl 时,页面源码含 __MINDSPACE_PAGE_DATA__
  6. HTML 含 page-data-client.js;已 bind 或发布后平台会注入 pageId
  7. HTML 不含硬编码 127.0.0.1: 作为 API 基址;不含 /api/survey/PLACEHOLDER_PAGE_ID
  8. 本地交付链接使用 http://127.0.0.1:8081/MindSpace/...;生产使用 https://m.tkmind.cn/MindSpace/...;禁止 http://本地服务/...
  9. 向用户说明:访客如何提交、管理员如何用口令查看记录
  10. HTML 未调用 softDeleteRows / deleteRows 等客户端不存在的方法;删除使用 deleteRow(dataset, rowId)
  11. 若 bind 报 quota_exceededoverQuota: true,停止交付并提示清理空间;grace 仅适用于「当次写完、尚未超配额」场景

回复格式

除数据能力外,优先返回 deliveryUrl / workspaceUrl 的 Markdown 链接 [标题](完整URL),并简要说明后台入口与口令(如有)。

  • 本地:http://127.0.0.1:8081/MindSpace/<用户ID>/public/xxx.html
  • 生产:https://m.tkmind.cn/MindSpace/<用户ID>/public/xxx.html
  • 必须附带 bind 返回的 pageId(UUID),便于用户在「页面数据」面板排查