vibe-coding-master 0.0.8 → 0.0.10
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.
- package/README.md +35 -24
- package/dist/backend/adapters/filesystem.js +0 -7
- package/dist/backend/api/app-settings-routes.js +8 -0
- package/dist/backend/api/message-routes.js +3 -1
- package/dist/backend/api/session-routes.js +7 -1
- package/dist/backend/api/task-routes.js +3 -10
- package/dist/backend/api/translation-routes.js +21 -3
- package/dist/backend/runtime/terminal-submit.js +20 -0
- package/dist/backend/server.js +8 -4
- package/dist/backend/services/app-settings-service.js +118 -15
- package/dist/backend/services/claude-transcript-service.js +12 -8
- package/dist/backend/services/command-dispatcher.js +2 -1
- package/dist/backend/services/message-service.js +10 -6
- package/dist/backend/services/project-service.js +5 -27
- package/dist/backend/services/session-service.js +7 -4
- package/dist/backend/services/task-service.js +66 -57
- package/dist/backend/services/translation-service.js +264 -77
- package/dist/backend/templates/harness/gitignore.js +1 -4
- package/dist/shared/types/app-settings.js +1 -0
- package/dist-frontend/assets/index-B1vIIwLq.js +88 -0
- package/dist-frontend/assets/index-DPyKuEOz.css +32 -0
- package/dist-frontend/index.html +2 -2
- package/docs/cc-best-practices.md +4 -4
- package/docs/product-design.md +71 -31
- package/docs/v1-architecture-design.md +90 -56
- package/docs/v1-implementation-plan.md +76 -62
- package/package.json +3 -1
- package/dist/backend/ws/translation-ws.js +0 -35
- package/dist-frontend/assets/index-CuiNNOzj.css +0 -32
- package/dist-frontend/assets/index-D59GuHCR.js +0 -58
|
@@ -1,11 +1,13 @@
|
|
|
1
|
+
import path from "node:path";
|
|
1
2
|
import { TRANSLATION_PROMPT_KEYS } from "../../shared/types/translation.js";
|
|
2
3
|
import { TranslationProviderError } from "../adapters/translation-provider.js";
|
|
3
4
|
import { VcmError } from "../errors.js";
|
|
5
|
+
import { submitTerminalInput } from "../runtime/terminal-submit.js";
|
|
4
6
|
import { buildTranslationPrompt, getTranslationPromptPreviews, parseTranslationWarning } from "./translation-prompts.js";
|
|
5
7
|
import { createTranslationQueueRegistry } from "./translation-queue.js";
|
|
6
8
|
const DEFAULT_SETTINGS = {
|
|
7
9
|
version: 1,
|
|
8
|
-
enabled:
|
|
10
|
+
enabled: true,
|
|
9
11
|
providerType: "openai-compatible",
|
|
10
12
|
baseUrl: "https://api.openai.com/v1",
|
|
11
13
|
model: "gpt-4o-mini",
|
|
@@ -59,7 +61,10 @@ export function createTranslationService(deps) {
|
|
|
59
61
|
state = {
|
|
60
62
|
listeners: new Set(),
|
|
61
63
|
seenTranscriptIds: new Set(),
|
|
62
|
-
entries: []
|
|
64
|
+
entries: [],
|
|
65
|
+
status: "ready",
|
|
66
|
+
events: [],
|
|
67
|
+
nextSeq: 1
|
|
63
68
|
};
|
|
64
69
|
sessionStates.set(sessionId, state);
|
|
65
70
|
}
|
|
@@ -71,40 +76,154 @@ export function createTranslationService(deps) {
|
|
|
71
76
|
listener(message);
|
|
72
77
|
}
|
|
73
78
|
}
|
|
74
|
-
|
|
79
|
+
function publishEntry(sessionId, entry) {
|
|
80
|
+
appendEvent(sessionId, { type: "entry", entry });
|
|
81
|
+
emit(sessionId, { type: "translation-entry", entry });
|
|
82
|
+
}
|
|
83
|
+
function publishStatus(sessionId, status) {
|
|
75
84
|
const state = getState(sessionId);
|
|
76
|
-
|
|
85
|
+
state.status = status;
|
|
86
|
+
appendEvent(sessionId, { type: "status", status });
|
|
87
|
+
emit(sessionId, { type: "translation-status", status });
|
|
88
|
+
}
|
|
89
|
+
function publishError(sessionId, message, id) {
|
|
90
|
+
const state = getState(sessionId);
|
|
91
|
+
state.status = "failed";
|
|
92
|
+
appendEvent(sessionId, { type: "error", id, message });
|
|
93
|
+
emit(sessionId, { type: "translation-error", id, message });
|
|
94
|
+
}
|
|
95
|
+
function appendEvent(sessionId, input) {
|
|
96
|
+
const state = getState(sessionId);
|
|
97
|
+
const event = {
|
|
98
|
+
...input,
|
|
99
|
+
seq: state.nextSeq++,
|
|
100
|
+
createdAt: now()
|
|
101
|
+
};
|
|
102
|
+
state.events.push(event);
|
|
103
|
+
void persistEvents(state);
|
|
104
|
+
return event;
|
|
105
|
+
}
|
|
106
|
+
async function prepareCache(input) {
|
|
107
|
+
const state = getState(input.sessionId);
|
|
108
|
+
state.repoRoot = input.repoRoot;
|
|
109
|
+
state.taskSlug = input.taskSlug;
|
|
110
|
+
state.role = input.role;
|
|
111
|
+
if (!deps.fs || !deps.projectService) {
|
|
112
|
+
return state;
|
|
113
|
+
}
|
|
114
|
+
const config = await deps.projectService.loadConfig(input.repoRoot);
|
|
115
|
+
const cachePath = getTranslationCachePath(input.repoRoot, config.stateRoot, input.taskSlug, input.role, input.sessionId);
|
|
116
|
+
state.cachePath = cachePath;
|
|
117
|
+
if (!state.cacheLoaded) {
|
|
118
|
+
await loadCachedEvents(state);
|
|
119
|
+
state.cacheLoaded = true;
|
|
120
|
+
}
|
|
121
|
+
await deps.fs.ensureDir(path.dirname(cachePath));
|
|
122
|
+
return state;
|
|
123
|
+
}
|
|
124
|
+
async function loadCachedEvents(state) {
|
|
125
|
+
if (!deps.fs || !state.cachePath || state.events.length > 0 || !(await deps.fs.pathExists(state.cachePath))) {
|
|
77
126
|
return;
|
|
78
127
|
}
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
128
|
+
const text = await deps.fs.readText(state.cachePath);
|
|
129
|
+
const events = [];
|
|
130
|
+
for (const line of text.split("\n")) {
|
|
131
|
+
if (!line.trim()) {
|
|
132
|
+
continue;
|
|
133
|
+
}
|
|
134
|
+
try {
|
|
135
|
+
events.push(JSON.parse(line));
|
|
136
|
+
}
|
|
137
|
+
catch {
|
|
138
|
+
// Ignore corrupt cache lines; transcript tailing remains the source of truth.
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
state.events = events.sort((left, right) => left.seq - right.seq);
|
|
142
|
+
state.nextSeq = Math.max(state.nextSeq, ...state.events.map((event) => event.seq + 1), 1);
|
|
143
|
+
for (const event of state.events) {
|
|
144
|
+
if (event.type === "entry") {
|
|
145
|
+
state.entries = upsertEntry(state.entries, event.entry);
|
|
146
|
+
}
|
|
147
|
+
else if (event.type === "status") {
|
|
148
|
+
state.status = event.status;
|
|
149
|
+
}
|
|
150
|
+
else if (event.type === "error") {
|
|
151
|
+
state.status = "failed";
|
|
152
|
+
}
|
|
82
153
|
}
|
|
83
|
-
|
|
84
|
-
|
|
154
|
+
}
|
|
155
|
+
async function persistEvents(state) {
|
|
156
|
+
if (!deps.fs || !state.cachePath) {
|
|
85
157
|
return;
|
|
86
158
|
}
|
|
87
|
-
|
|
88
|
-
|
|
159
|
+
const write = async () => {
|
|
160
|
+
const text = state.events.map((event) => JSON.stringify(event)).join("\n");
|
|
161
|
+
await deps.fs.writeText(state.cachePath, text ? `${text}\n` : "");
|
|
162
|
+
};
|
|
163
|
+
state.persistChain = (state.persistChain ?? Promise.resolve()).catch(() => undefined).then(write);
|
|
164
|
+
await state.persistChain;
|
|
165
|
+
}
|
|
166
|
+
async function compactEventsBefore(state, nextCursor) {
|
|
167
|
+
const normalizedCursor = Math.max(1, Math.floor(nextCursor));
|
|
168
|
+
const beforeCount = state.events.length;
|
|
169
|
+
state.events = state.events.filter((event) => event.seq >= normalizedCursor);
|
|
170
|
+
if (beforeCount !== state.events.length) {
|
|
171
|
+
await persistEvents(state);
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
function startTranscriptTail(roleSession) {
|
|
175
|
+
const state = getState(roleSession.id);
|
|
176
|
+
if (state.unsubscribeTranscript) {
|
|
89
177
|
return;
|
|
90
178
|
}
|
|
91
|
-
|
|
92
|
-
|
|
179
|
+
const replaySince = getTranscriptReplaySince(roleSession);
|
|
180
|
+
state.unsubscribeTranscript = deps.transcripts.subscribeToRoleSession(roleSession, (event) => {
|
|
181
|
+
void handleTranscriptEvent(roleSession.id, event).catch((error) => {
|
|
182
|
+
publishError(roleSession.id, error instanceof Error ? error.message : "Translation failed.");
|
|
183
|
+
});
|
|
184
|
+
}, {
|
|
185
|
+
onError(error) {
|
|
186
|
+
publishError(roleSession.id, error.message);
|
|
187
|
+
},
|
|
188
|
+
onPoll(checkedAt) {
|
|
189
|
+
emit(roleSession.id, { type: "translation-poll", checkedAt });
|
|
190
|
+
},
|
|
191
|
+
replaySince
|
|
192
|
+
});
|
|
193
|
+
}
|
|
194
|
+
async function handleTranscriptEvent(sessionId, event) {
|
|
195
|
+
const state = getState(sessionId);
|
|
196
|
+
if (state.seenTranscriptIds.has(event.id)) {
|
|
93
197
|
return;
|
|
94
198
|
}
|
|
95
|
-
|
|
96
|
-
|
|
199
|
+
const config = await loadConfig();
|
|
200
|
+
const { settings } = config;
|
|
201
|
+
let displayed = false;
|
|
202
|
+
if (event.kind === "text") {
|
|
203
|
+
displayed = processClaudeOutputText(sessionId, event.text, config, event.id);
|
|
204
|
+
if (displayed) {
|
|
205
|
+
state.lastAssistantText = event.text;
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
else if (event.kind === "question" || event.kind === "todo" || event.kind === "agent") {
|
|
209
|
+
displayed = processClaudeOutputText(sessionId, formatStructuredTranscriptEvent(event), config, event.id);
|
|
210
|
+
}
|
|
211
|
+
else if (event.kind === "tool_use" || event.kind === "tool_result") {
|
|
212
|
+
displayed = pushPreservedTranscriptEntry(sessionId, event.id, formatRawTranscriptEvent(event), settings);
|
|
213
|
+
}
|
|
214
|
+
if (displayed) {
|
|
215
|
+
state.seenTranscriptIds.add(event.id);
|
|
97
216
|
}
|
|
98
217
|
}
|
|
99
|
-
|
|
218
|
+
function processClaudeOutputText(sessionId, rawText, config, entryId) {
|
|
100
219
|
const session = deps.runtime.getSession(sessionId);
|
|
101
220
|
const roleSession = deps.sessionRegistry.get(sessionId);
|
|
102
221
|
if (!session && !roleSession) {
|
|
103
|
-
return;
|
|
222
|
+
return false;
|
|
104
223
|
}
|
|
105
|
-
const { settings, secrets } =
|
|
224
|
+
const { settings, secrets } = config;
|
|
106
225
|
if (!rawText.trim()) {
|
|
107
|
-
return;
|
|
226
|
+
return false;
|
|
108
227
|
}
|
|
109
228
|
const text = rawText;
|
|
110
229
|
const baseEntry = {
|
|
@@ -123,8 +242,8 @@ export function createTranslationService(deps) {
|
|
|
123
242
|
};
|
|
124
243
|
pushEntry(sessionId, baseEntry);
|
|
125
244
|
const queue = queues.getQueue(sessionId);
|
|
126
|
-
|
|
127
|
-
|
|
245
|
+
void queue.enqueue(async () => {
|
|
246
|
+
publishStatus(sessionId, "translating");
|
|
128
247
|
try {
|
|
129
248
|
const prompt = buildTranslationPrompt({
|
|
130
249
|
direction: "cc-output-to-user",
|
|
@@ -147,7 +266,7 @@ export function createTranslationService(deps) {
|
|
|
147
266
|
};
|
|
148
267
|
replaceEntry(sessionId, completed);
|
|
149
268
|
getState(sessionId).lastAssistantText = text;
|
|
150
|
-
|
|
269
|
+
publishStatus(sessionId, "ready");
|
|
151
270
|
}
|
|
152
271
|
catch (error) {
|
|
153
272
|
const failed = {
|
|
@@ -157,43 +276,43 @@ export function createTranslationService(deps) {
|
|
|
157
276
|
completedAt: now()
|
|
158
277
|
};
|
|
159
278
|
replaceEntry(sessionId, failed);
|
|
160
|
-
|
|
279
|
+
publishStatus(sessionId, "failed");
|
|
161
280
|
}
|
|
281
|
+
}).catch((error) => {
|
|
282
|
+
publishError(sessionId, error instanceof Error ? error.message : "Translation failed.");
|
|
162
283
|
});
|
|
284
|
+
return true;
|
|
163
285
|
}
|
|
164
286
|
function pushEntry(sessionId, entry) {
|
|
165
287
|
getState(sessionId).entries.push(entry);
|
|
166
|
-
|
|
288
|
+
publishEntry(sessionId, entry);
|
|
167
289
|
}
|
|
168
|
-
|
|
290
|
+
function pushPreservedTranscriptEntry(sessionId, entryId, sourceText, settings) {
|
|
169
291
|
const session = deps.runtime.getSession(sessionId);
|
|
170
292
|
const roleSession = deps.sessionRegistry.get(sessionId);
|
|
171
293
|
if (!session && !roleSession) {
|
|
172
|
-
return;
|
|
294
|
+
return false;
|
|
173
295
|
}
|
|
174
|
-
const
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
id: entryId,
|
|
187
|
-
translatedText: sourceText,
|
|
188
|
-
completedAt: now()
|
|
189
|
-
});
|
|
190
|
-
pushEntry(sessionId, entry);
|
|
296
|
+
const entry = createEntry({
|
|
297
|
+
taskSlug: roleSession?.taskSlug ?? session.taskSlug,
|
|
298
|
+
role: roleSession?.role ?? session.role,
|
|
299
|
+
direction: "cc-output-to-user",
|
|
300
|
+
sourceKind: "tool-output",
|
|
301
|
+
sourceText,
|
|
302
|
+
settings,
|
|
303
|
+
status: "preserved",
|
|
304
|
+
contextUsed: false,
|
|
305
|
+
id: entryId,
|
|
306
|
+
translatedText: sourceText,
|
|
307
|
+
completedAt: now()
|
|
191
308
|
});
|
|
309
|
+
pushEntry(sessionId, entry);
|
|
310
|
+
return true;
|
|
192
311
|
}
|
|
193
312
|
function replaceEntry(sessionId, entry) {
|
|
194
313
|
const state = getState(sessionId);
|
|
195
314
|
state.entries = state.entries.map((current) => current.id === entry.id ? entry : current);
|
|
196
|
-
|
|
315
|
+
publishEntry(sessionId, entry);
|
|
197
316
|
}
|
|
198
317
|
function createEntry(input) {
|
|
199
318
|
return {
|
|
@@ -214,6 +333,23 @@ export function createTranslationService(deps) {
|
|
|
214
333
|
model: input.settings.model
|
|
215
334
|
};
|
|
216
335
|
}
|
|
336
|
+
async function stopSessionInternal(sessionId, options = {}) {
|
|
337
|
+
const state = sessionStates.get(sessionId);
|
|
338
|
+
if (!state) {
|
|
339
|
+
return;
|
|
340
|
+
}
|
|
341
|
+
if (state.unsubscribeTranscript) {
|
|
342
|
+
state.unsubscribeTranscript();
|
|
343
|
+
state.unsubscribeTranscript = undefined;
|
|
344
|
+
}
|
|
345
|
+
queues.clearQueue(sessionId);
|
|
346
|
+
if (options.clearCache && state.cachePath && deps.fs?.removePath) {
|
|
347
|
+
await deps.fs.removePath(state.cachePath, { force: true });
|
|
348
|
+
state.events = [];
|
|
349
|
+
state.entries = [];
|
|
350
|
+
state.nextSeq = 1;
|
|
351
|
+
}
|
|
352
|
+
}
|
|
217
353
|
return {
|
|
218
354
|
async getSettings() {
|
|
219
355
|
const { settings, secrets } = await loadConfig();
|
|
@@ -239,15 +375,46 @@ export function createTranslationService(deps) {
|
|
|
239
375
|
const { settings, secrets } = await loadConfig();
|
|
240
376
|
return deps.provider.testConnection(settings, secrets);
|
|
241
377
|
},
|
|
242
|
-
async
|
|
243
|
-
const
|
|
244
|
-
if (!
|
|
378
|
+
async startSession(input) {
|
|
379
|
+
const roleSession = await deps.sessionService.getRoleSession(input.repoRoot, input.taskSlug, input.role);
|
|
380
|
+
if (!roleSession || roleSession.status !== "running") {
|
|
245
381
|
throw new VcmError({
|
|
246
|
-
code: "
|
|
247
|
-
message:
|
|
382
|
+
code: "SESSION_NOT_RUNNING",
|
|
383
|
+
message: `${input.role} session is not running.`,
|
|
248
384
|
statusCode: 409
|
|
249
385
|
});
|
|
250
386
|
}
|
|
387
|
+
const state = await prepareCache({
|
|
388
|
+
repoRoot: input.taskRepoRoot ?? input.repoRoot,
|
|
389
|
+
taskSlug: input.taskSlug,
|
|
390
|
+
role: input.role,
|
|
391
|
+
sessionId: roleSession.id
|
|
392
|
+
});
|
|
393
|
+
startTranscriptTail(roleSession);
|
|
394
|
+
return {
|
|
395
|
+
sessionId: roleSession.id,
|
|
396
|
+
status: state.status,
|
|
397
|
+
nextCursor: 1
|
|
398
|
+
};
|
|
399
|
+
},
|
|
400
|
+
async pollSessionEvents(sessionId, after, limit = 200) {
|
|
401
|
+
const state = getState(sessionId);
|
|
402
|
+
const cursor = Number.isFinite(after) ? Math.max(1, Math.floor(after)) : 1;
|
|
403
|
+
const maxEvents = Math.min(Math.max(1, Math.floor(limit)), 500);
|
|
404
|
+
await compactEventsBefore(state, cursor);
|
|
405
|
+
const events = state.events
|
|
406
|
+
.filter((event) => event.seq >= cursor)
|
|
407
|
+
.slice(0, maxEvents);
|
|
408
|
+
const nextCursor = events.length > 0 ? (events.at(-1)?.seq ?? cursor) + 1 : cursor;
|
|
409
|
+
return {
|
|
410
|
+
sessionId,
|
|
411
|
+
status: state.status,
|
|
412
|
+
nextCursor,
|
|
413
|
+
events
|
|
414
|
+
};
|
|
415
|
+
},
|
|
416
|
+
async translateUserInput(input) {
|
|
417
|
+
const { settings, secrets } = await loadConfig();
|
|
251
418
|
if (!input.text.trim()) {
|
|
252
419
|
throw new VcmError({
|
|
253
420
|
code: "TRANSLATION_INPUT_EMPTY",
|
|
@@ -256,6 +423,14 @@ export function createTranslationService(deps) {
|
|
|
256
423
|
});
|
|
257
424
|
}
|
|
258
425
|
const roleSession = await deps.sessionService.getRoleSession(input.repoRoot, input.taskSlug, input.role);
|
|
426
|
+
if (roleSession) {
|
|
427
|
+
await prepareCache({
|
|
428
|
+
repoRoot: input.taskRepoRoot ?? input.repoRoot,
|
|
429
|
+
taskSlug: input.taskSlug,
|
|
430
|
+
role: input.role,
|
|
431
|
+
sessionId: roleSession.id
|
|
432
|
+
});
|
|
433
|
+
}
|
|
259
434
|
const sessionState = roleSession ? getState(roleSession.id) : undefined;
|
|
260
435
|
const contextText = settings.contextEnabled && input.useContext !== false
|
|
261
436
|
? sessionState?.lastAssistantText
|
|
@@ -357,40 +532,40 @@ export function createTranslationService(deps) {
|
|
|
357
532
|
});
|
|
358
533
|
}
|
|
359
534
|
else {
|
|
360
|
-
|
|
361
|
-
state.unsubscribeTranscript = deps.transcripts.subscribeToRoleSession(roleSession, (event) => {
|
|
362
|
-
void handleTranscriptEvent(sessionId, event).catch((error) => {
|
|
363
|
-
emit(sessionId, {
|
|
364
|
-
type: "translation-error",
|
|
365
|
-
message: error instanceof Error ? error.message : "Translation failed."
|
|
366
|
-
});
|
|
367
|
-
});
|
|
368
|
-
}, {
|
|
369
|
-
onError(error) {
|
|
370
|
-
emit(sessionId, {
|
|
371
|
-
type: "translation-error",
|
|
372
|
-
message: error.message
|
|
373
|
-
});
|
|
374
|
-
},
|
|
375
|
-
replaySince
|
|
376
|
-
});
|
|
535
|
+
startTranscriptTail(roleSession);
|
|
377
536
|
}
|
|
378
537
|
}
|
|
379
538
|
void loadConfig().then(({ settings }) => {
|
|
380
|
-
listener({ type: "translation-status", status:
|
|
539
|
+
listener({ type: "translation-status", status: "ready" });
|
|
381
540
|
});
|
|
382
541
|
return () => {
|
|
383
542
|
state.listeners.delete(listener);
|
|
384
|
-
if (state.listeners.size === 0 && state.unsubscribeTranscript) {
|
|
385
|
-
state.unsubscribeTranscript();
|
|
386
|
-
state.unsubscribeTranscript = undefined;
|
|
387
|
-
}
|
|
388
543
|
};
|
|
389
544
|
},
|
|
390
|
-
clearSession(sessionId) {
|
|
545
|
+
async clearSession(sessionId) {
|
|
391
546
|
const state = getState(sessionId);
|
|
392
547
|
state.entries = [];
|
|
548
|
+
state.events = [];
|
|
549
|
+
state.nextSeq = 1;
|
|
393
550
|
queues.clearQueue(sessionId);
|
|
551
|
+
await persistEvents(state);
|
|
552
|
+
},
|
|
553
|
+
async stopSession(sessionId, options = {}) {
|
|
554
|
+
await stopSessionInternal(sessionId, options);
|
|
555
|
+
},
|
|
556
|
+
async stopTask(repoRoot, taskSlug, options = {}) {
|
|
557
|
+
for (const [sessionId, state] of sessionStates) {
|
|
558
|
+
if (state.repoRoot === repoRoot && state.taskSlug === taskSlug) {
|
|
559
|
+
await stopSessionInternal(sessionId, options);
|
|
560
|
+
}
|
|
561
|
+
}
|
|
562
|
+
if (options.clearCache && deps.fs?.removePath && deps.projectService) {
|
|
563
|
+
const config = await deps.projectService.loadConfig(repoRoot);
|
|
564
|
+
await deps.fs.removePath(path.join(repoRoot, config.stateRoot, "translation", taskSlug), {
|
|
565
|
+
recursive: true,
|
|
566
|
+
force: true
|
|
567
|
+
});
|
|
568
|
+
}
|
|
394
569
|
},
|
|
395
570
|
async retryTranslation(sessionId, translationId) {
|
|
396
571
|
const state = getState(sessionId);
|
|
@@ -409,7 +584,8 @@ export function createTranslationService(deps) {
|
|
|
409
584
|
statusCode: 400
|
|
410
585
|
});
|
|
411
586
|
}
|
|
412
|
-
await
|
|
587
|
+
const config = await loadConfig();
|
|
588
|
+
processClaudeOutputText(sessionId, original.sourceText, config);
|
|
413
589
|
return state.entries[state.entries.length - 1] ?? original;
|
|
414
590
|
}
|
|
415
591
|
};
|
|
@@ -422,12 +598,9 @@ export function createTranslationService(deps) {
|
|
|
422
598
|
statusCode: 409
|
|
423
599
|
});
|
|
424
600
|
}
|
|
425
|
-
deps.runtime
|
|
601
|
+
await submitTerminalInput(deps.runtime, record.id, text);
|
|
426
602
|
}
|
|
427
603
|
}
|
|
428
|
-
export function formatTerminalSubmit(text) {
|
|
429
|
-
return `${text.replace(/[\r\n]+$/g, "")}\r`;
|
|
430
|
-
}
|
|
431
604
|
function getTranscriptReplaySince(roleSession) {
|
|
432
605
|
const rawTimestamp = roleSession.startedAt ?? roleSession.updatedAt;
|
|
433
606
|
const timestampMs = Date.parse(rawTimestamp);
|
|
@@ -485,14 +658,28 @@ function formatUnknown(value) {
|
|
|
485
658
|
return String(value);
|
|
486
659
|
}
|
|
487
660
|
}
|
|
661
|
+
function upsertEntry(entries, entry) {
|
|
662
|
+
const index = entries.findIndex((current) => current.id === entry.id);
|
|
663
|
+
if (index === -1) {
|
|
664
|
+
return [...entries, entry];
|
|
665
|
+
}
|
|
666
|
+
return entries.map((current) => current.id === entry.id ? entry : current);
|
|
667
|
+
}
|
|
668
|
+
function getTranslationCachePath(repoRoot, stateRoot, taskSlug, role, sessionId) {
|
|
669
|
+
return path.join(repoRoot, stateRoot, "translation", taskSlug, role, `${sessionId}.jsonl`);
|
|
670
|
+
}
|
|
488
671
|
function normalizeSettings(input) {
|
|
489
672
|
const { apiKey: _apiKey, ...settings } = input;
|
|
490
673
|
return {
|
|
491
674
|
...DEFAULT_SETTINGS,
|
|
492
675
|
...settings,
|
|
493
676
|
version: 1,
|
|
677
|
+
enabled: true,
|
|
494
678
|
providerType: "openai-compatible",
|
|
495
679
|
workingLanguage: "en",
|
|
680
|
+
inputMode: "review-before-send",
|
|
681
|
+
translateOutput: true,
|
|
682
|
+
translateUserInput: true,
|
|
496
683
|
requestTimeoutMs: clampNumber(input.requestTimeoutMs, 3000, 120000, DEFAULT_SETTINGS.requestTimeoutMs),
|
|
497
684
|
temperature: clampNumber(input.temperature, 0, 1, DEFAULT_SETTINGS.temperature),
|
|
498
685
|
prompts: normalizePromptMap(input.prompts)
|
|
@@ -1,9 +1,6 @@
|
|
|
1
1
|
export function renderGitignoreHarnessRules() {
|
|
2
2
|
return [
|
|
3
3
|
"# VCM local app state, task metadata, session records, and task worktrees.",
|
|
4
|
-
".ai/vcm/"
|
|
5
|
-
"",
|
|
6
|
-
"# Legacy VCM local state from early versions. Keep ignored during migration.",
|
|
7
|
-
".vcm/"
|
|
4
|
+
".ai/vcm/"
|
|
8
5
|
].join("\n");
|
|
9
6
|
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export const THEME_MODES = ["system", "light", "dark"];
|