Compare commits

...

63 Commits

Author SHA1 Message Date
john 08ea5e05a9 fix(ops): restore stop_docker_pool_keep_standby function in migrate script
Memind CI / Test, build, and release guards (pull_request) Waiting to run
Repair a bash syntax error introduced when adding monitor and DeepSeek proxy phases.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-30 20:38:50 +08:00
john 0dfb2e2b82 docs(ops): record 103 goosed native migration and add migrate tooling
Memind CI / Test, build, and release guards (pull_request) Has been cancelled
Document the completed Docker-to-native goosed pool cutover on 103, add the production migrate script with backup/rollback paths, and extend local native pool soak/metrics helpers used as migration gates.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-30 20:24:22 +08:00
tkmind b549a390fe Merge pull request 'feat(mindspace): add page quota grace write and local delivery guardrails' (#41) from codex/mindspace-page-quota-delivery into main
Memind CI / Test, build, and release guards (push) Waiting to run
2026-07-30 02:56:45 +00:00
tkmind 418406517f Merge pull request 'fix(release-gate): isolate PAGE agent runs from split-service MCP routing' (#40) from codex/release-gate-page-agent-isolation into main
Memind CI / Test, build, and release guards (push) Has been cancelled
2026-07-30 02:56:28 +00:00
tkmind 2d52c9d0bb Merge pull request 'fix: route stable production goosed through DeepSeek no-think proxy' (#39) from codex/deepseek-stable-prod-proxy into main
Memind CI / Test, build, and release guards (push) Has been cancelled
2026-07-30 02:56:18 +00:00
tkmind 4ee70d3f6d Merge pull request 'feat(search): enable MindSearch defaults and sanitize user-facing search language' (#38) from codex/mindsearch-defaults-display into main
Memind CI / Test, build, and release guards (push) Has been cancelled
2026-07-30 02:55:58 +00:00
tkmind 1bd8392940 Merge pull request 'fix(wechat): page.generate fail-closed and vision image_url scrub' (#37) from codex/wechat-page-generate-vision-handoff into main
Memind CI / Test, build, and release guards (push) Has been cancelled
2026-07-30 02:55:47 +00:00
john 371900bae5 feat(mindspace): add page quota grace write and local delivery guardrails.
Memind CI / Test, build, and release guards (pull_request) Waiting to run
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
john d67ebb0c77 fix(release-gate): isolate PAGE agent runs from split-service MCP routing.
Memind CI / Test, build, and release guards (pull_request) Waiting to run
Gate stacks inherited developer MCP env and routed sandbox-fs writes to the
8082 workspace root, so PAGE/DATA scenarios failed with ENOENT despite a
healthy goosed. Sanitize isolated portal env, grant gate skills, and align
CHAT search assertions with the tkmind_search tool name.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-30 10:43:28 +08:00
john df5bcd87b5 fix: route stable production goosed through DeepSeek no-think proxy
Memind CI / Test, build, and release guards (pull_request) Waiting to run
DeepSeek V4 tool rounds fail with reasoning_content errors when stable
goosed hits api.deepseek.com directly. Default stable portal startup to
the 18036 compat proxy and add a standalone recovery script for 103 ops.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-30 10:43:26 +08:00
john 74150a30e3 feat(search): enable MindSearch defaults and sanitize user-facing search language.
Memind CI / Test, build, and release guards (pull_request) Waiting to run
Grant search_external and network egress for default users, enable search-enhanced skill, redact internal architecture terms from assistant UI text, and neutralize search skill prompts so chats use dual-engine web search without exposing vendor names.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-30 10:43:25 +08:00
john 53c5f6d9c2 fix(wechat): scrub image_url from sessions and harden vision handoff.
Memind CI / Test, build, and release guards (pull_request) Waiting to run
Strip image_url from persisted session payloads, keep image turns scoped to the active request, and align proxy/vision/page-data paths so WeChat image history does not leak into fresh sessions.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-30 10:43:24 +08:00
john 2f51041822 fix: fail closed when WeChat page.generate has no HTML artifact
Prevent text-only planning replies from marking WeChat delivery done when
static-page-publish never confirmed a public HTML file.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-30 10:43:24 +08:00
john b553107817 fix: make goosed remount health ignore canary
Memind CI / Test, build, and release guards (push) Failing after 1m25s
Stable remount checks required exactly 9 compose containers, but the
shared goosed-prod project also runs canary. Count only goosed-1..9 and
verify the /opt/portal bind plus a root marker under the live runtime.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-28 18:34:28 +08:00
john 065b1588b7 fix: deliver WeChat pages when image_make fails
Fall back to workspace share thumbnails so already-published HTML can be
sent instead of blocking on REQUIRED_FRESH. Also allow an explicit
ALLOW_DIRECT_STABLE_RELEASE path matching the pre-2026-07-23 direct
stable packaging flow.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-28 18:19:29 +08:00
john 7eedda092d fix: broaden WeChat session page continuation phrasing
Memind CI / Test, build, and release guards (push) Successful in 2m37s
Recognize retry, HTML page, page edits, and poem content edits as session continuations so delivery skips fresh-thumbnail forcing and retains agent context for multi-turn page work.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-28 16:54:42 +08:00
john 6307f974cf fix: treat decorated HTML page requests as WeChat immediate context
Memind CI / Test, build, and release guards (push) Successful in 4m32s
Match phrases like「生成一个精美的 HTML 页面」so poem follow-ups skip fresh-thumbnail forcing and reuse the same session poem context.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-28 16:27:28 +08:00
john 491a62aed6 fix: require write_file for WeChat immediate-context page requests
Memind CI / Test, build, and release guards (push) Failing after 12m9s
Skip fresh thumbnail forcing and retry in-session when load_skill completes without HTML落盘, so「把刚才的诗做成页面」can deliver the prior poem as a static page.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-28 15:27:23 +08:00
john e1ad094f93 fix: retain unlinked WeChat route for immediate-context page requests
Memind CI / Test, build, and release guards (push) Successful in 20m49s
Short follow-ups like "把刚才的诗做成页面" must keep the existing agent
session so Goose still sees the adjacent poem in snapshot history. Rotating
orphan routes before these requests dropped the poem and revived stale memory.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-28 14:38:53 +08:00
john b10f0a7600 merge: guard WeChat page follow-up context
Memind CI / Test, build, and release guards (push) Successful in 4m46s
2026-07-28 11:54:02 +08:00
john 31cfdf7a94 fix: guard WeChat page follow-up context 2026-07-28 11:53:00 +08:00
john 9b68dc6b77 merge: fix WeChat runtime delivery path
Memind CI / Test, build, and release guards (push) Successful in 1m59s
2026-07-28 09:50:38 +08:00
john 7f9453433c fix: isolate WeChat runtime delivery path 2026-07-28 09:33:25 +08:00
john 5a13950bc3 docs: close release gate policy branch
Memind CI / Test, build, and release guards (push) Successful in 1m36s
2026-07-28 01:17:40 +08:00
john 7a381db132 refactor: make production gate impact only
Memind CI / Test, build, and release guards (push) Has been cancelled
2026-07-28 01:13:28 +08:00
john 864197162d docs: close WeChat Page Data Aider path branch
Memind CI / Test, build, and release guards (push) Successful in 2m42s
2026-07-28 01:02:58 +08:00
john 8cd85a9b11 fix: pin Aider binary for portal canary
Memind CI / Test, build, and release guards (push) Has been cancelled
2026-07-28 01:01:03 +08:00
john 23dedf1264 docs: close WeChat Page Data canary branch
Memind CI / Test, build, and release guards (push) Successful in 1m50s
2026-07-28 00:39:21 +08:00
john 1e91195853 fix: enable WeChat Page Data review in canary
Memind CI / Test, build, and release guards (push) Has been cancelled
2026-07-28 00:38:18 +08:00
john 6070ec209a docs: close WeChat Page Data guard branch
Memind CI / Test, build, and release guards (push) Successful in 1m48s
2026-07-28 00:35:53 +08:00
john 8bfb7959fc fix: guard WeChat Page Data delivery with Aider review
Memind CI / Test, build, and release guards (push) Has been cancelled
2026-07-28 00:26:24 +08:00
john bc20e93893 docs: close retained local branches 2026-07-28 00:10:57 +08:00
john 0630157068 docs: register portal canary backup branch
Memind CI / Test, build, and release guards (push) Successful in 1m42s
2026-07-27 23:42:18 +08:00
john 7ba63a83b1 fix: split portal canary backup preflight
Memind CI / Test, build, and release guards (push) Successful in 1m40s
2026-07-27 23:22:05 +08:00
john 3de116d3e7 fix: hide platform actions from long image exports
Memind CI / Test, build, and release guards (push) Successful in 1m26s
2026-07-27 22:42:42 +08:00
john aff6ddf9d3 docs: keep completed branches as registered history 2026-07-27 22:42:42 +08:00
john a95681ea3a fix: treat pending Gitea CI as acceptable on synced main
Memind CI / Test, build, and release guards (push) Successful in 1m39s
Normal Portal releases should not block when commit status stays pending
after the candidate is already pushed to origin/main.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-27 22:36:40 +08:00
john 82b74d71ad fix: relax REL release gate checks for normal Portal publishes
Memind CI / Test, build, and release guards (push) Failing after 1m35s
Ignore local .runtime build output in REL-01, accept missing Gitea CI when
origin/main matches HEAD, and compare two clean runtime rebuilds for REL-03.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-27 22:26:01 +08:00
john 90ee4c38d5 fix: honor stored Rybbit analytics secret fallback
Memind CI / Test, build, and release guards (push) Successful in 1m37s
2026-07-27 22:14:06 +08:00
john c7718c683b fix: inject Rybbit tracking on m.tkmind.cn publication pages
Memind CI / Test, build, and release guards (push) Successful in 1m36s
Publication routes and share shells were missing the Rybbit tracker, and
bootstrap now reuses the stored MindSpace analytics secret when Rybbit env
secret is absent.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-27 22:10:07 +08:00
john 48c61a3279 fix: harden long conversation continuity
Memind CI / Test, build, and release guards (push) Successful in 1m28s
2026-07-27 21:39:34 +08:00
john 5ae166fc44 fix: exclude page actions from long images 2026-07-27 21:21:54 +08:00
john d1be5d9e2a fix: preserve replies after session recovery 2026-07-27 19:59:09 +08:00
john 17c59fde71 fix: isolate visual tool failures from agent runs 2026-07-27 19:20:23 +08:00
john 7002007128 feat: add public page view action 2026-07-27 18:35:29 +08:00
john 77be18d340 Merge branch 'codex/active-development-20260727'
Memind CI / Test, build, and release guards (push) Successful in 1m48s
2026-07-27 18:07:48 +08:00
john 6878827224 mindspace: decouple page list sync latency 2026-07-27 18:07:16 +08:00
john 40dcdaf49f mindspace: guard split-service contract drift 2026-07-27 17:48:34 +08:00
john 70264a4c4c mindspace: version runtime contracts and audit packages 2026-07-27 17:43:38 +08:00
john e94052ff24 mindspace: close authority boundaries 2026-07-27 15:34:35 +08:00
john dfab78c75a feat(workflow): add risk-based release gates 2026-07-27 10:38:24 +08:00
john c88623855f fix(dev): stop stale frontend when portal exits 2026-07-27 10:28:12 +08:00
john 6b479c16f9 test: make thumbnail refresh assertion portable
Memind CI / Test, build, and release guards (push) Successful in 1m50s
2026-07-27 09:43:43 +08:00
john 291178d2c3 chore: stop tracking generated runtime artifacts
Memind CI / Test, build, and release guards (push) Failing after 9m20s
2026-07-27 09:08:55 +08:00
john 56ff170a60 fix(chat): recover session list from snapshots 2026-07-27 09:08:55 +08:00
john 32da0c0583 fix(api): retry transient session reads safely 2026-07-27 09:08:54 +08:00
john 0708d56d61 docs: record obsolete branch disposition 2026-07-27 09:08:54 +08:00
john 5ca6ed9094 fix(release): allow stable fallback for release-only artifact promotions
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-27 08:43:00 +08:00
john cb38225704 fix(release): correct ahead/behind parsing for REL-02
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-27 08:40:15 +08:00
john 6e25008df2 fix(release): allow REL-02 when main is ahead with successful CI evidence
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-27 08:38:09 +08:00
john a6a9bb4eab feat(release): add incremental gate and auto CI status for 103 publish
When the runtime artifact is unchanged, carry forward prior gate results and
re-run only REL scenarios; resolve REL-02 from Gitea commit status and allow
stable promotion when canary matches the same artifact bundle.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-27 08:32:12 +08:00
tkmind 9e2aa4f71d Merge pull request 'fix(release): harden stable promotion after canary acceptance' (#35) from codex/fix-stable-promotion-release into main
Memind CI / Test, build, and release guards (push) Successful in 2m5s
2026-07-27 00:04:28 +00:00
john 41bf775c4c fix(release): harden stable promotion after canary acceptance
Memind CI / Test, build, and release guards (pull_request) Successful in 13m26s
Verify matching 103 canary evidence before 8081 promotion, fix agent-run
drain JSON parsing, tolerate macOS full-backup tar races, and extend goosed
remount health waits for non-interactive SSH releases.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-27 07:09:27 +08:00
398 changed files with 23913 additions and 61970 deletions
+31
View File
@@ -6,6 +6,12 @@
#
# 后端代理(server.mjs / vite dev proxy 共用)
TKMIND_API_TARGET=https://127.0.0.1:18006
# Native dual pool (local simulation): bash scripts/install-local-goosed-pool.sh
# TKMIND_API_TARGET_1=https://127.0.0.1:18007
# TKMIND_API_TARGETS=https://127.0.0.1:18006,https://127.0.0.1:18007
# GOOSED_NATIVE_POOL_PORTS=18006,18007
# GOOSED_RUNTIME=native # runtime-worker-metrics.mjs; auto detects when no docker pool
# Soak: node scripts/soak-local-goosed-pool.mjs --minutes 30 --interval 60
TKMIND_SERVER__SECRET_KEY=local-dev-secret
H5_PORT=8081
# Vite UI 预览用 5173Agent 交付的 MindSpace 公开页 + Page Data API 走 Portal8081)。
@@ -65,6 +71,22 @@ H5_PUBLIC_BASE_URL=http://127.0.0.1:5173
# MEMIND_AGENT_RUN_WORKER_ID=<optional-instance-id>
# MEMIND_AGENT_RUN_WORKER_EXPECT_RUNNING=0
# 本机 Aider / OpenHandsColima 迁出后走 native CLI,见 memind-local-aider / memind-local-openhands
# AIDER_BIN=/Users/john/.local/bin/aider
# GOOSE_AIDER_BIN=/Users/john/.openhands/bin/studio-aider
# OPENHANDS_BIN=/Users/john/.local/bin/openhands
# GOOSE_AIDER_BIN=/Users/john/Project/Memind/scripts/aider-goose-wrapper.sh
# GOOSE_OPENHANDS_BIN=/Users/john/Project/Memind/scripts/openhands-goose-wrapper.sh
# GOOSE_OPENHANDS_RUNNER=host
# 本地开发:普通用户 role=user 默认开启 aider/openhands;生产保持 0
# MEMIND_ROLE_CODE_TOOLS_DEFAULT=0
# MEMIND_TOOL_GATEWAY_ENABLED=0
# MEMIND_TOOL_GATEWAY_DRY_RUN=0
# MEMIND_TOOL_GATEWAY_DEFAULT_EXECUTOR=aider
# Orchestrator workermemind-local-orchestrator/.env 同步)OpenHands 进程执行器:
# MEMIND_EXECUTOR_OPENHANDS_COMMAND=/Users/john/Project/Memind/scripts/openhands-goose-wrapper.sh
# MEMIND_EXECUTOR_WORKSPACE_ROOT=/Users/john/Project/Memind
# LangGraph Orchestrator Shadow(默认完全不接入 Agent Run 请求路径)。
# 仅在独立 Orchestrator 已健康、service token 已配置后,先显式打开观察 wiring,
# 再通过 memindadm 选择 shadow。关闭 wiring 时不查询编排配置、不排队、不传用户数据。
@@ -319,6 +341,11 @@ VITE_TKMIND_WORKING_DIR=/Users/john/PycharmProjects/tkmind
# IMAGE_MAKE_SEMANTIC_REVIEW_MAX_ATTEMPTS=3
# H5_WECHAT_MP_FRESH_PAGE_THUMBNAILS=1
# H5_WECHAT_MP_FRESH_THUMBNAIL_REPAIR=0
# 微信 Page Data 独立 Aider 审核;默认关闭,灰度名单支持 userId/username/slug/displayName/nickname 或 *。
# 该链路不创建 h5_agent_runs,审核收据写入用户工作区 .memind/page-data-reviews/。
# H5_WECHAT_MP_PAGE_DATA_AIDER_REVIEW_ENABLED=0
# H5_WECHAT_MP_PAGE_DATA_AIDER_REVIEW_USERS=
# H5_WECHAT_MP_PAGE_DATA_AIDER_REVIEW_TIMEOUT_MS=900000
# Sandbox MCP 调用 Portal 内部生图入口;容器内通常使用 host.docker.internal。
# MINDSPACE_AGENT_API_BASE_URL=http://127.0.0.1:8081/api
@@ -326,6 +353,10 @@ VITE_TKMIND_WORKING_DIR=/Users/john/PycharmProjects/tkmind
# MindSearch / standalone Deep Search
# ---------------------------------------------------------------------------
# Production SearXNG on 103 is bound to the host at 127.0.0.1:20080.
# Outbound proxy for DuckDuckGo/Yandex lives in searxng-runtime shared config:
# /Users/john/Project/searxng-runtime/shared/.env
# SEARXNG_OUTGOING_PROXY=http://127.0.0.1:1082 (Shadowrocket MacPacket)
# Template source: /Users/john/Project/searxng-prod/settings.yml.template
# Goosed MCP endpoints are automatically rewritten to host.docker.internal.
# TKMIND_SEARCH_SEARXNG_URL=http://127.0.0.1:20080/search
# TKMIND_SEARCH_MCP_HOST_GATEWAY=host.docker.internal
-63
View File
@@ -1,63 +0,0 @@
Portal runtime artifact
This directory is meant to run on production without the source tree.
Required persisted items to inherit on the host:
.env
MindSpace/
data/
users/
.tailscale/
public/plaza-covers/
logs/
Bundled alongside server.mjs (required for sandbox-fs MCP):
mindspace-sandbox-mcp.mjs (esbuild bundle; includes schedule-service deps)
Post-deploy validation (scripts/check-mindspace-public-links.mjs):
Scans MindSpace/*/public/*.html for missing relative href/src/cover targets.
Runtime install mode: bundle-node-modules
Bundle target: node24
Platform agent skills (synced into user workspaces):
skills/
Key runtime differences must stay in .env, not in the artifact:
DATABASE_URL / MYSQL_*
H5_PUBLIC_BASE_URL
TKMIND_API_TARGETS / TKMIND_API_TARGET / TKMIND_API_TARGET_1
H5_USERS_ROOT / MINDSPACE_STORAGE_ROOT / MEMIND_SHARED_PUBLISH_ROOT
Deployment and operations transport:
H5 public domain: mm.tkmind.cn
Current public path: mm.tkmind.cn -> local nginx -> Portal :8081
Legacy rollback-only path: m.tkmind.cn -> 105 nginx -> reverse SSH tunnel -> Portal :8081
Future H5 traffic must not depend on 105 forwarding unless explicitly rolling back.
Streaming runtime operations:
node scripts/check-stream-runtime.mjs
node scripts/runtime-worker-drain.mjs status
node scripts/runtime-worker-drain.mjs reconcile
node scripts/runtime-worker-drain.mjs reconcile --apply
node scripts/runtime-worker-metrics.mjs status
node scripts/runtime-worker-metrics.mjs sample
bash scripts/install-runtime-metrics-agent.sh
node scripts/runtime-worker-heartbeat.mjs once
node scripts/runtime-worker-heartbeat.mjs serve
bash scripts/install-runtime-heartbeat-agent.sh
node scripts/runtime-slo-report.mjs
node scripts/runtime-slo-report.mjs --write-report
node scripts/runtime-slo-report.mjs --write-report --prune --retention-days 30
bash scripts/install-runtime-slo-report-agent.sh
bash scripts/install-runtime-slo-soak-agent.sh
node scripts/runtime-worker-drain.mjs drain goosed-3
node scripts/runtime-worker-drain.mjs undrain goosed-3
node scripts/check-tool-runtime.mjs
node scripts/check-agent-code-run-entry.mjs
curl -sk https://mm.tkmind.cn/api/runtime/status # includes toolRuntime.queue
node scripts/agent-run-worker.mjs --status
node scripts/agent-run-worker.mjs --once
bash scripts/install-agent-run-worker-agent.sh # installs disabled by default
node scripts/check-agent-run-worker.mjs # read-only LaunchAgent/queue check
node scripts/agent-run-guard.mjs # dry-run auto-pause guard check
node scripts/agent-run-guard.mjs --apply # stop worker and disable code-run gate when thresholds trip
bash scripts/install-agent-run-guard-agent.sh # installs guard LaunchAgent
-151
View File
@@ -1,151 +0,0 @@
import fs from 'node:fs';
import path from 'node:path';
const SKIP_REFERENCE = /^(?:https?:|mailto:|javascript:|data:|tel:|#|\/\/)/i;
const DOWNLOAD_EXTENSIONS = /\.(?:docx|doc|pdf|xlsx|xls|pptx|ppt|zip|rar|7z|csv)$/i;
export function isFilesystemPublicReference(raw) {
const value = String(raw ?? '').trim();
if (!value) return false;
if (SKIP_REFERENCE.test(value)) return false;
// /api/... and /MindSpace/... are server routes, not on-disk siblings of public HTML.
if (value.startsWith('/')) return false;
return true;
}
/** @deprecated use isFilesystemPublicReference */
export function isRelativePublicReference(raw) {
return isFilesystemPublicReference(raw);
}
export function isDownloadLikeReference(raw, source = '') {
if (!isFilesystemPublicReference(raw)) return false;
const ref = normalizeReferencePath(raw);
if (DOWNLOAD_EXTENSIONS.test(ref)) return true;
return /\bdownload\b/i.test(String(source ?? ''));
}
export function normalizeReferencePath(raw) {
const trimmed = String(raw ?? '')
.trim()
.split('#')[0]
.split('?')[0]
.trim();
if (!trimmed) return '';
try {
return decodeURIComponent(trimmed);
} catch {
return trimmed;
}
}
export function resolveReferencePath(htmlDir, raw) {
const ref = normalizeReferencePath(raw);
if (!ref) return null;
return path.resolve(htmlDir, ref);
}
function parseMindspaceCoverPaths(contentAttr) {
const paths = [];
if (!contentAttr) return paths;
let parsed = null;
try {
parsed = JSON.parse(contentAttr);
} catch {
for (const key of ['cover', 'image']) {
const match = contentAttr.match(new RegExp(`"${key}"\\s*:\\s*"([^"]+)"`));
if (match?.[1]) paths.push(match[1]);
}
return paths;
}
for (const key of ['cover', 'image']) {
if (parsed?.[key]) paths.push(String(parsed[key]));
}
return paths;
}
export function collectPublicHtmlReferences(html, htmlDir, { downloadsOnly = false } = {}) {
const refs = [];
const seen = new Set();
const add = (raw, source) => {
if (downloadsOnly && !isDownloadLikeReference(raw, source)) return;
if (!downloadsOnly && !isFilesystemPublicReference(raw)) return;
const ref = normalizeReferencePath(raw);
if (!ref) return;
const key = `${source}\0${ref}`;
if (seen.has(key)) return;
seen.add(key);
refs.push({
ref,
source,
resolvedPath: resolveReferencePath(htmlDir, ref),
});
};
for (const match of String(html ?? '').matchAll(/\b(?:href|src)\s*=\s*["']([^"']+)["']/gi)) {
add(match[1], match[0]);
}
if (!downloadsOnly) {
for (const match of String(html ?? '').matchAll(
/<meta\b[^>]*\bname\s*=\s*["']mindspace-cover["'][^>]*>/gi,
)) {
const tag = match[0];
const contentMatch =
tag.match(/\bcontent\s*=\s*"([^"]*)"/i) ?? tag.match(/\bcontent\s*=\s*'([^']*)'/i);
for (const coverPath of parseMindspaceCoverPaths(contentMatch?.[1])) {
add(coverPath, 'mindspace-cover');
}
}
}
return refs;
}
export function findMissingPublicHtmlReferences(htmlPath, html = null, options = {}) {
const resolvedHtmlPath = path.resolve(htmlPath);
const content = html ?? fs.readFileSync(resolvedHtmlPath, 'utf8');
const htmlDir = path.dirname(resolvedHtmlPath);
const missing = [];
const seen = new Set();
for (const item of collectPublicHtmlReferences(content, htmlDir, options)) {
const key = item.ref;
if (seen.has(key)) continue;
seen.add(key);
if (!item.resolvedPath || !fs.existsSync(item.resolvedPath)) {
missing.push({ htmlPath: resolvedHtmlPath, ...item });
}
}
return missing;
}
export function listPublicHtmlFiles(publishRoot, { userId = null } = {}) {
const root = path.resolve(publishRoot);
if (!fs.existsSync(root)) return [];
const userDirs = userId
? [path.join(root, userId)]
: fs.readdirSync(root, { withFileTypes: true }).filter((d) => d.isDirectory()).map((d) => path.join(root, d.name));
const files = [];
for (const userDir of userDirs) {
const publicDir = path.join(userDir, 'public');
if (!fs.existsSync(publicDir) || !fs.statSync(publicDir).isDirectory()) continue;
for (const name of fs.readdirSync(publicDir)) {
if (!name.toLowerCase().endsWith('.html')) continue;
files.push(path.join(publicDir, name));
}
}
return files.sort();
}
export function scanPublicHtmlLinks(publishRoot, options = {}) {
const issues = [];
for (const htmlPath of listPublicHtmlFiles(publishRoot, options)) {
issues.push(...findMissingPublicHtmlReferences(htmlPath, null, options));
}
return issues;
}
File diff suppressed because it is too large Load Diff
-22
View File
@@ -1,22 +0,0 @@
{
"name": "tkmind-h5-portal-runtime",
"private": true,
"type": "module",
"engines": {
"node": ">=22"
},
"dependencies": {
"@node-rs/argon2": "^2.0.2",
"@resvg/resvg-js": "^2.6.2",
"debug": "^4.4.3",
"express": "^4.21.2",
"http-proxy-middleware": "^3.0.3",
"jsonrepair": "^3.14.0",
"mysql2": "^3.22.5",
"playwright": "^1.61.1",
"qrcode": "^1.5.4",
"redis": "^4.7.1",
"sharp": "^0.35.2",
"undici": "^6.26.0"
}
}
@@ -1 +0,0 @@
nXjhx0ErC6MhQ0SZ
Binary file not shown.

Before

Width:  |  Height:  |  Size: 61 KiB

@@ -1,50 +0,0 @@
<!doctype html>
<html lang="zh-CN">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>微信分享预览 · 连云港三天两夜攻略 🌊</title>
<style>
* { box-sizing: border-box; }
body { margin: 0; min-height: 100vh; padding: 24px; background: #ececec; font-family: ui-sans-serif, -apple-system, BlinkMacSystemFont, sans-serif; color: #111; }
.wrap { max-width: 420px; margin: 0 auto; }
h1 { margin: 0 0 8px; font-size: 18px; }
.hint, .note { margin: 0 0 16px; color: #666; font-size: 13px; line-height: 1.6; }
.card { display: grid; grid-template-columns: 1fr 72px; gap: 12px; padding: 12px; border-radius: 8px; background: #fff; box-shadow: 0 1px 0 rgba(0,0,0,.06); }
.card-main { min-width: 0; }
.card-title { margin: 0; font-size: 16px; font-weight: 600; line-height: 1.35; display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden; }
.card-desc { margin: 6px 0 0; color: #888; font-size: 13px; line-height: 1.45; display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden; }
.card-foot { display: flex; align-items: center; gap: 6px; margin-top: 10px; color: #999; font-size: 12px; }
.card-foot img { width: 16px; height: 16px; border-radius: 999px; object-fit: cover; }
.card-thumb { width: 72px; height: 72px; border-radius: 4px; object-fit: cover; background: #f3f3f3; }
.card-thumb-empty { display: grid; place-items: center; color: #bbb; font-size: 12px; }
.meta { margin-top: 18px; padding: 14px; border-radius: 10px; background: rgba(255,255,255,.72); font-size: 12px; line-height: 1.7; word-break: break-all; }
code { background: rgba(0,0,0,.06); padding: 1px 4px; border-radius: 4px; }
</style>
</head>
<body>
<div class="wrap">
<h1>微信链接卡片预览</h1>
<p class="hint">本地模拟的是「粘贴链接后看到的卡片」,不是 JS-SDK 内部分享弹层。源链接:<code>https://m.tkmind.cn/MindSpace/john/public/lianyungang-guide.html</code></p>
<p class="note">修复后:标题 + 描述 + 缩略图 + 底部「TKMind 智趣」小图标应同时出现。</p>
<article class="card" aria-label="微信分享卡片预览">
<div class="card-main">
<h2 class="card-title">连云港三天两夜攻略 🌊</h2>
<p class="card-desc">三天两夜·沙滩酒店·必吃美食</p>
<div class="card-foot">
<img src="http://127.0.0.1:8081/brand/tkmind-icon.png" alt="" onerror="this.style.display='none'">
<span>TKMind 智趣</span>
</div>
</div>
<img class="card-thumb" src="https://images.unsplash.com/photo-1507525428034-b723cf961d3e?auto=format&amp;fit=crop&amp;w=200&amp;q=80" alt="">
</article>
<div class="meta">
<div><strong>og:title</strong> 连云港三天两夜攻略 🌊</div>
<div><strong>og:description</strong> 三天两夜·沙滩酒店·必吃美食</div>
<div><strong>og:site_name</strong> TKMind 智趣</div>
<div><strong>og:image</strong> https://images.unsplash.com/photo-1507525428034-b723cf961d3e?auto=format&amp;fit=crop&amp;w=200&amp;q=80</div>
<div><strong>icon</strong> /brand/tkmind-icon.png</div>
</div>
</div>
</body>
</html>
@@ -1,41 +0,0 @@
<!doctype html>
<html lang="zh-CN">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>微信分享预览工具</title>
<style>
* { box-sizing: border-box; }
body { margin: 0; min-height: 100vh; padding: 24px; background: #f3f3f3; font-family: ui-sans-serif, -apple-system, BlinkMacSystemFont, sans-serif; color: #111; }
.panel { max-width: 560px; margin: 0 auto; padding: 20px; border-radius: 16px; background: #fff; box-shadow: 0 8px 30px rgba(0,0,0,.06); }
h1 { margin: 0 0 8px; font-size: 22px; }
p { margin: 0 0 16px; color: #666; line-height: 1.6; font-size: 14px; }
label { display: block; margin-bottom: 8px; font-size: 13px; font-weight: 600; }
input { width: 100%; padding: 12px 14px; border: 1px solid #ddd; border-radius: 10px; font: inherit; }
button { margin-top: 14px; width: 100%; padding: 12px 14px; border: 0; border-radius: 10px; background: #2f6f57; color: #fff; font: inherit; font-weight: 700; cursor: pointer; }
code { background: rgba(0,0,0,.06); padding: 1px 4px; border-radius: 4px; }
</style>
</head>
<body>
<div class="panel">
<h1>微信分享预览</h1>
<p>输入已发布页面路径,本地模拟微信「粘贴链接后」看到的卡片。开发环境走 Portal API:<code>/dev/wechat-share-preview</code></p>
<form id="preview-form">
<label for="url">页面 URL 或路径</label>
<input id="url" name="url" placeholder="/MindSpace/john/public/demo.html" required>
<button type="submit">生成预览</button>
</form>
</div>
<script>
document.getElementById('preview-form').addEventListener('submit', (event) => {
event.preventDefault();
const value = document.getElementById('url').value.trim();
if (!value) return;
const target = value.startsWith('http')
? `/dev/wechat-share-preview?url=${encodeURIComponent(value)}`
: `/dev/wechat-share-preview?url=${encodeURIComponent(value.startsWith('/') ? value : `/${value}`)}`;
window.location.href = target;
});
</script>
</body>
</html>
-159
View File
@@ -1,159 +0,0 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta name="description" content="John 的专属欢迎页面 - TKMind 为您呈现">
<meta name="mindspace-cover" content='{"tag":"报告","emoji":"👋","accent":"#f093fb","accent2":"#24243e","subtitle":"Hello John","cover":"https://images.unsplash.com/photo-1579547945413-497e1b99dac0?w=1200&q=80"}'>
<title>Hello, John!</title>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
min-height: 100vh;
display: flex;
align-items: center;
justify-content: center;
background: linear-gradient(135deg, #0f0c29, #302b63, #24243e);
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
overflow: hidden;
}
/* 动态粒子背景 */
.bg-particles {
position: fixed;
inset: 0;
z-index: 0;
overflow: hidden;
}
.bg-particles span {
position: absolute;
width: 6px;
height: 6px;
background: rgba(255, 255, 255, 0.15);
border-radius: 50%;
animation: float 20s infinite;
}
.bg-particles span:nth-child(1) { top: 10%; left: 10%; animation-duration: 18s; animation-delay: 0s; }
.bg-particles span:nth-child(2) { top: 30%; left: 80%; width: 10px; height: 10px; animation-duration: 22s; animation-delay: 2s; }
.bg-particles span:nth-child(3) { top: 70%; left: 20%; animation-duration: 16s; animation-delay: 4s; }
.bg-particles span:nth-child(4) { top: 85%; left: 85%; width: 8px; height: 8px; animation-duration: 24s; animation-delay: 1s; }
.bg-particles span:nth-child(5) { top: 50%; left: 50%; width: 4px; height: 4px; animation-duration: 20s; animation-delay: 3s; }
.bg-particles span:nth-child(6) { top: 15%; left: 60%; animation-duration: 19s; animation-delay: 5s; }
.bg-particles span:nth-child(7) { top: 60%; left: 5%; width: 7px; height: 7px; animation-duration: 21s; animation-delay: 0.5s; }
.bg-particles span:nth-child(8) { top: 40%; left: 70%; width: 5px; height: 5px; animation-duration: 17s; animation-delay: 2.5s; }
@keyframes float {
0% { transform: translateY(0) rotate(0deg); opacity: 0; }
10% { opacity: 1; }
90% { opacity: 1; }
100% { transform: translateY(-100vh) rotate(720deg); opacity: 0; }
}
.card {
position: relative;
z-index: 1;
text-align: center;
padding: 60px 80px;
background: rgba(255, 255, 255, 0.05);
border-radius: 24px;
backdrop-filter: blur(20px);
-webkit-backdrop-filter: blur(20px);
border: 1px solid rgba(255, 255, 255, 0.1);
box-shadow:
0 25px 50px rgba(0, 0, 0, 0.3),
inset 0 1px 0 rgba(255, 255, 255, 0.1);
animation: fadeIn 1.2s ease-out;
}
h1 {
font-size: 72px;
font-weight: 700;
background: linear-gradient(135deg, #f093fb, #f5576c, #4facfe);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
background-clip: text;
margin-bottom: 16px;
animation: pulse 3s ease-in-out infinite;
letter-spacing: 4px;
}
p {
font-size: 24px;
color: rgba(255, 255, 255, 0.7);
letter-spacing: 2px;
}
.emoji {
font-size: 48px;
display: block;
margin-bottom: 24px;
animation: wave 2s ease-in-out infinite;
}
.footer {
margin-top: 36px;
font-size: 13px;
color: rgba(255, 255, 255, 0.3);
letter-spacing: 1px;
}
@keyframes fadeIn {
from {
opacity: 0;
transform: translateY(30px) scale(0.95);
}
to {
opacity: 1;
transform: translateY(0) scale(1);
}
}
@keyframes pulse {
0%, 100% { transform: scale(1); }
50% { transform: scale(1.03); }
}
@keyframes wave {
0%, 100% { transform: rotate(0deg); }
25% { transform: rotate(15deg); }
75% { transform: rotate(-15deg); }
}
@media (max-width: 600px) {
.card {
padding: 40px 32px;
margin: 20px;
border-radius: 16px;
}
h1 {
font-size: 48px;
}
p {
font-size: 18px;
}
.emoji {
font-size: 36px;
}
}
</style>
</head>
<body>
<div class="bg-particles">
<span></span><span></span><span></span><span></span>
<span></span><span></span><span></span><span></span>
</div>
<div class="card">
<span class="emoji">👋</span>
<h1>Hello!</h1>
<p>Welcome, John</p>
<div class="footer">powered by TKMind</div>
</div>
</body>
</html>
Binary file not shown.

Before

Width:  |  Height:  |  Size: 407 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 569 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 328 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 212 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 407 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 292 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 128 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 292 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 555 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 292 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 407 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 555 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 569 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 292 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 328 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 407 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 152 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 672 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 723 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 672 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 458 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 160 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 160 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 152 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 723 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 723 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 152 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 672 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 458 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 152 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 672 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 723 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 309 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 338 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 338 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 294 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 130 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 294 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 132 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 190 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 309 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 338 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 309 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 294 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 309 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 338 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 190 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 294 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 210 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 334 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 376 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 334 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 334 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 310 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 157 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 310 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 310 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 157 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 376 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 310 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 334 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 310 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 210 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 310 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 342 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 342 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 353 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 613 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 342 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 130 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 353 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 353 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 306 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 274 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 353 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 342 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 342 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 342 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 274 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 613 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 227 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 227 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 227 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 274 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 166 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 166 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 110 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 166 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 166 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 227 KiB

Some files were not shown because too many files have changed in this diff Show More