vibe-coding-master 0.3.11 → 0.3.12
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.
|
@@ -12,11 +12,13 @@ const CODEX_REVIEW_DIR = ".ai/vcm/codex-reviews";
|
|
|
12
12
|
const CODEX_CONFIG_PATH = ".ai/codex/config.toml";
|
|
13
13
|
const CODEX_TRANSLATOR_DIR = ".ai/codex-translator";
|
|
14
14
|
const CODEX_TRANSLATION_DIR = ".ai/vcm/translations";
|
|
15
|
+
const CODEX_TRANSLATOR_SESSION_PATH = ".ai/vcm/translations/session.json";
|
|
16
|
+
const CODEX_TRANSLATOR_LOG_PATH = ".ai/vcm/translations/codex-translator.log";
|
|
15
17
|
const CODEX_TRANSLATOR_CONFIG_PATH = ".ai/codex-translator/config.toml";
|
|
16
18
|
export function createSessionService(deps) {
|
|
17
19
|
const now = deps.now ?? (() => new Date().toISOString());
|
|
18
20
|
async function launchRoleSession(repoRoot, taskSlug, role, input, launchMode) {
|
|
19
|
-
const live = deps.registry.
|
|
21
|
+
const live = toRoleSessionRecordView(getRegisteredRoleSession(deps.registry, deps.runtime, taskSlug, role), deps.runtime);
|
|
20
22
|
if (live && live.status === "running") {
|
|
21
23
|
return live;
|
|
22
24
|
}
|
|
@@ -24,8 +26,11 @@ export function createSessionService(deps) {
|
|
|
24
26
|
const task = await deps.taskService.loadTask(repoRoot, taskSlug);
|
|
25
27
|
const taskRepoRoot = getTaskRuntimeRepoRoot(task);
|
|
26
28
|
const paths = deps.artifactService.getHandoffPaths(taskRepoRoot, task.handoffDir);
|
|
27
|
-
const persisted = await
|
|
29
|
+
const persisted = await loadPersistedRoleRecordForRole(deps.fs, repoRoot, taskRepoRoot, config.stateRoot, taskSlug, role);
|
|
28
30
|
const isCodexRole = isCodexRoleName(role);
|
|
31
|
+
const isTranslator = role === CODEX_TRANSLATOR_ROLE;
|
|
32
|
+
const sessionRepoRoot = isTranslator ? repoRoot : taskRepoRoot;
|
|
33
|
+
const sessionLogPath = isTranslator ? CODEX_TRANSLATOR_LOG_PATH : paths.roleLogPaths[role];
|
|
29
34
|
const permissionMode = normalizeClaudePermissionMode(input.permissionMode ?? persisted?.permissionMode);
|
|
30
35
|
const model = isCodexRole
|
|
31
36
|
? normalizeCodexModel(input.model ?? persisted?.model)
|
|
@@ -48,7 +53,7 @@ export function createSessionService(deps) {
|
|
|
48
53
|
? persisted.transcriptPath
|
|
49
54
|
: isCodexRole ? undefined : claudeTranscriptPath(taskRepoRoot, claudeSessionId);
|
|
50
55
|
const startCommand = isCodexRole
|
|
51
|
-
? await buildCodexStartCommand(deps.fs, repoRoot, taskRepoRoot, role, launchMode, model, effort, deps.sandboxMode)
|
|
56
|
+
? await buildCodexStartCommand(deps.fs, repoRoot, taskRepoRoot, role, launchMode, model, effort, deps.sandboxMode, launchMode === "resume" && hasCapturedCodexSession(persisted) ? claudeSessionId : undefined)
|
|
52
57
|
: {
|
|
53
58
|
...deps.claude.buildRoleStartCommand(role, config.claudeCommand, permissionMode, claudeSessionId, launchMode === "resume", model, effort),
|
|
54
59
|
cwd: taskRepoRoot
|
|
@@ -61,14 +66,14 @@ export function createSessionService(deps) {
|
|
|
61
66
|
cwd: startCommand.cwd,
|
|
62
67
|
env: {
|
|
63
68
|
VCM_API_URL: deps.apiUrl,
|
|
64
|
-
VCM_TASK_REPO_ROOT:
|
|
69
|
+
VCM_TASK_REPO_ROOT: sessionRepoRoot,
|
|
65
70
|
VCM_TASK_SLUG: taskSlug,
|
|
66
71
|
VCM_ROLE: role,
|
|
67
72
|
VCM_SESSION_ID: claudeSessionId
|
|
68
73
|
},
|
|
69
74
|
cols: input.cols,
|
|
70
75
|
rows: input.rows,
|
|
71
|
-
logPath: resolveRepoPath(
|
|
76
|
+
logPath: resolveRepoPath(sessionRepoRoot, sessionLogPath)
|
|
72
77
|
});
|
|
73
78
|
const timestamp = now();
|
|
74
79
|
const record = {
|
|
@@ -86,7 +91,7 @@ export function createSessionService(deps) {
|
|
|
86
91
|
cwd: startCommand.cwd,
|
|
87
92
|
terminalBackend: "node-pty",
|
|
88
93
|
pid: runtimeSession.pid,
|
|
89
|
-
logPath:
|
|
94
|
+
logPath: sessionLogPath,
|
|
90
95
|
roleCommandPath: isDispatchableRole(role)
|
|
91
96
|
? paths.roleCommandPaths[role]
|
|
92
97
|
: undefined,
|
|
@@ -97,7 +102,7 @@ export function createSessionService(deps) {
|
|
|
97
102
|
exitCode: runtimeSession.exitCode
|
|
98
103
|
};
|
|
99
104
|
deps.registry.upsert(record);
|
|
100
|
-
await
|
|
105
|
+
await persistRoleSessionRecord(deps.fs, repoRoot, taskRepoRoot, config.stateRoot, record);
|
|
101
106
|
return record;
|
|
102
107
|
}
|
|
103
108
|
return {
|
|
@@ -128,7 +133,7 @@ export function createSessionService(deps) {
|
|
|
128
133
|
deps.registry.upsert(updated);
|
|
129
134
|
const config = await deps.projectService.loadConfig(repoRoot);
|
|
130
135
|
const task = await deps.taskService.loadTask(repoRoot, taskSlug);
|
|
131
|
-
await
|
|
136
|
+
await persistRoleSessionRecord(deps.fs, repoRoot, getTaskRuntimeRepoRoot(task), config.stateRoot, updated);
|
|
132
137
|
return updated;
|
|
133
138
|
},
|
|
134
139
|
async restartRoleSession(repoRoot, taskSlug, role, input = {}) {
|
|
@@ -146,8 +151,8 @@ export function createSessionService(deps) {
|
|
|
146
151
|
const config = await deps.projectService.loadConfig(repoRoot);
|
|
147
152
|
const task = await deps.taskService.loadTask(repoRoot, taskSlug);
|
|
148
153
|
const taskRepoRoot = getTaskRuntimeRepoRoot(task);
|
|
149
|
-
const record = deps.registry.
|
|
150
|
-
?? await
|
|
154
|
+
const record = getRegisteredRoleSession(deps.registry, deps.runtime, taskSlug, role)
|
|
155
|
+
?? await loadPersistedRoleRecordForRole(deps.fs, repoRoot, taskRepoRoot, config.stateRoot, taskSlug, role);
|
|
151
156
|
if (!record) {
|
|
152
157
|
return undefined;
|
|
153
158
|
}
|
|
@@ -160,8 +165,12 @@ export function createSessionService(deps) {
|
|
|
160
165
|
const taskRepoRoot = getTaskRuntimeRepoRoot(task);
|
|
161
166
|
const persistedTaskSession = await loadPersistedTaskSessionRecord(deps.fs, taskRepoRoot, config.stateRoot, taskSlug);
|
|
162
167
|
for (const role of ROLE_NAMES) {
|
|
163
|
-
const
|
|
164
|
-
|
|
168
|
+
const record = role === CODEX_TRANSLATOR_ROLE
|
|
169
|
+
? getRegisteredRoleSession(deps.registry, deps.runtime, taskSlug, role)
|
|
170
|
+
?? await loadPersistedCodexTranslatorSession(deps.fs, repoRoot, taskSlug)
|
|
171
|
+
: deps.registry.getByRole(taskSlug, role)
|
|
172
|
+
?? normalizePersistedRoleRecord(persistedTaskSession?.roles[role]?.record);
|
|
173
|
+
const session = toRoleSessionRecordView(record, deps.runtime);
|
|
165
174
|
if (session) {
|
|
166
175
|
sessions.push(session);
|
|
167
176
|
}
|
|
@@ -189,7 +198,7 @@ export function createSessionService(deps) {
|
|
|
189
198
|
deps.registry.upsert(updated);
|
|
190
199
|
const config = await deps.projectService.loadConfig(repoRoot);
|
|
191
200
|
const task = await deps.taskService.loadTask(repoRoot, input.taskSlug);
|
|
192
|
-
await
|
|
201
|
+
await persistRoleSessionRecord(deps.fs, repoRoot, getTaskRuntimeRepoRoot(task), config.stateRoot, updated);
|
|
193
202
|
return updated;
|
|
194
203
|
},
|
|
195
204
|
recordClaudeHookEvent(repoRoot, input) {
|
|
@@ -218,7 +227,7 @@ export function createSessionService(deps) {
|
|
|
218
227
|
deps.registry.upsert(updated);
|
|
219
228
|
const config = await deps.projectService.loadConfig(repoRoot);
|
|
220
229
|
const task = await deps.taskService.loadTask(repoRoot, taskSlug);
|
|
221
|
-
await
|
|
230
|
+
await persistRoleSessionRecord(deps.fs, repoRoot, getTaskRuntimeRepoRoot(task), config.stateRoot, updated);
|
|
222
231
|
return updated;
|
|
223
232
|
},
|
|
224
233
|
async markRoleActivityIdle(repoRoot, taskSlug, role) {
|
|
@@ -236,7 +245,7 @@ export function createSessionService(deps) {
|
|
|
236
245
|
deps.registry.upsert(updated);
|
|
237
246
|
const config = await deps.projectService.loadConfig(repoRoot);
|
|
238
247
|
const task = await deps.taskService.loadTask(repoRoot, taskSlug);
|
|
239
|
-
await
|
|
248
|
+
await persistRoleSessionRecord(deps.fs, repoRoot, getTaskRuntimeRepoRoot(task), config.stateRoot, updated);
|
|
240
249
|
return updated;
|
|
241
250
|
}
|
|
242
251
|
};
|
|
@@ -263,7 +272,7 @@ function toRoleSessionRecordView(record, runtime) {
|
|
|
263
272
|
exitCode: runtimeSession.exitCode
|
|
264
273
|
};
|
|
265
274
|
}
|
|
266
|
-
async function buildCodexStartCommand(fs, baseRepoRoot, taskRepoRoot, role, launchMode, selectedModel, selectedEffort, sandboxMode) {
|
|
275
|
+
async function buildCodexStartCommand(fs, baseRepoRoot, taskRepoRoot, role, launchMode, selectedModel, selectedEffort, sandboxMode, resumeSessionId) {
|
|
267
276
|
const isTranslator = role === CODEX_TRANSLATOR_ROLE;
|
|
268
277
|
const codexDir = resolveRepoPath(isTranslator ? baseRepoRoot : taskRepoRoot, isTranslator ? CODEX_TRANSLATOR_DIR : CODEX_DIR);
|
|
269
278
|
const outputDir = resolveRepoPath(isTranslator ? baseRepoRoot : taskRepoRoot, isTranslator ? CODEX_TRANSLATION_DIR : CODEX_REVIEW_DIR);
|
|
@@ -278,7 +287,7 @@ async function buildCodexStartCommand(fs, baseRepoRoot, taskRepoRoot, role, laun
|
|
|
278
287
|
await fs.ensureDir(outputDir);
|
|
279
288
|
const config = await loadCodexSessionConfig(fs, isTranslator ? baseRepoRoot : taskRepoRoot, isTranslator ? CODEX_TRANSLATOR_CONFIG_PATH : CODEX_CONFIG_PATH);
|
|
280
289
|
const args = launchMode === "resume"
|
|
281
|
-
? ["resume", "--last"]
|
|
290
|
+
? resumeSessionId ? ["resume", resumeSessionId] : ["resume", "--last"]
|
|
282
291
|
: [];
|
|
283
292
|
args.push("--cd", codexDir);
|
|
284
293
|
if (isDevContainerSandbox(sandboxMode)) {
|
|
@@ -307,6 +316,9 @@ async function buildCodexStartCommand(fs, baseRepoRoot, taskRepoRoot, role, laun
|
|
|
307
316
|
display: [config.command, ...args].map(formatDisplayArg).join(" ")
|
|
308
317
|
};
|
|
309
318
|
}
|
|
319
|
+
function hasCapturedCodexSession(record) {
|
|
320
|
+
return Boolean(record?.lastHookEventAt || record?.transcriptPath);
|
|
321
|
+
}
|
|
310
322
|
function isDevContainerSandbox(value) {
|
|
311
323
|
return value?.toLowerCase() === "devcontainer";
|
|
312
324
|
}
|
|
@@ -353,6 +365,44 @@ function getHandoffArtifactPath(paths, role) {
|
|
|
353
365
|
}
|
|
354
366
|
return undefined;
|
|
355
367
|
}
|
|
368
|
+
function getRegisteredRoleSession(registry, runtime, taskSlug, role) {
|
|
369
|
+
if (role !== CODEX_TRANSLATOR_ROLE) {
|
|
370
|
+
return registry.getByRole(taskSlug, role);
|
|
371
|
+
}
|
|
372
|
+
const candidates = registry.list().filter((session) => session.role === role);
|
|
373
|
+
const live = candidates.find((session) => runtime.getSession(session.id)?.status === "running");
|
|
374
|
+
const scoped = live
|
|
375
|
+
?? candidates.find((session) => session.taskSlug === taskSlug)
|
|
376
|
+
?? candidates.sort(compareSessionUpdatedAtDesc)[0];
|
|
377
|
+
return scopeProjectRoleSession(scoped, taskSlug);
|
|
378
|
+
}
|
|
379
|
+
function compareSessionUpdatedAtDesc(left, right) {
|
|
380
|
+
return (right.updatedAt ?? "").localeCompare(left.updatedAt ?? "");
|
|
381
|
+
}
|
|
382
|
+
function scopeProjectRoleSession(record, taskSlug) {
|
|
383
|
+
if (!record || record.role !== CODEX_TRANSLATOR_ROLE) {
|
|
384
|
+
return record;
|
|
385
|
+
}
|
|
386
|
+
return {
|
|
387
|
+
...record,
|
|
388
|
+
taskSlug
|
|
389
|
+
};
|
|
390
|
+
}
|
|
391
|
+
async function loadPersistedRoleRecordForRole(fs, baseRepoRoot, taskRepoRoot, stateRoot, taskSlug, role) {
|
|
392
|
+
if (role === CODEX_TRANSLATOR_ROLE) {
|
|
393
|
+
return loadPersistedCodexTranslatorSession(fs, baseRepoRoot, taskSlug);
|
|
394
|
+
}
|
|
395
|
+
return loadPersistedRoleRecord(fs, taskRepoRoot, stateRoot, taskSlug, role);
|
|
396
|
+
}
|
|
397
|
+
async function loadPersistedCodexTranslatorSession(fs, repoRoot, taskSlug) {
|
|
398
|
+
const sessionPath = resolveRepoPath(repoRoot, CODEX_TRANSLATOR_SESSION_PATH);
|
|
399
|
+
if (!(await fs.pathExists(sessionPath))) {
|
|
400
|
+
return undefined;
|
|
401
|
+
}
|
|
402
|
+
const payload = await fs.readJson(sessionPath);
|
|
403
|
+
const record = normalizePersistedRoleRecord(isProjectRoleSessionFile(payload) ? payload.record : payload);
|
|
404
|
+
return scopeProjectRoleSession(record, taskSlug);
|
|
405
|
+
}
|
|
356
406
|
async function loadPersistedRoleRecord(fs, repoRoot, stateRoot, taskSlug, role) {
|
|
357
407
|
const current = await loadPersistedTaskSessionRecord(fs, repoRoot, stateRoot, taskSlug);
|
|
358
408
|
return normalizePersistedRoleRecord(current?.roles[role]?.record);
|
|
@@ -410,6 +460,27 @@ async function persistTaskSession(fs, repoRoot, stateRoot, session) {
|
|
|
410
460
|
}
|
|
411
461
|
});
|
|
412
462
|
}
|
|
463
|
+
async function persistRoleSessionRecord(fs, baseRepoRoot, taskRepoRoot, stateRoot, session) {
|
|
464
|
+
if (session.role === CODEX_TRANSLATOR_ROLE) {
|
|
465
|
+
await persistCodexTranslatorSession(fs, baseRepoRoot, session);
|
|
466
|
+
return;
|
|
467
|
+
}
|
|
468
|
+
await persistTaskSession(fs, taskRepoRoot, stateRoot, session);
|
|
469
|
+
}
|
|
470
|
+
async function persistCodexTranslatorSession(fs, repoRoot, session) {
|
|
471
|
+
await fs.writeJsonAtomic(resolveRepoPath(repoRoot, CODEX_TRANSLATOR_SESSION_PATH), {
|
|
472
|
+
version: 1,
|
|
473
|
+
role: session.role,
|
|
474
|
+
updatedAt: session.updatedAt,
|
|
475
|
+
record: {
|
|
476
|
+
...session,
|
|
477
|
+
taskSlug: session.taskSlug
|
|
478
|
+
}
|
|
479
|
+
});
|
|
480
|
+
}
|
|
481
|
+
function isProjectRoleSessionFile(value) {
|
|
482
|
+
return "record" in value && typeof value.record === "object" && value.record !== null;
|
|
483
|
+
}
|
|
413
484
|
function createEmptyTaskSessionRecord(taskSlug, updatedAt) {
|
|
414
485
|
return {
|
|
415
486
|
version: 1,
|
|
@@ -364,7 +364,27 @@ management pattern:
|
|
|
364
364
|
- persisted Codex session id
|
|
365
365
|
- long-lived terminal session for follow-up discussion
|
|
366
366
|
|
|
367
|
-
|
|
367
|
+
VCM persists the active translator session at:
|
|
368
|
+
|
|
369
|
+
```text
|
|
370
|
+
<baseRepoRoot>/.ai/vcm/translations/session.json
|
|
371
|
+
```
|
|
372
|
+
|
|
373
|
+
This record is project-level state, not task-level state. It stores the Codex
|
|
374
|
+
session id, selected model, selected effort, terminal cwd, log path, and hook
|
|
375
|
+
activity state needed to show `Resume` after VCM restarts or after the user opens
|
|
376
|
+
another task. The embedded terminal `Restart` control must stop the current
|
|
377
|
+
runtime process, create a fresh Codex session id, overwrite this record, and keep
|
|
378
|
+
old translation outputs intact.
|
|
379
|
+
|
|
380
|
+
When a Codex hook has captured the real Codex `session_id`, `Resume` must run
|
|
381
|
+
`codex resume <session_id>` so it reconnects the same translator conversation.
|
|
382
|
+
Before the first hook captures a real id, VCM may fall back to `codex resume
|
|
383
|
+
--last`.
|
|
384
|
+
|
|
385
|
+
Session identity is fixed by base repository. If VCM later supports multiple
|
|
386
|
+
parallel target-language translator sessions, split this file into a
|
|
387
|
+
target-language keyed session directory:
|
|
368
388
|
|
|
369
389
|
- one Codex Translator session per `<baseRepoRoot> + targetLanguage`
|
|
370
390
|
- different target languages must use different sessions to avoid terminology
|