Files
UHD_Media_Tool/apps/server
rootandClaude Opus 5.5 4a92dfd2dc feat(feeds): site-scoped RSS add infers account, parser and poll
Adding a feed from a site page now only asks for the URL, purpose
(discover / track / promotion) and an optional note. POST /feed-sources
with siteId infers the site account (passkey holder first), records the
site definition version as parserVersion, defaults the name to
"<site> RSS" and polling to 30 minutes, and returns non-blocking
warnings when the URL host does not match the site. The dialog shows the
redacted URL, fetches once right after saving and reports the item count
or the error.

The generic feed form on the sources page suggests linking a registered
site when the URL host matches (can be undone). name is now optional.

Takes effect after the stack is rebuilt.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-09-29 14:43:19 +08:00
..
2026-09-29 03:27:14 +08:00

@umt/server

服务进程(Deno):Hono HTTP API(前缀 /api/v1,OpenAPI 见 /api/openapi.json、/api/docs)、SSE 事件流与 worker 入口。--role api|worker|worker:agent|all 决定本进程承担哪部分,默认 all 单进程运行(worker 只认领 io 组任务,worker:agent 只认领 Agent 组;另有内部角色 --role fsworker,由 FsProxy 以子进程方式拉起执行单个文件操作,不要手动启动)。业务规则不写在这里,只做编排、鉴权、审计与协议适配。

模块骨架(W2)

src/
  main.ts                 进程入口:按 --role 装配
  context.ts              bootstrap():ServerContext、任务注册表(模块 + 空壳兜底)、outbox 路由合并、连接引擎
  http/app.ts             createApp():系统路由 + 逐个挂 SERVER_MODULES 的 registerRoutes
  worker/index.ts         startWorker():引擎 worker + OutboxRelay + 各模块 start()
  modules/
    types.ts              ServerModule / ServerContext / OutboxWriter / SecretStore / SettingsReader(共享,不随子任务改)
    index.ts              SERVER_MODULES 清单
    auth-settings/        W2-A(provided.ts 提供 secrets / settings 实现)
    sources-identity/     W2-B
    acquisition-organize/ W2-C
    file-manager/         W5-A:/files/*(浏览、mkdir/rename、move/copy/organize 两阶段、回收站、identify/probe/scrape、download),
                          见 docs/runbook/files.md
    agent/                W2-D:capability-registry.ts(authorize / audit / 幂等键 / 操作账本)、会话 API、runAgent、resolveDecision
                          (见 docs/runbook/agent.md;agent 模块可以 import 其它模块的 service 导出,反向不行)

分工规则:各 W2 子任务只改自己的 modules/<area>/ 目录,以及 packages/contracts、packages/db 中本域的文件(新迁移按编号顺延,开工前在群里认领编号)。modules/types.ts、modules/index.ts、context.ts、http/app.ts、worker/index.ts、main.ts 是共享装配,确需改动时单独提交并通知其他子任务;http/auth.ts、http/sse.ts 归 W2-A。跨模块只通过 DB 表 + outbox_event(事件类型在 contracts/events.ts)+ contracts 类型耦合,不互相 import 对方模块。

一个模块长这样(modules/<area>/index.ts 导出,目录内自由拆 routes.ts / service.ts / handlers/*.ts):

import { createRoute } from "@hono/zod-openapi";
import { capabilitySpec, EventType } from "@umt/contracts";
import { defineTask } from "@umt/tasks";
import { requireScope } from "../../http/auth.ts";
import type { ServerModule } from "../types.ts";

export const sourcesIdentityModule: ServerModule = {
  name: "sources-identity",
  // 路径相对 /api/v1,已过 API Key 鉴权;每个路由自己声明 scope
  registerRoutes(app, ctx) {
    app.use("/sites/*", requireScope("read"));
    app.openapi(createRoute({ method: "get", path: "/sites", /* … */ }), async (c) => c.json(/* … */));
  },
  // 同名任务只能由一个模块注册;未注册的 §3.1-8 任务由 @umt/tasks 空壳兜底
  registerTasks(registry, ctx) {
    registry.register(defineTask({ name: "resolveObservation", version: 1, /* … */ handler: async (t) => {
      await ctx.db.transaction().execute(async (trx) => {
        // …写业务表,同事务发事件(payload 按 contracts 校验)
        await ctx.outbox.emit(trx, EventType.releaseResolved, payload, { idempotencyKey: `release.resolved:${id}` });
      });
    } }));
  },
  // 消费别的模块发出的事件:type → 任务;同一 type 只能一个模块认领
  outboxRoutes: () => ({ "release.resolved": (ev) => ({ task: "evaluateAcquisition", input: { sourceReleaseId: (ev.payload as { sourceReleaseId: string }).sourceReleaseId } }) }),
  // 能力:规格一律取 contracts,处理器调本模块服务
  registerCapabilities(registry, ctx) {
    registry.register(capabilitySpec("release.search"), async (input, c) => ({ items: [], coverage: [] }));
  },
  // worker 角色的后台循环(qB 轮询、Feed 调度…);引擎与中继已启动
  async start(ctx) {},
  async stop() {},
};

ServerContext 字段:db(Kysely)、config、logger(pino,自动脱敏)、events(SSE 总线 EventBusPort)、engine(TaskEngine:enqueue/signal/cancel/get/list;register* 阶段不要调用)、outbox(emit(trx, type, payload, {idempotencyKey}) + worker 的 relay)、secrets(SecretStore,W2-A 实现前调用会抛错)、settings(读 settings 表并合并缺省)、capabilities(CapabilityRegistry)。domain ↔ contracts 的取值转换一律用 @umt/mapping。

角色:all 单进程时连接引擎会顺带跑引擎迁移;api / worker 分进程部署时由 --role migrate 预先迁移(db + openworkflow),两个进程都以 runMigrations:false 连接。