# 接续入口 · StoryForge 插件线(2026-09-20 立) > 🔴 **工作区**:E:/ProgramData/AI技能/dsh-plugin-forge —— ⚠️ **本文件是外来副本**(2026-09-21 19:37 由 forge 侧「两处同改」产生)。StoryForge 线自己的家是 `dsh-plugin-forge`,本副本**应迁移并删除**;`state.py` 已加归属校验(2026-09-21),会把它标成「**外来线**」并排除出 §2 / 口令。⛔ 今后**不再两处同改**。 > **唯一执行依据 = 本文件 §0 最新行 + §2 首行(本棒)**。⛔ 勿重做已完成棒、⛔ 勿跳序。 > 方案单一来源 = `E:\ProgramData\AI技能\dsh-plugin-forge\StoryForge改造为dsh插件_可行性与落地方案_20260920.md`(**v2**,只读 §二 / §三 / §四 / §七 / §九) > **as-built 说明(已建成什么样)= `E:\ProgramData\AI技能\dsh-plugin-forge\StoryForge插件_改造说明与数据落点_20260921.md`** —— 含**全部改造点**(上游 3 处 + 插件包 8 件 + 七条硬契约)、**平台数据库对接现状与路径**、**数据落点主表 + 铁律六条**、现状差距。⚠️ 该文件是**当前事实的唯一汇总面**,回答"改了什么 / 数据放哪"先看它 > 单子落点 = `D:\github\dsh_shenxian\dsh-server-docs\交接单\StoryForge插件线-序*.md`(**权威**)|本线目录留一份同步副本 > 目标一句话:把 StoryForge(上游 v3.9.1,AI 小说创作工具)**不 fork、只改 3 处**地做成 dsh 业务插件(形态 = 同源 iframe 内嵌)。 > 🔴 **权威接续入口 = `E:/ProgramData/AI技能/dsh-plugin-forge/接续入口_StoryForge插件线_20260920.md`(StoryForge 线自己的工作区)**;**本工作区(aliyun-dsh-server)这份是残留副本 —— ⛔ 不要在这里改、⛔ 不要两处同改**,要改只改 forge 那份(归属三律:入口只允许一份)。 --- ## §0 定序(线内串行,勿跳) | 序 | 单子 | 状态 | |---|---|---| | **1** | `StoryForge插件线-序1-上游改造与插件骨架.md` | ✅ **已落**(2026-09-20 23:45 → 09-21 00:05 · 执行棒 `StoryForge插件线-序1执行棒`) | | **2** | `StoryForge插件线-序2-部署与实例验收.md` | 🟡 **部分执行**(2026-09-21 00:42 → 01:1x · 执行棒 `StoryForge插件线-序2执行棒`)—— **S0–S5 ✅ 全绿**:host 半区 `/storyforge/*` 前缀路由已落、本机等价验收 **`4/4 PASS`(50 断言 0 失败)**、`npm pack` **1,754,963 B** / md5 `dca573b57814de6c205b7be6d3772760`、候选池 **200 / `blocked=0`**(未用 trust)、**实机证明插件被加载并注册路由**(标记 ×4)、实装 md5 逐字节一致、属主 114801 无 root。🔴 **S6 卡在平台 `mine/apply` 的「重启+探活」腿**(终态 `failed`、平台已 `restoreProfile` 自动回滚 ⇒ 实例当前**未启用**;真机判据 1–4 **未验**);四条证据判定**与本插件无关**。⛔ **链条已暂停** —— 见 §1 末条 + §2 | | 3 | 序3 — LLM 代理与配额(`/storyforge/api/llm/*`,**含拍板项**) | ⛔ **未立项**,等用户拍板;🔴 **新增硬前置**:须先解 §2 记录的 S6 阻塞(平台启用链路探活腿),否则装上亦看不见效果 | | **跨线** | `对接单_平台侧-mine-apply重启探活腿异常_20260921.md`(本线 → 平台线) | ⏳ **已出,待平台侧评估**(2026-09-21 06:2x 规划棒2)。落点 = **本工作区**(按用户 2026-09-21 约定) | **⛔ 全序遇到即停(写死)**:凡需决定「**模型 KEY 来源 / 每用户配额 / 是否由平台承担模型调用成本**」⇒ **立刻停下报告,不得自拍**(属 §1 边界外,归序3)。 **边界(全序通用)**:不 commit / 不 push;⛔ **本线文档只落本工作区,不写 `dsh-server-docs`**(因此**不涉及**共享锁);⛔ 不碰平台 `src/`;上传候选池 / 启用实例 / 重启**生产服务** / scp **到生产服务器** 属序2(序1 内禁止)。✅ **scp 到本机 Ubuntu 测试靶机不受此限**(见下)。 **🔴 验收环境(用户 2026-09-21 明令:「开发完成后在本机 ubuntu 上进行测试」)** | 项 | 值 | |---|---| | 靶机 | **本机 WSL2 · Ubuntu 26.04.1 LTS**,hostname `User-2026QYRQXO` | | 连法 | `ssh -i ~/.ssh/id_ed25519_verifyenv -o IdentitiesOnly=yes root@172.20.32.197` ⚠️ **`id_ed25519` 会被拒**(`Permission denied (publickey,password)`) | | ⛔ 禁路 | `wsl.exe` **被沙箱 Program Blacklist 拦**(提示明确「不得重试/绕行」)⇒ **一律走 ssh** | | 环境 | **node v22.23.2(与生产服务器同版本)** / python3 3.14.4 / 32 核 / 23.8 GB | | 落地约定 | `tar czf - -C <插件包> . \| ssh … 'mkdir -p /root/sf-smoke- && tar xzf - -C …'` —— **每次新目录、不删旧**(照 PLAYBOOK §17「换新目录名别删」) | | 冒烟入口 | 插件包内 **`scripts/smoke.mjs`**(2026-09-21 新增,**可复跑**;`package.json` 的 `files` 不含它 ⇒ **不进 tgz**) | | 判据 | **Ubuntu 上必须 `34/34` 全绿**(退出码 0) | 📌 **2026-09-21 首次对照实测(同一脚本、同一产物)**:**Windows 32 过 / 2 败** ⟷ **Ubuntu 34 过 / 0 败**;败的恰是两条 ★ **大小写敏感**项(`/storyforge/INDEX.HTML` 与资产名全大写:Windows 返 **200**、Ubuntu 正确返 **404**)⇒ **这就是必须有 Linux 靶机的实证理由**:Windows 文件系统不区分大小写,会把这类缺陷判成绿的。 --- ## §1 已落(只增不删,带证据) - **2026-09-20 规划棒**:可行性方案 v2 出(形态 **候选 A 同源 iframe**;不 fork;关 SW;provider 收敛 deepseek;M1 入口 = 全屏/新标签页;锚点锁 `v3.9.1`)。落点 `E:\ProgramData\AI技能\dsh-plugin-forge\`。 - **2026-09-20 23:45 序1 执行棒(收官)**: - 上游副本 `E:\github\storyforge` @ **`v3.9.1`** / sha **`7286497e`**(`clone` + `checkout` 均 rc=0)。 - **3 处上游改动全部落地**并**加严**:关 SW(删 `VitePWA` + 注册改 no-op + `index.html` 清理改全主机执行)|provider 收敛(18 条中仅 `deepseek` 启用,其余「待启用」)|删 dev 代理(**实测 12 条**,非单子写的 15 条)并改同源 `/storyforge/api/llm//...`。 - `npm ci`(764 包 / rc=0,Node 22.22.2 **无 engine 报错**)→ `npm run build`(`tsc -b && vite build`,**rc=0**)。 - 验收六条全绿:`v3.9.1` | 产物内 `https?://api\.` 与 `127.0.0.1:` **0 命中** | `dist/index.html` OK | 静态冒烟 `/storyforge/`=**200**、`sw.js`=**404** | `node --check lib/client.js` rc=0 | `exports.default` **0 命中**。 - 插件包骨架落 `E:\ProgramData\AI技能\dsh-plugin-forge\dsh-plugin-storyforge\`(`package.json` / `cordis.patch.yml` / `lib/{index.js,client.js}` / `dist/` 110 文件 / `LICENSE`;总 6,510,823 B;`inject=[]`、`dshCompat=">=0.1.5 <0.2"`、version `0.1.0`)。 - 证据全文 = 单子 **§八**(含 6 条「失败尝试与假阴性」);单子另加 **§九「交给 序2/序3 的输入」**(宿主待补路由三张表)。 - ⚠️ **未做浏览器渲染验证**(本机无 Chromium)⇒ 只到 **L2(命令)+ L3(对账)**。 - 🔴 **单子口径修正 3 处**(已在单子内就地标注):① 「coreutils 全部缺失」**不成立**(`dirname`/`ls`/`mkdir`/`md5sum` 全在 `/bin`,`handoff-guard.sh` 实跑成功)② dev 代理 **12 条**而非 15 条 ③「不再出现 `-proxy`」按**代码级**口径达成(注释内说明性提及有意保留)。 - **2026-09-21 00:3x 序2 规划棒(立单完成)** —— 只读前置取证 + 出单,**未改插件包/上游/平台代码**。产物 = `dsh-server-docs/交接单/StoryForge插件线-序2-部署与实例验收.md`(8 段模板,`.lock-02` 已原子占号)+ `交接单/README.md §一` 新增序2 一行。⚠️ **当时仍按旧约定落文档库**;**2026-09-21 起本线文档改为只落本工作区**(本工作区已有同内容副本,含 §八 回填证据)。 - 🔴 **本棒决定性取证(序2 执行棒的必读前置)**: - **路由 API 必须用 `kind`**:`E:\github\dsh-desktop-0.1.5rc1\packages\host\webserver\lib\index.js:176-178` 实测 `const table = route.kind === "exact" ? this.exact : this.prefixes;` ⇒ 既存插件(social-workbench / voxemw-cloud)写的 `exact: true` 在 0.1.5-rc.1 会落**前缀表**(语义漂移)⇒ 本包用 `kind:'prefix'`。 - **`registerFallback` 只有一座**且被官方 `@deepseek-ai/dsh-host-frontend-static` 占用 ⇒ SPA 回退**必须写在自注册 prefix handler 内**,否则注册即 throw ⇒ 整包加载失败。 - **回退只对「无扩展名」路径生效** ⇒ 带扩展名未命中一律 404,否则 `/storyforge/sw.js` 会回退成 HTML ⇒ SW 复活(M1 明令关闭)。 - **宿主半区 `inject` 声明方式**:模块级 `const inject = ['webServer']` + `export { apply, inject }`(social-workbench `lib/index.js:216,218` 实证);与 `package.json` 的 `dsh.client.inject`(管 client 半区)**是两回事**。 - **本序无需改平台 `src/`**:`src/supervisor/proxy.ts` 的 `targetPath` 默认 = `rawUrl`,**无路径白名单** ⇒ 未知前缀原样透传。 - **缓存判据**:代理给 `no-cache` 的条件 = 路径以 `/plugins/` 或 `/assets/` 开头 **或** `content-type` 含 `text/html`(`proxy.ts:487-491`)⇒ SPA 深链响应**必须**是 `text/html`,否则拿不到 `no-cache`。 - **投放链路**:`POST /api/plugins/business`(admin 会话,body `{file: base64, filename: *.tgz}`,同名整体替换,P0 命中默认 409 fail-closed)→ `POST /api/plugins/mine/apply`(`{plugins:[{id,enabled}]}`,**对已启用项是 noop** ⇒ 更新须「先停用再启用」)。admin 会话用 47 上 `node /opt/dshs/mksess.cjs`(**TTL 10 分钟,现建现用,用完删 `sessions` 行**)。 - **内存基线**:每实例 基础 **448 MiB** / 浮动上限 **1024 MiB**;本包 6.21 MiB 静态文件 ⇒ 宿主侧 RSS 增量 ≈ 0(每请求读盘、不缓存)。 - **2026-09-21 00:42 → 01:1x 序2 执行棒(部分收口 · 🔴 卡点已定性)** —— 单子 §五 S0→S7 走完,S0–S5 **全绿**,**S6 受阻**。证据全文 = 单子 **§八**(10 段已回填,含 8 条「失败尝试与假阴性」)。 - **S0**:全局执行锁 `mkdir` 成功(`OWNER` = 本会话)+ `.doing-StoryForge序2` 已占。 - **S1 前置 5 条全过**:`dsh 0.1.5-rc.1` / 域 `ai1net.com` | 🔴 **`kind` 决定性行实测在 `webserver/lib/index.js:177`**(单子写 176–178,**行号差 1**,内容逐字一致)| fallback 座位 `throw` 在 `:206`、占用方 `frontend-static/src/index.ts:124` | `inject` 声明法(social-workbench `:216/:218`)|包现状 `dist/` **110** / 总 **6,510,823 B** / `files` 字段**缺席**。**另证 `dist/sw.js` 不存在** ⇒ 判据 4 的 404 是真 404。 - **S2 实现(改 4 文件,回滚点存包外 `_backup-storyforge-xu2/`)**:`lib/index.js` 全文重写(`export const inject = ["webServer"]` + `ctx.effect(() => server.register({kind:'prefix', path:'/storyforge', handler}))`,**保留**原标记写入)|**新增** `lib/static.js`(纯函数 `planRequest` + 写响应 `serve`;**唯一语义差异 = `fallbackOnMiss`**:回退只对无扩展名路径生效)|`package.json` 加 `files` + 改过期 `description` |`cordis.patch.yml` **仅注释**(原写「无 inject」序2 后与事实相反)。 - **S2 验证 4/4 过**:`node --check` rc=0 ×3 | `exports.default` **0** | `kind:"prefix"` + `inject` 命中 | `files=['lib','dist','cordis.patch.yml','LICENSE']` / `0.1.0` / `client.inject=[]`。 - **S3 本机等价验收 = `4/4 PASS`**(**50 断言 0 失败、退出码 0**;真起 `node:http` + 调 `serve()`,非纯模拟):① `/storyforge/` = 200 `text/html;charset=utf-8` **4689 B 逐字节 == dist/index.html** ② hashed JS = 200 `text/javascript` **710117 B == 包内同名文件** ③ `/storyforge/settings` = 200 **含 `text/html`**(回退体逐字节 == index.html)④ `/storyforge/sw.js` = **404** 且体为空(未回退成 HTML);旁证:多级深链 200/html、POST 405、HEAD 200 无体、带扩展名未命中 404、越界非 200。 - **S4 打包**:`dsh-local-storyforge-0.1.0.tgz` = **1,754,963 B** / md5 **`dca573b57814de6c205b7be6d3772760`**;`package/dist/` = **110**(= 本机 dist 数);`package/{package.json,cordis.patch.yml,LICENSE}` + `lib/{index.js,static.js,client.js}` **6 条全中**;**无** `.bak`/`node_modules` 脏条目。 - **S5 候选池 ✅**:`POST /api/plugins/business` = **200**、`ok:true`、`replaced:false`、`trustedOverride:false`、🔴 **`blocked=0` ⇒ 未触发 409、未用 `trust` 放行**(单子 §四 预警的 vendor 假阳性**未出现**);`compat.level = unknown`(**非 `incompatible`**):`"插件未声明也未直接 import 任何 @deepseek-ai 包(扫描 106 个 js 文件)"`;18 条 `warnings` **全落 `package/dist/assets/**`**、`lib/**` 零命中。池内 `tgzPath = /var/lib/dshs/business-plugins/_dsh-local_storyforge.tgz`(**条目保留**)。 - **S6 实例启用 ⛔ 卡点**:靶 = admin 实例(uid **114801** / `w-47` / `dsh --profile web --port 20000`)。`mine/apply` 返 `taskId=397355a8a6d6fb78`,**终态 `failed`**(`安装失败,请重试或联系管理员`,93 s),阶段**恒停 `正在重启实例并探活…`**。**实装却成功**:`node_modules/@dsh-local/storyforge` 软链到位、`dist/index.html` md5 **`893e2034dd7e389e805b33310d051efb`** = 包内、属主全 **114801**、root 文件 **0**(R10 过)。平台随后 `restoreProfile` **自动回滚** ⇒ `bundles` 现**不含**本包。 - 🔴 **关键判据:插件不是肇事方(四条独立证据)**:① 实例内标记 `.dsh/.dsh-storyforge.log` 显示 **`route registered kind=prefix path=/storyforge` ×4**(`16:53:54 / 16:54:07 / 16:54:19 / 16:54:31`)⇒ 真实例内**加载成功、注册成功、dist 解析正确**;② `dsh_instances` 该行 `last_error=null` / `exit_code=null` / `epoch` 未变 / heartbeat 实时 / scope 持续 active ⇒ **从未崩溃**;③ **代码未走到「隔离/自动禁用」分支**(阶段无 `实例未能启动…`、无 `正在定位问题插件(1/1)`,`audit_log` 无 `plugin_incident`、无 `apply_business_plugins`)⇒ 是 `restartProbe()` **抛异常**,非探活判定为坏;④ 同平台 `2026-09-14T12:03:16Z` **有**该分支正常留痕的先例(`plugin_incident reason:"实例启动后崩溃"`)⇒ 本次无痕 = 未走到那里。 - ⚠️ **旁证(门户启用链路已长期不正常)**:`audit_log` 全表 `apply_business_plugins` **最后一条 = `2026-09-14T12:03:28Z`**;此后平台侧部署(含 09-20 MCN 线投放)实际走 **`ensure-biz-plugins.cjs` 直铺**(不写该审计)。 - ⚠️ **另记两条环境噪声(与本序无关)**:失败窗口 `guest.ai1net.com` 对真实浏览器返 **503**;日志大量 `[relay-dialer] 拨 ops/w-106:21000 失败:refused: busy`。 - ⛔ **未做(明示)**:**client 半区**(导航入口 + `