@contextgraph/agent 0.4.17 → 0.4.19

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/index.js CHANGED
@@ -4,11 +4,11 @@
4
4
  import { Command } from "commander";
5
5
  import { readFileSync as readFileSync2 } from "fs";
6
6
  import { fileURLToPath as fileURLToPath2 } from "url";
7
- import { dirname as dirname2, join as join4 } from "path";
7
+ import { dirname as dirname2, join as join6 } from "path";
8
8
 
9
9
  // src/callback-server.ts
10
10
  import http from "http";
11
- import { URL } from "url";
11
+ import { URL as URL2 } from "url";
12
12
  var MIN_PORT = 3e3;
13
13
  var MAX_PORT = 3100;
14
14
  async function findFreePort() {
@@ -38,7 +38,7 @@ async function startCallbackServer() {
38
38
  let callbackResolve = null;
39
39
  const connections = /* @__PURE__ */ new Set();
40
40
  const server = http.createServer((req, res) => {
41
- const url = new URL(req.url || "/", `http://localhost:${port}`);
41
+ const url = new URL2(req.url || "/", `http://localhost:${port}`);
42
42
  if (url.pathname === "/callback") {
43
43
  const token = url.searchParams.get("token");
44
44
  const userId = url.searchParams.get("userId");
@@ -859,6 +859,32 @@ async function executeClaude(options) {
859
859
  // Allow MCP tools to execute automatically
860
860
  maxTurns: 100,
861
861
  // Reasonable limit
862
+ // Enable skills: load from project .claude/skills/ and allow Skill tool
863
+ settingSources: ["project"],
864
+ allowedTools: [
865
+ // Core file operations
866
+ "Read",
867
+ "Write",
868
+ "Edit",
869
+ "MultiEdit",
870
+ // Search tools
871
+ "Grep",
872
+ "Glob",
873
+ // Execution
874
+ "Bash",
875
+ // Skills
876
+ "Skill",
877
+ // Agent orchestration
878
+ "Task",
879
+ // User interaction
880
+ "TodoWrite",
881
+ "AskUserQuestion",
882
+ // Web access
883
+ "WebFetch",
884
+ "WebSearch",
885
+ // MCP tools (pattern match for contextgraph MCP server)
886
+ "mcp__*"
887
+ ],
862
888
  env: {
863
889
  ...process.env,
864
890
  // Pass auth token through environment for MCP server
@@ -1310,208 +1336,219 @@ var HeartbeatManager = class {
1310
1336
  }
1311
1337
  };
1312
1338
 
1313
- // src/workflows/prepare.ts
1314
- var API_BASE_URL = "https://www.contextgraph.dev";
1315
- async function runPrepare(actionId, options) {
1316
- const credentials = await loadCredentials();
1317
- if (!credentials) {
1318
- console.error("\u274C Not authenticated. Run authentication first.");
1319
- process.exit(1);
1339
+ // src/workspace-setup.ts
1340
+ import { mkdtemp as mkdtemp2, rm as rm2 } from "fs/promises";
1341
+ import { tmpdir as tmpdir2 } from "os";
1342
+ import { join as join4 } from "path";
1343
+
1344
+ // src/workspace-prep.ts
1345
+ import { spawn as spawn2 } from "child_process";
1346
+ import { mkdtemp, rm } from "fs/promises";
1347
+ import { tmpdir } from "os";
1348
+ import { join as join3 } from "path";
1349
+
1350
+ // src/skill-injection.ts
1351
+ import { mkdir as mkdir2, writeFile } from "fs/promises";
1352
+ import { join as join2 } from "path";
1353
+ async function injectSkills(workspacePath, skills) {
1354
+ if (skills.length === 0) {
1355
+ console.log("\u{1F4DA} No skills to inject");
1356
+ return;
1320
1357
  }
1321
- if (isExpired(credentials) || isTokenExpired(credentials.clerkToken)) {
1322
- console.error("\u274C Token expired. Re-authenticate to continue.");
1323
- process.exit(1);
1358
+ console.log(`\u{1F4DA} Injecting ${skills.length} skill(s) into workspace...`);
1359
+ for (const skill of skills) {
1360
+ try {
1361
+ const skillDir = join2(workspacePath, ".claude", "skills", skill.name);
1362
+ await mkdir2(skillDir, { recursive: true });
1363
+ const triggerSection = skill.trigger ? `trigger: |
1364
+ ${skill.trigger.split("\n").map((line) => ` ${line}`).join("\n")}
1365
+ ` : "";
1366
+ const skillContent = `---
1367
+ name: ${skill.name}
1368
+ description: ${skill.description}
1369
+ ${triggerSection}---
1370
+
1371
+ ${skill.content}
1372
+ `;
1373
+ const skillFilePath = join2(skillDir, "SKILL.md");
1374
+ await writeFile(skillFilePath, skillContent, "utf-8");
1375
+ console.log(` \u2705 Injected skill: ${skill.name}`);
1376
+ } catch (error) {
1377
+ console.error(` \u274C Failed to inject skill "${skill.name}":`, error);
1378
+ throw error;
1379
+ }
1324
1380
  }
1325
- const logTransport = new LogTransportService(API_BASE_URL, credentials.clerkToken);
1326
- let runId;
1327
- let heartbeatManager;
1328
- let logBuffer;
1381
+ console.log(`\u2705 Skills injected successfully`);
1382
+ }
1383
+
1384
+ // src/skills-library-fetch.ts
1385
+ var API_BASE_URL = "https://www.contextgraph.dev";
1386
+ async function fetchSkillsLibrary(options) {
1387
+ const { authToken, runId } = options;
1329
1388
  try {
1330
- console.log("[Log Streaming] Creating run for prepare phase...");
1331
- runId = await logTransport.createRun(actionId, "prepare", {
1332
- startingCommit: options?.startingCommit
1333
- });
1334
- console.log(`[Log Streaming] Run created: ${runId}`);
1335
- console.log(`Fetching preparation instructions for action ${actionId}...
1336
- `);
1389
+ const url = new URL(`${API_BASE_URL}/api/skills/library`);
1390
+ if (runId) {
1391
+ url.searchParams.set("runId", runId);
1392
+ }
1337
1393
  const response = await fetchWithRetry(
1338
- `${API_BASE_URL}/api/prompts/prepare`,
1394
+ url.toString(),
1339
1395
  {
1340
- method: "POST",
1341
1396
  headers: {
1342
- "Authorization": `Bearer ${credentials.clerkToken}`,
1397
+ "x-authorization": `Bearer ${authToken}`,
1343
1398
  "Content-Type": "application/json"
1344
- },
1345
- body: JSON.stringify({ actionId, runId })
1399
+ }
1400
+ },
1401
+ {
1402
+ maxRetries: 2,
1403
+ baseDelayMs: 500
1346
1404
  }
1347
1405
  );
1348
1406
  if (!response.ok) {
1349
- const errorText = await response.text();
1350
- throw new Error(`Failed to fetch prepare prompt: ${response.statusText}
1351
- ${errorText}`);
1352
- }
1353
- const { prompt } = await response.json();
1354
- await logTransport.updateRunState("preparing");
1355
- heartbeatManager = new HeartbeatManager(API_BASE_URL, credentials.clerkToken, runId);
1356
- heartbeatManager.start();
1357
- console.log("[Log Streaming] Heartbeat started");
1358
- logBuffer = new LogBuffer(logTransport);
1359
- logBuffer.start();
1360
- console.log("Spawning Claude for preparation...\n");
1361
- const claudeResult = await executeClaude({
1362
- prompt,
1363
- cwd: options?.cwd || process.cwd(),
1364
- authToken: credentials.clerkToken,
1365
- model: options?.model || "claude-opus-4-5-20251101",
1366
- onLogEvent: (event) => {
1367
- logBuffer.push(event);
1368
- }
1407
+ console.warn(`\u26A0\uFE0F Skills library API returned ${response.status}: ${response.statusText}`);
1408
+ return [];
1409
+ }
1410
+ const data = await response.json();
1411
+ if (!data.success || !data.data?.skills) {
1412
+ console.warn("\u26A0\uFE0F Skills library API returned unexpected format");
1413
+ return [];
1414
+ }
1415
+ const skills = data.data.skills.map((skill) => {
1416
+ const name = skill.filename.replace(/\.md$/, "");
1417
+ return {
1418
+ name,
1419
+ description: skill.description,
1420
+ trigger: skill.trigger,
1421
+ content: skill.content
1422
+ };
1369
1423
  });
1370
- if (claudeResult.exitCode === 0) {
1371
- await logTransport.finishRun("success", {
1372
- exitCode: claudeResult.exitCode,
1373
- cost: claudeResult.cost,
1374
- usage: claudeResult.usage
1375
- });
1376
- console.log("\n\u2705 Preparation complete");
1377
- } else {
1378
- await logTransport.finishRun("error", {
1379
- exitCode: claudeResult.exitCode,
1380
- errorMessage: `Claude preparation failed with exit code ${claudeResult.exitCode}`
1381
- });
1382
- console.error(`
1383
- \u274C Claude preparation failed with exit code ${claudeResult.exitCode}`);
1384
- process.exit(1);
1385
- }
1424
+ return skills;
1386
1425
  } catch (error) {
1387
- if (runId) {
1388
- try {
1389
- await logTransport.finishRun("error", {
1390
- errorMessage: error instanceof Error ? error.message : String(error)
1391
- });
1392
- } catch (stateError) {
1393
- console.error("[Log Streaming] Failed to update run state:", stateError);
1394
- }
1395
- }
1396
- throw error;
1397
- } finally {
1398
- if (heartbeatManager) {
1399
- heartbeatManager.stop();
1400
- console.log("[Log Streaming] Heartbeat stopped");
1401
- }
1402
- if (logBuffer) {
1403
- await logBuffer.stop();
1404
- console.log("[Log Streaming] Logs flushed");
1405
- }
1426
+ console.warn("\u26A0\uFE0F Failed to fetch skills library:", error instanceof Error ? error.message : error);
1427
+ return [];
1406
1428
  }
1407
1429
  }
1408
1430
 
1409
- // src/workflows/execute.ts
1431
+ // src/workspace-prep.ts
1410
1432
  var API_BASE_URL2 = "https://www.contextgraph.dev";
1411
- async function runExecute(actionId, options) {
1412
- const credentials = await loadCredentials();
1413
- if (!credentials) {
1414
- console.error("\u274C Not authenticated. Run authentication first.");
1415
- process.exit(1);
1433
+ async function fetchGitHubCredentials(authToken) {
1434
+ const response = await fetchWithRetry(`${API_BASE_URL2}/api/cli/credentials`, {
1435
+ headers: {
1436
+ "x-authorization": `Bearer ${authToken}`,
1437
+ "Content-Type": "application/json"
1438
+ }
1439
+ });
1440
+ if (response.status === 401) {
1441
+ throw new Error("Authentication failed. Please re-authenticate.");
1416
1442
  }
1417
- if (isExpired(credentials) || isTokenExpired(credentials.clerkToken)) {
1418
- console.error("\u274C Token expired. Re-authenticate to continue.");
1419
- process.exit(1);
1443
+ if (response.status === 404) {
1444
+ throw new Error(
1445
+ "GitHub not connected. Please connect your GitHub account at https://contextgraph.dev/settings."
1446
+ );
1420
1447
  }
1421
- const logTransport = new LogTransportService(API_BASE_URL2, credentials.clerkToken);
1422
- let runId;
1423
- let heartbeatManager;
1424
- let logBuffer;
1425
- try {
1426
- console.log("[Log Streaming] Creating run...");
1427
- runId = await logTransport.createRun(actionId, "execute", {
1428
- startingCommit: options?.startingCommit
1448
+ if (!response.ok) {
1449
+ const errorText = await response.text();
1450
+ throw new Error(`Failed to fetch GitHub credentials: ${response.statusText}
1451
+ ${errorText}`);
1452
+ }
1453
+ return response.json();
1454
+ }
1455
+ function runGitCommand(args, cwd) {
1456
+ return new Promise((resolve, reject) => {
1457
+ const proc = spawn2("git", args, { cwd });
1458
+ let stdout = "";
1459
+ let stderr = "";
1460
+ proc.stdout.on("data", (data) => {
1461
+ stdout += data.toString();
1429
1462
  });
1430
- console.log(`[Log Streaming] Run created: ${runId}`);
1431
- console.log(`Fetching execution instructions for action ${actionId}...
1432
- `);
1433
- const response = await fetchWithRetry(
1434
- `${API_BASE_URL2}/api/prompts/execute`,
1435
- {
1436
- method: "POST",
1437
- headers: {
1438
- "Authorization": `Bearer ${credentials.clerkToken}`,
1439
- "Content-Type": "application/json"
1440
- },
1441
- body: JSON.stringify({ actionId, runId })
1463
+ proc.stderr.on("data", (data) => {
1464
+ stderr += data.toString();
1465
+ });
1466
+ proc.on("close", (code) => {
1467
+ if (code === 0) {
1468
+ resolve({ stdout, stderr });
1469
+ } else {
1470
+ reject(new Error(`git ${args[0]} failed (exit ${code}): ${stderr || stdout}`));
1442
1471
  }
1443
- );
1444
- if (!response.ok) {
1445
- const errorText = await response.text();
1446
- throw new Error(`Failed to fetch execute prompt: ${response.statusText}
1447
- ${errorText}`);
1472
+ });
1473
+ proc.on("error", (err) => {
1474
+ reject(new Error(`Failed to spawn git: ${err.message}`));
1475
+ });
1476
+ });
1477
+ }
1478
+ function buildAuthenticatedUrl(repoUrl, token) {
1479
+ if (repoUrl.startsWith("https://github.com/")) {
1480
+ return repoUrl.replace("https://github.com/", `https://${token}@github.com/`);
1481
+ }
1482
+ if (repoUrl.startsWith("https://github.com")) {
1483
+ return repoUrl.replace("https://github.com", `https://${token}@github.com`);
1484
+ }
1485
+ return repoUrl;
1486
+ }
1487
+ async function prepareWorkspace(repoUrl, options) {
1488
+ const { branch, authToken, runId, skipSkills } = options;
1489
+ const credentials = await fetchGitHubCredentials(authToken);
1490
+ const workspacePath = await mkdtemp(join3(tmpdir(), "cg-workspace-"));
1491
+ const cleanup = async () => {
1492
+ try {
1493
+ await rm(workspacePath, { recursive: true, force: true });
1494
+ } catch (error) {
1495
+ console.error(`Warning: Failed to cleanup workspace at ${workspacePath}:`, error);
1448
1496
  }
1449
- const { prompt } = await response.json();
1450
- await logTransport.updateRunState("executing");
1451
- heartbeatManager = new HeartbeatManager(API_BASE_URL2, credentials.clerkToken, runId);
1452
- heartbeatManager.start();
1453
- console.log("[Log Streaming] Heartbeat started");
1454
- logBuffer = new LogBuffer(logTransport);
1455
- logBuffer.start();
1456
- console.log("Spawning Claude for execution...\n");
1457
- const claudeResult = await executeClaude({
1458
- prompt,
1459
- cwd: options?.cwd || process.cwd(),
1460
- authToken: credentials.clerkToken,
1461
- ...options?.model ? { model: options.model } : {},
1462
- onLogEvent: (event) => {
1463
- logBuffer.push(event);
1497
+ };
1498
+ try {
1499
+ const cloneUrl = buildAuthenticatedUrl(repoUrl, credentials.githubToken);
1500
+ console.log(`\u{1F4C2} Cloning ${repoUrl}`);
1501
+ console.log(` \u2192 ${workspacePath}`);
1502
+ await runGitCommand(["clone", cloneUrl, workspacePath]);
1503
+ console.log(`\u2705 Repository cloned`);
1504
+ if (credentials.githubUsername) {
1505
+ await runGitCommand(["config", "user.name", credentials.githubUsername], workspacePath);
1506
+ }
1507
+ if (credentials.githubEmail) {
1508
+ await runGitCommand(["config", "user.email", credentials.githubEmail], workspacePath);
1509
+ }
1510
+ if (branch) {
1511
+ const { stdout } = await runGitCommand(
1512
+ ["ls-remote", "--heads", "origin", branch],
1513
+ workspacePath
1514
+ );
1515
+ const branchExists = stdout.trim().length > 0;
1516
+ if (branchExists) {
1517
+ console.log(`\u{1F33F} Checking out branch: ${branch}`);
1518
+ await runGitCommand(["checkout", branch], workspacePath);
1519
+ } else {
1520
+ console.log(`\u{1F331} Creating new branch: ${branch}`);
1521
+ await runGitCommand(["checkout", "-b", branch], workspacePath);
1464
1522
  }
1465
- });
1466
- if (claudeResult.exitCode === 0) {
1467
- await logTransport.finishRun("success", {
1468
- exitCode: claudeResult.exitCode,
1469
- cost: claudeResult.cost,
1470
- usage: claudeResult.usage
1471
- });
1472
- console.log("\n\u2705 Execution complete");
1473
- } else {
1474
- await logTransport.finishRun("error", {
1475
- exitCode: claudeResult.exitCode,
1476
- errorMessage: `Claude execution failed with exit code ${claudeResult.exitCode}`
1477
- });
1478
- throw new Error(`Claude execution failed with exit code ${claudeResult.exitCode}`);
1479
1523
  }
1480
- } catch (error) {
1481
- if (runId) {
1524
+ const { stdout: commitHash } = await runGitCommand(["rev-parse", "HEAD"], workspacePath);
1525
+ const startingCommit = commitHash.trim();
1526
+ console.log("");
1527
+ if (skipSkills) {
1528
+ console.log("\u{1F4DA} Skipping skill injection (--no-skills flag)");
1529
+ } else {
1482
1530
  try {
1483
- await logTransport.finishRun("error", {
1484
- errorMessage: error instanceof Error ? error.message : String(error)
1485
- });
1486
- } catch (stateError) {
1487
- console.error("[Log Streaming] Failed to update run state:", stateError);
1531
+ const librarySkills = await fetchSkillsLibrary({ authToken, runId });
1532
+ if (librarySkills.length > 0) {
1533
+ await injectSkills(workspacePath, librarySkills);
1534
+ } else {
1535
+ console.log("\u{1F4DA} No skills to inject (empty library)");
1536
+ }
1537
+ } catch (skillError) {
1538
+ console.warn("\u26A0\uFE0F Skill injection failed (agent will continue):", skillError);
1488
1539
  }
1489
1540
  }
1541
+ return { path: workspacePath, startingCommit, cleanup };
1542
+ } catch (error) {
1543
+ await cleanup();
1490
1544
  throw error;
1491
- } finally {
1492
- if (heartbeatManager) {
1493
- heartbeatManager.stop();
1494
- console.log("[Log Streaming] Heartbeat stopped");
1495
- }
1496
- if (logBuffer) {
1497
- await logBuffer.stop();
1498
- console.log("[Log Streaming] Logs flushed");
1499
- }
1500
1545
  }
1501
1546
  }
1502
1547
 
1503
- // src/workflows/agent.ts
1504
- import { randomUUID } from "crypto";
1505
- import { readFileSync } from "fs";
1506
- import { mkdtemp as mkdtemp2, rm as rm2 } from "fs/promises";
1507
- import { tmpdir as tmpdir2 } from "os";
1508
- import { fileURLToPath } from "url";
1509
- import { dirname, join as join3 } from "path";
1510
-
1511
1548
  // package.json
1512
1549
  var package_default = {
1513
1550
  name: "@contextgraph/agent",
1514
- version: "0.4.16",
1551
+ version: "0.4.19",
1515
1552
  description: "Autonomous agent for contextgraph action execution",
1516
1553
  type: "module",
1517
1554
  bin: {
@@ -1619,164 +1656,336 @@ var ApiClient = class {
1619
1656
  async claimNextAction(workerId) {
1620
1657
  const token = await this.getAuthToken();
1621
1658
  const response = await fetchWithRetry(
1622
- `${this.baseUrl}/api/worker/next?token=${encodeURIComponent(token)}`,
1659
+ `${this.baseUrl}/api/worker/next?token=${encodeURIComponent(token)}`,
1660
+ {
1661
+ method: "POST",
1662
+ headers: {
1663
+ "x-authorization": `Bearer ${token}`,
1664
+ "Content-Type": "application/json"
1665
+ },
1666
+ body: JSON.stringify({
1667
+ worker_id: workerId,
1668
+ agent_version: package_default.version
1669
+ })
1670
+ }
1671
+ );
1672
+ if (!response.ok) {
1673
+ const errorText = await response.text();
1674
+ throw new Error(`API error ${response.status}: ${errorText}`);
1675
+ }
1676
+ const result = await response.json();
1677
+ if (!result.success) {
1678
+ throw new Error(result.error || "API returned unsuccessful response");
1679
+ }
1680
+ return result.data;
1681
+ }
1682
+ async releaseClaim(params) {
1683
+ const token = await this.getAuthToken();
1684
+ const response = await fetchWithRetry(
1685
+ `${this.baseUrl}/api/worker/release?token=${encodeURIComponent(token)}`,
1686
+ {
1687
+ method: "POST",
1688
+ headers: {
1689
+ "x-authorization": `Bearer ${token}`,
1690
+ "Content-Type": "application/json"
1691
+ },
1692
+ body: JSON.stringify(params)
1693
+ }
1694
+ );
1695
+ if (!response.ok) {
1696
+ const errorText = await response.text();
1697
+ throw new Error(`API error ${response.status}: ${errorText}`);
1698
+ }
1699
+ const result = await response.json();
1700
+ if (!result.success) {
1701
+ throw new Error(result.error || "API returned unsuccessful response");
1702
+ }
1703
+ }
1704
+ };
1705
+
1706
+ // src/workspace-setup.ts
1707
+ var API_BASE_URL3 = "https://www.contextgraph.dev";
1708
+ async function setupWorkspaceForAction(actionId, options) {
1709
+ const { authToken, phase, startingCommit: startingCommitOverride, skipSkills } = options;
1710
+ let actionDetail = options.actionDetail;
1711
+ if (!actionDetail) {
1712
+ const apiClient2 = new ApiClient();
1713
+ console.log(`Fetching action details for ${actionId}...`);
1714
+ actionDetail = await apiClient2.getActionDetail(actionId);
1715
+ }
1716
+ const logTransport = new LogTransportService(API_BASE_URL3, authToken);
1717
+ console.log(`[Log Streaming] Creating run for ${phase} phase...`);
1718
+ const runId = await logTransport.createRun(actionId, phase, {
1719
+ startingCommit: startingCommitOverride
1720
+ });
1721
+ console.log(`[Log Streaming] Run created: ${runId}`);
1722
+ const repoUrl = actionDetail.resolved_repository_url || actionDetail.repository_url;
1723
+ const branch = actionDetail.resolved_branch || actionDetail.branch;
1724
+ let workspacePath;
1725
+ let cleanup;
1726
+ let startingCommit = startingCommitOverride;
1727
+ if (repoUrl) {
1728
+ const workspace = await prepareWorkspace(repoUrl, {
1729
+ branch: branch || void 0,
1730
+ authToken,
1731
+ runId,
1732
+ skipSkills
1733
+ });
1734
+ workspacePath = workspace.path;
1735
+ cleanup = workspace.cleanup;
1736
+ startingCommit = workspace.startingCommit;
1737
+ } else {
1738
+ console.log(`\u{1F4C2} No repository configured - creating blank workspace`);
1739
+ workspacePath = await mkdtemp2(join4(tmpdir2(), "cg-workspace-"));
1740
+ console.log(` \u2192 ${workspacePath}`);
1741
+ cleanup = async () => {
1742
+ try {
1743
+ await rm2(workspacePath, { recursive: true, force: true });
1744
+ } catch (error) {
1745
+ console.error(`Warning: Failed to cleanup workspace at ${workspacePath}:`, error);
1746
+ }
1747
+ };
1748
+ }
1749
+ return {
1750
+ workspacePath,
1751
+ cleanup,
1752
+ startingCommit,
1753
+ runId,
1754
+ logTransport
1755
+ };
1756
+ }
1757
+
1758
+ // src/workflows/prepare.ts
1759
+ var API_BASE_URL4 = "https://www.contextgraph.dev";
1760
+ async function runPrepare(actionId, options) {
1761
+ const credentials = await loadCredentials();
1762
+ if (!credentials) {
1763
+ console.error("\u274C Not authenticated. Run authentication first.");
1764
+ process.exit(1);
1765
+ }
1766
+ if (isExpired(credentials) || isTokenExpired(credentials.clerkToken)) {
1767
+ console.error("\u274C Token expired. Re-authenticate to continue.");
1768
+ process.exit(1);
1769
+ }
1770
+ let runId = options?.runId;
1771
+ let heartbeatManager;
1772
+ let logBuffer;
1773
+ let workspacePath;
1774
+ let cleanup;
1775
+ let logTransport;
1776
+ try {
1777
+ if (!runId) {
1778
+ const setup = await setupWorkspaceForAction(actionId, {
1779
+ authToken: credentials.clerkToken,
1780
+ phase: "prepare",
1781
+ startingCommit: options?.startingCommit,
1782
+ skipSkills: options?.skipSkills
1783
+ });
1784
+ workspacePath = setup.workspacePath;
1785
+ cleanup = setup.cleanup;
1786
+ runId = setup.runId;
1787
+ logTransport = setup.logTransport;
1788
+ } else {
1789
+ console.log(`[Log Streaming] Using pre-created run: ${runId}`);
1790
+ workspacePath = options?.cwd || process.cwd();
1791
+ logTransport = new LogTransportService(API_BASE_URL4, credentials.clerkToken, runId);
1792
+ }
1793
+ console.log(`Fetching preparation instructions for action ${actionId}...
1794
+ `);
1795
+ const response = await fetchWithRetry(
1796
+ `${API_BASE_URL4}/api/prompts/prepare`,
1623
1797
  {
1624
1798
  method: "POST",
1625
1799
  headers: {
1626
- "x-authorization": `Bearer ${token}`,
1800
+ "Authorization": `Bearer ${credentials.clerkToken}`,
1627
1801
  "Content-Type": "application/json"
1628
1802
  },
1629
- body: JSON.stringify({
1630
- worker_id: workerId,
1631
- agent_version: package_default.version
1632
- })
1803
+ body: JSON.stringify({ actionId, runId })
1633
1804
  }
1634
1805
  );
1635
1806
  if (!response.ok) {
1636
1807
  const errorText = await response.text();
1637
- throw new Error(`API error ${response.status}: ${errorText}`);
1808
+ throw new Error(`Failed to fetch prepare prompt: ${response.statusText}
1809
+ ${errorText}`);
1638
1810
  }
1639
- const result = await response.json();
1640
- if (!result.success) {
1641
- throw new Error(result.error || "API returned unsuccessful response");
1811
+ const { prompt } = await response.json();
1812
+ await logTransport.updateRunState("preparing");
1813
+ heartbeatManager = new HeartbeatManager(API_BASE_URL4, credentials.clerkToken, runId);
1814
+ heartbeatManager.start();
1815
+ console.log("[Log Streaming] Heartbeat started");
1816
+ logBuffer = new LogBuffer(logTransport);
1817
+ logBuffer.start();
1818
+ console.log("Spawning Claude for preparation...\n");
1819
+ const claudeResult = await executeClaude({
1820
+ prompt,
1821
+ cwd: workspacePath,
1822
+ authToken: credentials.clerkToken,
1823
+ model: options?.model || "claude-opus-4-5-20251101",
1824
+ onLogEvent: (event) => {
1825
+ logBuffer.push(event);
1826
+ }
1827
+ });
1828
+ if (claudeResult.exitCode === 0) {
1829
+ await logTransport.finishRun("success", {
1830
+ exitCode: claudeResult.exitCode,
1831
+ cost: claudeResult.cost,
1832
+ usage: claudeResult.usage
1833
+ });
1834
+ console.log("\n\u2705 Preparation complete");
1835
+ } else {
1836
+ await logTransport.finishRun("error", {
1837
+ exitCode: claudeResult.exitCode,
1838
+ errorMessage: `Claude preparation failed with exit code ${claudeResult.exitCode}`
1839
+ });
1840
+ console.error(`
1841
+ \u274C Claude preparation failed with exit code ${claudeResult.exitCode}`);
1842
+ process.exit(1);
1843
+ }
1844
+ } catch (error) {
1845
+ if (runId) {
1846
+ try {
1847
+ await logTransport.finishRun("error", {
1848
+ errorMessage: error instanceof Error ? error.message : String(error)
1849
+ });
1850
+ } catch (stateError) {
1851
+ console.error("[Log Streaming] Failed to update run state:", stateError);
1852
+ }
1853
+ }
1854
+ throw error;
1855
+ } finally {
1856
+ if (heartbeatManager) {
1857
+ heartbeatManager.stop();
1858
+ console.log("[Log Streaming] Heartbeat stopped");
1859
+ }
1860
+ if (logBuffer) {
1861
+ await logBuffer.stop();
1862
+ console.log("[Log Streaming] Logs flushed");
1863
+ }
1864
+ if (cleanup) {
1865
+ await cleanup();
1642
1866
  }
1643
- return result.data;
1644
1867
  }
1645
- async releaseClaim(params) {
1646
- const token = await this.getAuthToken();
1868
+ }
1869
+
1870
+ // src/workflows/execute.ts
1871
+ var API_BASE_URL5 = "https://www.contextgraph.dev";
1872
+ async function runExecute(actionId, options) {
1873
+ const credentials = await loadCredentials();
1874
+ if (!credentials) {
1875
+ console.error("\u274C Not authenticated. Run authentication first.");
1876
+ process.exit(1);
1877
+ }
1878
+ if (isExpired(credentials) || isTokenExpired(credentials.clerkToken)) {
1879
+ console.error("\u274C Token expired. Re-authenticate to continue.");
1880
+ process.exit(1);
1881
+ }
1882
+ let runId = options?.runId;
1883
+ let heartbeatManager;
1884
+ let logBuffer;
1885
+ let workspacePath;
1886
+ let cleanup;
1887
+ let logTransport;
1888
+ try {
1889
+ if (!runId) {
1890
+ const setup = await setupWorkspaceForAction(actionId, {
1891
+ authToken: credentials.clerkToken,
1892
+ phase: "execute",
1893
+ startingCommit: options?.startingCommit,
1894
+ skipSkills: options?.skipSkills
1895
+ });
1896
+ workspacePath = setup.workspacePath;
1897
+ cleanup = setup.cleanup;
1898
+ runId = setup.runId;
1899
+ logTransport = setup.logTransport;
1900
+ } else {
1901
+ console.log(`[Log Streaming] Using pre-created run: ${runId}`);
1902
+ workspacePath = options?.cwd || process.cwd();
1903
+ logTransport = new LogTransportService(API_BASE_URL5, credentials.clerkToken, runId);
1904
+ }
1905
+ console.log(`Fetching execution instructions for action ${actionId}...
1906
+ `);
1647
1907
  const response = await fetchWithRetry(
1648
- `${this.baseUrl}/api/worker/release?token=${encodeURIComponent(token)}`,
1908
+ `${API_BASE_URL5}/api/prompts/execute`,
1649
1909
  {
1650
1910
  method: "POST",
1651
1911
  headers: {
1652
- "x-authorization": `Bearer ${token}`,
1912
+ "Authorization": `Bearer ${credentials.clerkToken}`,
1653
1913
  "Content-Type": "application/json"
1654
1914
  },
1655
- body: JSON.stringify(params)
1915
+ body: JSON.stringify({ actionId, runId })
1656
1916
  }
1657
1917
  );
1658
1918
  if (!response.ok) {
1659
1919
  const errorText = await response.text();
1660
- throw new Error(`API error ${response.status}: ${errorText}`);
1661
- }
1662
- const result = await response.json();
1663
- if (!result.success) {
1664
- throw new Error(result.error || "API returned unsuccessful response");
1665
- }
1666
- }
1667
- };
1668
-
1669
- // src/workspace-prep.ts
1670
- import { spawn as spawn2 } from "child_process";
1671
- import { mkdtemp, rm } from "fs/promises";
1672
- import { tmpdir } from "os";
1673
- import { join as join2 } from "path";
1674
- var API_BASE_URL3 = "https://www.contextgraph.dev";
1675
- async function fetchGitHubCredentials(authToken) {
1676
- const response = await fetchWithRetry(`${API_BASE_URL3}/api/cli/credentials`, {
1677
- headers: {
1678
- "x-authorization": `Bearer ${authToken}`,
1679
- "Content-Type": "application/json"
1680
- }
1681
- });
1682
- if (response.status === 401) {
1683
- throw new Error("Authentication failed. Please re-authenticate.");
1684
- }
1685
- if (response.status === 404) {
1686
- throw new Error(
1687
- "GitHub not connected. Please connect your GitHub account at https://contextgraph.dev/settings."
1688
- );
1689
- }
1690
- if (!response.ok) {
1691
- const errorText = await response.text();
1692
- throw new Error(`Failed to fetch GitHub credentials: ${response.statusText}
1920
+ throw new Error(`Failed to fetch execute prompt: ${response.statusText}
1693
1921
  ${errorText}`);
1694
- }
1695
- return response.json();
1696
- }
1697
- function runGitCommand(args, cwd) {
1698
- return new Promise((resolve, reject) => {
1699
- const proc = spawn2("git", args, { cwd });
1700
- let stdout = "";
1701
- let stderr = "";
1702
- proc.stdout.on("data", (data) => {
1703
- stdout += data.toString();
1704
- });
1705
- proc.stderr.on("data", (data) => {
1706
- stderr += data.toString();
1707
- });
1708
- proc.on("close", (code) => {
1709
- if (code === 0) {
1710
- resolve({ stdout, stderr });
1711
- } else {
1712
- reject(new Error(`git ${args[0]} failed (exit ${code}): ${stderr || stdout}`));
1922
+ }
1923
+ const { prompt } = await response.json();
1924
+ await logTransport.updateRunState("executing");
1925
+ heartbeatManager = new HeartbeatManager(API_BASE_URL5, credentials.clerkToken, runId);
1926
+ heartbeatManager.start();
1927
+ console.log("[Log Streaming] Heartbeat started");
1928
+ logBuffer = new LogBuffer(logTransport);
1929
+ logBuffer.start();
1930
+ console.log("Spawning Claude for execution...\n");
1931
+ const claudeResult = await executeClaude({
1932
+ prompt,
1933
+ cwd: workspacePath,
1934
+ authToken: credentials.clerkToken,
1935
+ ...options?.model ? { model: options.model } : {},
1936
+ onLogEvent: (event) => {
1937
+ logBuffer.push(event);
1713
1938
  }
1714
1939
  });
1715
- proc.on("error", (err) => {
1716
- reject(new Error(`Failed to spawn git: ${err.message}`));
1717
- });
1718
- });
1719
- }
1720
- function buildAuthenticatedUrl(repoUrl, token) {
1721
- if (repoUrl.startsWith("https://github.com/")) {
1722
- return repoUrl.replace("https://github.com/", `https://${token}@github.com/`);
1723
- }
1724
- if (repoUrl.startsWith("https://github.com")) {
1725
- return repoUrl.replace("https://github.com", `https://${token}@github.com`);
1726
- }
1727
- return repoUrl;
1728
- }
1729
- async function prepareWorkspace(repoUrl, options) {
1730
- const { branch, authToken } = options;
1731
- const credentials = await fetchGitHubCredentials(authToken);
1732
- const workspacePath = await mkdtemp(join2(tmpdir(), "cg-workspace-"));
1733
- const cleanup = async () => {
1734
- try {
1735
- await rm(workspacePath, { recursive: true, force: true });
1736
- } catch (error) {
1737
- console.error(`Warning: Failed to cleanup workspace at ${workspacePath}:`, error);
1738
- }
1739
- };
1740
- try {
1741
- const cloneUrl = buildAuthenticatedUrl(repoUrl, credentials.githubToken);
1742
- console.log(`\u{1F4C2} Cloning ${repoUrl}`);
1743
- console.log(` \u2192 ${workspacePath}`);
1744
- await runGitCommand(["clone", cloneUrl, workspacePath]);
1745
- console.log(`\u2705 Repository cloned`);
1746
- if (credentials.githubUsername) {
1747
- await runGitCommand(["config", "user.name", credentials.githubUsername], workspacePath);
1748
- }
1749
- if (credentials.githubEmail) {
1750
- await runGitCommand(["config", "user.email", credentials.githubEmail], workspacePath);
1940
+ if (claudeResult.exitCode === 0) {
1941
+ await logTransport.finishRun("success", {
1942
+ exitCode: claudeResult.exitCode,
1943
+ cost: claudeResult.cost,
1944
+ usage: claudeResult.usage
1945
+ });
1946
+ console.log("\n\u2705 Execution complete");
1947
+ } else {
1948
+ await logTransport.finishRun("error", {
1949
+ exitCode: claudeResult.exitCode,
1950
+ errorMessage: `Claude execution failed with exit code ${claudeResult.exitCode}`
1951
+ });
1952
+ throw new Error(`Claude execution failed with exit code ${claudeResult.exitCode}`);
1751
1953
  }
1752
- if (branch) {
1753
- const { stdout } = await runGitCommand(
1754
- ["ls-remote", "--heads", "origin", branch],
1755
- workspacePath
1756
- );
1757
- const branchExists = stdout.trim().length > 0;
1758
- if (branchExists) {
1759
- console.log(`\u{1F33F} Checking out branch: ${branch}`);
1760
- await runGitCommand(["checkout", branch], workspacePath);
1761
- } else {
1762
- console.log(`\u{1F331} Creating new branch: ${branch}`);
1763
- await runGitCommand(["checkout", "-b", branch], workspacePath);
1954
+ } catch (error) {
1955
+ if (runId) {
1956
+ try {
1957
+ await logTransport.finishRun("error", {
1958
+ errorMessage: error instanceof Error ? error.message : String(error)
1959
+ });
1960
+ } catch (stateError) {
1961
+ console.error("[Log Streaming] Failed to update run state:", stateError);
1764
1962
  }
1765
1963
  }
1766
- const { stdout: commitHash } = await runGitCommand(["rev-parse", "HEAD"], workspacePath);
1767
- const startingCommit = commitHash.trim();
1768
- return { path: workspacePath, startingCommit, cleanup };
1769
- } catch (error) {
1770
- await cleanup();
1771
1964
  throw error;
1965
+ } finally {
1966
+ if (heartbeatManager) {
1967
+ heartbeatManager.stop();
1968
+ console.log("[Log Streaming] Heartbeat stopped");
1969
+ }
1970
+ if (logBuffer) {
1971
+ await logBuffer.stop();
1972
+ console.log("[Log Streaming] Logs flushed");
1973
+ }
1974
+ if (cleanup) {
1975
+ await cleanup();
1976
+ }
1772
1977
  }
1773
1978
  }
1774
1979
 
1775
1980
  // src/workflows/agent.ts
1981
+ import { randomUUID } from "crypto";
1982
+ import { readFileSync } from "fs";
1983
+ import { fileURLToPath } from "url";
1984
+ import { dirname, join as join5 } from "path";
1776
1985
  var __filename2 = fileURLToPath(import.meta.url);
1777
1986
  var __dirname2 = dirname(__filename2);
1778
1987
  var packageJson = JSON.parse(
1779
- readFileSync(join3(__dirname2, "../package.json"), "utf-8")
1988
+ readFileSync(join5(__dirname2, "../package.json"), "utf-8")
1780
1989
  );
1781
1990
  var INITIAL_POLL_INTERVAL = parseInt(process.env.WORKER_INITIAL_POLL_INTERVAL || "2000", 10);
1782
1991
  var MAX_POLL_INTERVAL = parseInt(process.env.WORKER_MAX_POLL_INTERVAL || "5000", 10);
@@ -1946,35 +2155,24 @@ async function runLocalAgent(options) {
1946
2155
  continue;
1947
2156
  }
1948
2157
  console.log(`Working: ${actionDetail.title}`);
1949
- const repoUrl = actionDetail.resolved_repository_url || actionDetail.repository_url;
1950
- const branch = actionDetail.resolved_branch || actionDetail.branch;
1951
2158
  let workspacePath;
1952
2159
  let cleanup;
1953
2160
  let startingCommit;
1954
- const needsRepo = repoUrl;
2161
+ let runId;
1955
2162
  try {
1956
- if (needsRepo) {
1957
- const workspace = await prepareWorkspace(repoUrl, {
1958
- branch: branch || void 0,
1959
- authToken: credentials.clerkToken
1960
- });
1961
- workspacePath = workspace.path;
1962
- cleanup = workspace.cleanup;
1963
- startingCommit = workspace.startingCommit;
1964
- } else {
1965
- console.log(`\u{1F4C2} No repository configured - creating blank workspace`);
1966
- workspacePath = await mkdtemp2(join3(tmpdir2(), "cg-workspace-"));
1967
- console.log(` \u2192 ${workspacePath}`);
1968
- cleanup = async () => {
1969
- try {
1970
- await rm2(workspacePath, { recursive: true, force: true });
1971
- } catch (error) {
1972
- console.error(`Warning: Failed to cleanup workspace at ${workspacePath}:`, error);
1973
- }
1974
- };
1975
- }
2163
+ const setup = await setupWorkspaceForAction(actionDetail.id, {
2164
+ authToken: credentials.clerkToken,
2165
+ phase,
2166
+ actionDetail,
2167
+ // Pass pre-fetched action detail to avoid redundant API call
2168
+ skipSkills: options?.skipSkills
2169
+ });
2170
+ workspacePath = setup.workspacePath;
2171
+ cleanup = setup.cleanup;
2172
+ startingCommit = setup.startingCommit;
2173
+ runId = setup.runId;
1976
2174
  if (phase === "prepare") {
1977
- await runPrepare(actionDetail.id, { cwd: workspacePath, startingCommit, model: options?.forceModel });
2175
+ await runPrepare(actionDetail.id, { cwd: workspacePath, startingCommit, model: options?.forceModel, runId });
1978
2176
  stats.prepared++;
1979
2177
  if (currentClaim && apiClient) {
1980
2178
  try {
@@ -1991,7 +2189,7 @@ async function runLocalAgent(options) {
1991
2189
  continue;
1992
2190
  }
1993
2191
  try {
1994
- await runExecute(actionDetail.id, { cwd: workspacePath, startingCommit, model: options?.forceModel });
2192
+ await runExecute(actionDetail.id, { cwd: workspacePath, startingCommit, model: options?.forceModel, runId });
1995
2193
  stats.executed++;
1996
2194
  console.log(`Completed: ${actionDetail.title}`);
1997
2195
  } catch (executeError) {
@@ -2040,14 +2238,15 @@ async function runLocalAgent(options) {
2040
2238
  var __filename3 = fileURLToPath2(import.meta.url);
2041
2239
  var __dirname3 = dirname2(__filename3);
2042
2240
  var packageJson2 = JSON.parse(
2043
- readFileSync2(join4(__dirname3, "../package.json"), "utf-8")
2241
+ readFileSync2(join6(__dirname3, "../package.json"), "utf-8")
2044
2242
  );
2045
2243
  var program = new Command();
2046
2244
  program.name("contextgraph-agent").description("Autonomous agent for contextgraph action execution").version(packageJson2.version);
2047
- program.command("run").description("Run continuous worker loop (claims and executes actions until Ctrl+C)").option("--force-haiku", "Force all workflows to use claude-haiku-4-5 instead of default models").action(async (options) => {
2245
+ program.command("run").description("Run continuous worker loop (claims and executes actions until Ctrl+C)").option("--force-haiku", "Force all workflows to use claude-haiku-4-5 instead of default models").option("--skip-skills", "Skip skill injection (for testing)").action(async (options) => {
2048
2246
  try {
2049
2247
  await runLocalAgent({
2050
- forceModel: options.forceHaiku ? "claude-haiku-4-5-20251001" : void 0
2248
+ forceModel: options.forceHaiku ? "claude-haiku-4-5-20251001" : void 0,
2249
+ skipSkills: options.skipSkills
2051
2250
  });
2052
2251
  } catch (error) {
2053
2252
  if (error instanceof Error) {
@@ -2070,17 +2269,17 @@ program.command("auth").description("Authenticate with contextgraph.dev").action
2070
2269
  process.exit(1);
2071
2270
  }
2072
2271
  });
2073
- program.command("prepare").argument("<action-id>", "Action ID to prepare").description("Prepare a single action").action(async (actionId) => {
2272
+ program.command("prepare").argument("<action-id>", "Action ID to prepare").description("Prepare a single action").option("--skip-skills", "Skip skill injection (for testing)").action(async (actionId, options) => {
2074
2273
  try {
2075
- await runPrepare(actionId);
2274
+ await runPrepare(actionId, { skipSkills: options.skipSkills });
2076
2275
  } catch (error) {
2077
2276
  console.error("Error preparing action:", error instanceof Error ? error.message : error);
2078
2277
  process.exit(1);
2079
2278
  }
2080
2279
  });
2081
- program.command("execute").argument("<action-id>", "Action ID to execute").description("Execute a single action").action(async (actionId) => {
2280
+ program.command("execute").argument("<action-id>", "Action ID to execute").description("Execute a single action").option("--skip-skills", "Skip skill injection (for testing)").action(async (actionId, options) => {
2082
2281
  try {
2083
- await runExecute(actionId);
2282
+ await runExecute(actionId, { skipSkills: options.skipSkills });
2084
2283
  } catch (error) {
2085
2284
  console.error("Error executing action:", error instanceof Error ? error.message : error);
2086
2285
  process.exit(1);