feat: 功能清单进智能体提示,补字幕按功能模块约定改为样板
- 外围功能模块(补字幕、原站视图、桌面模式、网页搜索、MCP、定时任务)声明 manifest,与 FEATURES 一一对应; 系统提示「能力地图」末尾列出外围功能(没有开关,能力只列本次可用的) - 补字幕:订阅 organization.finished(认出值得补字幕的整理,自动补字幕还没做,不建任务), 写入字幕后在同一事务发 subtitle.written;原有行为不变 Co-Authored-By: Claude Opus 5.5 <[email protected]> Claude-Session: https://claude.ai/code/session_01Cxwgnn2pbzQ7rXv3rq7fei
This commit is contained in:
1 parent
1db689d9a7
commit
7aa5182f3d
14 files changed
+237
-17
No files matched your search
@@ -1,3 +1,4 @@
|
||||
import { featureManifest } from "@umt/contracts";
|
||||
import { sourcesIdentityModule } from "../sources-identity/index.ts";
|
||||
import type { ServerContext, ServerModule } from "../types.ts";
|
||||
import { registerBrowserCapabilities } from "./capabilities.ts";
|
||||
@@ -36,6 +37,7 @@ export function createBrowserModule(opts: BrowserModuleOptions = {}): ServerModu
|
||||
};
|
||||
return {
|
||||
name: "browser",
|
||||
manifest: featureManifest("browser"),
|
||||
service,
|
||||
registerRoutes(app, ctx) {
|
||||
registerBrowserRoutes(app, () => service(ctx));
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { featureManifest } from "@umt/contracts";
|
||||
import { browserServiceFor } from "../browser/index.ts";
|
||||
import type { ServerContext, ServerModule } from "../types.ts";
|
||||
import { dbSessionChecks, desktopUpstream, registerDesktopRoutes } from "./desktop.ts";
|
||||
@@ -11,6 +12,7 @@ export function createDesktopModule(): ServerModule {
|
||||
let closeAll: (() => void) | null = null;
|
||||
return {
|
||||
name: "desktop",
|
||||
manifest: featureManifest("desktop"),
|
||||
registerRoutes(app, ctx: ServerContext) {
|
||||
const r = registerDesktopRoutes(app, {
|
||||
upstream: desktopUpstream({
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { featureManifest } from "@umt/contracts";
|
||||
import denoJson from "../../../deno.json" with { type: "json" };
|
||||
import type { ServerModule } from "../types.ts";
|
||||
import { createMcpConfigStore } from "./config-store.ts";
|
||||
@@ -13,6 +14,7 @@ export function createMcpModule(opts: { version?: string } = {}): ServerModule &
|
||||
const table = new McpSessionTable();
|
||||
return {
|
||||
name: "mcp",
|
||||
manifest: featureManifest("mcp"),
|
||||
sessions: table,
|
||||
registerRoutes(app, ctx) {
|
||||
registerMcpRoutes(app, ctx, {
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { featureManifest } from "@umt/contracts";
|
||||
import type { ServerContext, ServerModule } from "../types.ts";
|
||||
import { builtinAgentCheckDefs } from "./agent-checks.ts";
|
||||
import { registerScheduleCapabilities } from "./capabilities.ts";
|
||||
@@ -26,6 +27,7 @@ export function createSchedulesModule(opts: SchedulesModuleOptions = {}): Server
|
||||
let scheduler: Scheduler | null = null;
|
||||
return {
|
||||
name: "schedules",
|
||||
manifest: featureManifest("schedules"),
|
||||
scheduler: () => scheduler,
|
||||
registerRoutes(app, ctx) {
|
||||
registerScheduleRoutes(app, ctx, () => scheduleRegistry(ctx));
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
import { type EventPayload, parseEventPayload } from "@umt/contracts";
|
||||
import type { OutboxRouteFn } from "@umt/tasks";
|
||||
import type { ServerContext } from "../types.ts";
|
||||
|
||||
// 补字幕订阅的生命周期事件(ADR-006 功能模块样板)。
|
||||
// 「入库后自动补字幕」还没做(docs/plan/subtitles.md):这里只认出值得补字幕的整理,记一条调试日志,不建任务。
|
||||
// 以后做自动补字幕时把 null 换成 { task: "...", input } 即可,订阅关系不用再改。
|
||||
|
||||
const IMPORT_ACTIONS = new Set(["copy", "move", "in_place"]);
|
||||
|
||||
/** 整理结束事件里值得补字幕的:成功或部分成功、入库类动作(复制 / 移动 / 原地入库)、认到了作品 */
|
||||
export function subtitleCandidate(
|
||||
p: EventPayload<"organization.finished">,
|
||||
): { recordId: string; mediaEntityId: string } | null {
|
||||
if (p.outcome === "failed" || !IMPORT_ACTIONS.has(p.action) || !p.mediaEntityId) return null;
|
||||
return { recordId: p.recordId, mediaEntityId: p.mediaEntityId };
|
||||
}
|
||||
|
||||
export function subtitlesOutboxRoutes(ctx: Pick<ServerContext, "logger">): Record<string, OutboxRouteFn> {
|
||||
return {
|
||||
"organization.finished": (ev) => {
|
||||
const p = parseEventPayload("organization.finished", ev.payload);
|
||||
const c = p ? subtitleCandidate(p) : null;
|
||||
if (c) ctx.logger.debug(c, "补字幕:整理已完成,自动补字幕还没做,跳过");
|
||||
return null;
|
||||
},
|
||||
};
|
||||
}
|
||||
@@ -1,9 +1,16 @@
|
||||
import { Api, type SubtitleApplyRequest, type SubtitleFetchRequest, type SubtitleSource } from "@umt/contracts";
|
||||
import {
|
||||
Api,
|
||||
featureManifest,
|
||||
type SubtitleApplyRequest,
|
||||
type SubtitleFetchRequest,
|
||||
type SubtitleSource,
|
||||
} from "@umt/contracts";
|
||||
import { ApiException } from "../../http/errors.ts";
|
||||
import { route } from "../agent/http.ts";
|
||||
import { human } from "../browser/routes.ts";
|
||||
import { sourcesIdentityModule } from "../sources-identity/index.ts";
|
||||
import type { ServerModule } from "../types.ts";
|
||||
import { subtitlesOutboxRoutes } from "./events.ts";
|
||||
import {
|
||||
downloadFromSession,
|
||||
downloadOnline,
|
||||
@@ -13,10 +20,15 @@ import {
|
||||
} from "./online/index.ts";
|
||||
import { applySubtitles, parseSubtitles, subtitleTargets, subtitleWrites, undoSubtitleWrite } from "./service.ts";
|
||||
|
||||
/** 补字幕(docs/plan/subtitles.md):上传 / 在线下载的字幕解析、配对、写入、历史与撤销 */
|
||||
/**
|
||||
* 补字幕(docs/plan/subtitles.md):上传 / 在线下载的字幕解析、配对、写入、历史与撤销。
|
||||
* 功能模块样板(ADR-006):manifest 进功能清单(给智能体),订阅 organization.finished(events.ts),
|
||||
* 写入后发 subtitle.written;前端经插槽挂在作品页操作区与整理记录行菜单(apps/web/app/features/subtitles.tsx)。
|
||||
*/
|
||||
export function createSubtitlesModule(): ServerModule {
|
||||
return {
|
||||
name: "subtitles",
|
||||
manifest: featureManifest("subtitles"),
|
||||
registerRoutes(app, ctx) {
|
||||
const tag = { tag: "subtitles" };
|
||||
const rt = () => sourcesIdentityModule.runtime(ctx);
|
||||
@@ -69,6 +81,7 @@ export function createSubtitlesModule(): ServerModule {
|
||||
route(app, Api.subtitles.undo, ({ params }) => undoSubtitleWrite(ctx, params.id), tag);
|
||||
},
|
||||
registerTasks() {},
|
||||
outboxRoutes: (ctx) => subtitlesOutboxRoutes(ctx),
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -1,15 +1,16 @@
|
||||
import { copyFile, mkdir, readdir, rename, rm, stat, writeFile } from "node:fs/promises";
|
||||
import { dirname, join } from "node:path";
|
||||
import type {
|
||||
SubtitleApplyRequest,
|
||||
SubtitleApplyResult,
|
||||
SubtitleLanguage,
|
||||
SubtitleParsedItem,
|
||||
SubtitleParseResponse,
|
||||
SubtitleSource,
|
||||
SubtitleTarget,
|
||||
SubtitleTargetsResponse,
|
||||
SubtitleWriteView,
|
||||
import {
|
||||
lifecycleKey,
|
||||
type SubtitleApplyRequest,
|
||||
type SubtitleApplyResult,
|
||||
type SubtitleLanguage,
|
||||
type SubtitleParsedItem,
|
||||
type SubtitleParseResponse,
|
||||
type SubtitleSource,
|
||||
type SubtitleTarget,
|
||||
type SubtitleTargetsResponse,
|
||||
type SubtitleWriteView,
|
||||
} from "@umt/contracts";
|
||||
import type { SubtitleWriteTable } from "@umt/db";
|
||||
import {
|
||||
@@ -259,7 +260,7 @@ export async function applySubtitles(
|
||||
})
|
||||
.execute();
|
||||
}
|
||||
return await trx
|
||||
const w = await trx
|
||||
.insertInto("subtitle_write")
|
||||
.values({
|
||||
id,
|
||||
@@ -277,6 +278,20 @@ export async function applySubtitles(
|
||||
})
|
||||
.returningAll()
|
||||
.executeTakeFirstOrThrow();
|
||||
// 生命周期广播(ADR-006):字幕已写入
|
||||
await ctx.outbox.emit(
|
||||
trx,
|
||||
"subtitle.written",
|
||||
{
|
||||
writeId: w.id,
|
||||
mediaEntityId: w.media_entity_id,
|
||||
videoLocationId: t.locationId,
|
||||
language: w.language as SubtitleLanguage,
|
||||
source: w.source as SubtitleSource,
|
||||
},
|
||||
{ idempotencyKey: lifecycleKey("subtitle.written", w.id) },
|
||||
);
|
||||
return w;
|
||||
});
|
||||
written.push(writeView(row));
|
||||
} catch (e) {
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { featureManifest } from "@umt/contracts";
|
||||
import type { ServerContext, ServerModule } from "../types.ts";
|
||||
import { registerWebSearchCapabilities } from "./capabilities.ts";
|
||||
import { registerWebSearchRoutes } from "./routes.ts";
|
||||
@@ -24,6 +25,7 @@ export function createWebSearchModule(opts: WebSearchModuleOptions = {}): Server
|
||||
};
|
||||
return {
|
||||
name: "websearch",
|
||||
manifest: featureManifest("websearch"),
|
||||
service,
|
||||
registerRoutes(app, ctx) {
|
||||
registerWebSearchRoutes(app, () => service(ctx));
|
||||
|
||||
Reference in new issue
Block a user