Task 資料表與自然語句解析(單次提醒)、依原型的接案/觸發/逾期追擊演出、 委託與關係帳本/情緒的回饋迴路(含默契旗標)、主動關照(待辦偵測+作息交叉比對, 稀有度節流不重複台詞)、排程器可靠性(服務重啟後重新掃描 PENDING 任務掛回 JobQueue,逾期立即補觸發)。J.mjs 以 spawnSync 實際重啟服務驗證 J-7。 Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
71 lines
2.7 KiB
TypeScript
71 lines
2.7 KiB
TypeScript
import { Injectable, NotFoundException } from "@nestjs/common";
|
|
import { PrismaService } from "../prisma/prisma.service.js";
|
|
import { SentimentLedgerService } from "../relationship/sentiment-ledger.service.js";
|
|
import { EmotionService } from "../emotion/emotion.service.js";
|
|
import { TASK_LEDGER_EVENT, TASK_LEDGER_WEIGHT } from "./constants.js";
|
|
|
|
// J-5 任務與記憶、關係的迴路:完成/失敗誠實回報(引擎層),但角色演出可以是遺憾/得意的語氣。
|
|
@Injectable()
|
|
export class TaskFeedbackService {
|
|
constructor(
|
|
private readonly prisma: PrismaService,
|
|
private readonly sentimentLedger: SentimentLedgerService,
|
|
private readonly emotion: EmotionService,
|
|
) {}
|
|
|
|
async complete(taskId: string, now: Date = new Date()) {
|
|
const task = await this.prisma.client.task.findUnique({ where: { id: taskId } });
|
|
if (!task) {
|
|
throw new NotFoundException(`任務不存在:${taskId}`);
|
|
}
|
|
await this.prisma.client.task.update({
|
|
where: { id: taskId },
|
|
data: { status: "COMPLETED", completedAt: now },
|
|
});
|
|
await this.sentimentLedger.recordEvent(
|
|
task.characterId,
|
|
task.userId,
|
|
TASK_LEDGER_EVENT.COMPLETED,
|
|
TASK_LEDGER_WEIGHT.COMPLETED,
|
|
{ now },
|
|
);
|
|
return this.prisma.client.task.findUnique({ where: { id: taskId } });
|
|
}
|
|
|
|
async fail(taskId: string, now: Date = new Date()) {
|
|
const task = await this.prisma.client.task.findUnique({ where: { id: taskId } });
|
|
if (!task) {
|
|
throw new NotFoundException(`任務不存在:${taskId}`);
|
|
}
|
|
await this.prisma.client.task.update({ where: { id: taskId }, data: { status: "FAILED" } });
|
|
// 誠實回報失敗(引擎層行為固定),負向偏誤已由 E-2 機制放大,不需要在這裡重複加權。
|
|
await this.sentimentLedger.recordEvent(
|
|
task.characterId,
|
|
task.userId,
|
|
TASK_LEDGER_EVENT.FAILED,
|
|
TASK_LEDGER_WEIGHT.FAILED,
|
|
{ now },
|
|
);
|
|
return this.prisma.client.task.findUnique({ where: { id: taskId } });
|
|
}
|
|
|
|
// 觸發後被忽視(追擊次數已達門檻仍無回應):輕微失落,且如實記錄一筆負向事件。
|
|
async markIgnored(taskId: string, now: Date = new Date()) {
|
|
const task = await this.prisma.client.task.findUnique({ where: { id: taskId } });
|
|
if (!task) {
|
|
throw new NotFoundException(`任務不存在:${taskId}`);
|
|
}
|
|
await this.sentimentLedger.recordEvent(
|
|
task.characterId,
|
|
task.userId,
|
|
TASK_LEDGER_EVENT.IGNORED,
|
|
TASK_LEDGER_WEIGHT.IGNORED,
|
|
{ now },
|
|
);
|
|
await this.emotion.processInput(task.characterId, "", {
|
|
now,
|
|
baselineSignal: { tag: "SAD", intensity: 0.3 },
|
|
});
|
|
}
|
|
}
|