vibe-coding-master 0.4.40 → 0.4.42
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.
|
@@ -17,6 +17,17 @@ export function registerTranslationRoutes(app, deps) {
|
|
|
17
17
|
const project = await requireCurrentProject(deps.projectService);
|
|
18
18
|
return deps.translationService.pollSessionEvents(request.params.sessionId, Number(request.query.after ?? "1"), request.query.limit === undefined ? undefined : Number(request.query.limit), { repoRoot: project.repoRoot });
|
|
19
19
|
});
|
|
20
|
+
app.get("/api/tasks/:taskSlug/translation/feed", async (request) => {
|
|
21
|
+
const project = await requireCurrentProject(deps.projectService);
|
|
22
|
+
const task = await deps.taskService.loadTask(project.repoRoot, request.params.taskSlug);
|
|
23
|
+
return deps.translationService.pollTaskFeed({
|
|
24
|
+
repoRoot: project.repoRoot,
|
|
25
|
+
taskRepoRoot: getTaskRuntimeRepoRoot(task),
|
|
26
|
+
taskSlug: request.params.taskSlug,
|
|
27
|
+
after: Number(request.query.after ?? "1"),
|
|
28
|
+
limit: request.query.limit === undefined ? undefined : Number(request.query.limit)
|
|
29
|
+
});
|
|
30
|
+
});
|
|
20
31
|
app.post("/api/tasks/:taskSlug/sessions/:role/translation/input", async (request) => {
|
|
21
32
|
const project = await requireCurrentProject(deps.projectService);
|
|
22
33
|
const role = parseRole(request.params.role);
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import path from "node:path";
|
|
2
|
+
import { isVcmRoleName } from "../../shared/constants.js";
|
|
2
3
|
import { TRANSLATION_ENTRY_RETENTION_LIMIT } from "../../shared/types/translation.js";
|
|
3
4
|
import { VcmError } from "../errors.js";
|
|
4
5
|
import { submitTerminalInput } from "../runtime/terminal-submit.js";
|
|
@@ -11,12 +12,14 @@ const TRANSLATION_PROVIDER = "claude-code";
|
|
|
11
12
|
const TRANSLATION_MODEL = "translator";
|
|
12
13
|
const OUTPUT_TRANSLATION_BATCH_DELAY_MS = 10000;
|
|
13
14
|
const TRANSCRIPT_REPLAY_GRACE_MS = 5000;
|
|
15
|
+
const TRANSLATION_TASK_FEED_RETENTION_LIMIT = 2000;
|
|
14
16
|
export function createTranslationService(deps) {
|
|
15
17
|
const now = deps.now ?? (() => new Date().toISOString());
|
|
16
18
|
const id = deps.id ?? (() => `tr_${Date.now()}_${Math.random().toString(16).slice(2)}`);
|
|
17
19
|
const outputBatchDelayMs = Math.max(0, deps.outputBatchDelayMs ?? OUTPUT_TRANSLATION_BATCH_DELAY_MS);
|
|
18
20
|
const queues = createTranslationQueueRegistry();
|
|
19
21
|
const sessionStates = new Map();
|
|
22
|
+
const taskFeeds = new Map();
|
|
20
23
|
async function loadConfig() {
|
|
21
24
|
const preferences = await deps.appSettings.getPreferences();
|
|
22
25
|
return {
|
|
@@ -79,9 +82,49 @@ export function createTranslationService(deps) {
|
|
|
79
82
|
createdAt: now()
|
|
80
83
|
};
|
|
81
84
|
state.events.push(event);
|
|
85
|
+
appendTaskFeedEvent(sessionId, state, event);
|
|
82
86
|
void persistEvents(state);
|
|
83
87
|
return event;
|
|
84
88
|
}
|
|
89
|
+
function appendTaskFeedEvent(sessionId, state, event) {
|
|
90
|
+
if (!state.repoRoot || !state.taskSlug || !state.role) {
|
|
91
|
+
return;
|
|
92
|
+
}
|
|
93
|
+
const feed = getTaskFeed(state.repoRoot, state.taskSlug);
|
|
94
|
+
const sessionEventKey = getTaskFeedSessionEventKey(sessionId, event);
|
|
95
|
+
if (feed.seenSessionEvents.has(sessionEventKey)) {
|
|
96
|
+
return;
|
|
97
|
+
}
|
|
98
|
+
feed.seenSessionEvents.add(sessionEventKey);
|
|
99
|
+
feed.events.push({
|
|
100
|
+
seq: feed.nextSeq++,
|
|
101
|
+
sessionId,
|
|
102
|
+
role: state.role,
|
|
103
|
+
event
|
|
104
|
+
});
|
|
105
|
+
if (feed.events.length > TRANSLATION_TASK_FEED_RETENTION_LIMIT) {
|
|
106
|
+
feed.events = feed.events.slice(-TRANSLATION_TASK_FEED_RETENTION_LIMIT);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
function syncTaskFeedFromSessionState(sessionId, state) {
|
|
110
|
+
for (const event of state.events) {
|
|
111
|
+
appendTaskFeedEvent(sessionId, state, event);
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
function getTaskFeed(repoRoot, taskSlug) {
|
|
115
|
+
const key = getTaskFeedKey(repoRoot, taskSlug);
|
|
116
|
+
const current = taskFeeds.get(key);
|
|
117
|
+
if (current) {
|
|
118
|
+
return current;
|
|
119
|
+
}
|
|
120
|
+
const created = {
|
|
121
|
+
events: [],
|
|
122
|
+
nextSeq: 1,
|
|
123
|
+
seenSessionEvents: new Set()
|
|
124
|
+
};
|
|
125
|
+
taskFeeds.set(key, created);
|
|
126
|
+
return created;
|
|
127
|
+
}
|
|
85
128
|
async function prepareCache(input) {
|
|
86
129
|
const state = getState(input.sessionId);
|
|
87
130
|
state.repoRoot = input.repoRoot;
|
|
@@ -99,6 +142,7 @@ export function createTranslationService(deps) {
|
|
|
99
142
|
state.cacheLoaded = true;
|
|
100
143
|
pruneTranslationEntries(input.sessionId);
|
|
101
144
|
}
|
|
145
|
+
syncTaskFeedFromSessionState(input.sessionId, state);
|
|
102
146
|
await deps.fs.ensureDir(path.dirname(cachePath));
|
|
103
147
|
return state;
|
|
104
148
|
}
|
|
@@ -124,6 +168,7 @@ export function createTranslationService(deps) {
|
|
|
124
168
|
for (const event of state.events) {
|
|
125
169
|
if (event.type === "entry") {
|
|
126
170
|
state.entries = upsertEntry(state.entries, event.entry);
|
|
171
|
+
state.seenTranscriptIds.add(event.entry.id);
|
|
127
172
|
}
|
|
128
173
|
else if (event.type === "status") {
|
|
129
174
|
state.status = event.status;
|
|
@@ -159,10 +204,20 @@ export function createTranslationService(deps) {
|
|
|
159
204
|
const state = getState(roleSession.id);
|
|
160
205
|
state.taskSlug = roleSession.taskSlug;
|
|
161
206
|
state.role = roleSession.role;
|
|
162
|
-
|
|
207
|
+
const transcriptSessionKey = getTranscriptSessionKey(roleSession);
|
|
208
|
+
if (!transcriptSessionKey) {
|
|
163
209
|
return;
|
|
164
210
|
}
|
|
211
|
+
if (state.unsubscribeTranscript) {
|
|
212
|
+
if (state.transcriptSessionKey === transcriptSessionKey) {
|
|
213
|
+
return;
|
|
214
|
+
}
|
|
215
|
+
state.unsubscribeTranscript();
|
|
216
|
+
state.unsubscribeTranscript = undefined;
|
|
217
|
+
state.transcriptSessionKey = undefined;
|
|
218
|
+
}
|
|
165
219
|
const replaySince = getTranscriptReplaySince(roleSession);
|
|
220
|
+
state.transcriptSessionKey = transcriptSessionKey;
|
|
166
221
|
state.unsubscribeTranscript = deps.transcripts.subscribeToRoleSession(roleSession, (event) => {
|
|
167
222
|
void handleTranscriptEvent(roleSession.id, event).catch((error) => {
|
|
168
223
|
publishError(roleSession.id, normalizeTranslationError(error, "Process Claude transcript event for translation failed."));
|
|
@@ -537,6 +592,7 @@ export function createTranslationService(deps) {
|
|
|
537
592
|
state.unsubscribeTranscript();
|
|
538
593
|
state.unsubscribeTranscript = undefined;
|
|
539
594
|
}
|
|
595
|
+
state.transcriptSessionKey = undefined;
|
|
540
596
|
if (state.outputBatch?.timer) {
|
|
541
597
|
clearTimeout(state.outputBatch.timer);
|
|
542
598
|
}
|
|
@@ -617,6 +673,43 @@ export function createTranslationService(deps) {
|
|
|
617
673
|
events
|
|
618
674
|
};
|
|
619
675
|
},
|
|
676
|
+
async pollTaskFeed(input) {
|
|
677
|
+
const cursor = Number.isFinite(input.after) ? Math.max(1, Math.floor(input.after)) : 1;
|
|
678
|
+
const maxEvents = Math.min(Math.max(1, Math.floor(input.limit ?? 500)), 1000);
|
|
679
|
+
const roleSessions = await deps.sessionService.listRoleSessions(input.repoRoot, input.taskSlug);
|
|
680
|
+
const feedSessions = [];
|
|
681
|
+
for (const roleSession of roleSessions) {
|
|
682
|
+
if (!isVcmRoleName(roleSession.role)) {
|
|
683
|
+
continue;
|
|
684
|
+
}
|
|
685
|
+
const state = await prepareCache({
|
|
686
|
+
repoRoot: input.taskRepoRoot,
|
|
687
|
+
baseRepoRoot: input.repoRoot,
|
|
688
|
+
taskSlug: input.taskSlug,
|
|
689
|
+
role: roleSession.role,
|
|
690
|
+
sessionId: roleSession.id
|
|
691
|
+
});
|
|
692
|
+
if (roleSession.status === "running") {
|
|
693
|
+
startTranscriptTail(roleSession);
|
|
694
|
+
}
|
|
695
|
+
feedSessions.push({
|
|
696
|
+
sessionId: roleSession.id,
|
|
697
|
+
role: roleSession.role,
|
|
698
|
+
status: state.status
|
|
699
|
+
});
|
|
700
|
+
}
|
|
701
|
+
const feed = getTaskFeed(input.taskRepoRoot, input.taskSlug);
|
|
702
|
+
const events = feed.events
|
|
703
|
+
.filter((event) => event.seq >= cursor)
|
|
704
|
+
.slice(0, maxEvents);
|
|
705
|
+
const nextCursor = events.length > 0 ? (events.at(-1)?.seq ?? cursor) + 1 : cursor;
|
|
706
|
+
return {
|
|
707
|
+
taskSlug: input.taskSlug,
|
|
708
|
+
nextCursor,
|
|
709
|
+
sessions: feedSessions,
|
|
710
|
+
events
|
|
711
|
+
};
|
|
712
|
+
},
|
|
620
713
|
async recordConversationBoundary(input) {
|
|
621
714
|
const config = await loadConfig();
|
|
622
715
|
const state = await prepareCache({
|
|
@@ -1028,13 +1121,32 @@ function delay(ms) {
|
|
|
1028
1121
|
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
1029
1122
|
}
|
|
1030
1123
|
function getTranscriptReplaySince(roleSession) {
|
|
1031
|
-
const rawTimestamp = roleSession.
|
|
1124
|
+
const rawTimestamp = roleSession.lastTurnStartedAt
|
|
1125
|
+
?? roleSession.lastHookEventAt
|
|
1126
|
+
?? roleSession.updatedAt
|
|
1127
|
+
?? roleSession.startedAt;
|
|
1032
1128
|
const timestampMs = Date.parse(rawTimestamp);
|
|
1033
1129
|
if (!Number.isFinite(timestampMs)) {
|
|
1034
1130
|
return undefined;
|
|
1035
1131
|
}
|
|
1036
1132
|
return new Date(Math.max(0, timestampMs - TRANSCRIPT_REPLAY_GRACE_MS)).toISOString();
|
|
1037
1133
|
}
|
|
1134
|
+
function getTranscriptSessionKey(roleSession) {
|
|
1135
|
+
if (!roleSession.claudeSessionId && !roleSession.transcriptPath) {
|
|
1136
|
+
return undefined;
|
|
1137
|
+
}
|
|
1138
|
+
return [
|
|
1139
|
+
roleSession.cwd,
|
|
1140
|
+
roleSession.claudeSessionId,
|
|
1141
|
+
roleSession.transcriptPath
|
|
1142
|
+
].join("\n");
|
|
1143
|
+
}
|
|
1144
|
+
function getTaskFeedKey(repoRoot, taskSlug) {
|
|
1145
|
+
return `${repoRoot}\n${taskSlug}`;
|
|
1146
|
+
}
|
|
1147
|
+
function getTaskFeedSessionEventKey(sessionId, event) {
|
|
1148
|
+
return `${sessionId}:${event.seq}`;
|
|
1149
|
+
}
|
|
1038
1150
|
function formatStructuredTranscriptEvent(event) {
|
|
1039
1151
|
if (event.kind === "question") {
|
|
1040
1152
|
return event.question.questions.map((question, index) => {
|