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.getByRole(taskSlug, role);
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 loadPersistedRoleRecord(deps.fs, taskRepoRoot, config.stateRoot, taskSlug, role);
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: taskRepoRoot,
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(taskRepoRoot, paths.roleLogPaths[role])
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: paths.roleLogPaths[role],
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 persistTaskSession(deps.fs, taskRepoRoot, config.stateRoot, record);
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 persistTaskSession(deps.fs, getTaskRuntimeRepoRoot(task), config.stateRoot, updated);
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.getByRole(taskSlug, role)
150
- ?? await loadPersistedRoleRecord(deps.fs, taskRepoRoot, config.stateRoot, taskSlug, role);
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 session = toRoleSessionRecordView(deps.registry.getByRole(taskSlug, role)
164
- ?? normalizePersistedRoleRecord(persistedTaskSession?.roles[role]?.record), deps.runtime);
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 persistTaskSession(deps.fs, getTaskRuntimeRepoRoot(task), config.stateRoot, updated);
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 persistTaskSession(deps.fs, getTaskRuntimeRepoRoot(task), config.stateRoot, updated);
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 persistTaskSession(deps.fs, getTaskRuntimeRepoRoot(task), config.stateRoot, updated);
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
- Session identity is fixed by base repository and target language:
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
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "vibe-coding-master",
3
- "version": "0.3.11",
3
+ "version": "0.3.12",
4
4
  "description": "Local GUI session cockpit for Claude Code role sessions.",
5
5
  "type": "module",
6
6
  "files": [