vibe-coding-master 0.7.43 → 0.7.45
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 +64 -39
- package/dist/backend/adapters/claude-adapter.js +2 -2
- package/dist/backend/adapters/codex-bridge-adapter.js +213 -0
- package/dist/backend/api/app-settings-routes.js +6 -6
- package/dist/backend/api/artifact-routes.js +3 -0
- package/dist/backend/api/harness-routes.js +16 -0
- package/dist/backend/api/task-routes.js +1 -0
- package/dist/backend/api/workflow-control-routes.js +12 -0
- package/dist/backend/cli/install-vcm-harness.js +21 -0
- package/dist/backend/server.js +11 -9
- package/dist/backend/services/app-settings-service.js +11 -11
- package/dist/backend/services/artifact-service.js +7 -30
- package/dist/backend/services/auto-memory-service.js +640 -12
- package/dist/backend/services/claude-hook-service.js +80 -2
- package/dist/backend/services/{ccr-integration-service.js → codex-bridge-integration-service.js} +71 -71
- package/dist/backend/services/gate-review-service.js +173 -74
- package/dist/backend/services/harness-feedback-service.js +76 -78
- package/dist/backend/services/harness-service.js +27 -3
- package/dist/backend/services/runtime-coordinator-service.js +2 -1
- package/dist/backend/services/session-service.js +16 -16
- package/dist/backend/services/status-service.js +1 -0
- package/dist/backend/services/translation-worker-service.js +19 -4
- package/dist/backend/services/usage-analytics-service.js +3 -3
- package/dist/backend/services/workflow-control-service.js +96 -12
- package/dist/backend/templates/handoff.js +44 -2
- package/dist/backend/templates/harness/architect-agent.js +13 -7
- package/dist/backend/templates/harness/architect-scaffold-worker-agent.js +1 -1
- package/dist/backend/templates/harness/check-scaffold-ledger.js +234 -10
- package/dist/backend/templates/harness/claude-root.js +3 -2
- package/dist/backend/templates/harness/coder-agent.js +8 -0
- package/dist/backend/templates/harness/gate-review.js +144 -49
- package/dist/backend/templates/harness/harness-engineer-agent.js +25 -10
- package/dist/backend/templates/harness/project-manager-agent.js +16 -10
- package/dist/backend/templates/harness/resolve-durable-doc-assignment.js +60 -0
- package/dist/backend/templates/harness/tester-agent.js +13 -0
- package/dist/backend/templates/harness/vcm-ask-user-skill.js +82 -0
- package/dist/backend/templates/harness/vcm-code-navigation-skill.js +7 -5
- package/dist/backend/templates/harness/vcm-task-state-skill.js +2 -2
- package/dist/backend/templates/harness/vcm-workflow-review-skill.js +1 -1
- package/dist/shared/types/session.js +28 -22
- package/dist/shared/types/workflow.js +1 -0
- package/dist/shared/validation/artifact-check.js +3 -3
- package/dist/shared/validation/artifact-contract.js +1 -1
- package/dist/shared/validation/artifact-registry.js +16 -0
- package/dist-frontend/assets/index-DDmygnV6.css +32 -0
- package/dist-frontend/assets/{index-VW9tYPP5.js → index-nAV6toi8.js} +47 -47
- package/dist-frontend/index.html +2 -2
- package/package.json +1 -1
- package/scripts/claude-plugins/vcm-lsp-bridge/.claude-plugin/plugin.json +21 -6
- package/scripts/{ccr-api-key-helper.mjs → codex-bridge-api-key-helper.mjs} +1 -1
- package/scripts/harness-tools/vcm-artifact +1 -2
- package/scripts/harness-tools/vcm-bash-guard +1 -1
- package/dist/backend/adapters/ccr-gateway-adapter.js +0 -172
- package/dist-frontend/assets/index-B0d4Z6ny.css +0 -32
package/README.md
CHANGED
|
@@ -190,6 +190,12 @@ restart, but it does not infer transitions or choose the next role. Current
|
|
|
190
190
|
artifacts, Gate Review state, Round/Turn state, and the role rules remain
|
|
191
191
|
authoritative.
|
|
192
192
|
|
|
193
|
+
Whenever Project Manager asks the user a question, it first uses
|
|
194
|
+
`vcm-ask-user`. VCM records the wait and cancels any pending role dispatch in
|
|
195
|
+
one state update. Stop Hook then ends the PM turn without route scanning. Only
|
|
196
|
+
a new direct user message clears the wait, and the next role dispatch requires
|
|
197
|
+
a fresh Workflow Review.
|
|
198
|
+
|
|
193
199
|
Typical flow:
|
|
194
200
|
|
|
195
201
|
```text
|
|
@@ -237,18 +243,18 @@ Open `Usage Analytics` in the sidebar `Task` section to inspect native Claude
|
|
|
237
243
|
Code usage for the active task. The report shows task totals and breakdowns by
|
|
238
244
|
role and model for input, output, cache-read, and cache-creation tokens plus
|
|
239
245
|
estimated USD cost. It combines every restart and resumed Claude session for
|
|
240
|
-
all seven roles.
|
|
246
|
+
all seven roles. Codex Bridge usage is excluded.
|
|
241
247
|
|
|
242
248
|
VCM retains only aggregate task data in
|
|
243
249
|
`<task-worktree>/.ai/vcm/telemetry/usage.json`. The file is temporary runtime
|
|
244
250
|
state and is removed with the task worktree when the task is closed.
|
|
245
251
|
|
|
246
|
-
### GPT Through
|
|
252
|
+
### GPT Through Codex Bridge
|
|
247
253
|
|
|
248
|
-
VCM can launch its normal Claude Code sessions with
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
254
|
+
VCM can launch its normal Claude Code sessions with models provided by a
|
|
255
|
+
host-running Codex Bridge. The Bridge must already be installed and running,
|
|
256
|
+
and Codex must be logged in on the host. VCM does not manage the Bridge process
|
|
257
|
+
or Codex login.
|
|
252
258
|
|
|
253
259
|
VCM automatically checks the local-host and DevContainer endpoints:
|
|
254
260
|
|
|
@@ -257,36 +263,34 @@ http://127.0.0.1:3456
|
|
|
257
263
|
http://host.docker.internal:3456
|
|
258
264
|
```
|
|
259
265
|
|
|
260
|
-
Configure
|
|
261
|
-
DevContainer,
|
|
262
|
-
`Settings` section:
|
|
266
|
+
Configure Codex Bridge to listen on port `3456`. When VCM runs in a
|
|
267
|
+
DevContainer, start it with a container-reachable bind address such as
|
|
268
|
+
`codex-bridge serve --host 0.0.0.0 --port 3456`. In the VCM `Settings` section:
|
|
263
269
|
|
|
264
|
-
1. enter and save the
|
|
265
|
-
2. enable `
|
|
270
|
+
1. enter and save the Codex Bridge API key;
|
|
271
|
+
2. enable `Codex Bridge models`;
|
|
266
272
|
3. confirm the status is `available`;
|
|
267
|
-
4. select
|
|
273
|
+
4. select any discovered Codex Bridge model in a Session model control.
|
|
268
274
|
|
|
269
275
|
The key is stored in global VCM state (`~/.vcm/settings.json`) with owner-only
|
|
270
|
-
permissions and is never returned by the settings API. It is used for
|
|
271
|
-
checks, model discovery, and the
|
|
276
|
+
permissions and is never returned by the settings API. It is used for Bridge
|
|
277
|
+
checks, model discovery, and the Bridge-only `apiKeyHelper`. Bridge sessions receive a
|
|
272
278
|
child-only `--settings` override and use the isolated Claude configuration root
|
|
273
|
-
`~/.vcm/claude/
|
|
279
|
+
`~/.vcm/claude/codex-bridge`. VCM never edits `~/.claude/settings.json`. Native Claude
|
|
274
280
|
sessions keep their normal configuration and account authentication; VCM only
|
|
275
|
-
removes inherited environment variables that clearly point at the local
|
|
276
|
-
|
|
277
|
-
global Claude Code or Claude App takeover if those clients should remain on
|
|
278
|
-
Anthropic.
|
|
281
|
+
removes inherited environment variables that clearly point at the local Bridge
|
|
282
|
+
from the native child process.
|
|
279
283
|
|
|
280
|
-
|
|
284
|
+
Codex Bridge child processes use a hard context limit and auto-compaction window of
|
|
281
285
|
`258400` tokens. VCM sets Claude Code's proactive compaction threshold to `90%`
|
|
282
|
-
(about `232560` tokens), leaving headroom before the
|
|
286
|
+
(about `232560` tokens), leaving headroom before the Codex API limit. Native
|
|
283
287
|
Claude sessions receive no VCM-owned context override.
|
|
284
288
|
|
|
285
289
|
Resume keeps the provider recorded by the existing Session. Use Restart when
|
|
286
|
-
switching between
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
not fall back to another model.
|
|
290
|
+
switching between native Claude and Codex Bridge. If the Bridge is disabled,
|
|
291
|
+
unreachable, rejects the key, has unavailable Codex credentials, or no longer
|
|
292
|
+
exposes the selected model, VCM blocks the new Start, Resume, or Restart and
|
|
293
|
+
does not fall back to another model.
|
|
290
294
|
|
|
291
295
|
## Launch Template
|
|
292
296
|
|
|
@@ -329,7 +333,8 @@ strict `workflow-progress.md` transition through `vcm-workflow-review`. VCM
|
|
|
329
333
|
checks the confirmed dispatch history and current task artifacts, then grants
|
|
330
334
|
one matching route. The target role's `UserPromptSubmit` consumes that approval
|
|
331
335
|
and appends the confirmed transition. A rejected transition can be bypassed
|
|
332
|
-
only by
|
|
336
|
+
only by the user's exact one-time authorization recorded in the next Workflow
|
|
337
|
+
Progress submission.
|
|
333
338
|
|
|
334
339
|
If the flow stops, VCM always shows a blocking pause alert. `Pause alert sound`
|
|
335
340
|
only controls the looping sound. Enabling Gateway turns that preference off once;
|
|
@@ -475,11 +480,14 @@ Use it to:
|
|
|
475
480
|
appropriate
|
|
476
481
|
|
|
477
482
|
VCM bundles the Claude Code LSP bridge and loads it only for Architect sessions,
|
|
478
|
-
including
|
|
483
|
+
including Codex Bridge launches. The project environment must still
|
|
479
484
|
provide the language server for each detected language: `rust-analyzer`,
|
|
480
485
|
`typescript-language-server`, `pyright-langserver`, `gopls`, `clangd`, or
|
|
481
486
|
`jdtls`. Architect preloads `vcm-code-navigation` and uses LSP for semantic
|
|
482
|
-
relationships
|
|
487
|
+
relationships. Accessors, backing fields, trait items, implementations, wrappers,
|
|
488
|
+
and aliases are queried as separate symbols; a bounded source-text fallback may
|
|
489
|
+
only locate candidates after the correct LSP query is demonstrably partial.
|
|
490
|
+
Other roles use generated context and ordinary source reads.
|
|
483
491
|
Harness Studio reports
|
|
484
492
|
whether the server executable and plugin can run; the role Session performs the
|
|
485
493
|
real workspace indexing and semantic query retries. VCM uses Claude Code progress
|
|
@@ -498,10 +506,11 @@ files.
|
|
|
498
506
|
`Auto memory` is the switch for the entire automated memory workflow. During
|
|
499
507
|
Review Task Harness after Final Acceptance, Project Manager, Architect, Coder,
|
|
500
508
|
Tester, and an enabled Reviewer submit proposals in sequence through
|
|
501
|
-
`vcm-propose-memory`. Harness Engineer verifies and consolidates them
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
assigned Auto Memory
|
|
509
|
+
`vcm-propose-memory`. Harness Engineer verifies and consolidates them, commits
|
|
510
|
+
the reviewed memory, and writes the assigned machine-readable review result as
|
|
511
|
+
part of the same Task Harness Retrospective. Workflow roles cannot edit active
|
|
512
|
+
memory directly; Harness Engineer edits it only during the assigned Auto Memory
|
|
513
|
+
Retrospective.
|
|
505
514
|
|
|
506
515
|
When Auto Memory is enabled, the planning Architect writes a provisional memory
|
|
507
516
|
candidate before its post-planning Session restart. VCM snapshots that candidate
|
|
@@ -528,7 +537,10 @@ Final Acceptance
|
|
|
528
537
|
-> Review pending Harness Feedback
|
|
529
538
|
-> Review Auto Memory proposals, when Auto Memory is enabled
|
|
530
539
|
-> Harness Engineer updates and commits memory, when Auto Memory is enabled
|
|
531
|
-
|
|
540
|
+
-> Harness Engineer writes review-result.json, when Auto Memory is enabled
|
|
541
|
+
-> VCM validates and records the committed memory result
|
|
542
|
+
-> VCM dispatches durable-document assignments one at a time
|
|
543
|
+
-> Complete Task Harness Retrospective after every assignment completes
|
|
532
544
|
```
|
|
533
545
|
|
|
534
546
|
Memory proposal prompts sent to Project Manager, Architect, Coder, Tester, and
|
|
@@ -542,16 +554,29 @@ impact, and durable-document disposition, then evaluates every proposal item
|
|
|
542
554
|
independently. Each Add or Update decision records why the memory is necessary,
|
|
543
555
|
what happens if it is absent, whether it belongs in memory or a durable
|
|
544
556
|
document, and the exact final memory content when retained. VCM validates that
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
557
|
+
every existing entry and proposal has one structurally valid decision, every
|
|
558
|
+
move decision has one durable-document assignment, moved content has already
|
|
559
|
+
been removed from memory, and the commit changes only assigned memory host
|
|
560
|
+
files and only their `<VCM-memory>` blocks. VCM does not decide whether the
|
|
561
|
+
Harness Engineer's semantic judgment is correct. Harness Engineer review and
|
|
562
|
+
retrospective work remain tool role activity and do not participate in Round
|
|
563
|
+
completion.
|
|
564
|
+
|
|
565
|
+
For `move-to-durable-doc`, memory is removed in the Harness Engineer commit
|
|
566
|
+
before documentation work starts. VCM assigns architecture and known-issues
|
|
567
|
+
documents to Architect and testing documentation to Tester. For another target,
|
|
568
|
+
PM selects Architect, Coder, or Tester and asks the user only when ownership
|
|
569
|
+
cannot be determined. These assignments are backend-dispatched work rather than
|
|
570
|
+
a Docs-Only Flow. Each owner updates and commits the document, submits the exact
|
|
571
|
+
assignment ID in `docs-update-report.md`, and finishes before the next assignment
|
|
572
|
+
starts. Harness Studio shows assignment status and allows failed work to be
|
|
573
|
+
retried.
|
|
549
574
|
|
|
550
575
|
When Auto Memory is disabled, Review Task Harness does not collect proposals or
|
|
551
576
|
ask Harness Engineer to update memory. When enabled, both automatic and manual
|
|
552
577
|
review requests collect proposals before starting the retrospective. A failed
|
|
553
|
-
proposal collection
|
|
554
|
-
from Harness Studio.
|
|
578
|
+
proposal collection, combined retrospective memory review, or durable-document
|
|
579
|
+
assignment must be retried from Harness Studio.
|
|
555
580
|
|
|
556
581
|
## Closing a Task
|
|
557
582
|
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { roleRuntimeDisallowedTools, roleUsesLsp } from "../role-tool-policy.js";
|
|
2
|
-
import {
|
|
2
|
+
import { isCodexBridgeSessionModel } from "../../shared/types/session.js";
|
|
3
3
|
import { VcmError } from "../errors.js";
|
|
4
4
|
export function createClaudeAdapter(runner) {
|
|
5
5
|
return {
|
|
@@ -33,7 +33,7 @@ export function createClaudeAdapter(runner) {
|
|
|
33
33
|
if (claudeSessionId) {
|
|
34
34
|
args.push(resume ? "--resume" : "--session-id", claudeSessionId);
|
|
35
35
|
}
|
|
36
|
-
if (!
|
|
36
|
+
if (!isCodexBridgeSessionModel(model)) {
|
|
37
37
|
args.push("--model", model);
|
|
38
38
|
}
|
|
39
39
|
if (effort === "ultracode") {
|
|
@@ -0,0 +1,213 @@
|
|
|
1
|
+
import { CODEX_BRIDGE_BASE_URLS } from "../../shared/types/session.js";
|
|
2
|
+
const DEFAULT_TIMEOUT_MS = 3_000;
|
|
3
|
+
export function createCodexBridgeAdapter(deps = {}) {
|
|
4
|
+
const baseUrls = (deps.baseUrl ? [deps.baseUrl] : CODEX_BRIDGE_BASE_URLS)
|
|
5
|
+
.map((baseUrl) => baseUrl.replace(/\/+$/, ""));
|
|
6
|
+
const fetchImpl = deps.fetch ?? globalThis.fetch;
|
|
7
|
+
const timeoutMs = deps.timeoutMs ?? DEFAULT_TIMEOUT_MS;
|
|
8
|
+
return {
|
|
9
|
+
async probe(apiKey) {
|
|
10
|
+
const failures = [];
|
|
11
|
+
for (const baseUrl of baseUrls) {
|
|
12
|
+
const result = await probeEndpoint(fetchImpl, baseUrl, apiKey, timeoutMs);
|
|
13
|
+
if (result.baseUrl) {
|
|
14
|
+
return result;
|
|
15
|
+
}
|
|
16
|
+
failures.push(result);
|
|
17
|
+
}
|
|
18
|
+
return combineProbeFailures(baseUrls, failures);
|
|
19
|
+
}
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
async function probeEndpoint(fetchImpl, baseUrl, apiKey, timeoutMs) {
|
|
23
|
+
try {
|
|
24
|
+
const health = await requestJson(fetchImpl, `${baseUrl}/health`, timeoutMs);
|
|
25
|
+
if (!health.response.ok) {
|
|
26
|
+
return invalidResponse(`Codex Bridge health check at ${baseUrl} returned HTTP ${health.response.status}.`);
|
|
27
|
+
}
|
|
28
|
+
if (!isCodexBridgeHealth(health.payload)) {
|
|
29
|
+
return {
|
|
30
|
+
connectionState: "not-codex-bridge",
|
|
31
|
+
modelAvailable: false,
|
|
32
|
+
models: [],
|
|
33
|
+
error: `${baseUrl} did not identify as Codex Bridge.`
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
const headers = {
|
|
37
|
+
authorization: `Bearer ${apiKey}`,
|
|
38
|
+
"user-agent": "VibeCodingMaster"
|
|
39
|
+
};
|
|
40
|
+
const auth = await requestJson(fetchImpl, `${baseUrl}/auth/status`, timeoutMs, headers);
|
|
41
|
+
if (auth.response.status === 401 || auth.response.status === 403) {
|
|
42
|
+
return {
|
|
43
|
+
connectionState: "unauthorized",
|
|
44
|
+
modelAvailable: false,
|
|
45
|
+
models: [],
|
|
46
|
+
baseUrl,
|
|
47
|
+
error: "Codex Bridge rejected the configured API key."
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
if (!auth.response.ok) {
|
|
51
|
+
return invalidResponse(responseError("Codex Bridge auth status", baseUrl, auth.response, auth.payload), baseUrl);
|
|
52
|
+
}
|
|
53
|
+
const authState = readAuthState(auth.payload);
|
|
54
|
+
if (!authState) {
|
|
55
|
+
return invalidResponse("Codex Bridge returned an invalid /auth/status response.", baseUrl);
|
|
56
|
+
}
|
|
57
|
+
if (authState.state !== "ready") {
|
|
58
|
+
return {
|
|
59
|
+
connectionState: "codex-auth-unavailable",
|
|
60
|
+
modelAvailable: false,
|
|
61
|
+
models: [],
|
|
62
|
+
baseUrl,
|
|
63
|
+
error: authState.message
|
|
64
|
+
?? `Codex authentication is ${authState.state}. Open Codex or run codex login, then retry.`
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
const modelsResponse = await requestJson(fetchImpl, `${baseUrl}/v1/models`, timeoutMs, headers);
|
|
68
|
+
if (!modelsResponse.response.ok) {
|
|
69
|
+
const code = readErrorCode(modelsResponse.payload);
|
|
70
|
+
const connectionState = code?.startsWith("CODEX_AUTH_")
|
|
71
|
+
? "codex-auth-unavailable"
|
|
72
|
+
: modelsResponse.response.status === 401 || modelsResponse.response.status === 403
|
|
73
|
+
? "unauthorized"
|
|
74
|
+
: "invalid-response";
|
|
75
|
+
return {
|
|
76
|
+
connectionState,
|
|
77
|
+
modelAvailable: false,
|
|
78
|
+
models: [],
|
|
79
|
+
baseUrl,
|
|
80
|
+
error: responseError("Codex Bridge model discovery", baseUrl, modelsResponse.response, modelsResponse.payload)
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
const models = readModelDescriptors(modelsResponse.payload);
|
|
84
|
+
if (!models) {
|
|
85
|
+
return invalidResponse("Codex Bridge returned an invalid /v1/models response.", baseUrl);
|
|
86
|
+
}
|
|
87
|
+
return {
|
|
88
|
+
connectionState: "available",
|
|
89
|
+
modelAvailable: models.length > 0,
|
|
90
|
+
models,
|
|
91
|
+
baseUrl,
|
|
92
|
+
...(models.length > 0 ? {} : { error: "Codex Bridge did not expose any models." })
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
catch (error) {
|
|
96
|
+
const timedOut = isAbortError(error);
|
|
97
|
+
return {
|
|
98
|
+
connectionState: "unreachable",
|
|
99
|
+
modelAvailable: false,
|
|
100
|
+
models: [],
|
|
101
|
+
error: timedOut
|
|
102
|
+
? `Codex Bridge connection timed out after ${timeoutMs} ms at ${baseUrl}.`
|
|
103
|
+
: `Codex Bridge could not be reached from the VCM backend at ${baseUrl}. Reason: ${errorMessage(error)}`
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
function combineProbeFailures(baseUrls, failures) {
|
|
108
|
+
const lastFailure = failures.at(-1);
|
|
109
|
+
return {
|
|
110
|
+
connectionState: lastFailure?.connectionState ?? "unreachable",
|
|
111
|
+
modelAvailable: false,
|
|
112
|
+
models: [],
|
|
113
|
+
error: `Codex Bridge was not found at ${baseUrls.join(" or ")}. ${failures
|
|
114
|
+
.map((failure) => failure.error)
|
|
115
|
+
.filter(Boolean)
|
|
116
|
+
.join(" ")}`.trim()
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
async function requestJson(fetchImpl, url, timeoutMs, headers = {}) {
|
|
120
|
+
const controller = new AbortController();
|
|
121
|
+
const timeout = setTimeout(() => controller.abort(), timeoutMs);
|
|
122
|
+
try {
|
|
123
|
+
const response = await fetchImpl(url, {
|
|
124
|
+
method: "GET",
|
|
125
|
+
headers: {
|
|
126
|
+
accept: "application/json",
|
|
127
|
+
...headers
|
|
128
|
+
},
|
|
129
|
+
signal: controller.signal
|
|
130
|
+
});
|
|
131
|
+
let payload;
|
|
132
|
+
try {
|
|
133
|
+
payload = await response.json();
|
|
134
|
+
}
|
|
135
|
+
catch {
|
|
136
|
+
payload = undefined;
|
|
137
|
+
}
|
|
138
|
+
return { response, payload };
|
|
139
|
+
}
|
|
140
|
+
finally {
|
|
141
|
+
clearTimeout(timeout);
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
function isCodexBridgeHealth(value) {
|
|
145
|
+
return isObject(value)
|
|
146
|
+
&& value.status === "ok"
|
|
147
|
+
&& value.service === "codex-bridge";
|
|
148
|
+
}
|
|
149
|
+
function readAuthState(value) {
|
|
150
|
+
if (!isObject(value) || typeof value.state !== "string" || !value.state.trim()) {
|
|
151
|
+
return undefined;
|
|
152
|
+
}
|
|
153
|
+
return {
|
|
154
|
+
state: value.state.trim(),
|
|
155
|
+
...(typeof value.message === "string" && value.message.trim()
|
|
156
|
+
? { message: value.message.trim() }
|
|
157
|
+
: {})
|
|
158
|
+
};
|
|
159
|
+
}
|
|
160
|
+
function readModelDescriptors(value) {
|
|
161
|
+
if (!isObject(value) || !Array.isArray(value.data)) {
|
|
162
|
+
return undefined;
|
|
163
|
+
}
|
|
164
|
+
const models = [];
|
|
165
|
+
for (const item of value.data) {
|
|
166
|
+
if (!isObject(item) || typeof item.id !== "string" || !item.id.trim()) {
|
|
167
|
+
return undefined;
|
|
168
|
+
}
|
|
169
|
+
models.push({
|
|
170
|
+
id: item.id.trim(),
|
|
171
|
+
...(typeof item.display_name === "string" && item.display_name.trim()
|
|
172
|
+
? { displayName: item.display_name.trim() }
|
|
173
|
+
: {})
|
|
174
|
+
});
|
|
175
|
+
}
|
|
176
|
+
return models;
|
|
177
|
+
}
|
|
178
|
+
function responseError(operation, baseUrl, response, payload) {
|
|
179
|
+
const message = readErrorMessage(payload);
|
|
180
|
+
return `${operation} at ${baseUrl} returned HTTP ${response.status}.${message ? ` Reason: ${message}` : ""}`;
|
|
181
|
+
}
|
|
182
|
+
function readErrorCode(value) {
|
|
183
|
+
return isObject(value)
|
|
184
|
+
&& isObject(value.error)
|
|
185
|
+
&& typeof value.error.code === "string"
|
|
186
|
+
? value.error.code
|
|
187
|
+
: undefined;
|
|
188
|
+
}
|
|
189
|
+
function readErrorMessage(value) {
|
|
190
|
+
return isObject(value)
|
|
191
|
+
&& isObject(value.error)
|
|
192
|
+
&& typeof value.error.message === "string"
|
|
193
|
+
? value.error.message
|
|
194
|
+
: undefined;
|
|
195
|
+
}
|
|
196
|
+
function invalidResponse(error, baseUrl) {
|
|
197
|
+
return {
|
|
198
|
+
connectionState: "invalid-response",
|
|
199
|
+
modelAvailable: false,
|
|
200
|
+
models: [],
|
|
201
|
+
...(baseUrl ? { baseUrl } : {}),
|
|
202
|
+
error
|
|
203
|
+
};
|
|
204
|
+
}
|
|
205
|
+
function isAbortError(error) {
|
|
206
|
+
return error instanceof Error && error.name === "AbortError";
|
|
207
|
+
}
|
|
208
|
+
function errorMessage(error) {
|
|
209
|
+
return error instanceof Error ? error.message : String(error);
|
|
210
|
+
}
|
|
211
|
+
function isObject(value) {
|
|
212
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
213
|
+
}
|
|
@@ -5,13 +5,13 @@ export function registerAppSettingsRoutes(app, deps) {
|
|
|
5
5
|
app.put("/api/settings/preferences", async (request) => {
|
|
6
6
|
return deps.appSettings.updatePreferences(request.body ?? {});
|
|
7
7
|
});
|
|
8
|
-
app.get("/api/settings/
|
|
9
|
-
return deps.
|
|
8
|
+
app.get("/api/settings/codex-bridge", async () => {
|
|
9
|
+
return deps.codexBridgeIntegration.getStatus();
|
|
10
10
|
});
|
|
11
|
-
app.put("/api/settings/
|
|
12
|
-
return deps.
|
|
11
|
+
app.put("/api/settings/codex-bridge", async (request) => {
|
|
12
|
+
return deps.codexBridgeIntegration.updateSettings(request.body ?? {});
|
|
13
13
|
});
|
|
14
|
-
app.post("/api/settings/
|
|
15
|
-
return deps.
|
|
14
|
+
app.post("/api/settings/codex-bridge/check", async () => {
|
|
15
|
+
return deps.codexBridgeIntegration.checkConnection();
|
|
16
16
|
});
|
|
17
17
|
}
|
|
@@ -144,6 +144,9 @@ function artifactNameToPath(paths, artifactName) {
|
|
|
144
144
|
if (artifactName === "test-report.md") {
|
|
145
145
|
return paths.testReportPath;
|
|
146
146
|
}
|
|
147
|
+
if (artifactName === "docs-update-report.md") {
|
|
148
|
+
return paths.docsUpdateReportPath;
|
|
149
|
+
}
|
|
147
150
|
if (artifactName === "docs-sync-report.md") {
|
|
148
151
|
return paths.docsSyncReportPath;
|
|
149
152
|
}
|
|
@@ -208,6 +208,22 @@ export function registerHarnessRoutes(app, deps) {
|
|
|
208
208
|
const { project, task } = await requireHarnessTaskContext(deps, request.body?.taskSlug);
|
|
209
209
|
return deps.autoMemoryService.retryFailedReview(project.repoRoot, task.worktreePath);
|
|
210
210
|
});
|
|
211
|
+
app.post("/api/projects/harness/memory/assignments/retry", async (request) => {
|
|
212
|
+
const { project, task } = await requireHarnessTaskContext(deps, request.body?.taskSlug);
|
|
213
|
+
return deps.autoMemoryService.retryDurableDocAssignment(project.repoRoot, task.worktreePath, request.body?.assignmentId ?? "");
|
|
214
|
+
});
|
|
215
|
+
app.post("/api/projects/harness/memory/assignments/owner", async (request) => {
|
|
216
|
+
const { project, task } = await requireHarnessTaskContext(deps, request.body?.taskSlug);
|
|
217
|
+
const owner = request.body?.owner;
|
|
218
|
+
if (owner !== "architect" && owner !== "coder" && owner !== "tester") {
|
|
219
|
+
throw new VcmError({
|
|
220
|
+
code: "DURABLE_DOC_OWNER_INVALID",
|
|
221
|
+
message: "Durable-document owner must be architect, coder, or tester.",
|
|
222
|
+
statusCode: 400
|
|
223
|
+
});
|
|
224
|
+
}
|
|
225
|
+
return deps.autoMemoryService.resolveDurableDocAssignmentOwner(project.repoRoot, task.worktreePath, request.body?.assignmentId ?? "", owner);
|
|
226
|
+
});
|
|
211
227
|
}
|
|
212
228
|
async function persistHarnessEngineerDefaults(appSettings, session) {
|
|
213
229
|
const defaults = createDefaultToolSessionDefaults()["harness-engineer"];
|
|
@@ -235,6 +235,7 @@ function degradedArtifactSummary(handoffDir) {
|
|
|
235
235
|
architectDebugPath: `${handoffDir}/architect-debug.md`,
|
|
236
236
|
architectureDiagnosisPath: `${handoffDir}/architecture-diagnosis.md`,
|
|
237
237
|
testReportPath: `${handoffDir}/test-report.md`,
|
|
238
|
+
docsUpdateReportPath: `${handoffDir}/docs-update-report.md`,
|
|
238
239
|
docsSyncReportPath: `${handoffDir}/docs-sync-report.md`,
|
|
239
240
|
workflowProgressPath: `${handoffDir}/workflow-progress.md`,
|
|
240
241
|
finalAcceptancePath: `${handoffDir}/final-acceptance.md`
|
|
@@ -5,6 +5,18 @@ export function registerWorkflowControlRoutes(app, deps) {
|
|
|
5
5
|
const context = await getContext(deps, request.params.taskSlug);
|
|
6
6
|
return deps.workflowControlService.getState(context);
|
|
7
7
|
});
|
|
8
|
+
app.post("/api/tasks/:taskSlug/ask-user", async (request) => {
|
|
9
|
+
const context = await getContext(deps, request.params.taskSlug);
|
|
10
|
+
const question = typeof request.body?.question === "string" ? request.body.question.trim() : "";
|
|
11
|
+
if (!question) {
|
|
12
|
+
throw new VcmError({
|
|
13
|
+
code: "WORKFLOW_USER_QUESTION_REQUIRED",
|
|
14
|
+
message: "A non-empty user question is required.",
|
|
15
|
+
statusCode: 400
|
|
16
|
+
});
|
|
17
|
+
}
|
|
18
|
+
return deps.workflowControlService.requestUserInput(context, question);
|
|
19
|
+
});
|
|
8
20
|
}
|
|
9
21
|
async function getContext(deps, taskSlug) {
|
|
10
22
|
const project = await deps.projectService.getCurrentProject();
|
|
@@ -28,10 +28,12 @@ import { renderVcmLongRunningValidationSkillRules } from "../templates/harness/v
|
|
|
28
28
|
import { renderVcmProposeMemorySkillRules } from "../templates/harness/vcm-propose-memory-skill.js";
|
|
29
29
|
import { renderVcmReportHarnessIssueSkillRules } from "../templates/harness/vcm-report-harness-issue-skill.js";
|
|
30
30
|
import { renderVcmRouteMessageSkillRules } from "../templates/harness/vcm-route-message-skill.js";
|
|
31
|
+
import { renderAskUserTool, renderVcmAskUserSkillRules } from "../templates/harness/vcm-ask-user-skill.js";
|
|
31
32
|
import { renderUpdateTaskStateTool, renderVcmTaskStateSkillRules } from "../templates/harness/vcm-task-state-skill.js";
|
|
32
33
|
import { renderVcmWorkflowReviewSkillRules } from "../templates/harness/vcm-workflow-review-skill.js";
|
|
33
34
|
import { renderCheckScaffoldLedgerTool } from "../templates/harness/check-scaffold-ledger.js";
|
|
34
35
|
import { renderRequestArchitectRestartTool, renderRestartArchitectSkillRules } from "../templates/harness/restart-architect-skill.js";
|
|
36
|
+
import { renderResolveDurableDocAssignmentTool } from "../templates/harness/resolve-durable-doc-assignment.js";
|
|
35
37
|
import { readVcmPackageVersion } from "../app-version.js";
|
|
36
38
|
const CLI_DIR = path.dirname(fileURLToPath(import.meta.url));
|
|
37
39
|
const APP_ROOT = path.resolve(CLI_DIR, "../../..");
|
|
@@ -318,6 +320,12 @@ const WHOLE_FILES = [
|
|
|
318
320
|
mode: 0o644,
|
|
319
321
|
content: renderSkillFile("VCM Route Message Skill", "vcm-route-message", "Use when project-manager dispatches a VCM role or when a VCM role reports a question, result, blocker, or finding back to project-manager.", renderVcmRouteMessageSkillRules())
|
|
320
322
|
},
|
|
323
|
+
{
|
|
324
|
+
path: ".claude/skills/vcm-ask-user/SKILL.md",
|
|
325
|
+
category: "skill",
|
|
326
|
+
mode: 0o644,
|
|
327
|
+
content: renderSkillFile("VCM Ask User Skill", "vcm-ask-user", "Use whenever project-manager asks the user a question and must pause the workflow.", renderVcmAskUserSkillRules())
|
|
328
|
+
},
|
|
321
329
|
{
|
|
322
330
|
path: ".claude/skills/vcm-task-state/SKILL.md",
|
|
323
331
|
category: "skill",
|
|
@@ -360,6 +368,12 @@ const WHOLE_FILES = [
|
|
|
360
368
|
mode: 0o755,
|
|
361
369
|
content: renderRequestGateReviewTool()
|
|
362
370
|
},
|
|
371
|
+
{
|
|
372
|
+
path: ".ai/tools/vcm-ask-user",
|
|
373
|
+
category: "runtime-tool",
|
|
374
|
+
mode: 0o755,
|
|
375
|
+
content: renderAskUserTool()
|
|
376
|
+
},
|
|
363
377
|
{
|
|
364
378
|
path: ".ai/tools/update-task-state",
|
|
365
379
|
category: "runtime-tool",
|
|
@@ -378,6 +392,12 @@ const WHOLE_FILES = [
|
|
|
378
392
|
mode: 0o755,
|
|
379
393
|
content: renderRequestArchitectRestartTool()
|
|
380
394
|
},
|
|
395
|
+
{
|
|
396
|
+
path: ".ai/tools/resolve-durable-doc-assignment",
|
|
397
|
+
category: "runtime-tool",
|
|
398
|
+
mode: 0o755,
|
|
399
|
+
content: renderResolveDurableDocAssignmentTool()
|
|
400
|
+
},
|
|
381
401
|
{
|
|
382
402
|
path: ".ai/tools/run-long-check",
|
|
383
403
|
category: "runtime-tool",
|
|
@@ -642,6 +662,7 @@ function fixedDirectories() {
|
|
|
642
662
|
".claude/skills/vcm-harness-bootstrap/",
|
|
643
663
|
".claude/skills/vcm-long-running-validation/",
|
|
644
664
|
".claude/skills/vcm-route-message/",
|
|
665
|
+
".claude/skills/vcm-ask-user/",
|
|
645
666
|
".claude/skills/vcm-task-state/",
|
|
646
667
|
".claude/skills/vcm-workflow-review/",
|
|
647
668
|
".claude/skills/vcm-gate-review/",
|
package/dist/backend/server.js
CHANGED
|
@@ -4,13 +4,13 @@ import Fastify from "fastify";
|
|
|
4
4
|
import fastifyStatic from "@fastify/static";
|
|
5
5
|
import { createArtifactService } from "./services/artifact-service.js";
|
|
6
6
|
import { createClaudeAdapter } from "./adapters/claude-adapter.js";
|
|
7
|
-
import {
|
|
7
|
+
import { createCodexBridgeAdapter } from "./adapters/codex-bridge-adapter.js";
|
|
8
8
|
import { createCommandRunner } from "./adapters/command-runner.js";
|
|
9
9
|
import { createCommandDispatcher } from "./services/command-dispatcher.js";
|
|
10
10
|
import { createClaudeHookService } from "./services/claude-hook-service.js";
|
|
11
11
|
import { createGitAdapter } from "./adapters/git-adapter.js";
|
|
12
12
|
import { createAppSettingsService } from "./services/app-settings-service.js";
|
|
13
|
-
import {
|
|
13
|
+
import { createCodexBridgeIntegrationService } from "./services/codex-bridge-integration-service.js";
|
|
14
14
|
import { createAutoMemoryService } from "./services/auto-memory-service.js";
|
|
15
15
|
import { createArchitectRestartService } from "./services/architect-restart-service.js";
|
|
16
16
|
import { createRoleStallDetectorService } from "./services/role-stall-detector-service.js";
|
|
@@ -88,7 +88,7 @@ export async function createServer(deps, options = {}) {
|
|
|
88
88
|
registerDiagnosticsRoutes(app, { diagnosticsService: deps.diagnosticsService });
|
|
89
89
|
registerAppSettingsRoutes(app, {
|
|
90
90
|
appSettings: deps.appSettings,
|
|
91
|
-
|
|
91
|
+
codexBridgeIntegration: deps.codexBridgeIntegration
|
|
92
92
|
});
|
|
93
93
|
registerClaudeHookRoutes(app, { claudeHookService: deps.claudeHookService });
|
|
94
94
|
registerGateReviewRoutes(app, {
|
|
@@ -186,7 +186,7 @@ export async function createServer(deps, options = {}) {
|
|
|
186
186
|
onManualInterrupt: (sessionId) => deps.terminalInterruptService.handleManualInterrupt(sessionId)
|
|
187
187
|
});
|
|
188
188
|
app.addHook("onReady", async () => {
|
|
189
|
-
await deps.
|
|
189
|
+
await deps.codexBridgeIntegration.initialize();
|
|
190
190
|
await cleanupRecentTranslationRuntime(deps);
|
|
191
191
|
deps.terminalProcessExitService.start();
|
|
192
192
|
deps.runtimeCoordinator.start();
|
|
@@ -235,9 +235,9 @@ export function createDefaultServerDeps(options = {}) {
|
|
|
235
235
|
const git = createGitAdapter(runner);
|
|
236
236
|
const claude = createClaudeAdapter(runner);
|
|
237
237
|
const appSettings = createAppSettingsService({ fs });
|
|
238
|
-
const
|
|
238
|
+
const codexBridgeIntegration = createCodexBridgeIntegrationService({
|
|
239
239
|
settings: appSettings,
|
|
240
|
-
|
|
240
|
+
bridge: createCodexBridgeAdapter()
|
|
241
241
|
});
|
|
242
242
|
const runtime = createNodePtyTerminalRuntime({ fs });
|
|
243
243
|
const registry = createSessionRegistry();
|
|
@@ -255,7 +255,7 @@ export function createDefaultServerDeps(options = {}) {
|
|
|
255
255
|
projectService,
|
|
256
256
|
taskService,
|
|
257
257
|
taskWorkflowService,
|
|
258
|
-
|
|
258
|
+
codexBridgeIntegration,
|
|
259
259
|
apiUrl: options.apiUrl
|
|
260
260
|
});
|
|
261
261
|
const harnessService = createHarnessService({
|
|
@@ -332,6 +332,7 @@ export function createDefaultServerDeps(options = {}) {
|
|
|
332
332
|
appSettings,
|
|
333
333
|
sessionService,
|
|
334
334
|
roundService,
|
|
335
|
+
workflowControlService,
|
|
335
336
|
onArchitecturePlanDisposition: ({ repoRoot, taskSlug, accepted }) => architectRestartService.recordArchitectureGateDisposition(repoRoot, taskSlug, accepted)
|
|
336
337
|
});
|
|
337
338
|
const translationWorkerService = createTranslationWorkerService({
|
|
@@ -416,7 +417,8 @@ export function createDefaultServerDeps(options = {}) {
|
|
|
416
417
|
jobGuard: createJobGuardService(),
|
|
417
418
|
translationWorkerService,
|
|
418
419
|
architectRestartService,
|
|
419
|
-
roleStallDetector
|
|
420
|
+
roleStallDetector,
|
|
421
|
+
workflowControlService
|
|
420
422
|
});
|
|
421
423
|
const runtimeCoordinator = createRuntimeCoordinatorService({
|
|
422
424
|
appSettings,
|
|
@@ -456,7 +458,7 @@ export function createDefaultServerDeps(options = {}) {
|
|
|
456
458
|
});
|
|
457
459
|
return {
|
|
458
460
|
appSettings,
|
|
459
|
-
|
|
461
|
+
codexBridgeIntegration,
|
|
460
462
|
projectService,
|
|
461
463
|
taskService,
|
|
462
464
|
taskCloseService,
|