forked from bai/curriculum-project-hub
fix(hub): 启动重置状态 + lazy-init 修复空白消息 + busy 改表情
三个修复:
1. 启动时清锁+标死run为FAILED(防重启后卡死)
2. 空白消息修复:lazy-init 改为第一个 text-delta 立即创建消息
(不走 throttle);.then 加 fallback:没收到 text-delta 时用
result.text 发完整回复
3. busy 提示从文本'项目正在处理中'改为⏳表情回复
tsc rc=0。
This commit is contained in:
@@ -156,7 +156,7 @@ export function makeTriggerHandler(deps: TriggerDeps) {
|
||||
// ADR-0002: if locked by another run, reply busy.
|
||||
const existing = await currentLockRunId(deps.prisma, projectId);
|
||||
if (existing !== null) {
|
||||
await sendText(rt, chatId, "项目正在处理中,请稍候。");
|
||||
await reactToMessage(rt, msg.message_id, "Hourglass");
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -243,7 +243,7 @@ export function makeTriggerHandler(deps: TriggerDeps) {
|
||||
data: { status: "FAILED", error: "lock race", finishedAt: new Date() },
|
||||
});
|
||||
await writeAudit(deps.prisma, { runId: run.id, projectId, action: "run.lock_race", metadata: {} });
|
||||
await sendText(rt, chatId, "项目正在处理中,请稍候。");
|
||||
await reactToMessage(rt, msg.message_id, "Hourglass");
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -257,17 +257,23 @@ export function makeTriggerHandler(deps: TriggerDeps) {
|
||||
// Emoji reaction on the user's message (acknowledge receipt).
|
||||
await reactToMessage(rt, msg.message_id, "THUMBSUP");
|
||||
|
||||
// Stream agent response as text-as-card messages — patchable, markdown,
|
||||
// no truncation. Lower throttle (400ms) for smoother streaming.
|
||||
let currentMsgId = await sendTextMessage(rt, chatId, "");
|
||||
// Stream agent response as text-as-card messages. Lazy-init: don't send
|
||||
// anything until the first text-delta arrives (avoids empty placeholder).
|
||||
let currentMsgId: string | null = null;
|
||||
let accText = "";
|
||||
let lastPatch = 0;
|
||||
const PATCH_INTERVAL = 400;
|
||||
const MAX_MSG_LEN = 4000;
|
||||
|
||||
const flushText = async () => {
|
||||
if (currentMsgId === null) {
|
||||
// First chunk — create the message immediately (no throttle on init).
|
||||
currentMsgId = await sendTextMessage(rt, chatId, accText);
|
||||
lastPatch = Date.now();
|
||||
return;
|
||||
}
|
||||
const now = Date.now();
|
||||
if (now - lastPatch < PATCH_INTERVAL || currentMsgId === null) return;
|
||||
if (now - lastPatch < PATCH_INTERVAL) return;
|
||||
lastPatch = now;
|
||||
if (accText.length > MAX_MSG_LEN) {
|
||||
await patchTextMessage(rt, currentMsgId, accText.slice(0, MAX_MSG_LEN));
|
||||
@@ -294,9 +300,14 @@ export function makeTriggerHandler(deps: TriggerDeps) {
|
||||
},
|
||||
})
|
||||
.then(async (result) => {
|
||||
// Final flush: make sure the last message has complete text.
|
||||
// Final flush: patch the last message with complete text, or send
|
||||
// the full response if no streaming message was created yet.
|
||||
if (currentMsgId !== null && accText.length > 0) {
|
||||
await patchTextMessage(rt, currentMsgId, accText);
|
||||
} else if (currentMsgId === null && result.text.length > 0) {
|
||||
// No text-delta was received (e.g. model returned all at once).
|
||||
// Send the full text now.
|
||||
await sendTextMessage(rt, chatId, result.text);
|
||||
}
|
||||
await deps.prisma.agentRun.update({
|
||||
where: { id: run.id },
|
||||
|
||||
@@ -42,6 +42,16 @@ async function main(): Promise<void> {
|
||||
|
||||
const app = Fastify({ logger: true });
|
||||
|
||||
// Startup reset: clear stale locks + mark dead runs as FAILED.
|
||||
await prisma.projectAgentLock.deleteMany({});
|
||||
await prisma.agentRun.updateMany({
|
||||
where: { status: "ACTIVE" },
|
||||
data: { status: "FAILED", error: "process restart", finishedAt: new Date() },
|
||||
});
|
||||
app.log.info("startup: cleared stale locks + dead runs");
|
||||
|
||||
|
||||
|
||||
app.get("/api/healthz", async () => ({ ok: true, ts: Date.now() }));
|
||||
|
||||
// Model registry (role-as-data, ADR-0017). Claude Code SDK uses model
|
||||
|
||||
Reference in New Issue
Block a user