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
- if (state.unsubscribeTranscript) {
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.startedAt ?? roleSession.updatedAt;
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) => {