vibe-coding-master 0.7.15 → 0.7.17

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.
Files changed (33) hide show
  1. package/README.md +30 -9
  2. package/dist/backend/adapters/claude-adapter.js +4 -1
  3. package/dist/backend/api/session-routes.js +5 -0
  4. package/dist/backend/api/usage-analytics-routes.js +31 -0
  5. package/dist/backend/cli/install-vcm-harness.js +51 -2
  6. package/dist/backend/runtime/session-registry.js +7 -0
  7. package/dist/backend/server.js +26 -9
  8. package/dist/backend/services/architect-restart-service.js +136 -0
  9. package/dist/backend/services/ccr-integration-service.js +70 -11
  10. package/dist/backend/services/claude-hook-service.js +6 -0
  11. package/dist/backend/services/claude-transcript-service.js +10 -10
  12. package/dist/backend/services/gate-review-service.js +40 -0
  13. package/dist/backend/services/harness-service.js +51 -4
  14. package/dist/backend/services/message-service.js +5 -0
  15. package/dist/backend/services/runtime-coordinator-service.js +37 -42
  16. package/dist/backend/services/session-service.js +83 -19
  17. package/dist/backend/services/task-close-service.js +1 -0
  18. package/dist/backend/services/usage-analytics-service.js +346 -0
  19. package/dist/backend/templates/harness/architect-agent.js +18 -10
  20. package/dist/backend/templates/harness/architect-scaffold-worker-agent.js +23 -0
  21. package/dist/backend/templates/harness/claude-root.js +1 -1
  22. package/dist/backend/templates/harness/gate-review.js +7 -2
  23. package/dist/backend/templates/harness/project-manager-agent.js +1 -1
  24. package/dist/backend/templates/harness/restart-architect-skill.js +75 -0
  25. package/dist/backend/templates/harness/vcm-architecture-interview-skill.js +31 -5
  26. package/dist/backend/templates/harness/vcm-route-message-skill.js +3 -3
  27. package/dist/shared/types/usage-analytics.js +1 -0
  28. package/dist-frontend/assets/index-CStWyouh.js +97 -0
  29. package/dist-frontend/assets/{index-CiEUp9Si.css → index-Ci7z8tW3.css} +1 -1
  30. package/dist-frontend/index.html +2 -2
  31. package/package.json +1 -1
  32. package/dist/backend/adapters/claude-settings-adapter.js +0 -79
  33. package/dist-frontend/assets/index-BAE_pjXJ.js +0 -97
package/README.md CHANGED
@@ -228,6 +228,18 @@ such as a Dev Container or VM.
228
228
  Model and effort can be selected before start/resume/restart. Changes affect the
229
229
  next launched process, not a currently running Claude Code process.
230
230
 
231
+ ## Usage Analytics
232
+
233
+ Open `Usage Analytics` in the sidebar `Task` section to inspect native Claude
234
+ Code usage for the active task. The report shows task totals and breakdowns by
235
+ role and model for input, output, cache-read, and cache-creation tokens plus
236
+ estimated USD cost. It combines every restart and resumed Claude session for
237
+ all seven roles. CCR/GPT usage is excluded.
238
+
239
+ VCM retains only aggregate task data in
240
+ `<task-worktree>/.ai/vcm/telemetry/usage.json`. The file is temporary runtime
241
+ state and is removed with the task worktree when the task is closed.
242
+
231
243
  ### GPT Through Claude Code Router
232
244
 
233
245
  VCM can launch its normal Claude Code sessions with `GPT-5.6 Sol (CCR)` through
@@ -254,11 +266,17 @@ DevContainer, make the second endpoint reachable from the container. In the VCM
254
266
  The key is stored in global VCM state (`~/.vcm/settings.json`) with owner-only
255
267
  permissions and is never returned by the settings API. It is used for CCR
256
268
  checks, model discovery, and the GPT-only `apiKeyHelper`. GPT sessions receive a
257
- child-only `--settings` override; VCM removes inherited Anthropic token
258
- variables only from those child processes. VCM removes CCR-owned takeover
259
- entries from global Claude settings, so native Claude selections keep their
260
- normal launch and account authentication. If CCR is disabled, unreachable,
261
- rejects the key, or does not expose
269
+ child-only `--settings` override and use the isolated Claude configuration root
270
+ `~/.vcm/claude/ccr`. VCM never edits `~/.claude/settings.json`. Native Claude
271
+ sessions keep their normal configuration and account authentication; VCM only
272
+ removes inherited environment variables that clearly point at the local CCR
273
+ gateway from the native child process. Configure CCR without enabling its
274
+ global Claude Code or Claude App takeover if those clients should remain on
275
+ Anthropic.
276
+
277
+ Resume keeps the provider recorded by the existing Session. Use Restart when
278
+ switching between a native Claude model and `GPT-5.6 Sol (CCR)`. If CCR is
279
+ disabled, unreachable, rejects the key, or does not expose
262
280
  `Codex API/gpt-5.6-sol`, VCM blocks the new Start, Resume, or Restart and does
263
281
  not fall back to another model.
264
282
 
@@ -324,7 +342,9 @@ Conversation translation is controlled from the sidebar `Translation` section.
324
342
 
325
343
  VCM uses a task-scoped Translator role and Claude transcript JSONL files, not
326
344
  raw terminal text. Translation memory and completed file translations remain
327
- project-level durable data.
345
+ project-level durable data. When translation is enabled and the active task's
346
+ Harness is initialized, the backend automatically starts a fresh Translator for
347
+ the task or resumes its saved Session.
328
348
 
329
349
  Common controls:
330
350
 
@@ -428,9 +448,10 @@ Use it to:
428
448
  - merge task harness commits back to the connected repository branch when
429
449
  appropriate
430
450
 
431
- Harness Engineer is task-scoped and runs from the active task worktree. A new
432
- task receives its own Harness Engineer session. Durable memory is versioned with
433
- the project harness files.
451
+ Harness Engineer is task-scoped and runs from the active task worktree. The
452
+ backend automatically starts a fresh Harness Engineer for each active task or
453
+ resumes its saved Session. Durable memory is versioned with the project harness
454
+ files.
434
455
 
435
456
  ### Auto Memory
436
457
 
@@ -18,7 +18,7 @@ export function createClaudeAdapter(runner) {
18
18
  }
19
19
  return result.stdout.trim();
20
20
  },
21
- buildRoleStartCommand(role, command = "claude", permissionMode = "default", claudeSessionId, resume = false, model = "default", effort = "default", settingsOverride) {
21
+ buildRoleStartCommand(role, command = "claude", permissionMode = "default", claudeSessionId, resume = false, model = "default", effort = "default", settingsOverride, appendSystemPrompt) {
22
22
  const args = ["--agent", role];
23
23
  const sessionSettings = { ...settingsOverride };
24
24
  if (claudeSessionId) {
@@ -39,6 +39,9 @@ export function createClaudeAdapter(runner) {
39
39
  if (permissionMode !== "default") {
40
40
  args.push("--permission-mode", permissionMode);
41
41
  }
42
+ if (appendSystemPrompt) {
43
+ args.push("--append-system-prompt", appendSystemPrompt);
44
+ }
42
45
  return {
43
46
  command,
44
47
  args,
@@ -10,6 +10,11 @@ export function registerSessionRoutes(app, deps) {
10
10
  const role = parseRole(request.params.role);
11
11
  return deps.sessionService.startRoleSession(project.repoRoot, request.params.taskSlug, role, request.body);
12
12
  });
13
+ app.post("/api/tasks/:taskSlug/sessions/architect/restart-after-planning", async (request, reply) => {
14
+ const project = await requireCurrentProject(deps.projectService);
15
+ const result = await deps.architectRestartService.schedule(project.repoRoot, request.params.taskSlug);
16
+ return reply.code(202).send(result);
17
+ });
13
18
  app.post("/api/tasks/:taskSlug/sessions/:role/stop", async (request) => {
14
19
  const project = await requireCurrentProject(deps.projectService);
15
20
  const role = parseRole(request.params.role);
@@ -0,0 +1,31 @@
1
+ import { VcmError } from "../errors.js";
2
+ import { getTaskRuntimeRepoRoot } from "../services/task-service.js";
3
+ export function registerUsageAnalyticsRoutes(app, deps) {
4
+ app.post("/api/telemetry/v1/logs", async (request) => {
5
+ const project = await deps.projectService.getCurrentProject();
6
+ if (!project) {
7
+ return {};
8
+ }
9
+ const tasks = await deps.taskService.listTasks(project.repoRoot);
10
+ const activeTask = tasks.find((task) => task.cleanupStatus !== "cleaned");
11
+ if (!activeTask) {
12
+ return {};
13
+ }
14
+ const config = await deps.projectService.loadConfig(project.repoRoot);
15
+ await deps.usageAnalyticsService.ingest(getTaskRuntimeRepoRoot(activeTask), config.stateRoot, request.body);
16
+ return {};
17
+ });
18
+ app.get("/api/tasks/:taskSlug/usage-analytics", async (request) => {
19
+ const project = await deps.projectService.getCurrentProject();
20
+ if (!project) {
21
+ throw new VcmError({
22
+ code: "PROJECT_NOT_CONNECTED",
23
+ message: "Connect a repository before loading task usage analytics.",
24
+ statusCode: 409
25
+ });
26
+ }
27
+ const task = await deps.taskService.loadTask(project.repoRoot, request.params.taskSlug);
28
+ const config = await deps.projectService.loadConfig(project.repoRoot);
29
+ return deps.usageAnalyticsService.getReport(getTaskRuntimeRepoRoot(task), config.stateRoot, task.taskSlug);
30
+ });
31
+ }
@@ -7,6 +7,7 @@ import { fileURLToPath } from "node:url";
7
7
  import { renderArchitectHarnessRules } from "../templates/harness/architect-agent.js";
8
8
  import { renderCoderHarnessRules } from "../templates/harness/coder-agent.js";
9
9
  import { renderCoderWorkerHarnessRules } from "../templates/harness/coder-worker-agent.js";
10
+ import { renderArchitectScaffoldWorkerHarnessRules } from "../templates/harness/architect-scaffold-worker-agent.js";
10
11
  import { renderGateReviewerAgentRules, renderRequestGateReviewTool, renderTranslatorAgentRules, renderVcmGateReviewSkillRules } from "../templates/harness/gate-review.js";
11
12
  import { renderHarnessEngineerHarnessRules } from "../templates/harness/harness-engineer-agent.js";
12
13
  import { renderRootClaudeHarnessRules } from "../templates/harness/claude-root.js";
@@ -27,6 +28,7 @@ import { renderVcmReportHarnessIssueSkillRules } from "../templates/harness/vcm-
27
28
  import { renderVcmRouteMessageSkillRules } from "../templates/harness/vcm-route-message-skill.js";
28
29
  import { renderUpdateTaskStateTool, renderVcmTaskStateSkillRules } from "../templates/harness/vcm-task-state-skill.js";
29
30
  import { renderCheckScaffoldLedgerTool } from "../templates/harness/check-scaffold-ledger.js";
31
+ import { renderRequestArchitectRestartTool, renderRestartArchitectSkillRules } from "../templates/harness/restart-architect-skill.js";
30
32
  import { readVcmPackageVersion } from "../app-version.js";
31
33
  const CLI_DIR = path.dirname(fileURLToPath(import.meta.url));
32
34
  const APP_ROOT = path.resolve(CLI_DIR, "../../..");
@@ -59,7 +61,8 @@ const AGENT_FRONTMATTER = {
59
61
  description: "User-facing VCM orchestration role for task clarification, role routing, handoffs, acceptance, and PR preparation."
60
62
  },
61
63
  architect: {
62
- description: "VCM architecture role for plans, module boundaries, public contracts, verifiable behavior, and docs sync."
64
+ description: "VCM architecture role for plans, module boundaries, public contracts, verifiable behavior, and docs sync.",
65
+ tools: "Read, Grep, Glob, Bash, Edit, Write, Agent"
63
66
  },
64
67
  coder: {
65
68
  description: "VCM implementation role for scoped code changes and focused tests.",
@@ -81,6 +84,11 @@ const AGENT_FRONTMATTER = {
81
84
  "vcm-coder-worker": {
82
85
  description: "Bounded VCM implementation worker for assigned modules, files, and VCM:CODE markers from Coder.",
83
86
  model: "inherit"
87
+ },
88
+ "vcm-architect-scaffold-worker": {
89
+ description: "Foreground Architect worker for exact scaffold execution and scaffold validation.",
90
+ model: "opus",
91
+ effort: "xhigh"
84
92
  }
85
93
  };
86
94
  const MANAGED_FILES = [
@@ -195,6 +203,14 @@ const MANAGED_FILES = [
195
203
  commentStyle: "html",
196
204
  category: "agent-coder-worker",
197
205
  content: renderCoderWorkerHarnessRules()
206
+ },
207
+ {
208
+ path: ".claude/agents/vcm-architect-scaffold-worker.md",
209
+ title: "VCM Architect Scaffold Worker Agent",
210
+ agentName: "vcm-architect-scaffold-worker",
211
+ commentStyle: "html",
212
+ category: "agent-architect-scaffold-worker",
213
+ content: renderArchitectScaffoldWorkerHarnessRules()
198
214
  }
199
215
  ];
200
216
  const DURABLE_DOC_TEMPLATES = [
@@ -284,6 +300,12 @@ const WHOLE_FILES = [
284
300
  mode: 0o644,
285
301
  content: renderSkillFile("VCM Propose Memory Skill", "vcm-propose-memory", "Use only when VCM requests a role memory proposal during Task Harness Review.", renderVcmProposeMemorySkillRules())
286
302
  },
303
+ {
304
+ path: ".claude/skills/restart-architect/SKILL.md",
305
+ category: "skill",
306
+ mode: 0o644,
307
+ content: renderSkillFile("Restart Architect Skill", "restart-architect", "Use after Architect completes and commits architecture planning and scaffold work.", renderRestartArchitectSkillRules())
308
+ },
287
309
  {
288
310
  path: ".ai/tools/request-gate-review",
289
311
  category: "runtime-tool",
@@ -302,6 +324,12 @@ const WHOLE_FILES = [
302
324
  mode: 0o755,
303
325
  content: renderCheckScaffoldLedgerTool()
304
326
  },
327
+ {
328
+ path: ".ai/tools/request-architect-restart",
329
+ category: "runtime-tool",
330
+ mode: 0o755,
331
+ content: renderRequestArchitectRestartTool()
332
+ },
305
333
  {
306
334
  path: ".ai/tools/run-long-check",
307
335
  category: "runtime-tool",
@@ -526,6 +554,7 @@ function fixedDirectories() {
526
554
  ".claude/skills/vcm-gate-review/",
527
555
  ".claude/skills/vcm-report-harness-issue/",
528
556
  ".claude/skills/vcm-propose-memory/",
557
+ ".claude/skills/restart-architect/",
529
558
  ".ai/vcm/translations/",
530
559
  ".ai/vcm/gate-reviews/",
531
560
  ".ai/tools/",
@@ -586,6 +615,9 @@ async function installManagedFile({ projectRoot, definition, dryRun, operations
586
615
  if (definition.memoryBlock) {
587
616
  nextContent = ensureVcmMemoryBlock(nextContent);
588
617
  }
618
+ if (definition.agentName === "architect") {
619
+ nextContent = ensureAgentTool(nextContent, "Agent");
620
+ }
589
621
  await writeIfChanged({
590
622
  targetPath,
591
623
  relativePath: definition.path,
@@ -627,10 +659,27 @@ function renderNewManagedFile(definition, block) {
627
659
  const frontmatter = AGENT_FRONTMATTER[definition.agentName];
628
660
  const tools = frontmatter.tools ?? "Read, Grep, Glob, Bash, Edit, Write";
629
661
  const model = frontmatter.model ? `\nmodel: ${frontmatter.model}` : "";
630
- return `---\nname: ${definition.agentName}\ndescription: ${frontmatter.description}\ntools: ${tools}${model}\n---\n\n# ${definition.title}\n\n${block}${suffix ? `\n\n${suffix}` : ""}\n`;
662
+ const effort = frontmatter.effort ? `\neffort: ${frontmatter.effort}` : "";
663
+ return `---\nname: ${definition.agentName}\ndescription: ${frontmatter.description}\ntools: ${tools}${model}${effort}\n---\n\n# ${definition.title}\n\n${block}${suffix ? `\n\n${suffix}` : ""}\n`;
631
664
  }
632
665
  return `# ${definition.title}\n\n${block}${suffix ? `\n\n${suffix}` : ""}\n`;
633
666
  }
667
+ function ensureAgentTool(content, requiredTool) {
668
+ const frontmatterMatch = content.match(/^---\r?\n[\s\S]*?\r?\n---/);
669
+ if (!frontmatterMatch) {
670
+ return content;
671
+ }
672
+ const toolsMatch = frontmatterMatch[0].match(/^tools:\s*(.*)$/m);
673
+ if (!toolsMatch) {
674
+ return content.replace(/^(---\r?\n[\s\S]*?)(\r?\n---)/, `$1\ntools: ${requiredTool}$2`);
675
+ }
676
+ const tools = toolsMatch[1].split(",").map((tool) => tool.trim()).filter(Boolean);
677
+ if (tools.includes(requiredTool)) {
678
+ return content;
679
+ }
680
+ const nextTools = [...tools, requiredTool].join(", ");
681
+ return content.replace(frontmatterMatch[0], frontmatterMatch[0].replace(toolsMatch[0], `tools: ${nextTools}`));
682
+ }
634
683
  function migrateLegacyManagedFile(definition, currentContent, block) {
635
684
  const legacyContent = definition.legacyWholeFile?.trimEnd();
636
685
  if (!legacyContent) {
@@ -2,6 +2,13 @@ export function createSessionRegistry() {
2
2
  const sessions = new Map();
3
3
  return {
4
4
  upsert(session) {
5
+ for (const [sessionId, candidate] of sessions) {
6
+ if (sessionId !== session.id
7
+ && candidate.taskSlug === session.taskSlug
8
+ && candidate.role === session.role) {
9
+ sessions.delete(sessionId);
10
+ }
11
+ }
5
12
  sessions.set(session.id, session);
6
13
  },
7
14
  get(sessionId) {
@@ -5,7 +5,6 @@ import fastifyStatic from "@fastify/static";
5
5
  import { createArtifactService } from "./services/artifact-service.js";
6
6
  import { createClaudeAdapter } from "./adapters/claude-adapter.js";
7
7
  import { createCcrGatewayAdapter } from "./adapters/ccr-gateway-adapter.js";
8
- import { createClaudeSettingsAdapter } from "./adapters/claude-settings-adapter.js";
9
8
  import { createCommandRunner } from "./adapters/command-runner.js";
10
9
  import { createCommandDispatcher } from "./services/command-dispatcher.js";
11
10
  import { createClaudeHookService } from "./services/claude-hook-service.js";
@@ -13,6 +12,7 @@ import { createGitAdapter } from "./adapters/git-adapter.js";
13
12
  import { createAppSettingsService } from "./services/app-settings-service.js";
14
13
  import { createCcrIntegrationService } from "./services/ccr-integration-service.js";
15
14
  import { createAutoMemoryService } from "./services/auto-memory-service.js";
15
+ import { createArchitectRestartService } from "./services/architect-restart-service.js";
16
16
  import { createClaudeTranscriptService } from "./services/claude-transcript-service.js";
17
17
  import { createGateReviewService } from "./services/gate-review-service.js";
18
18
  import { createHarnessFeedbackService } from "./services/harness-feedback-service.js";
@@ -43,6 +43,7 @@ import { createTaskWorkflowService } from "./services/task-workflow-service.js";
43
43
  import { createTaskLaunchService } from "./services/task-launch-service.js";
44
44
  import { createTerminalInterruptService } from "./services/terminal-interrupt-service.js";
45
45
  import { createTranslationService } from "./services/translation-service.js";
46
+ import { createUsageAnalyticsService } from "./services/usage-analytics-service.js";
46
47
  import { createTurnReconcilerService } from "./services/turn-reconciler-service.js";
47
48
  import { createDiagnosticsService } from "./services/diagnostics-service.js";
48
49
  import { registerAppSettingsRoutes } from "./api/app-settings-routes.js";
@@ -58,6 +59,7 @@ import { registerRuntimeStateRoutes } from "./api/runtime-state-routes.js";
58
59
  import { registerSessionRoutes } from "./api/session-routes.js";
59
60
  import { registerTaskRoutes } from "./api/task-routes.js";
60
61
  import { registerTranslationRoutes } from "./api/translation-routes.js";
62
+ import { registerUsageAnalyticsRoutes } from "./api/usage-analytics-routes.js";
61
63
  import { registerTerminalWs } from "./ws/terminal-ws.js";
62
64
  import { toVcmError } from "./errors.js";
63
65
  import { readVcmPackageVersion } from "./app-version.js";
@@ -133,7 +135,8 @@ export async function createServer(deps, options = {}) {
133
135
  sessionService: deps.sessionService,
134
136
  commandDispatcher: deps.commandDispatcher,
135
137
  translationService: deps.translationService,
136
- roundService: deps.roundService
138
+ roundService: deps.roundService,
139
+ architectRestartService: deps.architectRestartService
137
140
  });
138
141
  registerArtifactRoutes(app, {
139
142
  projectService: deps.projectService,
@@ -156,6 +159,11 @@ export async function createServer(deps, options = {}) {
156
159
  sessionService: deps.sessionService,
157
160
  translationService: deps.translationService
158
161
  });
162
+ registerUsageAnalyticsRoutes(app, {
163
+ projectService: deps.projectService,
164
+ taskService: deps.taskService,
165
+ usageAnalyticsService: deps.usageAnalyticsService
166
+ });
159
167
  registerGatewayRoutes(app, { gatewayService: deps.gatewayService });
160
168
  registerTerminalWs(app, {
161
169
  runtime: deps.runtime,
@@ -209,11 +217,9 @@ export function createDefaultServerDeps(options = {}) {
209
217
  const git = createGitAdapter(runner);
210
218
  const claude = createClaudeAdapter(runner);
211
219
  const appSettings = createAppSettingsService({ fs });
212
- const claudeSettings = createClaudeSettingsAdapter({ fs });
213
220
  const ccrIntegration = createCcrIntegrationService({
214
221
  settings: appSettings,
215
- gateway: createCcrGatewayAdapter(),
216
- restoreNativeClaudeSettings: () => claudeSettings.restoreNativeSettings()
222
+ gateway: createCcrGatewayAdapter()
217
223
  });
218
224
  const runtime = createNodePtyTerminalRuntime({ fs });
219
225
  const registry = createSessionRegistry();
@@ -267,12 +273,18 @@ export function createDefaultServerDeps(options = {}) {
267
273
  sessionService,
268
274
  artifactService
269
275
  });
276
+ const architectRestartService = createArchitectRestartService({
277
+ fs,
278
+ taskService,
279
+ sessionService
280
+ });
270
281
  const messageService = createMessageService({
271
282
  fs,
272
283
  runtime,
273
284
  sessionService,
274
285
  taskService,
275
- taskWorkflowService
286
+ taskWorkflowService,
287
+ onRouteDelivered: ({ repoRoot, taskSlug, message }) => architectRestartService.recordRouteDelivered(repoRoot, taskSlug, message)
276
288
  });
277
289
  const taskLaunchService = createTaskLaunchService({
278
290
  projectService,
@@ -315,6 +327,7 @@ export function createDefaultServerDeps(options = {}) {
315
327
  roundService,
316
328
  appSettings
317
329
  });
330
+ const usageAnalyticsService = createUsageAnalyticsService({ fs });
318
331
  const gatewayChannels = createGatewayChannelRegistry([
319
332
  createWeixinIlinkChannel(),
320
333
  createLarkChannel()
@@ -334,7 +347,8 @@ export function createDefaultServerDeps(options = {}) {
334
347
  translationService,
335
348
  roundService,
336
349
  projectService,
337
- taskWorkflowService
350
+ taskWorkflowService,
351
+ architectRestartService
338
352
  });
339
353
  const gatewayService = createGatewayService({
340
354
  fs,
@@ -371,7 +385,8 @@ export function createDefaultServerDeps(options = {}) {
371
385
  autoMemoryService,
372
386
  gatewayService,
373
387
  jobGuard: createJobGuardService(),
374
- translationWorkerService
388
+ translationWorkerService,
389
+ architectRestartService
375
390
  });
376
391
  const turnReconciler = createTurnReconcilerService({
377
392
  sessionService,
@@ -415,6 +430,7 @@ export function createDefaultServerDeps(options = {}) {
415
430
  taskService,
416
431
  taskCloseService,
417
432
  taskWorkflowService,
433
+ architectRestartService,
418
434
  sessionService,
419
435
  artifactService,
420
436
  harnessService,
@@ -434,7 +450,8 @@ export function createDefaultServerDeps(options = {}) {
434
450
  runtimeRecoveryService,
435
451
  terminalInterruptService,
436
452
  runtime,
437
- diagnosticsService
453
+ diagnosticsService,
454
+ usageAnalyticsService
438
455
  };
439
456
  }
440
457
  export function getDefaultStaticDir() {
@@ -0,0 +1,136 @@
1
+ import path from "node:path";
2
+ import { resolveRepoPath } from "../adapters/filesystem.js";
3
+ import { VcmError } from "../errors.js";
4
+ import { getTaskRuntimeRepoRoot } from "./task-service.js";
5
+ const ARCHITECT_ROLE = "architect";
6
+ const PM_ROLE = "project-manager";
7
+ const COMPLETE_PLAN_PATTERN = /^Planning Result:\s*complete\s*$/im;
8
+ export const ARCHITECT_RESTORE_PROMPT = `This Architect session continues the current task after completed architecture planning.
9
+
10
+ Before performing any assigned work, read:
11
+ - .ai/vcm/handoffs/architecture-brief.md
12
+ - .ai/vcm/handoffs/architecture-evidence.md
13
+ - .ai/vcm/handoffs/architecture-plan.md
14
+ - the current scaffold commit and worktree state
15
+ - the latest Gate Review report when present
16
+
17
+ Treat the current artifacts and worktree as the source of truth. Do not repeat the completed interview or planning work unless current evidence contradicts them.`;
18
+ export function createArchitectRestartService(deps) {
19
+ const pendingByTask = new Map();
20
+ return {
21
+ async schedule(repoRoot, taskSlug) {
22
+ const session = await requireRunningArchitect(repoRoot, taskSlug);
23
+ await requireCompletePlan(repoRoot, taskSlug);
24
+ const key = taskKey(repoRoot, taskSlug);
25
+ const existing = pendingByTask.get(key);
26
+ if (existing?.sessionId === session.id) {
27
+ return { taskSlug, sessionId: session.id, status: "scheduled" };
28
+ }
29
+ pendingByTask.set(key, {
30
+ repoRoot,
31
+ taskSlug,
32
+ sessionId: session.id,
33
+ stopped: false,
34
+ executing: false
35
+ });
36
+ return { taskSlug, sessionId: session.id, status: "scheduled" };
37
+ },
38
+ async recordArchitectStop(repoRoot, taskSlug, sessionId) {
39
+ const pending = pendingByTask.get(taskKey(repoRoot, taskSlug));
40
+ if (!pending || pending.sessionId !== sessionId) {
41
+ return;
42
+ }
43
+ pending.stopped = true;
44
+ await tryRestart(pending);
45
+ },
46
+ async recordRouteDelivered(repoRoot, taskSlug, message) {
47
+ if (!isArchitectToPm(message)) {
48
+ return;
49
+ }
50
+ const pending = pendingByTask.get(taskKey(repoRoot, taskSlug));
51
+ if (!pending) {
52
+ return;
53
+ }
54
+ pending.deliveredMessageId = message.id;
55
+ await tryRestart(pending);
56
+ },
57
+ async recordRouteAccepted(repoRoot, taskSlug, message) {
58
+ if (!isArchitectToPm(message)) {
59
+ return;
60
+ }
61
+ const pending = pendingByTask.get(taskKey(repoRoot, taskSlug));
62
+ if (!pending) {
63
+ return;
64
+ }
65
+ pending.acceptedMessageId = message.id;
66
+ await tryRestart(pending);
67
+ },
68
+ clear(repoRoot, taskSlug) {
69
+ pendingByTask.delete(taskKey(repoRoot, taskSlug));
70
+ }
71
+ };
72
+ async function requireRunningArchitect(repoRoot, taskSlug) {
73
+ const session = await deps.sessionService.getRoleSession(repoRoot, taskSlug, ARCHITECT_ROLE);
74
+ if (!session || session.status !== "running") {
75
+ throw new VcmError({
76
+ code: "ARCHITECT_SESSION_NOT_RUNNING",
77
+ message: "Architect session is not running.",
78
+ statusCode: 409
79
+ });
80
+ }
81
+ return session;
82
+ }
83
+ async function requireCompletePlan(repoRoot, taskSlug) {
84
+ const task = await deps.taskService.loadTask(repoRoot, taskSlug);
85
+ const planPath = resolveRepoPath(getTaskRuntimeRepoRoot(task), path.posix.join(task.handoffDir, "architecture-plan.md"));
86
+ if (!(await deps.fs.pathExists(planPath))) {
87
+ throw incompletePlanError("architecture-plan.md does not exist.");
88
+ }
89
+ const content = await deps.fs.readText(planPath);
90
+ if (!COMPLETE_PLAN_PATTERN.test(content)) {
91
+ throw incompletePlanError("architecture-plan.md is not marked complete.");
92
+ }
93
+ }
94
+ async function tryRestart(pending) {
95
+ if (pending.executing
96
+ || !pending.stopped
97
+ || !pending.deliveredMessageId
98
+ || pending.deliveredMessageId !== pending.acceptedMessageId) {
99
+ return;
100
+ }
101
+ const session = await deps.sessionService.getRoleSession(pending.repoRoot, pending.taskSlug, ARCHITECT_ROLE);
102
+ if (!session
103
+ || session.id !== pending.sessionId
104
+ || session.status !== "running"
105
+ || session.activityStatus !== "idle") {
106
+ return;
107
+ }
108
+ pending.executing = true;
109
+ try {
110
+ await requireCompletePlan(pending.repoRoot, pending.taskSlug);
111
+ await deps.sessionService.restartRoleSession(pending.repoRoot, pending.taskSlug, ARCHITECT_ROLE, {
112
+ permissionMode: session.permissionMode,
113
+ model: session.model,
114
+ effort: session.effort,
115
+ appendSystemPrompt: ARCHITECT_RESTORE_PROMPT
116
+ });
117
+ pendingByTask.delete(taskKey(pending.repoRoot, pending.taskSlug));
118
+ }
119
+ catch {
120
+ pending.executing = false;
121
+ }
122
+ }
123
+ }
124
+ function isArchitectToPm(message) {
125
+ return message.fromRole === ARCHITECT_ROLE && message.toRole === PM_ROLE;
126
+ }
127
+ function taskKey(repoRoot, taskSlug) {
128
+ return `${repoRoot}\0${taskSlug}`;
129
+ }
130
+ function incompletePlanError(reason) {
131
+ return new VcmError({
132
+ code: "ARCHITECT_PLAN_INCOMPLETE",
133
+ message: `Architect restart cannot be scheduled. ${reason}`,
134
+ statusCode: 409
135
+ });
136
+ }
@@ -1,11 +1,15 @@
1
1
  import { CCR_GATEWAY_BASE_URL, CCR_GPT_MODEL_ID, createSessionModelOptions, isCcrSessionModel } from "../../shared/types/session.js";
2
+ import path from "node:path";
2
3
  import { fileURLToPath } from "node:url";
3
4
  import { VcmError } from "../errors.js";
5
+ import { resolveVcmDataDir } from "../vcm-data-dir.js";
4
6
  const DEFAULT_CACHE_TTL_MS = 10_000;
5
7
  const DEFAULT_API_KEY_HELPER_PATH = fileURLToPath(new URL("../../../scripts/ccr-api-key-helper.mjs", import.meta.url));
6
8
  export function createCcrIntegrationService(deps) {
7
9
  const now = deps.now ?? (() => new Date());
8
10
  const cacheTtlMs = deps.cacheTtlMs ?? DEFAULT_CACHE_TTL_MS;
11
+ const baseEnv = deps.baseEnv ?? process.env;
12
+ const configDir = deps.configDir ?? path.join(resolveVcmDataDir(baseEnv), "claude", "ccr");
9
13
  let cachedProbe;
10
14
  let inFlight;
11
15
  async function probe(force = false) {
@@ -67,9 +71,6 @@ export function createCcrIntegrationService(deps) {
67
71
  return {
68
72
  async initialize() {
69
73
  const settings = await deps.settings.getCcrIntegrationSettings();
70
- if (settings.apiKey) {
71
- await deps.restoreNativeClaudeSettings?.();
72
- }
73
74
  if (settings.enabled) {
74
75
  await probe(true);
75
76
  }
@@ -102,9 +103,6 @@ export function createCcrIntegrationService(deps) {
102
103
  enabled: nextEnabled,
103
104
  apiKey: nextApiKey
104
105
  });
105
- if (nextApiKey) {
106
- await deps.restoreNativeClaudeSettings?.();
107
- }
108
106
  if (clearApiKey || !nextEnabled) {
109
107
  cachedProbe = undefined;
110
108
  return buildStatus();
@@ -130,7 +128,7 @@ export function createCcrIntegrationService(deps) {
130
128
  },
131
129
  async getLaunchEnvironment(model) {
132
130
  if (!isCcrSessionModel(model)) {
133
- return {};
131
+ return buildNativeLaunchEnvironment(baseEnv);
134
132
  }
135
133
  const settings = await deps.settings.getCcrIntegrationSettings();
136
134
  if (!settings.enabled) {
@@ -164,15 +162,12 @@ export function createCcrIntegrationService(deps) {
164
162
  CODEXL_CLAUDE_CODE_MODEL: CCR_GPT_MODEL_ID,
165
163
  ANTHROPIC_SMALL_FAST_MODEL: CCR_GPT_MODEL_ID,
166
164
  CLAUDE_CODE_ENABLE_GATEWAY_MODEL_DISCOVERY: "1",
165
+ CLAUDE_CONFIG_DIR: configDir,
167
166
  NO_PROXY: noProxy,
168
167
  no_proxy: noProxy
169
168
  };
170
169
  },
171
170
  async getLaunchSettingsOverride(model) {
172
- const settings = await deps.settings.getCcrIntegrationSettings();
173
- if (settings.apiKey) {
174
- await deps.restoreNativeClaudeSettings?.();
175
- }
176
171
  if (!isCcrSessionModel(model)) {
177
172
  return undefined;
178
173
  }
@@ -182,6 +177,70 @@ export function createCcrIntegrationService(deps) {
182
177
  }
183
178
  };
184
179
  }
180
+ const CCR_BASE_URL_KEYS = [
181
+ "ANTHROPIC_BASE_URL",
182
+ "ANTHROPIC_API_BASE_URL",
183
+ "CLAUDE_AGENT_API_BASE_URL"
184
+ ];
185
+ const CCR_MODEL_KEYS = [
186
+ "ANTHROPIC_MODEL",
187
+ "ANTHROPIC_SMALL_FAST_MODEL"
188
+ ];
189
+ const CCR_ONLY_ENV_KEYS = [
190
+ "CCR_CLAUDE_CODE_MODEL",
191
+ "CODEXL_CLAUDE_CODE_MODEL",
192
+ "CLAUDE_CODE_ENABLE_GATEWAY_MODEL_DISCOVERY"
193
+ ];
194
+ export function buildNativeLaunchEnvironment(baseEnv) {
195
+ const environment = {};
196
+ let inheritedCcrTakeover = false;
197
+ if (baseEnv.CLAUDE_CONFIG_DIR?.trim()) {
198
+ environment.CLAUDE_CONFIG_DIR = baseEnv.CLAUDE_CONFIG_DIR.trim();
199
+ }
200
+ for (const key of CCR_BASE_URL_KEYS) {
201
+ if (isCcrGatewayUrl(baseEnv[key])) {
202
+ environment[key] = undefined;
203
+ inheritedCcrTakeover = true;
204
+ }
205
+ }
206
+ for (const key of CCR_MODEL_KEYS) {
207
+ if (isCcrModelName(baseEnv[key])) {
208
+ environment[key] = undefined;
209
+ inheritedCcrTakeover = true;
210
+ }
211
+ }
212
+ for (const key of CCR_ONLY_ENV_KEYS) {
213
+ if (baseEnv[key] !== undefined) {
214
+ environment[key] = undefined;
215
+ inheritedCcrTakeover = true;
216
+ }
217
+ }
218
+ if (inheritedCcrTakeover) {
219
+ environment.ANTHROPIC_AUTH_TOKEN = undefined;
220
+ environment.ANTHROPIC_API_KEY = undefined;
221
+ }
222
+ return environment;
223
+ }
224
+ function isCcrGatewayUrl(value) {
225
+ if (!value) {
226
+ return false;
227
+ }
228
+ try {
229
+ const url = new URL(value);
230
+ return url.port === "3456"
231
+ && ["127.0.0.1", "localhost", "host.docker.internal"].includes(url.hostname);
232
+ }
233
+ catch {
234
+ return false;
235
+ }
236
+ }
237
+ function isCcrModelName(value) {
238
+ if (!value) {
239
+ return false;
240
+ }
241
+ return value === CCR_GPT_MODEL_ID
242
+ || value.startsWith("anthropic/claude-ccr-h");
243
+ }
185
244
  function createStatus(input) {
186
245
  const reason = input.error
187
246
  ?? (input.enabled ? "CCR GPT-5.6 Sol is unavailable." : "Enable CCR GPT models in Settings.");