@controlvector/cv-agent 1.4.0 → 1.6.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/dist/bundle.cjs +400 -19
- package/dist/bundle.cjs.map +4 -4
- package/dist/commands/agent.d.ts.map +1 -1
- package/dist/commands/agent.js +102 -2
- package/dist/commands/agent.js.map +1 -1
- package/dist/commands/deploy-manifest.d.ts +67 -0
- package/dist/commands/deploy-manifest.d.ts.map +1 -0
- package/dist/commands/deploy-manifest.js +211 -0
- package/dist/commands/deploy-manifest.js.map +1 -0
- package/dist/commands/git-safety.d.ts +29 -0
- package/dist/commands/git-safety.d.ts.map +1 -0
- package/dist/commands/git-safety.js +116 -0
- package/dist/commands/git-safety.js.map +1 -0
- package/dist/utils/api.d.ts +15 -1
- package/dist/utils/api.d.ts.map +1 -1
- package/dist/utils/api.js +12 -4
- package/dist/utils/api.js.map +1 -1
- package/dist/utils/config.d.ts +20 -0
- package/dist/utils/config.d.ts.map +1 -1
- package/dist/utils/config.js +15 -0
- package/dist/utils/config.js.map +1 -1
- package/package.json +1 -1
package/dist/bundle.cjs
CHANGED
|
@@ -3037,7 +3037,7 @@ var {
|
|
|
3037
3037
|
} = import_index.default;
|
|
3038
3038
|
|
|
3039
3039
|
// src/commands/agent.ts
|
|
3040
|
-
var
|
|
3040
|
+
var import_node_child_process4 = require("node:child_process");
|
|
3041
3041
|
|
|
3042
3042
|
// node_modules/chalk/source/vendor/ansi-styles/index.js
|
|
3043
3043
|
var ANSI_BACKGROUND_OFFSET = 10;
|
|
@@ -3643,7 +3643,7 @@ async function apiCall(creds, method, path, body) {
|
|
|
3643
3643
|
body: body ? JSON.stringify(body) : void 0
|
|
3644
3644
|
});
|
|
3645
3645
|
}
|
|
3646
|
-
async function registerExecutor(creds, machineName, workingDir, repositoryId) {
|
|
3646
|
+
async function registerExecutor(creds, machineName, workingDir, repositoryId, metadata) {
|
|
3647
3647
|
const body = {
|
|
3648
3648
|
name: `cva:${machineName}`,
|
|
3649
3649
|
machine_name: machineName,
|
|
@@ -3657,6 +3657,11 @@ async function registerExecutor(creds, machineName, workingDir, repositoryId) {
|
|
|
3657
3657
|
if (repositoryId) {
|
|
3658
3658
|
body.repository_id = repositoryId;
|
|
3659
3659
|
}
|
|
3660
|
+
if (metadata?.role) body.role = metadata.role;
|
|
3661
|
+
if (metadata?.dispatch_guard) body.dispatch_guard = metadata.dispatch_guard;
|
|
3662
|
+
if (metadata?.tags) body.tags = metadata.tags;
|
|
3663
|
+
if (metadata?.owner_project) body.owner_project = metadata.owner_project;
|
|
3664
|
+
if (metadata?.integration) body.integration = metadata.integration;
|
|
3660
3665
|
const res = await apiCall(creds, "POST", "/api/v1/executors", body);
|
|
3661
3666
|
if (!res.ok) {
|
|
3662
3667
|
const err = await res.text();
|
|
@@ -4072,6 +4077,15 @@ ${source_default.yellow("\u26A0")} ${label} failed, retrying in ${delay}s... (${
|
|
|
4072
4077
|
var import_fs2 = require("fs");
|
|
4073
4078
|
var import_os2 = require("os");
|
|
4074
4079
|
var import_path2 = require("path");
|
|
4080
|
+
async function readWorkspaceConfig(workspaceRoot) {
|
|
4081
|
+
try {
|
|
4082
|
+
const { readFile } = await import("fs/promises");
|
|
4083
|
+
const content = await readFile((0, import_path2.join)(workspaceRoot, ".cva", "agent.json"), "utf-8");
|
|
4084
|
+
return JSON.parse(content);
|
|
4085
|
+
} catch {
|
|
4086
|
+
return {};
|
|
4087
|
+
}
|
|
4088
|
+
}
|
|
4075
4089
|
function getConfigPath() {
|
|
4076
4090
|
return (0, import_path2.join)((0, import_os2.homedir)(), ".config", "cva", "config.json");
|
|
4077
4091
|
}
|
|
@@ -4089,6 +4103,277 @@ async function writeConfig(config) {
|
|
|
4089
4103
|
await import_fs2.promises.writeFile(configPath, JSON.stringify(config, null, 2) + "\n", { mode: 384 });
|
|
4090
4104
|
}
|
|
4091
4105
|
|
|
4106
|
+
// src/commands/git-safety.ts
|
|
4107
|
+
var import_node_child_process2 = require("node:child_process");
|
|
4108
|
+
function git(cmd, cwd) {
|
|
4109
|
+
return (0, import_node_child_process2.execSync)(cmd, { cwd, encoding: "utf8", timeout: 3e4 }).trim();
|
|
4110
|
+
}
|
|
4111
|
+
function gitSafetyNet(workspaceRoot, taskTitle, taskId, branch) {
|
|
4112
|
+
const targetBranch = branch || "main";
|
|
4113
|
+
try {
|
|
4114
|
+
let statusOutput;
|
|
4115
|
+
try {
|
|
4116
|
+
statusOutput = git("git status --porcelain", workspaceRoot);
|
|
4117
|
+
} catch {
|
|
4118
|
+
return { hadChanges: false, filesAdded: 0, filesModified: 0, filesDeleted: 0, pushed: false, error: "git status failed" };
|
|
4119
|
+
}
|
|
4120
|
+
const lines = statusOutput.split("\n").filter(Boolean);
|
|
4121
|
+
if (lines.length === 0) {
|
|
4122
|
+
try {
|
|
4123
|
+
const unpushed = git(`git log origin/${targetBranch}..HEAD --oneline 2>/dev/null`, workspaceRoot);
|
|
4124
|
+
if (unpushed) {
|
|
4125
|
+
console.log(` [git-safety] Found unpushed commits \u2014 pushing now`);
|
|
4126
|
+
git(`git push origin ${targetBranch}`, workspaceRoot);
|
|
4127
|
+
return { hadChanges: false, filesAdded: 0, filesModified: 0, filesDeleted: 0, pushed: true };
|
|
4128
|
+
}
|
|
4129
|
+
} catch {
|
|
4130
|
+
}
|
|
4131
|
+
return { hadChanges: false, filesAdded: 0, filesModified: 0, filesDeleted: 0, pushed: false };
|
|
4132
|
+
}
|
|
4133
|
+
let added = 0, modified = 0, deleted = 0;
|
|
4134
|
+
for (const line of lines) {
|
|
4135
|
+
const code = line.substring(0, 2);
|
|
4136
|
+
if (code.includes("?")) added++;
|
|
4137
|
+
else if (code.includes("D")) deleted++;
|
|
4138
|
+
else if (code.includes("M") || code.includes("A")) modified++;
|
|
4139
|
+
else added++;
|
|
4140
|
+
}
|
|
4141
|
+
console.log(
|
|
4142
|
+
` [git-safety] ${lines.length} uncommitted changes (${added} new, ${modified} modified, ${deleted} deleted) \u2014 committing now`
|
|
4143
|
+
);
|
|
4144
|
+
git("git add -A", workspaceRoot);
|
|
4145
|
+
const shortId = taskId.substring(0, 8);
|
|
4146
|
+
const commitMsg = `task: ${taskTitle} [${shortId}]
|
|
4147
|
+
|
|
4148
|
+
Auto-committed by cv-agent git safety net.
|
|
4149
|
+
Task ID: ${taskId}
|
|
4150
|
+
Files: ${added} added, ${modified} modified, ${deleted} deleted`;
|
|
4151
|
+
let commitSha;
|
|
4152
|
+
try {
|
|
4153
|
+
const commitOutput = git(`git commit -m ${JSON.stringify(commitMsg)}`, workspaceRoot);
|
|
4154
|
+
const shaMatch = commitOutput.match(/\[[\w/]+ ([a-f0-9]+)\]/);
|
|
4155
|
+
commitSha = shaMatch ? shaMatch[1] : void 0;
|
|
4156
|
+
} catch (e) {
|
|
4157
|
+
if (!e.message?.includes("nothing to commit")) {
|
|
4158
|
+
return {
|
|
4159
|
+
hadChanges: true,
|
|
4160
|
+
filesAdded: added,
|
|
4161
|
+
filesModified: modified,
|
|
4162
|
+
filesDeleted: deleted,
|
|
4163
|
+
pushed: false,
|
|
4164
|
+
error: `Commit failed: ${e.message}`
|
|
4165
|
+
};
|
|
4166
|
+
}
|
|
4167
|
+
}
|
|
4168
|
+
try {
|
|
4169
|
+
git(`git push origin ${targetBranch}`, workspaceRoot);
|
|
4170
|
+
console.log(` [git-safety] Committed and pushed: ${commitSha || "ok"}`);
|
|
4171
|
+
} catch (pushErr) {
|
|
4172
|
+
console.log(` [git-safety] Push failed: ${pushErr.message}`);
|
|
4173
|
+
return {
|
|
4174
|
+
hadChanges: true,
|
|
4175
|
+
filesAdded: added,
|
|
4176
|
+
filesModified: modified,
|
|
4177
|
+
filesDeleted: deleted,
|
|
4178
|
+
commitSha,
|
|
4179
|
+
pushed: false,
|
|
4180
|
+
error: `Push failed: ${pushErr.message}`
|
|
4181
|
+
};
|
|
4182
|
+
}
|
|
4183
|
+
return {
|
|
4184
|
+
hadChanges: true,
|
|
4185
|
+
filesAdded: added,
|
|
4186
|
+
filesModified: modified,
|
|
4187
|
+
filesDeleted: deleted,
|
|
4188
|
+
commitSha,
|
|
4189
|
+
pushed: true
|
|
4190
|
+
};
|
|
4191
|
+
} catch (err) {
|
|
4192
|
+
return {
|
|
4193
|
+
hadChanges: false,
|
|
4194
|
+
filesAdded: 0,
|
|
4195
|
+
filesModified: 0,
|
|
4196
|
+
filesDeleted: 0,
|
|
4197
|
+
pushed: false,
|
|
4198
|
+
error: err.message
|
|
4199
|
+
};
|
|
4200
|
+
}
|
|
4201
|
+
}
|
|
4202
|
+
|
|
4203
|
+
// src/commands/deploy-manifest.ts
|
|
4204
|
+
var import_node_child_process3 = require("node:child_process");
|
|
4205
|
+
var import_node_fs = require("node:fs");
|
|
4206
|
+
var import_node_path = require("node:path");
|
|
4207
|
+
function exec(cmd, cwd, timeoutMs = 3e5, env2) {
|
|
4208
|
+
try {
|
|
4209
|
+
const stdout = (0, import_node_child_process3.execSync)(cmd, {
|
|
4210
|
+
cwd,
|
|
4211
|
+
encoding: "utf8",
|
|
4212
|
+
timeout: timeoutMs,
|
|
4213
|
+
env: env2 ? { ...process.env, ...env2 } : void 0,
|
|
4214
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
4215
|
+
});
|
|
4216
|
+
return { ok: true, stdout, stderr: "" };
|
|
4217
|
+
} catch (err) {
|
|
4218
|
+
return { ok: false, stdout: err.stdout || "", stderr: err.stderr || err.message || "" };
|
|
4219
|
+
}
|
|
4220
|
+
}
|
|
4221
|
+
async function httpStatus(url, timeoutMs = 1e4) {
|
|
4222
|
+
try {
|
|
4223
|
+
const controller = new AbortController();
|
|
4224
|
+
const timer = setTimeout(() => controller.abort(), timeoutMs);
|
|
4225
|
+
const res = await fetch(url, { signal: controller.signal });
|
|
4226
|
+
clearTimeout(timer);
|
|
4227
|
+
return res.status;
|
|
4228
|
+
} catch {
|
|
4229
|
+
return 0;
|
|
4230
|
+
}
|
|
4231
|
+
}
|
|
4232
|
+
async function checkHealth(url, timeoutSeconds) {
|
|
4233
|
+
const deadline = Date.now() + timeoutSeconds * 1e3;
|
|
4234
|
+
while (Date.now() < deadline) {
|
|
4235
|
+
const status = await httpStatus(url);
|
|
4236
|
+
if (status >= 200 && status < 500) return true;
|
|
4237
|
+
await new Promise((r) => setTimeout(r, 2e3));
|
|
4238
|
+
}
|
|
4239
|
+
return false;
|
|
4240
|
+
}
|
|
4241
|
+
function getHeadCommit(cwd) {
|
|
4242
|
+
try {
|
|
4243
|
+
return (0, import_node_child_process3.execSync)("git rev-parse HEAD", { cwd, encoding: "utf8", timeout: 5e3 }).trim();
|
|
4244
|
+
} catch {
|
|
4245
|
+
return null;
|
|
4246
|
+
}
|
|
4247
|
+
}
|
|
4248
|
+
function loadDeployManifest(workspaceRoot) {
|
|
4249
|
+
const manifestPath = (0, import_node_path.join)(workspaceRoot, ".cva", "deploy.json");
|
|
4250
|
+
if (!(0, import_node_fs.existsSync)(manifestPath)) return null;
|
|
4251
|
+
try {
|
|
4252
|
+
const raw = (0, import_node_fs.readFileSync)(manifestPath, "utf8");
|
|
4253
|
+
const manifest = JSON.parse(raw);
|
|
4254
|
+
if (!manifest.version || manifest.version < 1) {
|
|
4255
|
+
console.log(" [deploy] Invalid manifest version");
|
|
4256
|
+
return null;
|
|
4257
|
+
}
|
|
4258
|
+
return manifest;
|
|
4259
|
+
} catch (err) {
|
|
4260
|
+
console.log(` [deploy] Failed to read .cva/deploy.json: ${err.message}`);
|
|
4261
|
+
return null;
|
|
4262
|
+
}
|
|
4263
|
+
}
|
|
4264
|
+
async function postTaskDeploy(workspaceRoot, taskId, log) {
|
|
4265
|
+
const manifest = loadDeployManifest(workspaceRoot);
|
|
4266
|
+
if (!manifest) {
|
|
4267
|
+
return { deployed: false, steps: [{ step: "manifest", status: "skipped", message: "No .cva/deploy.json" }] };
|
|
4268
|
+
}
|
|
4269
|
+
const steps = [];
|
|
4270
|
+
const preDeployCommit = getHeadCommit(workspaceRoot);
|
|
4271
|
+
if (manifest.build?.steps) {
|
|
4272
|
+
for (const buildStep of manifest.build.steps) {
|
|
4273
|
+
const stepName = `build:${buildStep.name}`;
|
|
4274
|
+
console.log(` [deploy] Building: ${buildStep.name}`);
|
|
4275
|
+
log("lifecycle", `Building: ${buildStep.name}`);
|
|
4276
|
+
const start = Date.now();
|
|
4277
|
+
const cwd = (0, import_node_path.join)(workspaceRoot, buildStep.working_dir || ".");
|
|
4278
|
+
const timeoutMs = (buildStep.timeout_seconds || 300) * 1e3;
|
|
4279
|
+
const result = exec(buildStep.command, cwd, timeoutMs);
|
|
4280
|
+
if (!result.ok) {
|
|
4281
|
+
const msg = `Build failed: ${buildStep.name}
|
|
4282
|
+
${result.stderr.slice(-500)}`;
|
|
4283
|
+
steps.push({ step: stepName, status: "failed", message: msg, durationMs: Date.now() - start });
|
|
4284
|
+
log("error", msg);
|
|
4285
|
+
return { deployed: false, steps, error: msg };
|
|
4286
|
+
}
|
|
4287
|
+
steps.push({ step: stepName, status: "ok", durationMs: Date.now() - start });
|
|
4288
|
+
}
|
|
4289
|
+
}
|
|
4290
|
+
if (manifest.migrate?.command) {
|
|
4291
|
+
console.log(" [deploy] Running migration...");
|
|
4292
|
+
log("lifecycle", "Applying database migration");
|
|
4293
|
+
const start = Date.now();
|
|
4294
|
+
const env2 = manifest.migrate.env || {};
|
|
4295
|
+
const result = exec(manifest.migrate.command, workspaceRoot, 6e4, env2);
|
|
4296
|
+
if (!result.ok) {
|
|
4297
|
+
const msg = `Migration failed:
|
|
4298
|
+
${result.stderr.slice(-500)}`;
|
|
4299
|
+
steps.push({ step: "migrate", status: "failed", message: msg, durationMs: Date.now() - start });
|
|
4300
|
+
log("error", msg);
|
|
4301
|
+
return { deployed: false, steps, error: msg };
|
|
4302
|
+
}
|
|
4303
|
+
steps.push({ step: "migrate", status: "ok", durationMs: Date.now() - start });
|
|
4304
|
+
}
|
|
4305
|
+
if (manifest.service && manifest.service.type !== "none" && manifest.service.restart_command) {
|
|
4306
|
+
console.log(` [deploy] Restarting service: ${manifest.service.name || "default"}`);
|
|
4307
|
+
log("lifecycle", `Restarting service: ${manifest.service.name || "default"}`);
|
|
4308
|
+
const start = Date.now();
|
|
4309
|
+
const result = exec(manifest.service.restart_command, workspaceRoot, 3e4);
|
|
4310
|
+
if (!result.ok) {
|
|
4311
|
+
const msg = `Restart failed:
|
|
4312
|
+
${result.stderr.slice(-300)}`;
|
|
4313
|
+
steps.push({ step: "restart", status: "failed", message: msg, durationMs: Date.now() - start });
|
|
4314
|
+
} else {
|
|
4315
|
+
steps.push({ step: "restart", status: "ok", durationMs: Date.now() - start });
|
|
4316
|
+
}
|
|
4317
|
+
const waitMs = (manifest.service.startup_wait_seconds || 5) * 1e3;
|
|
4318
|
+
await new Promise((r) => setTimeout(r, waitMs));
|
|
4319
|
+
}
|
|
4320
|
+
if (manifest.verify) {
|
|
4321
|
+
console.log(" [deploy] Verifying deployment...");
|
|
4322
|
+
log("lifecycle", "Verifying deployment");
|
|
4323
|
+
const timeoutSec = manifest.verify.timeout_seconds || 30;
|
|
4324
|
+
if (manifest.verify.health_url) {
|
|
4325
|
+
const healthy = await checkHealth(manifest.verify.health_url, timeoutSec);
|
|
4326
|
+
if (!healthy) {
|
|
4327
|
+
const msg = `Health check failed: ${manifest.verify.health_url} did not respond within ${timeoutSec}s`;
|
|
4328
|
+
steps.push({ step: "verify:health", status: "failed", message: msg });
|
|
4329
|
+
log("error", msg);
|
|
4330
|
+
if (manifest.rollback?.auto_rollback_on_verify_failure && preDeployCommit) {
|
|
4331
|
+
await rollback(workspaceRoot, preDeployCommit, manifest, log);
|
|
4332
|
+
return { deployed: false, steps, error: msg, rolledBack: true };
|
|
4333
|
+
}
|
|
4334
|
+
return { deployed: false, steps, error: msg };
|
|
4335
|
+
}
|
|
4336
|
+
steps.push({ step: "verify:health", status: "ok" });
|
|
4337
|
+
}
|
|
4338
|
+
for (const test of manifest.verify.smoke_tests || []) {
|
|
4339
|
+
const status = await httpStatus(test.url);
|
|
4340
|
+
if (!test.expected_status.includes(status)) {
|
|
4341
|
+
const msg = `Smoke test "${test.name}" failed: got ${status}, expected ${test.expected_status.join("|")}`;
|
|
4342
|
+
steps.push({ step: `verify:${test.name}`, status: "failed", message: msg });
|
|
4343
|
+
log("error", msg);
|
|
4344
|
+
if (manifest.rollback?.auto_rollback_on_verify_failure && preDeployCommit) {
|
|
4345
|
+
await rollback(workspaceRoot, preDeployCommit, manifest, log);
|
|
4346
|
+
return { deployed: false, steps, error: msg, rolledBack: true };
|
|
4347
|
+
}
|
|
4348
|
+
return { deployed: false, steps, error: msg };
|
|
4349
|
+
}
|
|
4350
|
+
steps.push({ step: `verify:${test.name}`, status: "ok" });
|
|
4351
|
+
}
|
|
4352
|
+
}
|
|
4353
|
+
console.log(" [deploy] Deployment verified");
|
|
4354
|
+
log("lifecycle", "Deployment verified successfully");
|
|
4355
|
+
return { deployed: true, steps };
|
|
4356
|
+
}
|
|
4357
|
+
async function rollback(workspaceRoot, targetCommit, manifest, log) {
|
|
4358
|
+
console.log(` [deploy] Rolling back to ${targetCommit.substring(0, 8)}`);
|
|
4359
|
+
log("lifecycle", `Rolling back to ${targetCommit.substring(0, 8)}`);
|
|
4360
|
+
try {
|
|
4361
|
+
exec(`git checkout ${targetCommit}`, workspaceRoot, 1e4);
|
|
4362
|
+
} catch {
|
|
4363
|
+
log("error", "Rollback: git checkout failed");
|
|
4364
|
+
return;
|
|
4365
|
+
}
|
|
4366
|
+
if (manifest.build?.steps) {
|
|
4367
|
+
for (const step of manifest.build.steps) {
|
|
4368
|
+
exec(step.command, (0, import_node_path.join)(workspaceRoot, step.working_dir || "."), (step.timeout_seconds || 300) * 1e3);
|
|
4369
|
+
}
|
|
4370
|
+
}
|
|
4371
|
+
if (manifest.service?.restart_command) {
|
|
4372
|
+
exec(manifest.service.restart_command, workspaceRoot, 3e4);
|
|
4373
|
+
}
|
|
4374
|
+
log("lifecycle", "Rollback complete");
|
|
4375
|
+
}
|
|
4376
|
+
|
|
4092
4377
|
// src/commands/agent.ts
|
|
4093
4378
|
var AUTH_ERROR_PATTERNS = [
|
|
4094
4379
|
"Not logged in",
|
|
@@ -4110,7 +4395,7 @@ function containsAuthError(text) {
|
|
|
4110
4395
|
}
|
|
4111
4396
|
async function checkClaudeAuth() {
|
|
4112
4397
|
try {
|
|
4113
|
-
const output = (0,
|
|
4398
|
+
const output = (0, import_node_child_process4.execSync)("claude --version 2>&1", {
|
|
4114
4399
|
encoding: "utf8",
|
|
4115
4400
|
timeout: 1e4,
|
|
4116
4401
|
env: { ...process.env }
|
|
@@ -4321,7 +4606,7 @@ async function launchAutoApproveMode(prompt, options) {
|
|
|
4321
4606
|
if (sessionId && !isContinue) {
|
|
4322
4607
|
args.push("--session-id", sessionId);
|
|
4323
4608
|
}
|
|
4324
|
-
const child = (0,
|
|
4609
|
+
const child = (0, import_node_child_process4.spawn)("claude", args, {
|
|
4325
4610
|
cwd: options.cwd,
|
|
4326
4611
|
stdio: ["inherit", "pipe", "pipe"],
|
|
4327
4612
|
env: options.spawnEnv || { ...process.env }
|
|
@@ -4455,7 +4740,7 @@ ${source_default.red("!")} Claude Code auth failure (stderr): "${authError}"`);
|
|
|
4455
4740
|
}
|
|
4456
4741
|
async function launchRelayMode(prompt, options) {
|
|
4457
4742
|
return new Promise((resolve, reject) => {
|
|
4458
|
-
const child = (0,
|
|
4743
|
+
const child = (0, import_node_child_process4.spawn)("claude", [], {
|
|
4459
4744
|
cwd: options.cwd,
|
|
4460
4745
|
stdio: ["pipe", "pipe", "pipe"],
|
|
4461
4746
|
env: options.spawnEnv || { ...process.env }
|
|
@@ -4681,19 +4966,19 @@ async function handleSelfUpdate(task, state, creds) {
|
|
|
4681
4966
|
let output = "";
|
|
4682
4967
|
if (source === "npm" || source.startsWith("npm:")) {
|
|
4683
4968
|
const pkg = source === "npm" ? "@controlvector/cv-agent@latest" : source.replace("npm:", "");
|
|
4684
|
-
output = (0,
|
|
4969
|
+
output = (0, import_node_child_process4.execSync)(`npm install -g ${pkg} 2>&1`, { encoding: "utf8", timeout: 12e4 });
|
|
4685
4970
|
} else if (source.startsWith("git:")) {
|
|
4686
4971
|
const repoPath = source.replace("git:", "");
|
|
4687
|
-
output = (0,
|
|
4972
|
+
output = (0, import_node_child_process4.execSync)(`cd ${repoPath} && git pull && npm install && npm run build && npm link 2>&1`, {
|
|
4688
4973
|
encoding: "utf8",
|
|
4689
4974
|
timeout: 3e5
|
|
4690
4975
|
});
|
|
4691
4976
|
} else {
|
|
4692
|
-
output = (0,
|
|
4977
|
+
output = (0, import_node_child_process4.execSync)(`npm install -g @controlvector/cv-agent@latest 2>&1`, { encoding: "utf8", timeout: 12e4 });
|
|
4693
4978
|
}
|
|
4694
4979
|
let newVersion = "unknown";
|
|
4695
4980
|
try {
|
|
4696
|
-
newVersion = (0,
|
|
4981
|
+
newVersion = (0, import_node_child_process4.execSync)("cva --version 2>/dev/null || echo unknown", { encoding: "utf8" }).trim();
|
|
4697
4982
|
} catch {
|
|
4698
4983
|
}
|
|
4699
4984
|
postTaskEvent(creds, task.id, {
|
|
@@ -4719,7 +5004,7 @@ async function handleSelfUpdate(task, state, creds) {
|
|
|
4719
5004
|
if (task.input?.constraints?.includes("restart")) {
|
|
4720
5005
|
console.log(source_default.yellow("Restarting agent with updated binary..."));
|
|
4721
5006
|
const args = process.argv.slice(1).join(" ");
|
|
4722
|
-
(0,
|
|
5007
|
+
(0, import_node_child_process4.execSync)(`nohup cva ${args} > /tmp/cva-restart.log 2>&1 &`, { stdio: "ignore" });
|
|
4723
5008
|
process.exit(0);
|
|
4724
5009
|
}
|
|
4725
5010
|
} catch (err) {
|
|
@@ -4751,7 +5036,7 @@ async function runAgent(options) {
|
|
|
4751
5036
|
process.exit(1);
|
|
4752
5037
|
}
|
|
4753
5038
|
try {
|
|
4754
|
-
(0,
|
|
5039
|
+
(0, import_node_child_process4.execSync)("claude --version", { stdio: "pipe", timeout: 5e3 });
|
|
4755
5040
|
} catch {
|
|
4756
5041
|
console.log();
|
|
4757
5042
|
console.log(source_default.red("Claude Code CLI not found.") + " Install it first:");
|
|
@@ -4780,7 +5065,7 @@ async function runAgent(options) {
|
|
|
4780
5065
|
currentAuthStatus = "api_key_fallback";
|
|
4781
5066
|
}
|
|
4782
5067
|
try {
|
|
4783
|
-
(0,
|
|
5068
|
+
(0, import_node_child_process4.execSync)("cv --version", { stdio: "pipe", timeout: 5e3 });
|
|
4784
5069
|
} catch {
|
|
4785
5070
|
console.log(source_default.yellow("!") + " cv-git CLI not found. Claude Code will fall back to raw git commands.");
|
|
4786
5071
|
console.log(` Install it: ${source_default.cyan("npm install -g @controlvector/cv-git")}`);
|
|
@@ -4800,7 +5085,7 @@ async function runAgent(options) {
|
|
|
4800
5085
|
}
|
|
4801
5086
|
let detectedRepoId;
|
|
4802
5087
|
try {
|
|
4803
|
-
const remoteUrl = (0,
|
|
5088
|
+
const remoteUrl = (0, import_node_child_process4.execSync)("git remote get-url origin 2>/dev/null", {
|
|
4804
5089
|
cwd: workingDir,
|
|
4805
5090
|
encoding: "utf8",
|
|
4806
5091
|
timeout: 5e3
|
|
@@ -4821,8 +5106,47 @@ async function runAgent(options) {
|
|
|
4821
5106
|
}
|
|
4822
5107
|
} catch {
|
|
4823
5108
|
}
|
|
5109
|
+
const wsConfig = await readWorkspaceConfig(workingDir);
|
|
5110
|
+
const globalConfig = await readConfig();
|
|
5111
|
+
const role = options.role || wsConfig.role || globalConfig.role || "development";
|
|
5112
|
+
const dispatchGuard = options.dispatchGuard || wsConfig.dispatch_guard || globalConfig.dispatch_guard || "open";
|
|
5113
|
+
const tags = (options.tags?.split(",") || wsConfig.tags || globalConfig.tags)?.map((t) => t.trim()).filter(Boolean);
|
|
5114
|
+
const ownerProject = options.ownerProject || wsConfig.owner_project || globalConfig.owner_project;
|
|
5115
|
+
let integration;
|
|
5116
|
+
if (options.integration || wsConfig.integration) {
|
|
5117
|
+
if (options.integration) {
|
|
5118
|
+
const safeTasks = options.safeTasks?.split(",").map((t) => t.trim()).filter(Boolean);
|
|
5119
|
+
const unsafeTasks = safeTasks ? ["code_change", "deploy", "test", "custom"].filter((t) => !safeTasks.includes(t)) : void 0;
|
|
5120
|
+
integration = {
|
|
5121
|
+
system: options.integration,
|
|
5122
|
+
description: options.integrationDescription || `Integrated into ${options.integration}`,
|
|
5123
|
+
service_port: options.integrationPort ? parseInt(options.integrationPort, 10) : void 0,
|
|
5124
|
+
safe_task_types: safeTasks,
|
|
5125
|
+
unsafe_task_types: unsafeTasks
|
|
5126
|
+
};
|
|
5127
|
+
} else if (wsConfig.integration) {
|
|
5128
|
+
integration = wsConfig.integration;
|
|
5129
|
+
}
|
|
5130
|
+
}
|
|
5131
|
+
const executorMeta = {
|
|
5132
|
+
role,
|
|
5133
|
+
dispatch_guard: dispatchGuard,
|
|
5134
|
+
tags,
|
|
5135
|
+
owner_project: ownerProject,
|
|
5136
|
+
integration
|
|
5137
|
+
};
|
|
5138
|
+
if (role !== "development") {
|
|
5139
|
+
console.log(source_default.yellow(` Role: ${role}`));
|
|
5140
|
+
}
|
|
5141
|
+
if (integration) {
|
|
5142
|
+
console.log(source_default.yellow(` Integration: ${integration.system}${integration.service_port ? ` (port ${integration.service_port})` : ""}`));
|
|
5143
|
+
}
|
|
5144
|
+
if (dispatchGuard !== "open") {
|
|
5145
|
+
const guardIcon = dispatchGuard === "locked" ? "\u{1F512}" : "\u26A0\uFE0F";
|
|
5146
|
+
console.log(source_default.yellow(` Guard: ${guardIcon} ${dispatchGuard}`));
|
|
5147
|
+
}
|
|
4824
5148
|
const executor = await withRetry(
|
|
4825
|
-
() => registerExecutor(creds, machineName, workingDir, detectedRepoId),
|
|
5149
|
+
() => registerExecutor(creds, machineName, workingDir, detectedRepoId, executorMeta),
|
|
4826
5150
|
"Executor registration"
|
|
4827
5151
|
);
|
|
4828
5152
|
const mode = options.autoApprove ? "auto-approve" : "relay";
|
|
@@ -4970,6 +5294,33 @@ ${source_default.red("Timeout")} Task timed out after ${formatDuration(timeoutMs
|
|
|
4970
5294
|
});
|
|
4971
5295
|
}
|
|
4972
5296
|
console.log(source_default.gray("\n" + "-".repeat(60)));
|
|
5297
|
+
if (result.exitCode === 0 || result.exitCode === 1) {
|
|
5298
|
+
try {
|
|
5299
|
+
const gitSafety = gitSafetyNet(
|
|
5300
|
+
options.workingDir,
|
|
5301
|
+
task.title,
|
|
5302
|
+
task.id,
|
|
5303
|
+
task.branch
|
|
5304
|
+
);
|
|
5305
|
+
if (gitSafety.hadChanges) {
|
|
5306
|
+
sendTaskLog(
|
|
5307
|
+
creds,
|
|
5308
|
+
state.executorId,
|
|
5309
|
+
task.id,
|
|
5310
|
+
"git",
|
|
5311
|
+
`Safety net: committed ${gitSafety.filesAdded + gitSafety.filesModified} files (${gitSafety.commitSha || "ok"})`,
|
|
5312
|
+
{ added: gitSafety.filesAdded, modified: gitSafety.filesModified, deleted: gitSafety.filesDeleted }
|
|
5313
|
+
);
|
|
5314
|
+
} else if (gitSafety.pushed) {
|
|
5315
|
+
sendTaskLog(creds, state.executorId, task.id, "git", "Safety net: pushed unpushed commits");
|
|
5316
|
+
}
|
|
5317
|
+
if (gitSafety.error) {
|
|
5318
|
+
sendTaskLog(creds, state.executorId, task.id, "info", `Git safety warning: ${gitSafety.error}`);
|
|
5319
|
+
}
|
|
5320
|
+
} catch (gitErr) {
|
|
5321
|
+
sendTaskLog(creds, state.executorId, task.id, "info", `Git safety net error: ${gitErr.message}`);
|
|
5322
|
+
}
|
|
5323
|
+
}
|
|
4973
5324
|
const postGitState = capturePostTaskState(options.workingDir, preGitState);
|
|
4974
5325
|
const payload = buildCompletionPayload(result.exitCode, preGitState, postGitState, startTime, result.output);
|
|
4975
5326
|
const elapsed = formatDuration(Date.now() - startTime);
|
|
@@ -4978,6 +5329,28 @@ ${source_default.red("Timeout")} Task timed out after ${formatDuration(timeoutMs
|
|
|
4978
5329
|
...postGitState.filesModified,
|
|
4979
5330
|
...postGitState.filesDeleted
|
|
4980
5331
|
];
|
|
5332
|
+
let deployResult;
|
|
5333
|
+
if (result.exitCode === 0) {
|
|
5334
|
+
try {
|
|
5335
|
+
const logFn = (type, msg) => {
|
|
5336
|
+
sendTaskLog(creds, state.executorId, task.id, type, msg);
|
|
5337
|
+
};
|
|
5338
|
+
deployResult = await postTaskDeploy(options.workingDir, task.id, logFn);
|
|
5339
|
+
if (deployResult.deployed) {
|
|
5340
|
+
sendTaskLog(creds, state.executorId, task.id, "lifecycle", "Post-task deploy: verified");
|
|
5341
|
+
} else if (deployResult.error) {
|
|
5342
|
+
sendTaskLog(
|
|
5343
|
+
creds,
|
|
5344
|
+
state.executorId,
|
|
5345
|
+
task.id,
|
|
5346
|
+
"error",
|
|
5347
|
+
`Post-task deploy failed: ${deployResult.error}${deployResult.rolledBack ? " (rolled back)" : ""}`
|
|
5348
|
+
);
|
|
5349
|
+
}
|
|
5350
|
+
} catch (deployErr) {
|
|
5351
|
+
sendTaskLog(creds, state.executorId, task.id, "info", `Deploy manifest error: ${deployErr.message}`);
|
|
5352
|
+
}
|
|
5353
|
+
}
|
|
4981
5354
|
postTaskEvent(creds, task.id, {
|
|
4982
5355
|
event_type: "completed",
|
|
4983
5356
|
content: {
|
|
@@ -5121,6 +5494,14 @@ function agentCommand() {
|
|
|
5121
5494
|
cmd.option("--poll-interval <seconds>", "How often to check for tasks, minimum 3 (default: 5)", "5");
|
|
5122
5495
|
cmd.option("--working-dir <path>", "Working directory for Claude Code (default: current directory)", ".");
|
|
5123
5496
|
cmd.option("--auto-approve", "Pre-approve all tool permissions (uses -p mode)", false);
|
|
5497
|
+
cmd.option("--role <role>", "Executor role: development, production, ci, staging");
|
|
5498
|
+
cmd.option("--integration <system>", "System this executor is integrated into (e.g. nyx-forge)");
|
|
5499
|
+
cmd.option("--integration-description <desc>", "Description of the integration");
|
|
5500
|
+
cmd.option("--integration-port <port>", "Port the integrated service runs on");
|
|
5501
|
+
cmd.option("--safe-tasks <types>", "Comma-separated task types safe for this executor (e.g. review,research)");
|
|
5502
|
+
cmd.option("--dispatch-guard <guard>", "Dispatch guard: open, confirm, locked (default: open)");
|
|
5503
|
+
cmd.option("--tags <tags>", "Comma-separated tags for this executor");
|
|
5504
|
+
cmd.option("--owner-project <project>", "Project this executor belongs to");
|
|
5124
5505
|
cmd.action(async (opts) => {
|
|
5125
5506
|
await runAgent(opts);
|
|
5126
5507
|
});
|
|
@@ -5228,7 +5609,7 @@ function authCommand() {
|
|
|
5228
5609
|
}
|
|
5229
5610
|
|
|
5230
5611
|
// src/commands/remote.ts
|
|
5231
|
-
var
|
|
5612
|
+
var import_node_child_process5 = require("node:child_process");
|
|
5232
5613
|
function getGitHost(apiUrl) {
|
|
5233
5614
|
return apiUrl.replace(/^https?:\/\//, "").replace(/^api\./, "git.");
|
|
5234
5615
|
}
|
|
@@ -5243,16 +5624,16 @@ async function remoteAdd(ownerRepo) {
|
|
|
5243
5624
|
}
|
|
5244
5625
|
const remoteUrl = `https://${gitHost}/${owner}/${repo}.git`;
|
|
5245
5626
|
try {
|
|
5246
|
-
const existing = (0,
|
|
5627
|
+
const existing = (0, import_node_child_process5.execSync)("git remote get-url cvhub", { encoding: "utf-8", timeout: 5e3 }).trim();
|
|
5247
5628
|
if (existing === remoteUrl) {
|
|
5248
5629
|
console.log(source_default.gray(`Remote 'cvhub' already set to ${remoteUrl}`));
|
|
5249
5630
|
return;
|
|
5250
5631
|
}
|
|
5251
|
-
(0,
|
|
5632
|
+
(0, import_node_child_process5.execSync)(`git remote set-url cvhub ${remoteUrl}`, { timeout: 5e3 });
|
|
5252
5633
|
console.log(source_default.green("Updated") + ` remote 'cvhub' -> ${remoteUrl}`);
|
|
5253
5634
|
} catch {
|
|
5254
5635
|
try {
|
|
5255
|
-
(0,
|
|
5636
|
+
(0, import_node_child_process5.execSync)(`git remote add cvhub ${remoteUrl}`, { timeout: 5e3 });
|
|
5256
5637
|
console.log(source_default.green("Added") + ` remote 'cvhub' -> ${remoteUrl}`);
|
|
5257
5638
|
} catch (err) {
|
|
5258
5639
|
console.log(source_default.red("Failed to add remote:") + ` ${err.message}`);
|
|
@@ -5444,7 +5825,7 @@ function statusCommand() {
|
|
|
5444
5825
|
|
|
5445
5826
|
// src/index.ts
|
|
5446
5827
|
var program2 = new Command();
|
|
5447
|
-
program2.name("cva").description("CV-Hub Agent \u2014 bridges Claude Code with CV-Hub task dispatch").version(true ? "1.
|
|
5828
|
+
program2.name("cva").description("CV-Hub Agent \u2014 bridges Claude Code with CV-Hub task dispatch").version(true ? "1.6.0" : "1.1.0");
|
|
5448
5829
|
program2.addCommand(agentCommand());
|
|
5449
5830
|
program2.addCommand(authCommand());
|
|
5450
5831
|
program2.addCommand(remoteCommand());
|