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.
- package/README.md +30 -9
- package/dist/backend/adapters/claude-adapter.js +4 -1
- package/dist/backend/api/session-routes.js +5 -0
- package/dist/backend/api/usage-analytics-routes.js +31 -0
- package/dist/backend/cli/install-vcm-harness.js +51 -2
- package/dist/backend/runtime/session-registry.js +7 -0
- package/dist/backend/server.js +26 -9
- package/dist/backend/services/architect-restart-service.js +136 -0
- package/dist/backend/services/ccr-integration-service.js +70 -11
- package/dist/backend/services/claude-hook-service.js +6 -0
- package/dist/backend/services/claude-transcript-service.js +10 -10
- package/dist/backend/services/gate-review-service.js +40 -0
- package/dist/backend/services/harness-service.js +51 -4
- package/dist/backend/services/message-service.js +5 -0
- package/dist/backend/services/runtime-coordinator-service.js +37 -42
- package/dist/backend/services/session-service.js +83 -19
- package/dist/backend/services/task-close-service.js +1 -0
- package/dist/backend/services/usage-analytics-service.js +346 -0
- package/dist/backend/templates/harness/architect-agent.js +18 -10
- package/dist/backend/templates/harness/architect-scaffold-worker-agent.js +23 -0
- package/dist/backend/templates/harness/claude-root.js +1 -1
- package/dist/backend/templates/harness/gate-review.js +7 -2
- package/dist/backend/templates/harness/project-manager-agent.js +1 -1
- package/dist/backend/templates/harness/restart-architect-skill.js +75 -0
- package/dist/backend/templates/harness/vcm-architecture-interview-skill.js +31 -5
- package/dist/backend/templates/harness/vcm-route-message-skill.js +3 -3
- package/dist/shared/types/usage-analytics.js +1 -0
- package/dist-frontend/assets/index-CStWyouh.js +97 -0
- package/dist-frontend/assets/{index-CiEUp9Si.css → index-Ci7z8tW3.css} +1 -1
- package/dist-frontend/index.html +2 -2
- package/package.json +1 -1
- package/dist/backend/adapters/claude-settings-adapter.js +0 -79
- 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
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
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.
|
|
432
|
-
|
|
433
|
-
the project harness
|
|
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
|
-
|
|
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) {
|
package/dist/backend/server.js
CHANGED
|
@@ -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.");
|