vibe-coding-master 0.6.1 → 0.6.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
|
@@ -21,6 +21,8 @@ const COMMANDS_ALLOWED_WHEN_DISABLED = new Set([
|
|
|
21
21
|
"projects",
|
|
22
22
|
"tasks"
|
|
23
23
|
]);
|
|
24
|
+
class HandledGatewayInboundError extends Error {
|
|
25
|
+
}
|
|
24
26
|
export function createGatewayService(deps) {
|
|
25
27
|
const now = deps.now ?? (() => new Date().toISOString());
|
|
26
28
|
const larkRegistration = deps.larkRegistration ?? createLarkRegistrationClient();
|
|
@@ -219,6 +221,10 @@ export function createGatewayService(deps) {
|
|
|
219
221
|
return;
|
|
220
222
|
}
|
|
221
223
|
const command = parseGatewayCommand(update.text);
|
|
224
|
+
if (command.kind === "plain" && settings.enabled) {
|
|
225
|
+
await handlePlainInbound(update, command.text);
|
|
226
|
+
return;
|
|
227
|
+
}
|
|
222
228
|
try {
|
|
223
229
|
const output = await executeCommand(command, settings);
|
|
224
230
|
await reply(await deps.settings.loadSettings(), update.fromUserId, output);
|
|
@@ -247,6 +253,69 @@ export function createGatewayService(deps) {
|
|
|
247
253
|
});
|
|
248
254
|
}
|
|
249
255
|
}
|
|
256
|
+
async function handlePlainInbound(update, text) {
|
|
257
|
+
try {
|
|
258
|
+
const target = await resolvePlainTextPmTarget(text);
|
|
259
|
+
if (typeof target === "string") {
|
|
260
|
+
await reply(await deps.settings.loadSettings(), update.fromUserId, target);
|
|
261
|
+
await recordInbound(update, "ok", "plain");
|
|
262
|
+
return;
|
|
263
|
+
}
|
|
264
|
+
if (target.settings.translationEnabled) {
|
|
265
|
+
await reply(await deps.settings.loadSettings(), update.fromUserId, "已收到,正在翻译...");
|
|
266
|
+
}
|
|
267
|
+
else {
|
|
268
|
+
await reply(await deps.settings.loadSettings(), update.fromUserId, "已收到,正在发送给 PM...");
|
|
269
|
+
}
|
|
270
|
+
const englishText = target.settings.translationEnabled
|
|
271
|
+
? await translatePlainTextForPm(target, text, update)
|
|
272
|
+
: text;
|
|
273
|
+
await submitTerminalInput(deps.runtime, target.session.id, englishText);
|
|
274
|
+
await reply(await deps.settings.loadSettings(), update.fromUserId, target.settings.translationEnabled
|
|
275
|
+
? formatGatewayInputTranslationSuccess(englishText)
|
|
276
|
+
: "已发送给 PM。");
|
|
277
|
+
await recordInbound(update, "ok", "plain");
|
|
278
|
+
}
|
|
279
|
+
catch (error) {
|
|
280
|
+
if (error instanceof HandledGatewayInboundError) {
|
|
281
|
+
return;
|
|
282
|
+
}
|
|
283
|
+
const message = errorMessage(error);
|
|
284
|
+
await reply(await deps.settings.loadSettings(), update.fromUserId, `Error: ${message}`);
|
|
285
|
+
await recordInbound(update, "error", "plain", message);
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
async function translatePlainTextForPm(target, text, update) {
|
|
289
|
+
try {
|
|
290
|
+
return (await deps.translationService.translateUserInput({
|
|
291
|
+
repoRoot: target.project.repoRoot,
|
|
292
|
+
taskRepoRoot: getTaskRuntimeRepoRoot(target.task),
|
|
293
|
+
taskSlug: target.task.taskSlug,
|
|
294
|
+
role: "project-manager",
|
|
295
|
+
text,
|
|
296
|
+
useContext: false,
|
|
297
|
+
send: false
|
|
298
|
+
})).englishPreview;
|
|
299
|
+
}
|
|
300
|
+
catch (error) {
|
|
301
|
+
const message = errorMessage(error);
|
|
302
|
+
await reply(await deps.settings.loadSettings(), update.fromUserId, formatGatewayInputTranslationFailure(message));
|
|
303
|
+
await recordInbound(update, "error", "plain", message);
|
|
304
|
+
throw new HandledGatewayInboundError();
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
async function recordInbound(update, result, command, error) {
|
|
308
|
+
await recordMessageStatus("inbound", result, update.text, error, command);
|
|
309
|
+
await deps.audit.record({
|
|
310
|
+
type: "gateway.command",
|
|
311
|
+
result,
|
|
312
|
+
messageId: update.messageId,
|
|
313
|
+
userId: update.fromUserId,
|
|
314
|
+
command,
|
|
315
|
+
preview: update.text,
|
|
316
|
+
error
|
|
317
|
+
});
|
|
318
|
+
}
|
|
250
319
|
async function saveInboundMetadata(settings, update, options = {}) {
|
|
251
320
|
const bindMode = options.bind ?? "never";
|
|
252
321
|
return deps.settings.saveSettings({
|
|
@@ -633,6 +702,25 @@ export function createGatewayService(deps) {
|
|
|
633
702
|
return lines.join("\n");
|
|
634
703
|
}
|
|
635
704
|
async function sendPlainTextToPm(text) {
|
|
705
|
+
const target = await resolvePlainTextPmTarget(text);
|
|
706
|
+
if (typeof target === "string") {
|
|
707
|
+
return target;
|
|
708
|
+
}
|
|
709
|
+
const englishText = target.settings.translationEnabled
|
|
710
|
+
? (await deps.translationService.translateUserInput({
|
|
711
|
+
repoRoot: target.project.repoRoot,
|
|
712
|
+
taskRepoRoot: getTaskRuntimeRepoRoot(target.task),
|
|
713
|
+
taskSlug: target.task.taskSlug,
|
|
714
|
+
role: "project-manager",
|
|
715
|
+
text,
|
|
716
|
+
useContext: false,
|
|
717
|
+
send: false
|
|
718
|
+
})).englishPreview
|
|
719
|
+
: text;
|
|
720
|
+
await submitTerminalInput(deps.runtime, target.session.id, englishText);
|
|
721
|
+
return "Sent to PM.";
|
|
722
|
+
}
|
|
723
|
+
async function resolvePlainTextPmTarget(text) {
|
|
636
724
|
if (!text.trim()) {
|
|
637
725
|
return "Empty message ignored.";
|
|
638
726
|
}
|
|
@@ -657,21 +745,13 @@ export function createGatewayService(deps) {
|
|
|
657
745
|
if (session.activityStatus === "running") {
|
|
658
746
|
return "PM is still working on the current turn. Please wait and send again later.";
|
|
659
747
|
}
|
|
660
|
-
|
|
661
|
-
? (await deps.translationService.translateUserInput({
|
|
662
|
-
repoRoot: project.repoRoot,
|
|
663
|
-
taskRepoRoot: getTaskRuntimeRepoRoot(task),
|
|
664
|
-
taskSlug: task.taskSlug,
|
|
665
|
-
role: "project-manager",
|
|
666
|
-
text,
|
|
667
|
-
useContext: false,
|
|
668
|
-
send: false
|
|
669
|
-
})).englishPreview
|
|
670
|
-
: text;
|
|
671
|
-
await submitTerminalInput(deps.runtime, session.id, englishText);
|
|
672
|
-
return "Sent to PM.";
|
|
748
|
+
return { project, task, session, settings };
|
|
673
749
|
}
|
|
674
750
|
async function reply(settings, userId, text) {
|
|
751
|
+
await sendGatewayText(settings, userId, text);
|
|
752
|
+
await recordMessageStatus("outbound", "ok", text);
|
|
753
|
+
}
|
|
754
|
+
async function sendGatewayText(settings, userId, text) {
|
|
675
755
|
const account = toAccount(settings);
|
|
676
756
|
if (!account) {
|
|
677
757
|
return;
|
|
@@ -685,7 +765,6 @@ export function createGatewayService(deps) {
|
|
|
685
765
|
contextToken,
|
|
686
766
|
text
|
|
687
767
|
});
|
|
688
|
-
await recordMessageStatus("outbound", "ok", text);
|
|
689
768
|
}
|
|
690
769
|
async function recordMessageStatus(direction, result, preview, error, command) {
|
|
691
770
|
const settings = await deps.settings.loadSettings();
|
|
@@ -1055,19 +1134,25 @@ export function createGatewayService(deps) {
|
|
|
1055
1134
|
if (!text) {
|
|
1056
1135
|
return;
|
|
1057
1136
|
}
|
|
1058
|
-
const
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1137
|
+
const roundNotice = await getGatewayRoundNotice(input.repoRoot, input.taskSlug);
|
|
1138
|
+
const originalMessage = formatGatewayPmOriginalReply(text, roundNotice);
|
|
1139
|
+
await sendGatewayText(settings, boundUserId, originalMessage);
|
|
1140
|
+
let output;
|
|
1141
|
+
if (settings.translationEnabled) {
|
|
1142
|
+
output = await renderGatewayPmOutput({
|
|
1143
|
+
settings,
|
|
1144
|
+
repoRoot: input.repoRoot,
|
|
1145
|
+
taskSlug: input.taskSlug,
|
|
1146
|
+
sourceText: text,
|
|
1147
|
+
sourceEntryIds: nextEvents.map((event) => event.id)
|
|
1148
|
+
});
|
|
1149
|
+
await sendGatewayText(settings, boundUserId, output.translationFailed
|
|
1150
|
+
? formatGatewayPmTranslationFailure(output.translationError)
|
|
1151
|
+
: formatGatewayPmTranslatedReply(output.text));
|
|
1152
|
+
}
|
|
1153
|
+
else {
|
|
1154
|
+
clearFailedTranslation(input.repoRoot, input.taskSlug);
|
|
1155
|
+
}
|
|
1071
1156
|
const lastEvent = nextEvents.at(-1);
|
|
1072
1157
|
const current = await deps.settings.loadSettings();
|
|
1073
1158
|
await deps.settings.saveSettings({
|
|
@@ -1082,19 +1167,19 @@ export function createGatewayService(deps) {
|
|
|
1082
1167
|
lastMessageStatus: {
|
|
1083
1168
|
checkedAt: now(),
|
|
1084
1169
|
direction: "outbound",
|
|
1085
|
-
result: output
|
|
1170
|
+
result: output?.translationFailed ? "error" : "ok",
|
|
1086
1171
|
command: "pm-stop",
|
|
1087
|
-
preview: output
|
|
1088
|
-
error: output
|
|
1172
|
+
preview: (output?.text ?? originalMessage).slice(0, 160),
|
|
1173
|
+
error: output?.translationError
|
|
1089
1174
|
},
|
|
1090
1175
|
updatedAt: now()
|
|
1091
1176
|
});
|
|
1092
1177
|
await deps.audit.record({
|
|
1093
1178
|
type: "gateway.pm_push",
|
|
1094
|
-
result: output
|
|
1179
|
+
result: output?.translationFailed ? "error" : "ok",
|
|
1095
1180
|
command: "pm-stop",
|
|
1096
|
-
preview: output.text,
|
|
1097
|
-
error: output
|
|
1181
|
+
preview: output ? `${originalMessage}\n\n${output.text}` : originalMessage,
|
|
1182
|
+
error: output?.translationError
|
|
1098
1183
|
});
|
|
1099
1184
|
},
|
|
1100
1185
|
getDiagnostics() {
|
|
@@ -1109,12 +1194,75 @@ export function createGatewayService(deps) {
|
|
|
1109
1194
|
}
|
|
1110
1195
|
return settings.latestPmReplies[latestPmReplyKey(settings.currentProjectId, settings.currentTaskSlug)];
|
|
1111
1196
|
}
|
|
1197
|
+
async function getGatewayRoundNotice(repoRoot, taskSlug) {
|
|
1198
|
+
try {
|
|
1199
|
+
const [projectConfig, task] = await Promise.all([
|
|
1200
|
+
deps.projectService.loadConfig(repoRoot),
|
|
1201
|
+
deps.taskService.loadTask(repoRoot, taskSlug)
|
|
1202
|
+
]);
|
|
1203
|
+
const round = await deps.roundService.getSessionRoundState({
|
|
1204
|
+
repoRoot,
|
|
1205
|
+
stateRepoRoot: getTaskRuntimeRepoRoot(task),
|
|
1206
|
+
stateRoot: projectConfig.stateRoot,
|
|
1207
|
+
taskSlug
|
|
1208
|
+
});
|
|
1209
|
+
return formatGatewayRoundNotice(round);
|
|
1210
|
+
}
|
|
1211
|
+
catch {
|
|
1212
|
+
return undefined;
|
|
1213
|
+
}
|
|
1214
|
+
}
|
|
1215
|
+
function formatGatewayRoundNotice(round) {
|
|
1216
|
+
const roundLabel = round.roundSequence ? `第 ${round.roundSequence} 轮` : "当前 round";
|
|
1217
|
+
if (round.status === "stopped") {
|
|
1218
|
+
return `${roundLabel}已结束。现在需要你给出下一步指令。`;
|
|
1219
|
+
}
|
|
1220
|
+
const activeRole = round.activeRole ? `,当前角色:${round.activeRole}` : "";
|
|
1221
|
+
return `${roundLabel}运行中${activeRole}。`;
|
|
1222
|
+
}
|
|
1223
|
+
function formatGatewayPmOriginalReply(text, roundNotice) {
|
|
1224
|
+
return [
|
|
1225
|
+
"PM final reply 原文:",
|
|
1226
|
+
"",
|
|
1227
|
+
text.trim(),
|
|
1228
|
+
roundNotice ? "" : undefined,
|
|
1229
|
+
roundNotice ? `Round: ${roundNotice}` : undefined
|
|
1230
|
+
].filter((line) => line !== undefined).join("\n");
|
|
1231
|
+
}
|
|
1232
|
+
function formatGatewayPmTranslatedReply(text) {
|
|
1233
|
+
return [
|
|
1234
|
+
"PM final reply 翻译:",
|
|
1235
|
+
"",
|
|
1236
|
+
text.trim()
|
|
1237
|
+
].join("\n");
|
|
1238
|
+
}
|
|
1239
|
+
function formatGatewayPmTranslationFailure(error) {
|
|
1240
|
+
return [
|
|
1241
|
+
GATEWAY_TRANSLATION_FAILURE_TEXT,
|
|
1242
|
+
error ? `原因:${error}` : undefined
|
|
1243
|
+
].filter((line) => line !== undefined).join("\n");
|
|
1244
|
+
}
|
|
1245
|
+
function formatGatewayInputTranslationSuccess(englishText) {
|
|
1246
|
+
return [
|
|
1247
|
+
"翻译完成,已发送给 PM:",
|
|
1248
|
+
"",
|
|
1249
|
+
englishText.trim()
|
|
1250
|
+
].join("\n");
|
|
1251
|
+
}
|
|
1252
|
+
function formatGatewayInputTranslationFailure(error) {
|
|
1253
|
+
return [
|
|
1254
|
+
"翻译失败,消息未发送给 PM。",
|
|
1255
|
+
`原因:${error}`,
|
|
1256
|
+
"请重新输入后再试。"
|
|
1257
|
+
].join("\n");
|
|
1258
|
+
}
|
|
1112
1259
|
async function renderLatestPmReply(settings, reply) {
|
|
1113
1260
|
const rendered = await renderGatewayPmOutput({
|
|
1114
1261
|
settings,
|
|
1115
1262
|
repoRoot: reply.repoRoot,
|
|
1116
1263
|
taskSlug: reply.taskSlug,
|
|
1117
|
-
sourceText: reply.text
|
|
1264
|
+
sourceText: reply.text,
|
|
1265
|
+
sourceEntryIds: reply.transcriptEventId ? [reply.transcriptEventId] : undefined
|
|
1118
1266
|
});
|
|
1119
1267
|
return {
|
|
1120
1268
|
...rendered,
|
|
@@ -1134,7 +1282,8 @@ export function createGatewayService(deps) {
|
|
|
1134
1282
|
repoRoot: input.repoRoot,
|
|
1135
1283
|
taskSlug: input.taskSlug,
|
|
1136
1284
|
role: "project-manager",
|
|
1137
|
-
text: input.sourceText
|
|
1285
|
+
text: input.sourceText,
|
|
1286
|
+
sourceEntryIds: input.sourceEntryIds
|
|
1138
1287
|
});
|
|
1139
1288
|
clearFailedTranslation(input.repoRoot, input.taskSlug);
|
|
1140
1289
|
return {
|
|
@@ -13,6 +13,8 @@ const TRANSLATION_MODEL = "translator";
|
|
|
13
13
|
const OUTPUT_TRANSLATION_BATCH_DELAY_MS = 10000;
|
|
14
14
|
const TRANSCRIPT_REPLAY_GRACE_MS = 5000;
|
|
15
15
|
const TRANSLATION_TASK_FEED_RETENTION_LIMIT = 2000;
|
|
16
|
+
const GATEWAY_TRANSLATION_REUSE_GRACE_MS = 1500;
|
|
17
|
+
const GATEWAY_TRANSLATION_REUSE_POLL_MS = 50;
|
|
16
18
|
export function createTranslationService(deps) {
|
|
17
19
|
const now = deps.now ?? (() => new Date().toISOString());
|
|
18
20
|
const id = deps.id ?? (() => `tr_${Date.now()}_${Math.random().toString(16).slice(2)}`);
|
|
@@ -1004,6 +1006,10 @@ export function createTranslationService(deps) {
|
|
|
1004
1006
|
},
|
|
1005
1007
|
async translateGatewayOutput(input) {
|
|
1006
1008
|
const config = await loadConfig();
|
|
1009
|
+
const reusable = await findReusableGatewayOutputTranslation(input, config);
|
|
1010
|
+
if (reusable) {
|
|
1011
|
+
return reusable.trim();
|
|
1012
|
+
}
|
|
1007
1013
|
const translation = await translateText({
|
|
1008
1014
|
repoRoot: input.repoRoot,
|
|
1009
1015
|
taskSlug: input.taskSlug,
|
|
@@ -1033,6 +1039,148 @@ export function createTranslationService(deps) {
|
|
|
1033
1039
|
};
|
|
1034
1040
|
}
|
|
1035
1041
|
};
|
|
1042
|
+
async function findReusableGatewayOutputTranslation(input, config) {
|
|
1043
|
+
const graceDeadline = Date.now() + (input.sourceEntryIds?.length ? GATEWAY_TRANSLATION_REUSE_GRACE_MS : 0);
|
|
1044
|
+
while (true) {
|
|
1045
|
+
const lookup = await lookupGatewayOutputTranslation(input);
|
|
1046
|
+
if (lookup.kind === "translated") {
|
|
1047
|
+
return lookup.text;
|
|
1048
|
+
}
|
|
1049
|
+
if (lookup.kind === "active") {
|
|
1050
|
+
return waitForReusableGatewayOutputTranslation(input, config);
|
|
1051
|
+
}
|
|
1052
|
+
if (!input.sourceEntryIds?.length || Date.now() >= graceDeadline) {
|
|
1053
|
+
return undefined;
|
|
1054
|
+
}
|
|
1055
|
+
await delay(GATEWAY_TRANSLATION_REUSE_POLL_MS);
|
|
1056
|
+
}
|
|
1057
|
+
}
|
|
1058
|
+
async function waitForReusableGatewayOutputTranslation(input, config) {
|
|
1059
|
+
const deadline = Date.now() + config.requestTimeoutMs;
|
|
1060
|
+
while (Date.now() <= deadline) {
|
|
1061
|
+
const lookup = await lookupGatewayOutputTranslation(input);
|
|
1062
|
+
if (lookup.kind === "translated") {
|
|
1063
|
+
return lookup.text;
|
|
1064
|
+
}
|
|
1065
|
+
if (lookup.kind !== "active") {
|
|
1066
|
+
return undefined;
|
|
1067
|
+
}
|
|
1068
|
+
await delay(GATEWAY_TRANSLATION_REUSE_POLL_MS);
|
|
1069
|
+
}
|
|
1070
|
+
throw new VcmError({
|
|
1071
|
+
code: "TRANSLATION_TIMEOUT",
|
|
1072
|
+
message: "Gateway output translation timed out while waiting for the existing PM reply translation.",
|
|
1073
|
+
statusCode: 504
|
|
1074
|
+
});
|
|
1075
|
+
}
|
|
1076
|
+
async function lookupGatewayOutputTranslation(input) {
|
|
1077
|
+
const states = await getGatewayOutputCandidateStates(input);
|
|
1078
|
+
return selectGatewayOutputTranslation(states, input);
|
|
1079
|
+
}
|
|
1080
|
+
async function getGatewayOutputCandidateStates(input) {
|
|
1081
|
+
const states = [];
|
|
1082
|
+
const seen = new Set();
|
|
1083
|
+
const add = (state) => {
|
|
1084
|
+
if (seen.has(state) || !isGatewayOutputCandidateState(state, input)) {
|
|
1085
|
+
return;
|
|
1086
|
+
}
|
|
1087
|
+
seen.add(state);
|
|
1088
|
+
states.push(state);
|
|
1089
|
+
};
|
|
1090
|
+
const roleSession = await getGatewayRoleSession(input);
|
|
1091
|
+
if (roleSession) {
|
|
1092
|
+
const state = await prepareCache({
|
|
1093
|
+
repoRoot: roleSession.cwd,
|
|
1094
|
+
baseRepoRoot: input.repoRoot,
|
|
1095
|
+
taskSlug: input.taskSlug,
|
|
1096
|
+
role: input.role,
|
|
1097
|
+
sessionId: roleSession.id
|
|
1098
|
+
});
|
|
1099
|
+
if (roleSession.status === "running") {
|
|
1100
|
+
startTranscriptTail(roleSession);
|
|
1101
|
+
}
|
|
1102
|
+
add(state);
|
|
1103
|
+
}
|
|
1104
|
+
for (const state of sessionStates.values()) {
|
|
1105
|
+
add(state);
|
|
1106
|
+
}
|
|
1107
|
+
return states;
|
|
1108
|
+
}
|
|
1109
|
+
async function getGatewayRoleSession(input) {
|
|
1110
|
+
try {
|
|
1111
|
+
return await deps.sessionService.getRoleSession(input.repoRoot, input.taskSlug, input.role);
|
|
1112
|
+
}
|
|
1113
|
+
catch {
|
|
1114
|
+
return undefined;
|
|
1115
|
+
}
|
|
1116
|
+
}
|
|
1117
|
+
function isGatewayOutputCandidateState(state, input) {
|
|
1118
|
+
if (state.taskSlug !== input.taskSlug || state.role !== input.role) {
|
|
1119
|
+
return false;
|
|
1120
|
+
}
|
|
1121
|
+
return state.baseRepoRoot === input.repoRoot
|
|
1122
|
+
|| state.repoRoot === input.repoRoot
|
|
1123
|
+
|| Boolean(state.repoRoot?.startsWith(`${input.repoRoot}${path.sep}`));
|
|
1124
|
+
}
|
|
1125
|
+
function selectGatewayOutputTranslation(states, input) {
|
|
1126
|
+
const sourceEntryIds = normalizeGatewaySourceEntryIds(input.sourceEntryIds);
|
|
1127
|
+
if (sourceEntryIds.length > 0) {
|
|
1128
|
+
const entries = sourceEntryIds
|
|
1129
|
+
.map((entryId) => findGatewayOutputEntryById(states, input, entryId));
|
|
1130
|
+
const foundEntries = entries.filter((entry) => Boolean(entry));
|
|
1131
|
+
if (foundEntries.length === sourceEntryIds.length) {
|
|
1132
|
+
if (foundEntries.some(isActiveTranslationEntry)) {
|
|
1133
|
+
return { kind: "active" };
|
|
1134
|
+
}
|
|
1135
|
+
if (foundEntries.every(isReusableGatewayOutputEntry)) {
|
|
1136
|
+
return {
|
|
1137
|
+
kind: "translated",
|
|
1138
|
+
text: foundEntries.map((entry) => entry.translatedText).join("\n\n")
|
|
1139
|
+
};
|
|
1140
|
+
}
|
|
1141
|
+
}
|
|
1142
|
+
else if (foundEntries.some(isActiveTranslationEntry)) {
|
|
1143
|
+
return { kind: "active" };
|
|
1144
|
+
}
|
|
1145
|
+
}
|
|
1146
|
+
const normalizedSourceText = normalizeGatewaySourceText(input.text);
|
|
1147
|
+
const sourceMatches = states
|
|
1148
|
+
.flatMap((state) => state.entries)
|
|
1149
|
+
.filter((entry) => isGatewayOutputEntry(entry, input)
|
|
1150
|
+
&& normalizeGatewaySourceText(entry.sourceText) === normalizedSourceText);
|
|
1151
|
+
const translated = [...sourceMatches].reverse().find(isReusableGatewayOutputEntry);
|
|
1152
|
+
if (translated) {
|
|
1153
|
+
return { kind: "translated", text: translated.translatedText };
|
|
1154
|
+
}
|
|
1155
|
+
if (sourceMatches.some(isActiveTranslationEntry)) {
|
|
1156
|
+
return { kind: "active" };
|
|
1157
|
+
}
|
|
1158
|
+
return { kind: "missing" };
|
|
1159
|
+
}
|
|
1160
|
+
function findGatewayOutputEntryById(states, input, entryId) {
|
|
1161
|
+
for (const state of states) {
|
|
1162
|
+
const entry = state.entries.find((candidate) => candidate.id === entryId && isGatewayOutputEntry(candidate, input));
|
|
1163
|
+
if (entry) {
|
|
1164
|
+
return entry;
|
|
1165
|
+
}
|
|
1166
|
+
}
|
|
1167
|
+
return undefined;
|
|
1168
|
+
}
|
|
1169
|
+
function isGatewayOutputEntry(entry, input) {
|
|
1170
|
+
return entry.taskSlug === input.taskSlug
|
|
1171
|
+
&& entry.role === input.role
|
|
1172
|
+
&& entry.direction === "cc-output-to-user"
|
|
1173
|
+
&& entry.sourceKind === "prose";
|
|
1174
|
+
}
|
|
1175
|
+
function isReusableGatewayOutputEntry(entry) {
|
|
1176
|
+
return entry.status === "translated" && Boolean(entry.translatedText.trim());
|
|
1177
|
+
}
|
|
1178
|
+
function normalizeGatewaySourceEntryIds(sourceEntryIds) {
|
|
1179
|
+
return Array.from(new Set((sourceEntryIds ?? []).map((entryId) => entryId.trim()).filter(Boolean)));
|
|
1180
|
+
}
|
|
1181
|
+
function normalizeGatewaySourceText(text) {
|
|
1182
|
+
return text.trim();
|
|
1183
|
+
}
|
|
1036
1184
|
async function writeToCurrentRole(repoRoot, taskSlug, role, text) {
|
|
1037
1185
|
const record = await deps.sessionService.getRoleSession(repoRoot, taskSlug, role);
|
|
1038
1186
|
if (!record || record.status !== "running") {
|