vibe-coding-master 0.8.13 → 0.9.0
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 +7 -3
- package/dist/backend/cli/install-vcm-harness.js +57 -18
- package/dist/backend/gateway/gateway-service.js +5 -3
- package/dist/backend/runtime/terminal-submit.js +1 -0
- package/dist/backend/server.js +14 -4
- package/dist/backend/services/app-settings-service.js +3 -2
- package/dist/backend/services/auto-memory-service.js +4 -3
- package/dist/backend/services/claude-hook-service.js +6 -0
- package/dist/backend/services/claude-transcript-service.js +2 -13
- package/dist/backend/services/claude-version-check.js +46 -0
- package/dist/backend/services/gate-review-service.js +47 -8
- package/dist/backend/services/harness-service.js +41 -20
- package/dist/backend/services/message-service.js +30 -6
- package/dist/backend/services/project-service.js +8 -2
- package/dist/backend/services/runtime-recovery-service.js +36 -9
- package/dist/backend/services/session-service.js +10 -19
- package/dist/backend/services/task-close-service.js +1 -0
- package/dist/backend/services/terminal-process-exit-service.js +4 -1
- package/dist/backend/services/workflow-control-service.js +55 -11
- package/dist/backend/templates/harness/architect-worker-frontmatter.js +33 -0
- package/dist/backend/templates/harness/check-scaffold-ledger.js +480 -511
- package/dist/backend/templates/harness/gate-review.js +676 -643
- package/dist/backend/templates/harness/node-tool-runtime.js +102 -0
- package/dist/backend/templates/harness/resolve-durable-doc-assignment.js +49 -55
- package/dist/backend/templates/harness/restart-architect-skill.js +49 -58
- package/dist/backend/templates/harness/vcm-ask-user-skill.js +57 -59
- package/dist/backend/templates/harness/vcm-task-state-skill.js +66 -72
- package/dist/shared/types/app-settings.js +31 -0
- package/dist-frontend/assets/index-CShYYQQa.css +32 -0
- package/dist-frontend/assets/{index-DqANomt1.js → index-DOvvS76Q.js} +41 -41
- package/dist-frontend/index.html +2 -2
- package/package.json +1 -1
- package/scripts/harness-tools/check-durable-docs +332 -298
- package/scripts/harness-tools/generate-module-index +1047 -512
- package/scripts/harness-tools/generate-public-surface +1231 -1069
- package/scripts/harness-tools/package.json +3 -0
- package/scripts/harness-tools/run-long-check +608 -531
- package/scripts/harness-tools/vcm-artifact +181 -113
- package/scripts/harness-tools/vcm-bash-guard +839 -605
- package/scripts/harness-tools/vcm-subagent-guard +67 -63
- package/scripts/harness-tools/watch-job +313 -238
- package/dist-frontend/assets/index-xgiQeUD5.css +0 -32
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
// Shared JavaScript source embedded in Node harness tools. Installed tools run
|
|
2
|
+
// standalone, so each rendered tool carries its own copy. The source avoids
|
|
3
|
+
// backticks and "${" so it can be embedded in template literals unchanged.
|
|
4
|
+
export function renderNodeToolHeader(description) {
|
|
5
|
+
return `#!/usr/bin/env node
|
|
6
|
+
"use strict";
|
|
7
|
+
// ${description}
|
|
8
|
+
`;
|
|
9
|
+
}
|
|
10
|
+
// parseToolArguments(argv, spec) mirrors the argparse behavior the tools rely on:
|
|
11
|
+
// long options as "--name value" or "--name=value", required options, choices,
|
|
12
|
+
// repeatable options, flags, positionals, and usage errors that exit with 2.
|
|
13
|
+
export const NODE_TOOL_ARGUMENT_PARSER = String.raw `
|
|
14
|
+
function parseToolArguments(argv, spec) {
|
|
15
|
+
const prog = require("node:path").basename(process.argv[1] || "tool");
|
|
16
|
+
const options = spec.options || {};
|
|
17
|
+
const positionals = spec.positionals || [];
|
|
18
|
+
const usage = "usage: " + prog
|
|
19
|
+
+ Object.keys(options).map((name) => " [" + name + (options[name].flag ? "" : " " + name.slice(2).toUpperCase()) + "]").join("")
|
|
20
|
+
+ positionals.map((name) => " " + name).join("");
|
|
21
|
+
const fail = (message) => {
|
|
22
|
+
process.stderr.write(usage + "\n" + prog + ": error: " + message + "\n");
|
|
23
|
+
process.exit(2);
|
|
24
|
+
};
|
|
25
|
+
const values = {};
|
|
26
|
+
for (const [name, option] of Object.entries(options)) {
|
|
27
|
+
values[option.dest] = option.append ? [] : option.flag ? false : option.default;
|
|
28
|
+
}
|
|
29
|
+
const remaining = [];
|
|
30
|
+
for (let index = 0; index < argv.length; index += 1) {
|
|
31
|
+
const argument = argv[index];
|
|
32
|
+
if (argument === "-h" || argument === "--help") {
|
|
33
|
+
process.stdout.write(usage + "\n\n" + (spec.description || "") + "\n");
|
|
34
|
+
process.exit(0);
|
|
35
|
+
}
|
|
36
|
+
if (!argument.startsWith("--") || argument === "--") {
|
|
37
|
+
remaining.push(argument);
|
|
38
|
+
continue;
|
|
39
|
+
}
|
|
40
|
+
const separator = argument.indexOf("=");
|
|
41
|
+
const name = separator >= 0 ? argument.slice(0, separator) : argument;
|
|
42
|
+
const option = options[name];
|
|
43
|
+
if (!option) {
|
|
44
|
+
fail("unrecognized arguments: " + argument);
|
|
45
|
+
}
|
|
46
|
+
if (option.flag) {
|
|
47
|
+
values[option.dest] = true;
|
|
48
|
+
continue;
|
|
49
|
+
}
|
|
50
|
+
let value;
|
|
51
|
+
if (separator >= 0) {
|
|
52
|
+
value = argument.slice(separator + 1);
|
|
53
|
+
} else {
|
|
54
|
+
if (index + 1 >= argv.length) {
|
|
55
|
+
fail("argument " + name + ": expected one argument");
|
|
56
|
+
}
|
|
57
|
+
index += 1;
|
|
58
|
+
value = argv[index];
|
|
59
|
+
}
|
|
60
|
+
if (option.choices && !option.choices.includes(value)) {
|
|
61
|
+
fail("argument " + name + ": invalid choice: '" + value + "' (choose from " + option.choices.map((choice) => "'" + choice + "'").join(", ") + ")");
|
|
62
|
+
}
|
|
63
|
+
if (option.append) {
|
|
64
|
+
values[option.dest].push(value);
|
|
65
|
+
} else {
|
|
66
|
+
values[option.dest] = value;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
if (remaining.length > positionals.length) {
|
|
70
|
+
fail("unrecognized arguments: " + remaining.slice(positionals.length).join(" "));
|
|
71
|
+
}
|
|
72
|
+
positionals.forEach((name, index) => {
|
|
73
|
+
values[name] = remaining[index];
|
|
74
|
+
});
|
|
75
|
+
const missing = [
|
|
76
|
+
...positionals.filter((name) => values[name] === undefined),
|
|
77
|
+
...Object.entries(options).filter(([, option]) => option.required && values[option.dest] === undefined).map(([name]) => name)
|
|
78
|
+
];
|
|
79
|
+
if (missing.length > 0) {
|
|
80
|
+
fail("the following arguments are required: " + missing.join(", "));
|
|
81
|
+
}
|
|
82
|
+
return values;
|
|
83
|
+
}
|
|
84
|
+
`;
|
|
85
|
+
// postJson(url, body, timeoutMs) returns { ok, status, statusText, text } or
|
|
86
|
+
// throws on network failure or timeout, like urllib.request.urlopen.
|
|
87
|
+
export const NODE_TOOL_HTTP = String.raw `
|
|
88
|
+
async function postJson(url, body, timeoutMs) {
|
|
89
|
+
const response = await fetch(url, {
|
|
90
|
+
method: "POST",
|
|
91
|
+
headers: { "content-type": "application/json" },
|
|
92
|
+
body: JSON.stringify(body),
|
|
93
|
+
signal: AbortSignal.timeout(timeoutMs)
|
|
94
|
+
});
|
|
95
|
+
return { ok: response.ok, status: response.status, statusText: response.statusText, text: await response.text() };
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
function networkErrorText(error) {
|
|
99
|
+
const cause = error && error.cause;
|
|
100
|
+
return (cause && (cause.message || cause.code)) || (error && error.message) || String(error);
|
|
101
|
+
}
|
|
102
|
+
`;
|
|
@@ -1,60 +1,54 @@
|
|
|
1
|
+
import { NODE_TOOL_ARGUMENT_PARSER, NODE_TOOL_HTTP, renderNodeToolHeader } from "./node-tool-runtime.js";
|
|
1
2
|
export function renderResolveDurableDocAssignmentTool() {
|
|
2
|
-
return
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
def emit(status, message=None):
|
|
12
|
-
payload = {"status": status}
|
|
13
|
-
if message:
|
|
14
|
-
payload["message"] = message
|
|
15
|
-
print(json.dumps(payload, ensure_ascii=False))
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
def main():
|
|
19
|
-
parser = argparse.ArgumentParser(description="Assign a VCM durable-document update to a workflow role.")
|
|
20
|
-
parser.add_argument("--assignment", required=True)
|
|
21
|
-
parser.add_argument("--owner", required=True, choices=["architect", "coder", "tester"])
|
|
22
|
-
args = parser.parse_args()
|
|
23
|
-
|
|
24
|
-
if os.environ.get("VCM_ROLE") != "project-manager":
|
|
25
|
-
emit("rejected", "Only project-manager may resolve a durable-document owner.")
|
|
26
|
-
return 2
|
|
27
|
-
|
|
28
|
-
api_url = os.environ.get("VCM_API_URL", "").rstrip("/")
|
|
29
|
-
task_slug = os.environ.get("VCM_TASK_SLUG", "").strip()
|
|
30
|
-
if not api_url or not task_slug:
|
|
31
|
-
emit("rejected", "VCM_API_URL or VCM_TASK_SLUG is unavailable.")
|
|
32
|
-
return 2
|
|
33
|
-
|
|
34
|
-
request = urllib.request.Request(
|
|
35
|
-
api_url + "/api/projects/harness/memory/assignments/owner",
|
|
36
|
-
data=json.dumps({
|
|
37
|
-
"taskSlug": task_slug,
|
|
38
|
-
"assignmentId": args.assignment,
|
|
39
|
-
"owner": args.owner,
|
|
40
|
-
}).encode("utf-8"),
|
|
41
|
-
headers={"content-type": "application/json"},
|
|
42
|
-
method="POST",
|
|
43
|
-
)
|
|
44
|
-
try:
|
|
45
|
-
with urllib.request.urlopen(request, timeout=5):
|
|
46
|
-
pass
|
|
47
|
-
emit("accepted")
|
|
48
|
-
return 0
|
|
49
|
-
except urllib.error.HTTPError as error:
|
|
50
|
-
detail = error.read().decode("utf-8", errors="replace")
|
|
51
|
-
emit("rejected", f"VCM rejected the owner: HTTP {error.code} {detail}")
|
|
52
|
-
except (OSError, ValueError, urllib.error.URLError) as error:
|
|
53
|
-
emit("failed", f"VCM could not record the owner: {error}")
|
|
54
|
-
return 2
|
|
3
|
+
return String.raw `${renderNodeToolHeader("Assign a VCM durable-document update to a workflow role.")}${NODE_TOOL_ARGUMENT_PARSER}${NODE_TOOL_HTTP}
|
|
4
|
+
function emit(status, message) {
|
|
5
|
+
const payload = { status };
|
|
6
|
+
if (message) {
|
|
7
|
+
payload.message = message;
|
|
8
|
+
}
|
|
9
|
+
process.stdout.write(JSON.stringify(payload) + "\n");
|
|
10
|
+
}
|
|
55
11
|
|
|
12
|
+
async function main() {
|
|
13
|
+
const args = parseToolArguments(process.argv.slice(2), {
|
|
14
|
+
description: "Assign a VCM durable-document update to a workflow role.",
|
|
15
|
+
options: {
|
|
16
|
+
"--assignment": { dest: "assignment", required: true },
|
|
17
|
+
"--owner": { dest: "owner", required: true, choices: ["architect", "coder", "tester"] }
|
|
18
|
+
}
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
if (process.env.VCM_ROLE !== "project-manager") {
|
|
22
|
+
emit("rejected", "Only project-manager may resolve a durable-document owner.");
|
|
23
|
+
return 2;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const apiUrl = (process.env.VCM_API_URL || "").replace(/\/+$/, "");
|
|
27
|
+
const taskSlug = (process.env.VCM_TASK_SLUG || "").trim();
|
|
28
|
+
if (!apiUrl || !taskSlug) {
|
|
29
|
+
emit("rejected", "VCM_API_URL or VCM_TASK_SLUG is unavailable.");
|
|
30
|
+
return 2;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
let response;
|
|
34
|
+
try {
|
|
35
|
+
response = await postJson(apiUrl + "/api/projects/harness/memory/assignments/owner", {
|
|
36
|
+
taskSlug,
|
|
37
|
+
assignmentId: args.assignment,
|
|
38
|
+
owner: args.owner
|
|
39
|
+
}, 5000);
|
|
40
|
+
} catch (error) {
|
|
41
|
+
emit("failed", "VCM could not record the owner: " + networkErrorText(error));
|
|
42
|
+
return 2;
|
|
43
|
+
}
|
|
44
|
+
if (!response.ok) {
|
|
45
|
+
emit("rejected", "VCM rejected the owner: HTTP " + response.status + " " + response.text);
|
|
46
|
+
return 2;
|
|
47
|
+
}
|
|
48
|
+
emit("accepted");
|
|
49
|
+
return 0;
|
|
50
|
+
}
|
|
56
51
|
|
|
57
|
-
|
|
58
|
-
sys.exit(main())
|
|
52
|
+
main().then((code) => process.exit(code));
|
|
59
53
|
`;
|
|
60
54
|
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { NODE_TOOL_HTTP, renderNodeToolHeader } from "./node-tool-runtime.js";
|
|
1
2
|
export function renderRestartArchitectSkillRules() {
|
|
2
3
|
return `## Purpose
|
|
3
4
|
|
|
@@ -23,67 +24,57 @@ Then write the completed Architect-to-PM route message and end the turn. VCM kee
|
|
|
23
24
|
Do not use this skill for incomplete planning, user clarification, Debug Mode, Architecture Diagnosis Mode, or docs sync.`;
|
|
24
25
|
}
|
|
25
26
|
export function renderRequestArchitectRestartTool() {
|
|
26
|
-
return
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
import urllib.error
|
|
31
|
-
import urllib.parse
|
|
32
|
-
import urllib.request
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
def emit(status, **fields):
|
|
36
|
-
payload = {"status": status, **fields}
|
|
37
|
-
print(json.dumps(payload, ensure_ascii=False))
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
def main():
|
|
41
|
-
if os.environ.get("VCM_ROLE") != "architect":
|
|
42
|
-
emit("rejected", message="Only architect may schedule the post-planning restart.")
|
|
43
|
-
return 2
|
|
27
|
+
return String.raw `${renderNodeToolHeader("Schedule the Architect post-planning restart.")}${NODE_TOOL_HTTP}
|
|
28
|
+
function emit(status, fields) {
|
|
29
|
+
process.stdout.write(JSON.stringify({ status, ...(fields || {}) }) + "\n");
|
|
30
|
+
}
|
|
44
31
|
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
32
|
+
async function main() {
|
|
33
|
+
if (process.env.VCM_ROLE !== "architect") {
|
|
34
|
+
emit("rejected", { message: "Only architect may schedule the post-planning restart." });
|
|
35
|
+
return 2;
|
|
36
|
+
}
|
|
50
37
|
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
request = urllib.request.Request(
|
|
58
|
-
url,
|
|
59
|
-
data=b"{}",
|
|
60
|
-
headers={"content-type": "application/json"},
|
|
61
|
-
method="POST",
|
|
62
|
-
)
|
|
63
|
-
try:
|
|
64
|
-
with urllib.request.urlopen(request, timeout=5) as response:
|
|
65
|
-
payload = json.loads(response.read().decode("utf-8"))
|
|
66
|
-
emit(
|
|
67
|
-
payload.get("status", "scheduled"),
|
|
68
|
-
taskSlug=task_slug,
|
|
69
|
-
sessionId=payload.get("sessionId"),
|
|
70
|
-
memoryCandidatePath=payload.get("memoryCandidatePath"),
|
|
71
|
-
)
|
|
72
|
-
return 0
|
|
73
|
-
except urllib.error.HTTPError as error:
|
|
74
|
-
try:
|
|
75
|
-
payload = json.loads(error.read().decode("utf-8"))
|
|
76
|
-
message = payload.get("error", {}).get("message", str(error))
|
|
77
|
-
except Exception:
|
|
78
|
-
message = str(error)
|
|
79
|
-
emit("rejected", message=message)
|
|
80
|
-
return 2
|
|
81
|
-
except (OSError, ValueError, urllib.error.URLError) as error:
|
|
82
|
-
emit("failed", message=str(error))
|
|
83
|
-
return 2
|
|
38
|
+
const apiUrl = (process.env.VCM_API_URL || "").replace(/\/+$/, "");
|
|
39
|
+
const taskSlug = (process.env.VCM_TASK_SLUG || "").trim();
|
|
40
|
+
if (!apiUrl || !taskSlug) {
|
|
41
|
+
emit("rejected", { message: "VCM_API_URL or VCM_TASK_SLUG is unavailable." });
|
|
42
|
+
return 2;
|
|
43
|
+
}
|
|
84
44
|
|
|
45
|
+
const url = apiUrl + "/api/tasks/" + encodeURIComponent(taskSlug) + "/sessions/architect/restart-after-planning";
|
|
46
|
+
let response;
|
|
47
|
+
try {
|
|
48
|
+
response = await postJson(url, {}, 5000);
|
|
49
|
+
} catch (error) {
|
|
50
|
+
emit("failed", { message: networkErrorText(error) });
|
|
51
|
+
return 2;
|
|
52
|
+
}
|
|
53
|
+
if (!response.ok) {
|
|
54
|
+
let message = "HTTP Error " + response.status + ": " + response.statusText;
|
|
55
|
+
try {
|
|
56
|
+
const payload = JSON.parse(response.text);
|
|
57
|
+
message = (payload.error && payload.error.message) || message;
|
|
58
|
+
} catch {
|
|
59
|
+
// Keep the HTTP status message when the error body is not JSON.
|
|
60
|
+
}
|
|
61
|
+
emit("rejected", { message });
|
|
62
|
+
return 2;
|
|
63
|
+
}
|
|
64
|
+
try {
|
|
65
|
+
const payload = JSON.parse(response.text);
|
|
66
|
+
emit(payload.status || "scheduled", {
|
|
67
|
+
taskSlug,
|
|
68
|
+
sessionId: payload.sessionId ?? null,
|
|
69
|
+
memoryCandidatePath: payload.memoryCandidatePath ?? null
|
|
70
|
+
});
|
|
71
|
+
return 0;
|
|
72
|
+
} catch (error) {
|
|
73
|
+
emit("failed", { message: error.message });
|
|
74
|
+
return 2;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
85
77
|
|
|
86
|
-
|
|
87
|
-
sys.exit(main())
|
|
78
|
+
main().then((code) => process.exit(code));
|
|
88
79
|
`;
|
|
89
80
|
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { NODE_TOOL_ARGUMENT_PARSER, NODE_TOOL_HTTP, renderNodeToolHeader } from "./node-tool-runtime.js";
|
|
1
2
|
export function renderVcmAskUserSkillRules() {
|
|
2
3
|
return `## Purpose
|
|
3
4
|
|
|
@@ -17,66 +18,63 @@ Every question pauses the workflow. PM may defer a question by not asking it; on
|
|
|
17
18
|
`;
|
|
18
19
|
}
|
|
19
20
|
export function renderAskUserTool() {
|
|
20
|
-
return
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
payload = {"status": status}
|
|
32
|
-
if message:
|
|
33
|
-
payload["message"] = message
|
|
34
|
-
if state is not None:
|
|
35
|
-
payload["state"] = state
|
|
36
|
-
print(json.dumps(payload, ensure_ascii=False))
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
def main():
|
|
40
|
-
parser = argparse.ArgumentParser(description="Pause VCM workflow for an exact user question.")
|
|
41
|
-
parser.add_argument("--question", required=True)
|
|
42
|
-
args = parser.parse_args()
|
|
43
|
-
|
|
44
|
-
if os.environ.get("VCM_ROLE") != "project-manager":
|
|
45
|
-
emit("failed", "Only project-manager may ask the user through VCM workflow control.")
|
|
46
|
-
return 2
|
|
47
|
-
|
|
48
|
-
question = args.question.strip()
|
|
49
|
-
if not question:
|
|
50
|
-
emit("failed", "A non-empty user question is required.")
|
|
51
|
-
return 2
|
|
52
|
-
|
|
53
|
-
api_url = os.environ.get("VCM_API_URL", "").rstrip("/")
|
|
54
|
-
task_slug = os.environ.get("VCM_TASK_SLUG", "").strip()
|
|
55
|
-
if not api_url or not task_slug:
|
|
56
|
-
emit("failed", "VCM_API_URL or VCM_TASK_SLUG is unavailable; the workflow was not paused.")
|
|
57
|
-
return 2
|
|
58
|
-
|
|
59
|
-
url = f"{api_url}/api/tasks/{urllib.parse.quote(task_slug, safe='')}/ask-user"
|
|
60
|
-
request = urllib.request.Request(
|
|
61
|
-
url,
|
|
62
|
-
data=json.dumps({"question": question}).encode("utf-8"),
|
|
63
|
-
headers={"content-type": "application/json"},
|
|
64
|
-
method="POST",
|
|
65
|
-
)
|
|
66
|
-
try:
|
|
67
|
-
with urllib.request.urlopen(request, timeout=5) as response:
|
|
68
|
-
state = json.loads(response.read().decode("utf-8"))
|
|
69
|
-
emit("awaiting_user", state=state)
|
|
70
|
-
return 0
|
|
71
|
-
except urllib.error.HTTPError as error:
|
|
72
|
-
detail = error.read().decode("utf-8", errors="replace")
|
|
73
|
-
emit("failed", f"VCM rejected the user question: HTTP {error.code} {detail}")
|
|
74
|
-
except (OSError, ValueError, urllib.error.URLError) as error:
|
|
75
|
-
emit("failed", f"VCM could not register the user question: {error}")
|
|
76
|
-
return 1
|
|
21
|
+
return String.raw `${renderNodeToolHeader("Pause VCM workflow for an exact user question.")}${NODE_TOOL_ARGUMENT_PARSER}${NODE_TOOL_HTTP}
|
|
22
|
+
function emit(status, message, state) {
|
|
23
|
+
const payload = { status };
|
|
24
|
+
if (message) {
|
|
25
|
+
payload.message = message;
|
|
26
|
+
}
|
|
27
|
+
if (state !== undefined) {
|
|
28
|
+
payload.state = state;
|
|
29
|
+
}
|
|
30
|
+
process.stdout.write(JSON.stringify(payload) + "\n");
|
|
31
|
+
}
|
|
77
32
|
|
|
33
|
+
async function main() {
|
|
34
|
+
const args = parseToolArguments(process.argv.slice(2), {
|
|
35
|
+
description: "Pause VCM workflow for an exact user question.",
|
|
36
|
+
options: { "--question": { dest: "question", required: true } }
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
if (process.env.VCM_ROLE !== "project-manager") {
|
|
40
|
+
emit("failed", "Only project-manager may ask the user through VCM workflow control.");
|
|
41
|
+
return 2;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const question = args.question.trim();
|
|
45
|
+
if (!question) {
|
|
46
|
+
emit("failed", "A non-empty user question is required.");
|
|
47
|
+
return 2;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
const apiUrl = (process.env.VCM_API_URL || "").replace(/\/+$/, "");
|
|
51
|
+
const taskSlug = (process.env.VCM_TASK_SLUG || "").trim();
|
|
52
|
+
if (!apiUrl || !taskSlug) {
|
|
53
|
+
emit("failed", "VCM_API_URL or VCM_TASK_SLUG is unavailable; the workflow was not paused.");
|
|
54
|
+
return 2;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
const url = apiUrl + "/api/tasks/" + encodeURIComponent(taskSlug) + "/ask-user";
|
|
58
|
+
let response;
|
|
59
|
+
try {
|
|
60
|
+
response = await postJson(url, { question }, 5000);
|
|
61
|
+
} catch (error) {
|
|
62
|
+
emit("failed", "VCM could not register the user question: " + networkErrorText(error));
|
|
63
|
+
return 1;
|
|
64
|
+
}
|
|
65
|
+
if (!response.ok) {
|
|
66
|
+
emit("failed", "VCM rejected the user question: HTTP " + response.status + " " + response.text);
|
|
67
|
+
return 1;
|
|
68
|
+
}
|
|
69
|
+
try {
|
|
70
|
+
emit("awaiting_user", undefined, JSON.parse(response.text));
|
|
71
|
+
return 0;
|
|
72
|
+
} catch (error) {
|
|
73
|
+
emit("failed", "VCM could not register the user question: " + error.message);
|
|
74
|
+
return 1;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
78
77
|
|
|
79
|
-
|
|
80
|
-
sys.exit(main())
|
|
78
|
+
main().then((code) => process.exit(code));
|
|
81
79
|
`;
|
|
82
80
|
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { NODE_TOOL_ARGUMENT_PARSER, NODE_TOOL_HTTP, renderNodeToolHeader } from "./node-tool-runtime.js";
|
|
1
2
|
export function renderVcmTaskStateSkillRules() {
|
|
2
3
|
return `## Purpose
|
|
3
4
|
|
|
@@ -21,79 +22,72 @@ If declaration fails, report the warning when relevant and continue the existing
|
|
|
21
22
|
`;
|
|
22
23
|
}
|
|
23
24
|
export function renderUpdateTaskStateTool() {
|
|
24
|
-
return
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
payload = {"status": status}
|
|
36
|
-
if message:
|
|
37
|
-
payload["message"] = message
|
|
38
|
-
if state is not None:
|
|
39
|
-
payload["state"] = state
|
|
40
|
-
print(json.dumps(payload, ensure_ascii=False))
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
def main():
|
|
44
|
-
parser = argparse.ArgumentParser(description="Declare advisory VCM task workflow state.")
|
|
45
|
-
parser.add_argument("--flow")
|
|
46
|
-
parser.add_argument("--step")
|
|
47
|
-
parser.add_argument("--branch")
|
|
48
|
-
parser.add_argument("--resume-point")
|
|
49
|
-
parser.add_argument("--status")
|
|
50
|
-
parser.add_argument("--evidence", action="append", default=[])
|
|
51
|
-
args = parser.parse_args()
|
|
52
|
-
|
|
53
|
-
if os.environ.get("VCM_ROLE") != "project-manager":
|
|
54
|
-
emit("warning", "Only project-manager may declare VCM task workflow state.")
|
|
55
|
-
return 0
|
|
56
|
-
|
|
57
|
-
api_url = os.environ.get("VCM_API_URL", "").rstrip("/")
|
|
58
|
-
task_slug = os.environ.get("VCM_TASK_SLUG", "").strip()
|
|
59
|
-
if not api_url or not task_slug:
|
|
60
|
-
emit("warning", "VCM_API_URL or VCM_TASK_SLUG is unavailable; task state was not updated.")
|
|
61
|
-
return 0
|
|
62
|
-
|
|
63
|
-
payload = {}
|
|
64
|
-
for key, value in (
|
|
65
|
-
("flow", args.flow),
|
|
66
|
-
("step", args.step),
|
|
67
|
-
("branch", args.branch),
|
|
68
|
-
("resumePoint", args.resume_point),
|
|
69
|
-
("status", args.status),
|
|
70
|
-
):
|
|
71
|
-
if value is not None:
|
|
72
|
-
payload[key] = value
|
|
73
|
-
if args.evidence:
|
|
74
|
-
payload["evidenceRefs"] = args.evidence
|
|
75
|
-
|
|
76
|
-
if not payload:
|
|
77
|
-
emit("warning", "No task workflow fields were supplied.")
|
|
78
|
-
return 0
|
|
79
|
-
|
|
80
|
-
url = f"{api_url}/api/tasks/{urllib.parse.quote(task_slug, safe='')}/workflow-state"
|
|
81
|
-
request = urllib.request.Request(
|
|
82
|
-
url,
|
|
83
|
-
data=json.dumps(payload).encode("utf-8"),
|
|
84
|
-
headers={"content-type": "application/json"},
|
|
85
|
-
method="POST",
|
|
86
|
-
)
|
|
87
|
-
try:
|
|
88
|
-
with urllib.request.urlopen(request, timeout=3) as response:
|
|
89
|
-
state = json.loads(response.read().decode("utf-8"))
|
|
90
|
-
emit("updated", state=state)
|
|
91
|
-
except (OSError, ValueError, urllib.error.URLError, urllib.error.HTTPError) as error:
|
|
92
|
-
emit("warning", f"Task state was not updated: {error}")
|
|
93
|
-
return 0
|
|
25
|
+
return String.raw `${renderNodeToolHeader("Declare advisory VCM task workflow state.")}${NODE_TOOL_ARGUMENT_PARSER}${NODE_TOOL_HTTP}
|
|
26
|
+
function emit(status, message, state) {
|
|
27
|
+
const payload = { status };
|
|
28
|
+
if (message) {
|
|
29
|
+
payload.message = message;
|
|
30
|
+
}
|
|
31
|
+
if (state !== undefined) {
|
|
32
|
+
payload.state = state;
|
|
33
|
+
}
|
|
34
|
+
process.stdout.write(JSON.stringify(payload) + "\n");
|
|
35
|
+
}
|
|
94
36
|
|
|
37
|
+
async function main() {
|
|
38
|
+
const args = parseToolArguments(process.argv.slice(2), {
|
|
39
|
+
description: "Declare advisory VCM task workflow state.",
|
|
40
|
+
options: {
|
|
41
|
+
"--flow": { dest: "flow" },
|
|
42
|
+
"--step": { dest: "step" },
|
|
43
|
+
"--branch": { dest: "branch" },
|
|
44
|
+
"--resume-point": { dest: "resumePoint" },
|
|
45
|
+
"--status": { dest: "status" },
|
|
46
|
+
"--evidence": { dest: "evidence", append: true }
|
|
47
|
+
}
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
if (process.env.VCM_ROLE !== "project-manager") {
|
|
51
|
+
emit("warning", "Only project-manager may declare VCM task workflow state.");
|
|
52
|
+
return 0;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
const apiUrl = (process.env.VCM_API_URL || "").replace(/\/+$/, "");
|
|
56
|
+
const taskSlug = (process.env.VCM_TASK_SLUG || "").trim();
|
|
57
|
+
if (!apiUrl || !taskSlug) {
|
|
58
|
+
emit("warning", "VCM_API_URL or VCM_TASK_SLUG is unavailable; task state was not updated.");
|
|
59
|
+
return 0;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
const payload = {};
|
|
63
|
+
for (const key of ["flow", "step", "branch", "resumePoint", "status"]) {
|
|
64
|
+
if (args[key] !== undefined) {
|
|
65
|
+
payload[key] = args[key];
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
if (args.evidence.length > 0) {
|
|
69
|
+
payload.evidenceRefs = args.evidence;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
if (Object.keys(payload).length === 0) {
|
|
73
|
+
emit("warning", "No task workflow fields were supplied.");
|
|
74
|
+
return 0;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
const url = apiUrl + "/api/tasks/" + encodeURIComponent(taskSlug) + "/workflow-state";
|
|
78
|
+
try {
|
|
79
|
+
const response = await postJson(url, payload, 3000);
|
|
80
|
+
if (!response.ok) {
|
|
81
|
+
emit("warning", "Task state was not updated: HTTP Error " + response.status + ": " + response.statusText);
|
|
82
|
+
return 0;
|
|
83
|
+
}
|
|
84
|
+
emit("updated", undefined, JSON.parse(response.text));
|
|
85
|
+
} catch (error) {
|
|
86
|
+
emit("warning", "Task state was not updated: " + networkErrorText(error));
|
|
87
|
+
}
|
|
88
|
+
return 0;
|
|
89
|
+
}
|
|
95
90
|
|
|
96
|
-
|
|
97
|
-
sys.exit(main())
|
|
91
|
+
main().then((code) => process.exit(code));
|
|
98
92
|
`;
|
|
99
93
|
}
|
|
@@ -1,4 +1,35 @@
|
|
|
1
1
|
import { VCM_ROLE_NAMES } from "../constants.js";
|
|
2
|
+
export const ARCHITECT_WORKER_TYPES = ["evidence", "scaffold", "validation"];
|
|
3
|
+
// "inherit" runs the worker on the parent Architect Session's model.
|
|
4
|
+
export const ARCHITECT_WORKER_MODELS = ["inherit", "fable", "opus", "sonnet"];
|
|
5
|
+
// "default" omits effort from the worker definition.
|
|
6
|
+
export const ARCHITECT_WORKER_EFFORTS = ["default", "low", "medium", "high", "xhigh", "max"];
|
|
7
|
+
// Workers run cheaper than the Architect that delegates to them: evidence reading
|
|
8
|
+
// still needs judgment, while scaffold and validation execution are mechanical.
|
|
9
|
+
export const DEFAULT_ARCHITECT_WORKER_SETTINGS = {
|
|
10
|
+
evidence: { model: "opus", effort: "high" },
|
|
11
|
+
scaffold: { model: "sonnet", effort: "medium" },
|
|
12
|
+
validation: { model: "sonnet", effort: "low" }
|
|
13
|
+
};
|
|
14
|
+
export function normalizeArchitectWorkerSettings(input) {
|
|
15
|
+
const candidate = input && typeof input === "object" ? input : {};
|
|
16
|
+
const normalized = {};
|
|
17
|
+
for (const type of ARCHITECT_WORKER_TYPES) {
|
|
18
|
+
const entry = candidate[type] && typeof candidate[type] === "object"
|
|
19
|
+
? candidate[type]
|
|
20
|
+
: {};
|
|
21
|
+
const fallback = DEFAULT_ARCHITECT_WORKER_SETTINGS[type];
|
|
22
|
+
normalized[type] = {
|
|
23
|
+
model: ARCHITECT_WORKER_MODELS.includes(entry.model)
|
|
24
|
+
? entry.model
|
|
25
|
+
: fallback.model,
|
|
26
|
+
effort: ARCHITECT_WORKER_EFFORTS.includes(entry.effort)
|
|
27
|
+
? entry.effort
|
|
28
|
+
: fallback.effort
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
return normalized;
|
|
32
|
+
}
|
|
2
33
|
export const THEME_MODES = ["system", "light", "dark"];
|
|
3
34
|
export const PERMISSION_REQUEST_MODES = ["off", "allowAll"];
|
|
4
35
|
export const DEFAULT_TRANSLATION_TARGET_LANGUAGE = "zh-CN";
|