Compare commits

..

4 Commits

Author SHA1 Message Date
john 36d3a91f8d test(chat): widen intent-router timeout fallback window
Memind CI / Test, build, and release guards (push) Successful in 5m32s
CI failed on a 5ms/20ms race that let the LLM reply win before the timeout fallback. Give the abort path more slack so the guard stays deterministic.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-13 16:15:16 +08:00
john 4e789663a1 feat(seo): index public online pages without confirmation
Memind CI / Test, build, and release guards (push) Failing after 5m19s
Drop the user_confirmed_at gate so public, online, unexpired pages can enter sitemap/llms and receive SEO/GEO tags. Defaults now enable all discovery switches; stored all-off config is still preserved until admin saves.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-13 15:51:48 +08:00
john 8a88cd53e8 fix(h5-chat): resolve logged-in blank page from canSubmit TDZ
Memind CI / Test, build, and release guards (push) Failing after 4m28s
Move canSubmit above sendButtonLabel so authenticated ChatPanel render no longer throws before initialization.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-13 11:33:26 +08:00
john baf89c5d04 docs: register 2026-08-12 dev and related branch dispositions
Memind CI / Test, build, and release guards (push) Successful in 4m10s
Record closure for achievements showcase, WeChat menu rename, runtime profile packaging, SEO/GEO admin catalog, and achievements delivery fix branches.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-13 10:26:49 +08:00
13 changed files with 180 additions and 29 deletions
+2 -2
View File
@@ -1243,11 +1243,11 @@ test('createChatIntentRouter coerces LLM search route to web for realtime query'
test('createChatIntentRouter timeout fallback prefers agent even when policy requests direct_chat', async () => {
const router = createChatIntentRouter({
enabled: true,
timeoutMs: 5,
timeoutMs: 40,
fallbackRoute: CHAT_INTENT_ROUTE.DIRECT_CHAT,
llmProviderService: {
async createChatCompletion() {
await new Promise((resolve) => setTimeout(resolve, 20));
await new Promise((resolve) => setTimeout(resolve, 250));
return { ok: true, reply: '{"route":"direct_chat","confidence":0.9,"reason":"x","suggested_skill":null,"agent_brief":""}' };
},
},
+109
View File
@@ -527,6 +527,115 @@ Portal,避免在线修改稳定 `.env`,并确保稳定 8081 与其他用户
- 保留本地分支名用于审计追溯。
- **不要** merge、cherry-pick 或从该分支继续开发。
## `feature/2026-08-12-dev`
**状态:禁止再次引用。改动已提交并进入 `origin/main`,该分支保留仅用于只读追溯。**
审计日期:2026-08-13
分支 HEAD`421e204`
`origin/main` 对应提交:`421e204`
### 原始用途
MindSpace 成果展示:分页管理、统计聚合、Achievements 面板 UI 与 Portal API。
### 验证摘要
- Git 祖先检查确认分支 HEAD 已进入 `origin/main`
- `node --test mindspace-page-achievement-list.test.mjs mindspace-page-achievement-stats.test.mjs`5/5 通过。
### 最终处置
- 保留本地分支名用于审计追溯。
- **不要** merge、cherry-pick 或从该分支继续开发。
- 后续交付修复见 `fix/achievements-delivery-release`(已进入 `origin/main` @ `53b0d2c`)。
## `feature/wechat-memind-menu`
**状态:禁止再次引用。改动已提交并进入 `origin/main`,该分支保留仅用于只读追溯。**
审计日期:2026-08-13
分支 HEAD`e9ea55c`
`origin/main` 对应提交:`e9ea55c`
### 原始用途
微信公众号自定义菜单文案由 TKMind 更名为 Memind`scripts/wechat-mp-menu.mjs`)。
### 验证摘要
- Git 祖先检查确认分支 HEAD 已进入 `origin/main`
### 最终处置
- 保留本地分支名用于审计追溯。
- **不要** merge、cherry-pick 或从该分支继续开发。
## `fix/runtime-profile-packaging`
**状态:禁止再次引用。改动已提交并进入 `origin/main`,该分支保留仅用于只读追溯。**
审计日期:2026-08-13
分支 HEAD`6542a43`
`origin/main` 对应提交:`6542a43`
### 原始用途
Portal runtime 打包纳入 `memind-runtime-profile`,修复 WeChat 菜单脚本在 105/103 发布链路中缺少 runtime profile 的问题。
### 验证摘要
- Git 祖先检查确认分支 HEAD 已进入 `origin/main`
### 最终处置
- 保留本地分支名用于审计追溯。
- **不要** merge、cherry-pick 或从该分支继续开发。
## `feature/seo-geo-admin-catalog`
**状态:禁止再次引用。改动已提交并进入 `origin/main`,该分支保留仅用于只读追溯。**
审计日期:2026-08-13
分支 HEAD`fdc234e`
`origin/main` 对应提交:`fdc234e`
### 原始用途
管理后台 SEO/GEO 收录目录与爬虫统计(`mindspace-seo-geo-admin-catalog.mjs`);管理 UI 在 memind_adm 5174Memind 侧为共享业务模块与 API。
### 验证摘要
- Git 祖先检查确认分支 HEAD 已进入 `origin/main`
- `node --test mindspace-seo-geo-admin-catalog.test.mjs`3/3 通过。
### 最终处置
- 保留本地分支名用于审计追溯。
- **不要** merge、cherry-pick 或从该分支继续开发。
## `fix/achievements-delivery-release`
**状态:禁止再次引用。改动已提交并进入 `origin/main`,该分支保留仅用于只读追溯。**
审计日期:2026-08-13
分支 HEAD`53b0d2c`
`origin/main` 对应提交:`53b0d2c`
### 原始用途
成果展示交付跟进:Finish 时在 `finally` 释放静态 HTML 交付契约,避免重编辑页面卡在 HTTP 409;成果面板按 Asia/Shanghai 日历日分组,消除重复日期标题。
### 验证摘要
- Git 祖先检查确认分支 HEAD 已进入 `origin/main`
- `node --test mindspace-delivery-contract.test.mjs`3/3 通过。
### 最终处置
- 保留本地分支名用于审计追溯。
- **不要** merge、cherry-pick 或从该分支继续开发。
## `feature/h5-queued-chat-submit`
**状态:禁止再次引用。改动已提交并进入 `origin/main`,该分支保留仅用于只读追溯。**
+4 -4
View File
@@ -205,15 +205,15 @@ Plaza 本地开发说明见 [plaza-local.md](./plaza-local.md)。生产发布、
## MindSpace SEO / GEO(本地联调)
默认关闭,与生产一致。在 **memind_adm**(见上表)打开 **MindSpace 配置**保存 **SEO / GEO** 卡片中的总开关与子开关。
默认开启。在 **memind_adm**(见上表)打开 **MindSpace 配置**可按需关闭 **SEO / GEO** 卡片中的总开关与子开关。
| 开关 | 作用 |
|------|------|
| 总开关 `seoGeo.enabled` | 关闭时交付链与改前一致;开启后按收录策略注入 meta / JSON-LD |
| 总开关 `seoGeo.enabled` | 关闭时注入 meta / JSON-LD;开启后按收录策略注入 |
| `seo.sitemap` / `seo.robots` / `geo.llms` | 控制 `GET /sitemap.xml``/robots.txt``/llms.txt` |
| `seo.baiduPush` | 用户确认公开 MindSpace 页或 Plaza 发帖后推送百度 |
| `seo.baiduPush` | 公开 MindSpace 页或 Plaza 发帖后推送百度 |
可索引页条件:`public` + `online` + `user_confirmed_at` 非空。私有页、未确认公开、embed 预览一律 `noindex`
可索引页条件:`public` + `online` + 未过期。密码、登录可见、owner_only、已过期、embed 预览一律 `noindex`
本地验证:
+4 -4
View File
@@ -4,11 +4,11 @@
## 保护内容
1. **收录策略硬规则**:仅 `access_mode=public``status=online` `user_confirmed_at` 非空的发布页允许 SEO/GEO 注入与 sitemap/llms 收录。
2. **私有页强制 noindex**:密码、登录可见、owner_only、未确认公开、工作区预览直链等必须注入 `noindex, nofollow` 并设置 `X-Robots-Tag`
3. **总开关默认关闭**`mindspace_config.seo_geo_config.enabled=false` 时,交付链行为与未上线前一致
1. **收录策略硬规则**:仅 `access_mode=public``status=online`未过期的发布页允许 SEO/GEO 注入与 sitemap/llms 收录。不再要求 `user_confirmed_at`
2. **私有页强制 noindex**:密码、登录可见、owner_only、已过期、工作区预览直链等必须注入 `noindex, nofollow` 并设置 `X-Robots-Tag`
3. **总开关默认开启**`mindspace_config.seo_geo_config` 缺省为全开;库内已保存的旧值仍以数据库为准,需在 memind_adm MindSpace 配置页保存后才会改写生产
4. **配置来源**memind_adm MindSpace 配置页 → `PATCH /admin-api/mindspace/config` → Portal `loadMindSpaceConfigCached()`
5. **百度推送**:仅在 admin 开启 `seo.baiduPush` 且页面可索引时触发;MindSpace 确认公开与 Plaza 发帖共用该开关。
5. **百度推送**:仅在 admin 开启 `seo.baiduPush` 且页面可索引时触发;公开页发布与 Plaza 发帖共用该开关。
## 必跑验证
+8 -8
View File
@@ -59,18 +59,18 @@ function parseJsonObject(value, fallback = {}) {
export function defaultSeoGeoConfig() {
return {
enabled: false,
enabled: true,
seo: {
enabled: false,
enabled: true,
canonical: true,
sitemap: false,
robotsTxt: false,
baiduPush: false,
sitemap: true,
robotsTxt: true,
baiduPush: true,
},
geo: {
enabled: false,
jsonLd: false,
llmsTxt: false,
enabled: true,
jsonLd: true,
llmsTxt: true,
},
};
}
+38 -1
View File
@@ -9,6 +9,18 @@ import {
updateMindSpaceConfig,
} from './mindspace-config.mjs';
test('defaultSeoGeoConfig enables all discovery switches', () => {
const config = defaultSeoGeoConfig();
assert.equal(config.enabled, true);
assert.equal(config.seo.enabled, true);
assert.equal(config.seo.sitemap, true);
assert.equal(config.seo.robotsTxt, true);
assert.equal(config.seo.baiduPush, true);
assert.equal(config.geo.enabled, true);
assert.equal(config.geo.jsonLd, true);
assert.equal(config.geo.llmsTxt, true);
});
test('defaultMindSpaceConfig falls back to env or 5', () => {
assert.equal(defaultMindSpaceConfig({ MINDSPACE_FREE_PUBLIC_PAGE_LIMIT: '8' }).publicPageLimit, 8);
assert.equal(defaultMindSpaceConfig({ MINDSPACE_FREE_PUBLIC_PAGE_LIMIT: '0' }).publicPageLimit, 5);
@@ -24,7 +36,7 @@ test('normalizeSeoGeoConfig merges nested switches', () => {
assert.equal(config.enabled, true);
assert.equal(config.seo.sitemap, true);
assert.equal(config.geo.llmsTxt, true);
assert.equal(config.seo.baiduPush, false);
assert.equal(config.seo.baiduPush, true);
});
test('ensureMindSpaceConfig seeds the default row only when empty', async () => {
@@ -45,6 +57,31 @@ test('ensureMindSpaceConfig seeds the default row only when empty', async () =>
assert.deepEqual(calls[2].params.slice(0, 3), ['public_page_limit', '9', '公开页面数量上限']);
});
test('loadMindSpaceConfig keeps stored all-off seoGeo instead of new defaults', async () => {
const pool = {
async query(sql) {
if (sql.includes('FROM mindspace_config')) {
return [[
{
key: 'seo_geo_config',
value: JSON.stringify({
enabled: false,
seo: { enabled: false, canonical: true, sitemap: false, robotsTxt: false, baiduPush: false },
geo: { enabled: false, jsonLd: false, llmsTxt: false },
}),
},
]];
}
return [[]];
},
};
const config = await loadMindSpaceConfig(pool);
assert.equal(config.seoGeo.enabled, false);
assert.equal(config.seoGeo.seo.sitemap, false);
assert.equal(config.seoGeo.geo.jsonLd, false);
});
test('loadMindSpaceConfig merges stored config with fallback', async () => {
const pool = {
async query(sql) {
+1 -2
View File
@@ -40,7 +40,6 @@ export function isPublicationIndexable(publication, { now = Date.now() } = {}) {
if (!snapshot) return false;
if (snapshot.status !== 'online') return false;
if (snapshot.accessMode !== INDEXABLE_ACCESS_MODE) return false;
if (snapshot.userConfirmedAt == null) return false;
if (snapshot.expiresAt != null && snapshot.expiresAt <= now) return false;
return true;
}
@@ -74,7 +73,7 @@ export function resolveIndexPolicy({
return {
mode: 'indexable',
injectNoindex: false,
reason: 'public_confirmed',
reason: 'public_online',
seoEnabled: Boolean(seoGeoConfig.seo?.enabled),
geoEnabled: Boolean(seoGeoConfig.geo?.enabled),
canonicalEnabled: seoGeoConfig.seo?.canonical !== false,
+8 -1
View File
@@ -17,6 +17,13 @@ test('isPublicationIndexable accepts confirmed public online publications', () =
assert.equal(isPublicationIndexable(indexablePublication), true);
});
test('isPublicationIndexable accepts public online pages without confirmation', () => {
assert.equal(
isPublicationIndexable({ ...indexablePublication, userConfirmedAt: null }),
true,
);
});
test('isPublicationIndexable rejects private access modes', () => {
for (const accessMode of ['password', 'private_link', 'login_required', 'owner_only']) {
assert.equal(
@@ -44,7 +51,7 @@ test('resolveIndexPolicy noindexes non-public publications when enabled', () =>
assert.equal(policy.injectNoindex, true);
});
test('resolveIndexPolicy marks confirmed public pages indexable', () => {
test('resolveIndexPolicy marks public online pages indexable', () => {
const policy = resolveIndexPolicy({
seoGeoConfig: {
enabled: true,
+1 -2
View File
@@ -32,7 +32,6 @@ export async function listIndexablePublications(
LEFT JOIN h5_page_records p ON p.id = pr.page_id
WHERE pr.status = 'online'
AND pr.access_mode = 'public'
AND pr.user_confirmed_at IS NOT NULL
AND (pr.expires_at IS NULL OR pr.expires_at > ?)
ORDER BY pr.published_at DESC
LIMIT ? OFFSET ?`,
@@ -153,7 +152,7 @@ export function renderRobotsTxt({
export function renderLlmsTxt(entries, { origin = '' } = {}) {
const header = [
'# TKMind MindSpace public pages',
'# Only confirmed public publications are listed here.',
'# Public online publications are listed here.',
'',
];
const body = entries
+1 -1
View File
@@ -108,6 +108,6 @@ test('listAdminSeoGeoPublicationCatalog merges zero-traffic pages with view stat
assert.equal(result.data[0].indexable, true);
assert.equal(result.data[1].publicationId, 'pub-2');
assert.equal(result.data[1].totalViews, 0);
assert.equal(result.data[1].indexable, false);
assert.equal(result.data[1].indexable, true);
assert.match(result.data[1].publicUrl, /^https:\/\/m\.tkmind\.cn\//);
});
+2 -2
View File
@@ -24,7 +24,7 @@ test('decorateMindSpaceSeoGeoHtml noindexes private publications', () => {
assert.match(result.html, /noindex, nofollow/);
});
test('decorateMindSpaceSeoGeoHtml injects seo and geo for public pages', () => {
test('decorateMindSpaceSeoGeoHtml injects seo and geo for unconfirmed public pages', () => {
const result = decorateMindSpaceSeoGeoHtml(
'<html><head><title>Demo</title><meta name="description" content="摘要"></head><body></body></html>',
{
@@ -32,7 +32,7 @@ test('decorateMindSpaceSeoGeoHtml injects seo and geo for public pages', () => {
publication: {
status: 'online',
accessMode: 'public',
userConfirmedAt: Date.now(),
userConfirmedAt: null,
publicUrl: '/u/john/pages/demo',
},
context: {
+1 -1
View File
@@ -146,7 +146,7 @@ npm run check:mindspace-cover
## SEO / GEO 元数据(推荐)
平台在 memind_adm **MindSpace 配置 → SEO / GEO** 开启后,会对**已确认公开**`access_mode=public`用户已确认)的发布页自动注入 canonical、结构化数据等。Agent 仍应写好基础 meta,以提升搜索与 AI 引用质量:
平台在 memind_adm **MindSpace 配置 → SEO / GEO** 开启后,会对**公开在线**`access_mode=public` `status=online`)的发布页自动注入 canonical、结构化数据等。Agent 仍应写好基础 meta,以提升搜索与 AI 引用质量:
```html
<title>页面真实主题</title>
+1 -1
View File
@@ -521,6 +521,7 @@ export function ChatPanel({
: chatState === 'waiting'
? '请求已提交…'
: null;
const canSubmit = input.trim() || pendingImages.length > 0 || pendingFiles.length > 0;
const sendButtonLabel =
uploadingImage || uploadingFile
? '上传中…'
@@ -579,7 +580,6 @@ export function ChatPanel({
};
const voiceDisabled = inputBlocked || uploadingImage || uploadingFile;
const canSubmit = input.trim() || pendingImages.length > 0 || pendingFiles.length > 0;
const uploadDisabled = !canUpload || busy || uploadingImage || uploadingFile || offlineBlocked || !!pendingTool;
const revokePendingImage = (item: PendingChatImage) => {