forked from EduCraft/curriculum-project-hub
feat(hub): 真实飞书读取 + 状态卡片 + 权限 gate + 部署脚本
FeishuRead(ADR-0003): feishuContextTool 从 stub 换成真实 lark im.v1.message.get(单条 trigger/reply/status_card)+ list(thread 回复); ADR-0003 chat 授权不变式不变(handler 已 gate)。 StatusCard: sendCard + buildRunStatusCard——完成/出错发 interactive card (status 文案 + model 选择器 + 取消按钮带 runId),替代纯文本。 Permission(ADR-0004, project-wise): triggerAgent 查 PermissionGrant 对 project 的 edit+ 能力(manage⊇edit 单调);sender open_id 当 principal (子类型学 OPEN,务实选择,permission.ts 标注);per-artifact OPEN; settings 组合规则 OPEN。无权限回 '无权限触发'。 Deploy: cph-hub.service systemd unit(ExecStartPre 跑 prisma migrate)+ install_service.sh(idempotent,seed env)+ deploy_platform.sh (rsync + npm ci + build + restart + healthz)。 client.ts 抽 createLarkClient/startFeishuListenerWithClient,tools 与 ws 共用同一 client。 tsc rc=0;prisma validate 通过;deploy 脚本 bash -n 通过。
This commit is contained in:
@@ -15,12 +15,13 @@
|
||||
*/
|
||||
import type { PrismaClient } from "@prisma/client";
|
||||
import type { FastifyBaseLogger } from "fastify";
|
||||
import { sendText, type FeishuRuntime, type MessageReceiveEvent } from "./client.js";
|
||||
import { sendText, sendCard, buildRunStatusCard, type FeishuRuntime, type MessageReceiveEvent } from "./client.js";
|
||||
import type { AgentProvider } from "../agent/provider.js";
|
||||
import type { ToolRegistry } from "../agent/tools.js";
|
||||
import type { ModelRegistry } from "../agent/models.js";
|
||||
import { runAgent, type ProjectContext } from "../agent/runner.js";
|
||||
import { acquireLock, currentLockRunId, releaseLock } from "../lock.js";
|
||||
import { canTriggerAgent } from "../permission.js";
|
||||
|
||||
interface TriggerDeps {
|
||||
readonly prisma: PrismaClient;
|
||||
@@ -56,6 +57,17 @@ export function makeTriggerHandler(deps: TriggerDeps) {
|
||||
return;
|
||||
}
|
||||
const projectId = binding.projectId;
|
||||
// ADR-0004: triggerAgent requires edit on the project. principal=sender
|
||||
// open_id (sub-typology OPEN — pragmatic choice, see permission.ts).
|
||||
const senderOpenId = event.sender.sender_id.open_id ?? "";
|
||||
if (senderOpenId !== "") {
|
||||
const perm = await canTriggerAgent(deps.prisma, projectId, senderOpenId);
|
||||
if (!perm.allowed) {
|
||||
deps.logger.info({ projectId, senderOpenId, reason: perm.reason }, "feishu trigger: permission denied");
|
||||
await sendText(rt, chatId, "无权限触发。");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// ADR-0002: if locked by another run, reply busy.
|
||||
const existing = await currentLockRunId(deps.prisma, projectId);
|
||||
@@ -160,14 +172,24 @@ export function makeTriggerHandler(deps: TriggerDeps) {
|
||||
finishedAt: new Date(),
|
||||
},
|
||||
});
|
||||
await sendText(rt, chatId, statusReply(result.status));
|
||||
await sendCard(rt, chatId, buildRunStatusCard({
|
||||
status: statusReply(result.status),
|
||||
model,
|
||||
models: deps.models.listModels(),
|
||||
runId: run.id,
|
||||
}));
|
||||
})
|
||||
.catch(async (e) => {
|
||||
await deps.prisma.agentRun.update({
|
||||
where: { id: run.id },
|
||||
data: { status: "FAILED", error: String(e), finishedAt: new Date() },
|
||||
});
|
||||
await sendText(rt, chatId, "处理出错,已释放项目锁。");
|
||||
await sendCard(rt, chatId, buildRunStatusCard({
|
||||
status: "处理出错",
|
||||
model,
|
||||
models: deps.models.listModels(),
|
||||
runId: run.id,
|
||||
}));
|
||||
})
|
||||
.finally(async () => {
|
||||
await releaseLock(deps.prisma, run.id);
|
||||
|
||||
Reference in New Issue
Block a user