@versatly/workgraph 0.2.0 → 0.3.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.
@@ -4,28 +4,32 @@ var __export = (target, all) => {
4
4
  __defProp(target, name, { get: all[name], enumerable: true });
5
5
  };
6
6
 
7
- // src/types.ts
8
- var THREAD_STATUS_TRANSITIONS = {
9
- open: ["active", "cancelled"],
10
- active: ["blocked", "done", "cancelled", "open"],
11
- blocked: ["active", "cancelled"],
12
- done: [],
13
- cancelled: ["open"]
14
- };
7
+ // src/mcp-server.ts
8
+ var mcp_server_exports = {};
9
+ __export(mcp_server_exports, {
10
+ createWorkgraphMcpServer: () => createWorkgraphMcpServer,
11
+ startWorkgraphMcpServer: () => startWorkgraphMcpServer
12
+ });
13
+ import { McpServer, ResourceTemplate } from "@modelcontextprotocol/sdk/server/mcp.js";
14
+ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
15
+ import { z } from "zod";
15
16
 
16
- // src/registry.ts
17
- var registry_exports = {};
18
- __export(registry_exports, {
19
- defineType: () => defineType,
20
- extendType: () => extendType,
21
- getType: () => getType,
22
- listTypes: () => listTypes,
23
- loadRegistry: () => loadRegistry,
24
- registryPath: () => registryPath,
25
- saveRegistry: () => saveRegistry
17
+ // src/dispatch.ts
18
+ var dispatch_exports = {};
19
+ __export(dispatch_exports, {
20
+ createAndExecuteRun: () => createAndExecuteRun,
21
+ createRun: () => createRun,
22
+ executeRun: () => executeRun,
23
+ followup: () => followup,
24
+ listRuns: () => listRuns,
25
+ logs: () => logs,
26
+ markRun: () => markRun,
27
+ status: () => status,
28
+ stop: () => stop
26
29
  });
27
- import fs2 from "fs";
28
- import path2 from "path";
30
+ import fs6 from "fs";
31
+ import path6 from "path";
32
+ import { randomUUID } from "crypto";
29
33
 
30
34
  // src/ledger.ts
31
35
  var ledger_exports = {};
@@ -359,7 +363,37 @@ function stableStringify(value) {
359
363
  return `{${keys.map((key) => `${JSON.stringify(key)}:${stableStringify(obj[key])}`).join(",")}}`;
360
364
  }
361
365
 
366
+ // src/store.ts
367
+ var store_exports = {};
368
+ __export(store_exports, {
369
+ activeThreads: () => activeThreads,
370
+ blockedThreads: () => blockedThreads,
371
+ create: () => create,
372
+ findByField: () => findByField,
373
+ list: () => list,
374
+ openThreads: () => openThreads,
375
+ read: () => read,
376
+ remove: () => remove,
377
+ threadsInSpace: () => threadsInSpace,
378
+ update: () => update
379
+ });
380
+ import fs5 from "fs";
381
+ import path5 from "path";
382
+ import matter from "gray-matter";
383
+
362
384
  // src/registry.ts
385
+ var registry_exports = {};
386
+ __export(registry_exports, {
387
+ defineType: () => defineType,
388
+ extendType: () => extendType,
389
+ getType: () => getType,
390
+ listTypes: () => listTypes,
391
+ loadRegistry: () => loadRegistry,
392
+ registryPath: () => registryPath,
393
+ saveRegistry: () => saveRegistry
394
+ });
395
+ import fs2 from "fs";
396
+ import path2 from "path";
363
397
  var REGISTRY_FILE = ".workgraph/registry.json";
364
398
  var CURRENT_VERSION = 1;
365
399
  var BUILT_IN_TYPES = [
@@ -690,24 +724,6 @@ function ensureBuiltIns(registry) {
690
724
  return registry;
691
725
  }
692
726
 
693
- // src/store.ts
694
- var store_exports = {};
695
- __export(store_exports, {
696
- activeThreads: () => activeThreads,
697
- blockedThreads: () => blockedThreads,
698
- create: () => create,
699
- findByField: () => findByField,
700
- list: () => list,
701
- openThreads: () => openThreads,
702
- read: () => read,
703
- remove: () => remove,
704
- threadsInSpace: () => threadsInSpace,
705
- update: () => update
706
- });
707
- import fs5 from "fs";
708
- import path5 from "path";
709
- import matter from "gray-matter";
710
-
711
727
  // src/graph.ts
712
728
  var graph_exports = {};
713
729
  __export(graph_exports, {
@@ -1270,6 +1286,81 @@ function validateRefValue(workspacePath, rawRef, allowedTypes) {
1270
1286
  return { ok: true };
1271
1287
  }
1272
1288
 
1289
+ // src/orientation.ts
1290
+ var orientation_exports = {};
1291
+ __export(orientation_exports, {
1292
+ brief: () => brief,
1293
+ checkpoint: () => checkpoint,
1294
+ intake: () => intake,
1295
+ statusSnapshot: () => statusSnapshot
1296
+ });
1297
+
1298
+ // src/query.ts
1299
+ var query_exports = {};
1300
+ __export(query_exports, {
1301
+ keywordSearch: () => keywordSearch,
1302
+ queryPrimitives: () => queryPrimitives
1303
+ });
1304
+ function queryPrimitives(workspacePath, filters = {}) {
1305
+ const typeNames = filters.type ? [filters.type] : listTypes(workspacePath).map((type) => type.name);
1306
+ const all = typeNames.flatMap((typeName) => list(workspacePath, typeName));
1307
+ const matched = all.filter((instance) => matchesFilters(instance, filters));
1308
+ const offset = Math.max(0, filters.offset ?? 0);
1309
+ const limited = filters.limit && filters.limit >= 0 ? matched.slice(offset, offset + filters.limit) : matched.slice(offset);
1310
+ return limited;
1311
+ }
1312
+ function keywordSearch(workspacePath, text, filters = {}) {
1313
+ return queryPrimitives(workspacePath, {
1314
+ ...filters,
1315
+ text
1316
+ });
1317
+ }
1318
+ function matchesFilters(instance, filters) {
1319
+ if (filters.status && String(instance.fields.status ?? "") !== filters.status) return false;
1320
+ if (filters.owner && String(instance.fields.owner ?? "") !== filters.owner) return false;
1321
+ if (filters.tag && !hasTag(instance, filters.tag)) return false;
1322
+ if (filters.pathIncludes && !instance.path.includes(filters.pathIncludes)) return false;
1323
+ if (filters.updatedAfter && !isDateOnOrAfter(instance.fields.updated, filters.updatedAfter)) return false;
1324
+ if (filters.updatedBefore && !isDateOnOrBefore(instance.fields.updated, filters.updatedBefore)) return false;
1325
+ if (filters.createdAfter && !isDateOnOrAfter(instance.fields.created, filters.createdAfter)) return false;
1326
+ if (filters.createdBefore && !isDateOnOrBefore(instance.fields.created, filters.createdBefore)) return false;
1327
+ if (filters.text && !containsText(instance, filters.text)) return false;
1328
+ return true;
1329
+ }
1330
+ function hasTag(instance, tag) {
1331
+ const tags = instance.fields.tags;
1332
+ if (!Array.isArray(tags)) return false;
1333
+ return tags.map((value) => String(value)).includes(tag);
1334
+ }
1335
+ function containsText(instance, text) {
1336
+ const haystack = [
1337
+ instance.path,
1338
+ instance.type,
1339
+ stringifyFields(instance.fields),
1340
+ instance.body
1341
+ ].join("\n").toLowerCase();
1342
+ return haystack.includes(text.toLowerCase());
1343
+ }
1344
+ function stringifyFields(fields) {
1345
+ try {
1346
+ return JSON.stringify(fields);
1347
+ } catch {
1348
+ return "";
1349
+ }
1350
+ }
1351
+ function isDateOnOrAfter(value, thresholdIso) {
1352
+ const ts = Date.parse(String(value ?? ""));
1353
+ const threshold = Date.parse(thresholdIso);
1354
+ if (!Number.isFinite(ts) || !Number.isFinite(threshold)) return false;
1355
+ return ts >= threshold;
1356
+ }
1357
+ function isDateOnOrBefore(value, thresholdIso) {
1358
+ const ts = Date.parse(String(value ?? ""));
1359
+ const threshold = Date.parse(thresholdIso);
1360
+ if (!Number.isFinite(ts) || !Number.isFinite(threshold)) return false;
1361
+ return ts <= threshold;
1362
+ }
1363
+
1273
1364
  // src/thread.ts
1274
1365
  var thread_exports = {};
1275
1366
  __export(thread_exports, {
@@ -1289,6 +1380,17 @@ __export(thread_exports, {
1289
1380
  release: () => release,
1290
1381
  unblock: () => unblock
1291
1382
  });
1383
+
1384
+ // src/types.ts
1385
+ var THREAD_STATUS_TRANSITIONS = {
1386
+ open: ["active", "cancelled"],
1387
+ active: ["blocked", "done", "cancelled", "open"],
1388
+ blocked: ["active", "cancelled"],
1389
+ done: [],
1390
+ cancelled: ["open"]
1391
+ };
1392
+
1393
+ // src/thread.ts
1292
1394
  function createThread(workspacePath, title, goal, actor, opts = {}) {
1293
1395
  const normalizedSpace = opts.space ? normalizeWorkspaceRef(opts.space) : void 0;
1294
1396
  const contextRefs = opts.context_refs ?? [];
@@ -1531,632 +1633,50 @@ function normalizeWorkspaceRef(value) {
1531
1633
  return unwrapped.endsWith(".md") ? unwrapped : `${unwrapped}.md`;
1532
1634
  }
1533
1635
 
1534
- // src/workspace.ts
1535
- var workspace_exports = {};
1536
- __export(workspace_exports, {
1537
- initWorkspace: () => initWorkspace,
1538
- isWorkgraphWorkspace: () => isWorkgraphWorkspace,
1539
- workspaceConfigPath: () => workspaceConfigPath
1540
- });
1541
- import fs7 from "fs";
1542
- import path7 from "path";
1543
-
1544
- // src/bases.ts
1545
- var bases_exports = {};
1546
- __export(bases_exports, {
1547
- generateBasesFromPrimitiveRegistry: () => generateBasesFromPrimitiveRegistry,
1548
- primitiveRegistryManifestPath: () => primitiveRegistryManifestPath,
1549
- readPrimitiveRegistryManifest: () => readPrimitiveRegistryManifest,
1550
- syncPrimitiveRegistryManifest: () => syncPrimitiveRegistryManifest
1551
- });
1552
- import fs6 from "fs";
1553
- import path6 from "path";
1554
- import YAML from "yaml";
1555
- var REGISTRY_MANIFEST_FILE = ".workgraph/primitive-registry.yaml";
1556
- var DEFAULT_BASES_DIR = ".workgraph/bases";
1557
- function primitiveRegistryManifestPath(workspacePath) {
1558
- return path6.join(workspacePath, REGISTRY_MANIFEST_FILE);
1559
- }
1560
- function readPrimitiveRegistryManifest(workspacePath) {
1561
- const manifestPath = primitiveRegistryManifestPath(workspacePath);
1562
- if (!fs6.existsSync(manifestPath)) {
1563
- throw new Error(`Primitive registry manifest not found: ${manifestPath}`);
1564
- }
1565
- const raw = fs6.readFileSync(manifestPath, "utf-8");
1566
- return YAML.parse(raw);
1567
- }
1568
- function syncPrimitiveRegistryManifest(workspacePath) {
1569
- const registry = loadRegistry(workspacePath);
1570
- const manifest = {
1571
- version: 1,
1572
- generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
1573
- primitives: Object.values(registry.types).map((primitive) => ({
1574
- name: primitive.name,
1575
- directory: primitive.directory,
1576
- canonical: primitive.builtIn,
1577
- builtIn: primitive.builtIn,
1578
- fields: Object.entries(primitive.fields).map(([name, field]) => ({
1579
- name,
1580
- type: field.type,
1581
- ...field.required ? { required: true } : {},
1582
- ...field.description ? { description: field.description } : {}
1583
- }))
1584
- })).sort((a, b) => a.name.localeCompare(b.name))
1585
- };
1586
- const manifestPath = primitiveRegistryManifestPath(workspacePath);
1587
- ensureDirectory(path6.dirname(manifestPath));
1588
- fs6.writeFileSync(manifestPath, YAML.stringify(manifest), "utf-8");
1589
- return manifest;
1590
- }
1591
- function generateBasesFromPrimitiveRegistry(workspacePath, options = {}) {
1592
- const manifest = readPrimitiveRegistryManifest(workspacePath);
1593
- const includeNonCanonical = options.includeNonCanonical === true;
1594
- const outputDirectory = path6.join(workspacePath, options.outputDirectory ?? DEFAULT_BASES_DIR);
1595
- ensureDirectory(outputDirectory);
1596
- const generated = [];
1597
- const primitives = manifest.primitives.filter(
1598
- (primitive) => includeNonCanonical ? true : primitive.canonical
1599
- );
1600
- for (const primitive of primitives) {
1601
- const relBasePath = `${primitive.name}.base`;
1602
- const absBasePath = path6.join(outputDirectory, relBasePath);
1603
- const content = renderBaseFile(primitive);
1604
- fs6.writeFileSync(absBasePath, content, "utf-8");
1605
- generated.push(path6.relative(workspacePath, absBasePath).replace(/\\/g, "/"));
1606
- }
1636
+ // src/orientation.ts
1637
+ function statusSnapshot(workspacePath) {
1638
+ const threads = list(workspacePath, "thread");
1639
+ const allPrimitives = queryPrimitives(workspacePath);
1640
+ const byType = allPrimitives.reduce((acc, instance) => {
1641
+ acc[instance.type] = (acc[instance.type] ?? 0) + 1;
1642
+ return acc;
1643
+ }, {});
1644
+ const claims = allClaims(workspacePath);
1645
+ const ready = listReadyThreads(workspacePath);
1607
1646
  return {
1608
- outputDirectory: path6.relative(workspacePath, outputDirectory).replace(/\\/g, "/"),
1609
- generated: generated.sort()
1610
- };
1611
- }
1612
- function renderBaseFile(primitive) {
1613
- const columnFields = primitive.fields.map((field) => field.name).filter((name, idx, arr) => arr.indexOf(name) === idx);
1614
- const baseDoc = {
1615
- id: primitive.name,
1616
- title: `${titleCase(primitive.name)} Base`,
1617
- source: {
1618
- type: "folder",
1619
- path: primitive.directory,
1620
- extension: "md"
1647
+ generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
1648
+ threads: {
1649
+ total: threads.length,
1650
+ open: threads.filter((item) => item.fields.status === "open").length,
1651
+ active: threads.filter((item) => item.fields.status === "active").length,
1652
+ blocked: threads.filter((item) => item.fields.status === "blocked").length,
1653
+ done: threads.filter((item) => item.fields.status === "done").length,
1654
+ cancelled: threads.filter((item) => item.fields.status === "cancelled").length,
1655
+ ready: ready.length
1621
1656
  },
1622
- views: [
1623
- {
1624
- id: "table",
1625
- type: "table",
1626
- name: "All",
1627
- columns: ["file.name", ...columnFields]
1628
- }
1629
- ]
1630
- };
1631
- return YAML.stringify(baseDoc);
1632
- }
1633
- function ensureDirectory(dirPath) {
1634
- if (!fs6.existsSync(dirPath)) fs6.mkdirSync(dirPath, { recursive: true });
1635
- }
1636
- function titleCase(value) {
1637
- return value.split(/[-_]/g).filter(Boolean).map((segment) => segment[0].toUpperCase() + segment.slice(1)).join(" ");
1638
- }
1639
-
1640
- // src/workspace.ts
1641
- var WORKGRAPH_CONFIG_FILE = ".workgraph.json";
1642
- function workspaceConfigPath(workspacePath) {
1643
- return path7.join(workspacePath, WORKGRAPH_CONFIG_FILE);
1644
- }
1645
- function isWorkgraphWorkspace(workspacePath) {
1646
- return fs7.existsSync(workspaceConfigPath(workspacePath));
1647
- }
1648
- function initWorkspace(targetPath, options = {}) {
1649
- const resolvedPath = path7.resolve(targetPath);
1650
- const configPath = workspaceConfigPath(resolvedPath);
1651
- if (fs7.existsSync(configPath)) {
1652
- throw new Error(`Workgraph workspace already initialized at ${resolvedPath}`);
1653
- }
1654
- const createdDirectories = [];
1655
- ensureDir(resolvedPath, createdDirectories);
1656
- ensureDir(path7.join(resolvedPath, ".workgraph"), createdDirectories);
1657
- const registry = loadRegistry(resolvedPath);
1658
- saveRegistry(resolvedPath, registry);
1659
- syncPrimitiveRegistryManifest(resolvedPath);
1660
- if (options.createTypeDirs !== false) {
1661
- const types = listTypes(resolvedPath);
1662
- for (const typeDef of types) {
1663
- ensureDir(path7.join(resolvedPath, typeDef.directory), createdDirectories);
1657
+ claims: {
1658
+ active: claims.size
1659
+ },
1660
+ primitives: {
1661
+ total: allPrimitives.length,
1662
+ byType
1664
1663
  }
1665
- }
1666
- const now = (/* @__PURE__ */ new Date()).toISOString();
1667
- const config = {
1668
- name: options.name ?? path7.basename(resolvedPath),
1669
- version: "1.0.0",
1670
- mode: "workgraph",
1671
- createdAt: now,
1672
- updatedAt: now
1673
1664
  };
1674
- fs7.writeFileSync(configPath, JSON.stringify(config, null, 2) + "\n", "utf-8");
1675
- if (options.createReadme !== false) {
1676
- writeReadmeIfMissing(resolvedPath, config.name);
1677
- }
1678
- const bases = options.createBases === false ? { generated: [] } : generateBasesFromPrimitiveRegistry(resolvedPath);
1679
- loadPolicyRegistry(resolvedPath);
1680
- refreshWikiLinkGraphIndex(resolvedPath);
1681
- return {
1682
- workspacePath: resolvedPath,
1683
- configPath,
1684
- config,
1685
- createdDirectories,
1686
- seededTypes: listTypes(resolvedPath).map((t) => t.name),
1687
- generatedBases: bases.generated,
1688
- primitiveRegistryManifestPath: ".workgraph/primitive-registry.yaml"
1689
- };
1690
- }
1691
- function ensureDir(dirPath, createdDirectories) {
1692
- if (fs7.existsSync(dirPath)) return;
1693
- fs7.mkdirSync(dirPath, { recursive: true });
1694
- createdDirectories.push(dirPath);
1695
1665
  }
1696
- function writeReadmeIfMissing(workspacePath, name) {
1697
- const readmePath = path7.join(workspacePath, "README.md");
1698
- if (fs7.existsSync(readmePath)) return;
1699
- const content = `# ${name}
1700
-
1701
- Agent-first workgraph workspace for multi-agent coordination.
1702
-
1703
- ## Quickstart
1704
-
1705
- \`\`\`bash
1706
- workgraph thread list --json
1707
- workgraph thread next --claim --actor agent-a --json
1708
- workgraph ledger show --count 20 --json
1709
- \`\`\`
1710
- `;
1711
- fs7.writeFileSync(readmePath, content, "utf-8");
1712
- }
1713
-
1714
- // src/command-center.ts
1715
- var command_center_exports = {};
1716
- __export(command_center_exports, {
1717
- generateCommandCenter: () => generateCommandCenter
1718
- });
1719
- import fs8 from "fs";
1720
- import path8 from "path";
1721
- function generateCommandCenter(workspacePath, options = {}) {
1722
- const actor = options.actor ?? "system";
1723
- const recentCount = options.recentCount ?? 15;
1724
- const relOutputPath = options.outputPath ?? "Command Center.md";
1725
- const absOutputPath = resolvePathWithinWorkspace(workspacePath, relOutputPath);
1726
- const normalizedOutputPath = path8.relative(workspacePath, absOutputPath).replace(/\\/g, "/");
1727
- const allThreads = list(workspacePath, "thread");
1728
- const openThreads2 = allThreads.filter((thread) => thread.fields.status === "open");
1729
- const activeThreads2 = allThreads.filter((thread) => thread.fields.status === "active");
1730
- const blockedThreads2 = allThreads.filter((thread) => thread.fields.status === "blocked");
1731
- const doneThreads = allThreads.filter((thread) => thread.fields.status === "done");
1732
- const claims = allClaims(workspacePath);
1733
- const recentEvents = recent(workspacePath, recentCount);
1734
- const content = renderCommandCenter({
1666
+ function brief(workspacePath, actor, options = {}) {
1667
+ const myClaims = [...allClaims(workspacePath).entries()].filter(([, owner]) => owner === actor).map(([target]) => read(workspacePath, target)).filter((instance) => instance !== null);
1668
+ const myOpenThreads = queryPrimitives(workspacePath, {
1669
+ type: "thread",
1670
+ owner: actor
1671
+ }).filter((instance) => ["open", "active"].includes(String(instance.fields.status)));
1672
+ return {
1735
1673
  generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
1736
- openThreads: openThreads2,
1737
- activeThreads: activeThreads2,
1738
- blockedThreads: blockedThreads2,
1739
- doneThreads,
1740
- claims: [...claims.entries()].map(([target, owner]) => ({ target, owner })),
1741
- recentEvents
1742
- });
1743
- const parentDir = path8.dirname(absOutputPath);
1744
- if (!fs8.existsSync(parentDir)) fs8.mkdirSync(parentDir, { recursive: true });
1745
- const existed = fs8.existsSync(absOutputPath);
1746
- fs8.writeFileSync(absOutputPath, content, "utf-8");
1747
- append(
1748
- workspacePath,
1749
1674
  actor,
1750
- existed ? "update" : "create",
1751
- normalizedOutputPath,
1752
- "command-center",
1753
- {
1754
- generated: true,
1755
- open_threads: openThreads2.length,
1756
- active_claims: claims.size,
1757
- recent_events: recentEvents.length
1758
- }
1759
- );
1760
- return {
1761
- outputPath: normalizedOutputPath,
1762
- stats: {
1763
- totalThreads: allThreads.length,
1764
- openThreads: openThreads2.length,
1765
- activeThreads: activeThreads2.length,
1766
- blockedThreads: blockedThreads2.length,
1767
- doneThreads: doneThreads.length,
1768
- activeClaims: claims.size,
1769
- recentEvents: recentEvents.length
1770
- },
1771
- content
1772
- };
1773
- }
1774
- function resolvePathWithinWorkspace(workspacePath, outputPath) {
1775
- const base = path8.resolve(workspacePath);
1776
- const resolved = path8.resolve(base, outputPath);
1777
- if (!resolved.startsWith(base + path8.sep) && resolved !== base) {
1778
- throw new Error(`Invalid command-center output path: ${outputPath}`);
1779
- }
1780
- return resolved;
1781
- }
1782
- function renderCommandCenter(input) {
1783
- const header = [
1784
- "# Workgraph Command Center",
1785
- "",
1786
- `Generated: ${input.generatedAt}`,
1787
- ""
1788
- ];
1789
- const statusBlock = [
1790
- "## Thread Status",
1791
- "",
1792
- `- Open: ${input.openThreads.length}`,
1793
- `- Active: ${input.activeThreads.length}`,
1794
- `- Blocked: ${input.blockedThreads.length}`,
1795
- `- Done: ${input.doneThreads.length}`,
1796
- ""
1797
- ];
1798
- const openTable = [
1799
- "## Open Threads",
1800
- "",
1801
- "| Priority | Title | Path |",
1802
- "|---|---|---|",
1803
- ...input.openThreads.length > 0 ? input.openThreads.map((thread) => `| ${String(thread.fields.priority ?? "medium")} | ${String(thread.fields.title ?? "Untitled")} | \`${thread.path}\` |`) : ["| - | None | - |"],
1804
- ""
1805
- ];
1806
- const claimsSection = [
1807
- "## Active Claims",
1808
- "",
1809
- ...input.claims.length > 0 ? input.claims.map((claim2) => `- ${claim2.owner} -> \`${claim2.target}\``) : ["- None"],
1810
- ""
1811
- ];
1812
- const blockedSection = [
1813
- "## Blocked Threads",
1814
- "",
1815
- ...input.blockedThreads.length > 0 ? input.blockedThreads.map((thread) => {
1816
- const deps = Array.isArray(thread.fields.deps) ? thread.fields.deps.join(", ") : "";
1817
- return `- ${String(thread.fields.title ?? thread.path)} (\`${thread.path}\`)${deps ? ` blocked by: ${deps}` : ""}`;
1818
- }) : ["- None"],
1819
- ""
1820
- ];
1821
- const recentSection = [
1822
- "## Recent Ledger Activity",
1823
- "",
1824
- ...input.recentEvents.length > 0 ? input.recentEvents.map((event) => `- ${event.ts} ${event.op} ${event.actor} -> \`${event.target}\``) : ["- No activity"],
1825
- ""
1826
- ];
1827
- return [
1828
- ...header,
1829
- ...statusBlock,
1830
- ...openTable,
1831
- ...claimsSection,
1832
- ...blockedSection,
1833
- ...recentSection
1834
- ].join("\n");
1835
- }
1836
-
1837
- // src/skill.ts
1838
- var skill_exports = {};
1839
- __export(skill_exports, {
1840
- listSkills: () => listSkills,
1841
- loadSkill: () => loadSkill,
1842
- promoteSkill: () => promoteSkill,
1843
- proposeSkill: () => proposeSkill,
1844
- skillDiff: () => skillDiff,
1845
- skillHistory: () => skillHistory,
1846
- writeSkill: () => writeSkill
1847
- });
1848
- import fs9 from "fs";
1849
- import path9 from "path";
1850
- function writeSkill(workspacePath, title, body, actor, options = {}) {
1851
- const slug = skillSlug(title);
1852
- const bundleSkillPath = folderSkillPath(slug);
1853
- const legacyPath = legacySkillPath(slug);
1854
- const existing = read(workspacePath, bundleSkillPath) ?? read(workspacePath, legacyPath);
1855
- const status2 = options.status ?? existing?.fields.status ?? "draft";
1856
- if (existing && options.expectedUpdatedAt) {
1857
- const currentUpdatedAt = String(existing.fields.updated ?? "");
1858
- if (currentUpdatedAt !== options.expectedUpdatedAt) {
1859
- throw new Error(`Concurrent skill update detected for ${existing.path}. Expected updated="${options.expectedUpdatedAt}" but found "${currentUpdatedAt}".`);
1860
- }
1861
- }
1862
- if (!existing) {
1863
- ensureSkillBundleScaffold(workspacePath, slug);
1864
- const created = create(workspacePath, "skill", {
1865
- title,
1866
- owner: options.owner ?? actor,
1867
- version: options.version ?? "0.1.0",
1868
- status: status2,
1869
- distribution: options.distribution ?? "tailscale-shared-vault",
1870
- tailscale_path: options.tailscalePath,
1871
- reviewers: options.reviewers ?? [],
1872
- depends_on: options.dependsOn ?? [],
1873
- tags: options.tags ?? []
1874
- }, body, actor, {
1875
- pathOverride: bundleSkillPath
1876
- });
1877
- writeSkillManifest(workspacePath, slug, created, actor);
1878
- return created;
1879
- }
1880
- const updated = update(workspacePath, existing.path, {
1881
- title,
1882
- owner: options.owner ?? existing.fields.owner ?? actor,
1883
- version: options.version ?? existing.fields.version ?? "0.1.0",
1884
- status: status2,
1885
- distribution: options.distribution ?? existing.fields.distribution ?? "tailscale-shared-vault",
1886
- tailscale_path: options.tailscalePath ?? existing.fields.tailscale_path,
1887
- reviewers: options.reviewers ?? existing.fields.reviewers ?? [],
1888
- depends_on: options.dependsOn ?? existing.fields.depends_on ?? [],
1889
- tags: options.tags ?? existing.fields.tags ?? []
1890
- }, body, actor);
1891
- writeSkillManifest(workspacePath, slug, updated, actor);
1892
- return updated;
1893
- }
1894
- function loadSkill(workspacePath, skillRef) {
1895
- const normalizedCandidates = normalizeSkillRefCandidates(skillRef);
1896
- const skill = normalizedCandidates.map((candidate) => read(workspacePath, candidate)).find((entry) => entry !== null);
1897
- if (!skill) throw new Error(`Skill not found: ${skillRef}`);
1898
- if (skill.type !== "skill") throw new Error(`Target is not a skill primitive: ${skillRef}`);
1899
- return skill;
1900
- }
1901
- function listSkills(workspacePath, options = {}) {
1902
- let skills = list(workspacePath, "skill");
1903
- if (options.status) {
1904
- skills = skills.filter((skill) => skill.fields.status === options.status);
1905
- }
1906
- if (options.updatedSince) {
1907
- const threshold = Date.parse(options.updatedSince);
1908
- if (Number.isFinite(threshold)) {
1909
- skills = skills.filter((skill) => {
1910
- const updatedAt = Date.parse(String(skill.fields.updated ?? ""));
1911
- return Number.isFinite(updatedAt) && updatedAt >= threshold;
1912
- });
1913
- }
1914
- }
1915
- return skills;
1916
- }
1917
- function proposeSkill(workspacePath, skillRef, actor, options = {}) {
1918
- const skill = loadSkill(workspacePath, skillRef);
1919
- const slug = skillSlug(String(skill.fields.title ?? skillRef));
1920
- let proposalThread = options.proposalThread;
1921
- if (!proposalThread && options.createThreadIfMissing !== false) {
1922
- const createdThread = createThread(
1923
- workspacePath,
1924
- `Review skill: ${String(skill.fields.title)}`,
1925
- `Review and approve skill ${skill.path} for activation.`,
1926
- actor,
1927
- {
1928
- priority: "medium",
1929
- space: options.space,
1930
- context_refs: [skill.path]
1931
- }
1932
- );
1933
- proposalThread = createdThread.path;
1934
- }
1935
- const updated = update(workspacePath, skill.path, {
1936
- status: "proposed",
1937
- proposal_thread: proposalThread ?? skill.fields.proposal_thread,
1938
- proposed_at: (/* @__PURE__ */ new Date()).toISOString(),
1939
- reviewers: options.reviewers ?? skill.fields.reviewers ?? []
1940
- }, void 0, actor);
1941
- writeSkillManifest(workspacePath, slug, updated, actor);
1942
- return updated;
1943
- }
1944
- function skillHistory(workspacePath, skillRef, options = {}) {
1945
- const skill = loadSkill(workspacePath, skillRef);
1946
- const entries = historyOf(workspacePath, skill.path);
1947
- if (options.limit && options.limit > 0) {
1948
- return entries.slice(-options.limit);
1949
- }
1950
- return entries;
1951
- }
1952
- function skillDiff(workspacePath, skillRef) {
1953
- const skill = loadSkill(workspacePath, skillRef);
1954
- const entries = historyOf(workspacePath, skill.path).filter((entry) => entry.op === "create" || entry.op === "update");
1955
- const latest = entries.length > 0 ? entries[entries.length - 1] : null;
1956
- const previous = entries.length > 1 ? entries[entries.length - 2] : null;
1957
- const changedFields = Array.isArray(latest?.data?.changed) ? latest.data.changed.map((value) => String(value)) : latest?.op === "create" ? Object.keys(skill.fields) : [];
1958
- return {
1959
- path: skill.path,
1960
- latestEntryTs: latest?.ts ?? null,
1961
- previousEntryTs: previous?.ts ?? null,
1962
- changedFields
1963
- };
1964
- }
1965
- function promoteSkill(workspacePath, skillRef, actor, options = {}) {
1966
- const skill = loadSkill(workspacePath, skillRef);
1967
- const slug = skillSlug(String(skill.fields.title ?? skillRef));
1968
- const currentVersion = String(skill.fields.version ?? "0.1.0");
1969
- const nextVersion = options.version ?? bumpPatchVersion(currentVersion);
1970
- const updated = update(workspacePath, skill.path, {
1971
- status: "active",
1972
- version: nextVersion,
1973
- promoted_at: (/* @__PURE__ */ new Date()).toISOString()
1974
- }, void 0, actor);
1975
- writeSkillManifest(workspacePath, slug, updated, actor);
1976
- return updated;
1977
- }
1978
- function skillSlug(title) {
1979
- return title.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "").slice(0, 80);
1980
- }
1981
- function normalizeSkillRefCandidates(skillRef) {
1982
- const raw = skillRef.trim();
1983
- if (!raw) return [];
1984
- if (raw.includes("/")) {
1985
- const normalized = raw.endsWith(".md") ? raw : `${raw}.md`;
1986
- if (normalized.endsWith("/SKILL.md")) return [normalized];
1987
- if (normalized.endsWith("/SKILL")) return [`${normalized}.md`];
1988
- if (normalized.endsWith(".md")) {
1989
- const noExt = normalized.slice(0, -3);
1990
- return [normalized, `${noExt}/SKILL.md`];
1991
- }
1992
- return [normalized, `${normalized}/SKILL.md`];
1993
- }
1994
- const slug = skillSlug(raw);
1995
- return [folderSkillPath(slug), legacySkillPath(slug)];
1996
- }
1997
- function bumpPatchVersion(version) {
1998
- const match = version.match(/^(\d+)\.(\d+)\.(\d+)$/);
1999
- if (!match) return "0.1.0";
2000
- const major = Number.parseInt(match[1], 10);
2001
- const minor = Number.parseInt(match[2], 10);
2002
- const patch = Number.parseInt(match[3], 10) + 1;
2003
- return `${major}.${minor}.${patch}`;
2004
- }
2005
- function folderSkillPath(slug) {
2006
- return `skills/${slug}/SKILL.md`;
2007
- }
2008
- function legacySkillPath(slug) {
2009
- return `skills/${slug}.md`;
2010
- }
2011
- function ensureSkillBundleScaffold(workspacePath, slug) {
2012
- const skillRoot = path9.join(workspacePath, "skills", slug);
2013
- fs9.mkdirSync(skillRoot, { recursive: true });
2014
- for (const subdir of ["scripts", "examples", "tests", "assets"]) {
2015
- fs9.mkdirSync(path9.join(skillRoot, subdir), { recursive: true });
2016
- }
2017
- }
2018
- function writeSkillManifest(workspacePath, slug, skill, actor) {
2019
- const manifestPath = path9.join(workspacePath, "skills", slug, "skill-manifest.json");
2020
- const dir = path9.dirname(manifestPath);
2021
- fs9.mkdirSync(dir, { recursive: true });
2022
- const manifest = {
2023
- version: 1,
2024
- slug,
2025
- title: String(skill.fields.title ?? slug),
2026
- primitivePath: skill.path,
2027
- owner: String(skill.fields.owner ?? actor),
2028
- skillVersion: String(skill.fields.version ?? "0.1.0"),
2029
- status: String(skill.fields.status ?? "draft"),
2030
- dependsOn: Array.isArray(skill.fields.depends_on) ? skill.fields.depends_on.map((value) => String(value)) : [],
2031
- components: {
2032
- skillDoc: "SKILL.md",
2033
- scriptsDir: "scripts/",
2034
- examplesDir: "examples/",
2035
- testsDir: "tests/",
2036
- assetsDir: "assets/"
2037
- },
2038
- updatedAt: (/* @__PURE__ */ new Date()).toISOString()
2039
- };
2040
- fs9.writeFileSync(manifestPath, JSON.stringify(manifest, null, 2) + "\n", "utf-8");
2041
- }
2042
-
2043
- // src/query.ts
2044
- var query_exports = {};
2045
- __export(query_exports, {
2046
- keywordSearch: () => keywordSearch,
2047
- queryPrimitives: () => queryPrimitives
2048
- });
2049
- function queryPrimitives(workspacePath, filters = {}) {
2050
- const typeNames = filters.type ? [filters.type] : listTypes(workspacePath).map((type) => type.name);
2051
- const all = typeNames.flatMap((typeName) => list(workspacePath, typeName));
2052
- const matched = all.filter((instance) => matchesFilters(instance, filters));
2053
- const offset = Math.max(0, filters.offset ?? 0);
2054
- const limited = filters.limit && filters.limit >= 0 ? matched.slice(offset, offset + filters.limit) : matched.slice(offset);
2055
- return limited;
2056
- }
2057
- function keywordSearch(workspacePath, text, filters = {}) {
2058
- return queryPrimitives(workspacePath, {
2059
- ...filters,
2060
- text
2061
- });
2062
- }
2063
- function matchesFilters(instance, filters) {
2064
- if (filters.status && String(instance.fields.status ?? "") !== filters.status) return false;
2065
- if (filters.owner && String(instance.fields.owner ?? "") !== filters.owner) return false;
2066
- if (filters.tag && !hasTag(instance, filters.tag)) return false;
2067
- if (filters.pathIncludes && !instance.path.includes(filters.pathIncludes)) return false;
2068
- if (filters.updatedAfter && !isDateOnOrAfter(instance.fields.updated, filters.updatedAfter)) return false;
2069
- if (filters.updatedBefore && !isDateOnOrBefore(instance.fields.updated, filters.updatedBefore)) return false;
2070
- if (filters.createdAfter && !isDateOnOrAfter(instance.fields.created, filters.createdAfter)) return false;
2071
- if (filters.createdBefore && !isDateOnOrBefore(instance.fields.created, filters.createdBefore)) return false;
2072
- if (filters.text && !containsText(instance, filters.text)) return false;
2073
- return true;
2074
- }
2075
- function hasTag(instance, tag) {
2076
- const tags = instance.fields.tags;
2077
- if (!Array.isArray(tags)) return false;
2078
- return tags.map((value) => String(value)).includes(tag);
2079
- }
2080
- function containsText(instance, text) {
2081
- const haystack = [
2082
- instance.path,
2083
- instance.type,
2084
- stringifyFields(instance.fields),
2085
- instance.body
2086
- ].join("\n").toLowerCase();
2087
- return haystack.includes(text.toLowerCase());
2088
- }
2089
- function stringifyFields(fields) {
2090
- try {
2091
- return JSON.stringify(fields);
2092
- } catch {
2093
- return "";
2094
- }
2095
- }
2096
- function isDateOnOrAfter(value, thresholdIso) {
2097
- const ts = Date.parse(String(value ?? ""));
2098
- const threshold = Date.parse(thresholdIso);
2099
- if (!Number.isFinite(ts) || !Number.isFinite(threshold)) return false;
2100
- return ts >= threshold;
2101
- }
2102
- function isDateOnOrBefore(value, thresholdIso) {
2103
- const ts = Date.parse(String(value ?? ""));
2104
- const threshold = Date.parse(thresholdIso);
2105
- if (!Number.isFinite(ts) || !Number.isFinite(threshold)) return false;
2106
- return ts <= threshold;
2107
- }
2108
-
2109
- // src/orientation.ts
2110
- var orientation_exports = {};
2111
- __export(orientation_exports, {
2112
- brief: () => brief,
2113
- checkpoint: () => checkpoint,
2114
- intake: () => intake,
2115
- statusSnapshot: () => statusSnapshot
2116
- });
2117
- function statusSnapshot(workspacePath) {
2118
- const threads = list(workspacePath, "thread");
2119
- const allPrimitives = queryPrimitives(workspacePath);
2120
- const byType = allPrimitives.reduce((acc, instance) => {
2121
- acc[instance.type] = (acc[instance.type] ?? 0) + 1;
2122
- return acc;
2123
- }, {});
2124
- const claims = allClaims(workspacePath);
2125
- const ready = listReadyThreads(workspacePath);
2126
- return {
2127
- generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
2128
- threads: {
2129
- total: threads.length,
2130
- open: threads.filter((item) => item.fields.status === "open").length,
2131
- active: threads.filter((item) => item.fields.status === "active").length,
2132
- blocked: threads.filter((item) => item.fields.status === "blocked").length,
2133
- done: threads.filter((item) => item.fields.status === "done").length,
2134
- cancelled: threads.filter((item) => item.fields.status === "cancelled").length,
2135
- ready: ready.length
2136
- },
2137
- claims: {
2138
- active: claims.size
2139
- },
2140
- primitives: {
2141
- total: allPrimitives.length,
2142
- byType
2143
- }
2144
- };
2145
- }
2146
- function brief(workspacePath, actor, options = {}) {
2147
- const myClaims = [...allClaims(workspacePath).entries()].filter(([, owner]) => owner === actor).map(([target]) => read(workspacePath, target)).filter((instance) => instance !== null);
2148
- const myOpenThreads = queryPrimitives(workspacePath, {
2149
- type: "thread",
2150
- owner: actor
2151
- }).filter((instance) => ["open", "active"].includes(String(instance.fields.status)));
2152
- return {
2153
- generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
2154
- actor,
2155
- myClaims,
2156
- myOpenThreads,
2157
- blockedThreads: blockedThreads(workspacePath),
2158
- nextReadyThreads: listReadyThreads(workspacePath).slice(0, options.nextCount ?? 5),
2159
- recentActivity: recent(workspacePath, options.recentCount ?? 12)
1675
+ myClaims,
1676
+ myOpenThreads,
1677
+ blockedThreads: blockedThreads(workspacePath),
1678
+ nextReadyThreads: listReadyThreads(workspacePath).slice(0, options.nextCount ?? 5),
1679
+ recentActivity: recent(workspacePath, options.recentCount ?? 12)
2160
1680
  };
2161
1681
  }
2162
1682
  function checkpoint(workspacePath, actor, summary, options = {}) {
@@ -2196,155 +1716,269 @@ function intake(workspacePath, actor, observation, options = {}) {
2196
1716
  });
2197
1717
  }
2198
1718
 
2199
- // src/board.ts
2200
- var board_exports = {};
2201
- __export(board_exports, {
2202
- generateKanbanBoard: () => generateKanbanBoard,
2203
- syncKanbanBoard: () => syncKanbanBoard
2204
- });
2205
- import fs10 from "fs";
2206
- import path10 from "path";
2207
- function generateKanbanBoard(workspacePath, options = {}) {
2208
- const threads = list(workspacePath, "thread");
2209
- const grouped = groupThreads(threads);
2210
- const includeCancelled = options.includeCancelled === true;
2211
- const lanes = [
2212
- { title: "Backlog", items: grouped.open, checkChar: " " },
2213
- { title: "In Progress", items: grouped.active, checkChar: " " },
2214
- { title: "Blocked", items: grouped.blocked, checkChar: " " },
2215
- { title: "Done", items: grouped.done, checkChar: "x" }
2216
- ];
2217
- if (includeCancelled) {
2218
- lanes.push({ title: "Cancelled", items: grouped.cancelled, checkChar: "x" });
2219
- }
2220
- const content = renderKanbanMarkdown(lanes);
2221
- const relOutputPath = options.outputPath ?? "ops/Workgraph Board.md";
2222
- const absOutputPath = resolvePathWithinWorkspace2(workspacePath, relOutputPath);
2223
- const parentDir = path10.dirname(absOutputPath);
2224
- if (!fs10.existsSync(parentDir)) fs10.mkdirSync(parentDir, { recursive: true });
2225
- fs10.writeFileSync(absOutputPath, content, "utf-8");
2226
- return {
2227
- outputPath: path10.relative(workspacePath, absOutputPath).replace(/\\/g, "/"),
2228
- generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
2229
- counts: {
2230
- backlog: grouped.open.length,
2231
- inProgress: grouped.active.length,
2232
- blocked: grouped.blocked.length,
2233
- done: grouped.done.length,
2234
- cancelled: grouped.cancelled.length
2235
- },
2236
- content
2237
- };
2238
- }
2239
- function syncKanbanBoard(workspacePath, options = {}) {
2240
- return generateKanbanBoard(workspacePath, options);
2241
- }
2242
- function groupThreads(threads) {
2243
- const groups = {
2244
- open: [],
2245
- active: [],
2246
- blocked: [],
2247
- done: [],
2248
- cancelled: []
2249
- };
2250
- for (const thread of threads) {
2251
- const status2 = String(thread.fields.status ?? "open");
2252
- switch (status2) {
2253
- case "active":
2254
- groups.active.push(thread);
2255
- break;
2256
- case "blocked":
2257
- groups.blocked.push(thread);
2258
- break;
2259
- case "done":
2260
- groups.done.push(thread);
2261
- break;
2262
- case "cancelled":
2263
- groups.cancelled.push(thread);
2264
- break;
2265
- case "open":
2266
- default:
2267
- groups.open.push(thread);
1719
+ // src/adapter-cursor-cloud.ts
1720
+ var DEFAULT_MAX_STEPS = 200;
1721
+ var DEFAULT_STEP_DELAY_MS = 25;
1722
+ var DEFAULT_AGENT_COUNT = 3;
1723
+ var CursorCloudAdapter = class {
1724
+ name = "cursor-cloud";
1725
+ async create(_input) {
1726
+ return {
1727
+ runId: "adapter-managed",
1728
+ status: "queued"
1729
+ };
1730
+ }
1731
+ async status(runId) {
1732
+ return { runId, status: "running" };
1733
+ }
1734
+ async followup(runId, _actor, _input) {
1735
+ return { runId, status: "running" };
1736
+ }
1737
+ async stop(runId, _actor) {
1738
+ return { runId, status: "cancelled" };
1739
+ }
1740
+ async logs(_runId) {
1741
+ return [];
1742
+ }
1743
+ async execute(input) {
1744
+ const start = Date.now();
1745
+ const logs2 = [];
1746
+ const agentPool = normalizeAgents(input.agents, input.actor);
1747
+ const maxSteps = normalizeInt(input.maxSteps, DEFAULT_MAX_STEPS, 1, 5e3);
1748
+ const stepDelayMs = normalizeInt(input.stepDelayMs, DEFAULT_STEP_DELAY_MS, 0, 5e3);
1749
+ const claimedByAgent = {};
1750
+ const completedByAgent = {};
1751
+ let stepsExecuted = 0;
1752
+ let completionCount = 0;
1753
+ let failureCount = 0;
1754
+ let cancelled = false;
1755
+ for (const agent of agentPool) {
1756
+ claimedByAgent[agent] = 0;
1757
+ completedByAgent[agent] = 0;
1758
+ }
1759
+ pushLog(logs2, "info", `Run ${input.runId} started with agents: ${agentPool.join(", ")}`);
1760
+ pushLog(logs2, "info", `Objective: ${input.objective}`);
1761
+ while (stepsExecuted < maxSteps) {
1762
+ if (input.isCancelled?.()) {
1763
+ cancelled = true;
1764
+ pushLog(logs2, "warn", `Run ${input.runId} received cancellation signal.`);
2268
1765
  break;
1766
+ }
1767
+ const claimedThisRound = [];
1768
+ for (const agent of agentPool) {
1769
+ try {
1770
+ const claimed = input.space ? claimNextReadyInSpace(input.workspacePath, agent, input.space) : claimNextReady(input.workspacePath, agent);
1771
+ if (!claimed) {
1772
+ continue;
1773
+ }
1774
+ const path7 = claimed.path;
1775
+ const goal = String(claimed.fields.goal ?? claimed.fields.title ?? path7);
1776
+ claimedThisRound.push({ agent, threadPath: path7, goal });
1777
+ claimedByAgent[agent] += 1;
1778
+ pushLog(logs2, "info", `${agent} claimed ${path7}`);
1779
+ } catch (error) {
1780
+ pushLog(logs2, "warn", `${agent} claim skipped: ${errorMessage(error)}`);
1781
+ }
1782
+ }
1783
+ if (claimedThisRound.length === 0) {
1784
+ const readyRemaining = listReady(input.workspacePath, input.space).length;
1785
+ if (readyRemaining === 0) {
1786
+ pushLog(logs2, "info", "No ready threads remaining; autonomous loop complete.");
1787
+ break;
1788
+ }
1789
+ if (stepDelayMs > 0) {
1790
+ await sleep(stepDelayMs);
1791
+ }
1792
+ continue;
1793
+ }
1794
+ await Promise.all(claimedThisRound.map(async (claimed) => {
1795
+ if (input.isCancelled?.()) {
1796
+ cancelled = true;
1797
+ return;
1798
+ }
1799
+ if (stepDelayMs > 0) {
1800
+ await sleep(stepDelayMs);
1801
+ }
1802
+ try {
1803
+ done(
1804
+ input.workspacePath,
1805
+ claimed.threadPath,
1806
+ claimed.agent,
1807
+ `Completed by ${claimed.agent} during dispatch run ${input.runId}. Goal: ${claimed.goal}`
1808
+ );
1809
+ completionCount += 1;
1810
+ completedByAgent[claimed.agent] += 1;
1811
+ pushLog(logs2, "info", `${claimed.agent} completed ${claimed.threadPath}`);
1812
+ } catch (error) {
1813
+ failureCount += 1;
1814
+ pushLog(logs2, "error", `${claimed.agent} failed to complete ${claimed.threadPath}: ${errorMessage(error)}`);
1815
+ }
1816
+ }));
1817
+ stepsExecuted += claimedThisRound.length;
1818
+ if (cancelled) break;
2269
1819
  }
2270
- }
2271
- const byPriority = (a, b) => {
2272
- const rank = (value) => {
2273
- switch (String(value ?? "medium")) {
2274
- case "urgent":
2275
- return 0;
2276
- case "high":
2277
- return 1;
2278
- case "medium":
2279
- return 2;
2280
- case "low":
2281
- return 3;
2282
- default:
2283
- return 4;
1820
+ const readyAfter = listReady(input.workspacePath, input.space);
1821
+ const activeAfter = input.space ? threadsInSpace(input.workspacePath, input.space).filter((candidate) => candidate.fields.status === "active") : activeThreads(input.workspacePath);
1822
+ const openAfter = input.space ? threadsInSpace(input.workspacePath, input.space).filter((candidate) => candidate.fields.status === "open") : openThreads(input.workspacePath);
1823
+ const blockedAfter = input.space ? threadsInSpace(input.workspacePath, input.space).filter((candidate) => candidate.fields.status === "blocked") : blockedThreads(input.workspacePath);
1824
+ const elapsedMs = Date.now() - start;
1825
+ const summary = renderSummary({
1826
+ objective: input.objective,
1827
+ runId: input.runId,
1828
+ completed: completionCount,
1829
+ failed: failureCount,
1830
+ stepsExecuted,
1831
+ readyRemaining: readyAfter.length,
1832
+ openRemaining: openAfter.length,
1833
+ blockedRemaining: blockedAfter.length,
1834
+ activeRemaining: activeAfter.length,
1835
+ elapsedMs,
1836
+ claimedByAgent,
1837
+ completedByAgent,
1838
+ cancelled
1839
+ });
1840
+ if (input.createCheckpoint !== false) {
1841
+ try {
1842
+ checkpoint(
1843
+ input.workspacePath,
1844
+ input.actor,
1845
+ `Dispatch run ${input.runId} completed autonomous execution.`,
1846
+ {
1847
+ next: readyAfter.slice(0, 10).map((entry) => entry.path),
1848
+ blocked: blockedAfter.slice(0, 10).map((entry) => entry.path),
1849
+ tags: ["dispatch", "autonomous-run"]
1850
+ }
1851
+ );
1852
+ pushLog(logs2, "info", `Checkpoint recorded for run ${input.runId}.`);
1853
+ } catch (error) {
1854
+ pushLog(logs2, "warn", `Checkpoint creation skipped: ${errorMessage(error)}`);
1855
+ }
1856
+ }
1857
+ if (cancelled) {
1858
+ return {
1859
+ status: "cancelled",
1860
+ output: summary,
1861
+ logs: logs2,
1862
+ metrics: {
1863
+ completed: completionCount,
1864
+ failed: failureCount,
1865
+ readyRemaining: readyAfter.length,
1866
+ openRemaining: openAfter.length,
1867
+ blockedRemaining: blockedAfter.length,
1868
+ elapsedMs,
1869
+ claimedByAgent,
1870
+ completedByAgent
1871
+ }
1872
+ };
1873
+ }
1874
+ if (failureCount > 0) {
1875
+ return {
1876
+ status: "failed",
1877
+ error: summary,
1878
+ logs: logs2,
1879
+ metrics: {
1880
+ completed: completionCount,
1881
+ failed: failureCount,
1882
+ readyRemaining: readyAfter.length,
1883
+ openRemaining: openAfter.length,
1884
+ blockedRemaining: blockedAfter.length,
1885
+ elapsedMs,
1886
+ claimedByAgent,
1887
+ completedByAgent
1888
+ }
1889
+ };
1890
+ }
1891
+ const status2 = readyAfter.length === 0 && activeAfter.length === 0 ? "succeeded" : "failed";
1892
+ if (status2 === "failed") {
1893
+ pushLog(logs2, "warn", "Execution stopped with actionable work still remaining.");
1894
+ }
1895
+ return {
1896
+ status: status2,
1897
+ output: summary,
1898
+ logs: logs2,
1899
+ metrics: {
1900
+ completed: completionCount,
1901
+ failed: failureCount,
1902
+ readyRemaining: readyAfter.length,
1903
+ openRemaining: openAfter.length,
1904
+ blockedRemaining: blockedAfter.length,
1905
+ elapsedMs,
1906
+ claimedByAgent,
1907
+ completedByAgent
2284
1908
  }
2285
1909
  };
2286
- return rank(a.fields.priority) - rank(b.fields.priority) || String(a.fields.title).localeCompare(String(b.fields.title));
2287
- };
2288
- groups.open.sort(byPriority);
2289
- groups.active.sort(byPriority);
2290
- groups.blocked.sort(byPriority);
2291
- groups.done.sort(byPriority);
2292
- groups.cancelled.sort(byPriority);
2293
- return groups;
2294
- }
2295
- function renderKanbanMarkdown(lanes) {
2296
- const settings = {
2297
- "kanban-plugin": "board"
2298
- };
1910
+ }
1911
+ };
1912
+ function normalizeAgents(agents, actor) {
1913
+ const fromInput = (agents ?? []).map((entry) => String(entry).trim()).filter(Boolean);
1914
+ if (fromInput.length > 0) return [...new Set(fromInput)];
1915
+ return Array.from({ length: DEFAULT_AGENT_COUNT }, (_, idx) => `${actor}-worker-${idx + 1}`);
1916
+ }
1917
+ function normalizeInt(rawValue, fallback, min, max) {
1918
+ const value = Number.isFinite(rawValue) ? Number(rawValue) : fallback;
1919
+ return Math.min(max, Math.max(min, Math.trunc(value)));
1920
+ }
1921
+ function pushLog(target, level, message) {
1922
+ target.push({
1923
+ ts: (/* @__PURE__ */ new Date()).toISOString(),
1924
+ level,
1925
+ message
1926
+ });
1927
+ }
1928
+ function listReady(workspacePath, space) {
1929
+ return space ? listReadyThreadsInSpace(workspacePath, space) : listReadyThreads(workspacePath);
1930
+ }
1931
+ function errorMessage(error) {
1932
+ return error instanceof Error ? error.message : String(error);
1933
+ }
1934
+ function sleep(ms) {
1935
+ return new Promise((resolve) => {
1936
+ setTimeout(resolve, ms);
1937
+ });
1938
+ }
1939
+ function renderSummary(data) {
2299
1940
  const lines = [
2300
- "---",
2301
- "kanban-plugin: board",
2302
- "---",
2303
- ""
1941
+ `Autonomous dispatch summary for ${data.runId}`,
1942
+ `Objective: ${data.objective}`,
1943
+ `Completed threads: ${data.completed}`,
1944
+ `Failed completions: ${data.failed}`,
1945
+ `Scheduler steps executed: ${data.stepsExecuted}`,
1946
+ `Ready remaining: ${data.readyRemaining}`,
1947
+ `Open remaining: ${data.openRemaining}`,
1948
+ `Blocked remaining: ${data.blockedRemaining}`,
1949
+ `Active remaining: ${data.activeRemaining}`,
1950
+ `Elapsed ms: ${data.elapsedMs}`,
1951
+ `Cancelled: ${data.cancelled ? "yes" : "no"}`,
1952
+ "",
1953
+ "Claims by agent:",
1954
+ ...Object.entries(data.claimedByAgent).map(([agent, count]) => `- ${agent}: ${count}`),
1955
+ "",
1956
+ "Completions by agent:",
1957
+ ...Object.entries(data.completedByAgent).map(([agent, count]) => `- ${agent}: ${count}`)
2304
1958
  ];
2305
- for (const lane of lanes) {
2306
- lines.push(`## ${lane.title}`);
2307
- lines.push("");
2308
- for (const thread of lane.items) {
2309
- const title = String(thread.fields.title ?? thread.path);
2310
- const priority = String(thread.fields.priority ?? "medium");
2311
- lines.push(`- [${lane.checkChar}] [[${thread.path}|${title}]] (#${priority})`);
2312
- }
2313
- lines.push("");
2314
- lines.push("");
2315
- lines.push("");
2316
- }
2317
- lines.push("%% kanban:settings");
2318
- lines.push("```");
2319
- lines.push(JSON.stringify(settings));
2320
- lines.push("```");
2321
- lines.push("%%");
2322
- lines.push("");
2323
1959
  return lines.join("\n");
2324
1960
  }
2325
- function resolvePathWithinWorkspace2(workspacePath, outputPath) {
2326
- const base = path10.resolve(workspacePath);
2327
- const resolved = path10.resolve(base, outputPath);
2328
- if (!resolved.startsWith(base + path10.sep) && resolved !== base) {
2329
- throw new Error(`Invalid board output path: ${outputPath}`);
1961
+
1962
+ // src/runtime-adapter-registry.ts
1963
+ var adapterFactories = /* @__PURE__ */ new Map([
1964
+ ["cursor-cloud", () => new CursorCloudAdapter()]
1965
+ ]);
1966
+ function resolveDispatchAdapter(name) {
1967
+ const safeName = normalizeName(name);
1968
+ const factory = adapterFactories.get(safeName);
1969
+ if (!factory) {
1970
+ throw new Error(`Unknown dispatch adapter "${name}". Registered adapters: ${listDispatchAdapters().join(", ") || "none"}.`);
2330
1971
  }
2331
- return resolved;
1972
+ return factory();
1973
+ }
1974
+ function listDispatchAdapters() {
1975
+ return [...adapterFactories.keys()].sort((a, b) => a.localeCompare(b));
1976
+ }
1977
+ function normalizeName(name) {
1978
+ return String(name || "").trim().toLowerCase();
2332
1979
  }
2333
1980
 
2334
1981
  // src/dispatch.ts
2335
- var dispatch_exports = {};
2336
- __export(dispatch_exports, {
2337
- createRun: () => createRun,
2338
- followup: () => followup,
2339
- listRuns: () => listRuns,
2340
- logs: () => logs,
2341
- markRun: () => markRun,
2342
- status: () => status,
2343
- stop: () => stop
2344
- });
2345
- import fs11 from "fs";
2346
- import path11 from "path";
2347
- import { randomUUID } from "crypto";
2348
1982
  var RUNS_FILE = ".workgraph/dispatch-runs.json";
2349
1983
  function createRun(workspacePath, input) {
2350
1984
  const state = loadRuns(workspacePath);
@@ -2410,11 +2044,18 @@ function markRun(workspacePath, runId, actor, nextStatus, options = {}) {
2410
2044
  const run = setStatus(workspacePath, runId, actor, nextStatus, `Run moved to ${nextStatus}.`);
2411
2045
  if (options.output) run.output = options.output;
2412
2046
  if (options.error) run.error = options.error;
2047
+ if (options.contextPatch && Object.keys(options.contextPatch).length > 0) {
2048
+ run.context = {
2049
+ ...run.context ?? {},
2050
+ ...options.contextPatch
2051
+ };
2052
+ }
2413
2053
  const state = loadRuns(workspacePath);
2414
2054
  const target = state.runs.find((entry) => entry.id === runId);
2415
2055
  if (target) {
2416
2056
  target.output = run.output;
2417
2057
  target.error = run.error;
2058
+ target.context = run.context;
2418
2059
  target.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
2419
2060
  saveRuns(workspacePath, state);
2420
2061
  syncRunPrimitive(workspacePath, target, actor);
@@ -2431,6 +2072,62 @@ function listRuns(workspacePath, options = {}) {
2431
2072
  }
2432
2073
  return runs;
2433
2074
  }
2075
+ async function executeRun(workspacePath, runId, input) {
2076
+ const existing = status(workspacePath, runId);
2077
+ if (!["queued", "running"].includes(existing.status)) {
2078
+ throw new Error(`Run ${runId} is in terminal status "${existing.status}" and cannot be executed.`);
2079
+ }
2080
+ const adapter = resolveDispatchAdapter(existing.adapter);
2081
+ if (!adapter.execute) {
2082
+ throw new Error(`Dispatch adapter "${existing.adapter}" does not implement execute().`);
2083
+ }
2084
+ if (existing.status === "queued") {
2085
+ setStatus(workspacePath, runId, input.actor, "running", `Run started on adapter "${existing.adapter}".`);
2086
+ }
2087
+ const execution = await adapter.execute({
2088
+ workspacePath,
2089
+ runId,
2090
+ actor: input.actor,
2091
+ objective: existing.objective,
2092
+ context: existing.context,
2093
+ agents: input.agents,
2094
+ maxSteps: input.maxSteps,
2095
+ stepDelayMs: input.stepDelayMs,
2096
+ space: input.space,
2097
+ createCheckpoint: input.createCheckpoint,
2098
+ isCancelled: () => status(workspacePath, runId).status === "cancelled"
2099
+ });
2100
+ appendRunLogs(workspacePath, runId, input.actor, execution.logs);
2101
+ const finalStatus = execution.status;
2102
+ if (finalStatus === "queued" || finalStatus === "running") {
2103
+ throw new Error(`Adapter returned invalid terminal status "${finalStatus}" for execute().`);
2104
+ }
2105
+ return markRun(workspacePath, runId, input.actor, finalStatus, {
2106
+ output: execution.output,
2107
+ error: execution.error,
2108
+ contextPatch: execution.metrics ? { adapter_metrics: execution.metrics } : void 0
2109
+ });
2110
+ }
2111
+ async function createAndExecuteRun(workspacePath, createInput, executeInput = {}) {
2112
+ const run = createRun(workspacePath, createInput);
2113
+ return executeRun(workspacePath, run.id, {
2114
+ actor: createInput.actor,
2115
+ ...executeInput
2116
+ });
2117
+ }
2118
+ function appendRunLogs(workspacePath, runId, actor, logEntries) {
2119
+ if (logEntries.length === 0) return;
2120
+ const state = loadRuns(workspacePath);
2121
+ const run = state.runs.find((entry) => entry.id === runId);
2122
+ if (!run) throw new Error(`Run not found: ${runId}`);
2123
+ run.logs.push(...logEntries);
2124
+ run.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
2125
+ saveRuns(workspacePath, state);
2126
+ append(workspacePath, actor, "update", `.workgraph/runs/${run.id}`, "run", {
2127
+ log_append_count: logEntries.length
2128
+ });
2129
+ syncRunPrimitive(workspacePath, run, actor);
2130
+ }
2434
2131
  function setStatus(workspacePath, runId, actor, statusValue, logMessage) {
2435
2132
  const state = loadRuns(workspacePath);
2436
2133
  const run = state.runs.find((entry) => entry.id === runId);
@@ -2448,16 +2145,16 @@ function setStatus(workspacePath, runId, actor, statusValue, logMessage) {
2448
2145
  return run;
2449
2146
  }
2450
2147
  function runsPath(workspacePath) {
2451
- return path11.join(workspacePath, RUNS_FILE);
2148
+ return path6.join(workspacePath, RUNS_FILE);
2452
2149
  }
2453
2150
  function loadRuns(workspacePath) {
2454
2151
  const rPath = runsPath(workspacePath);
2455
- if (!fs11.existsSync(rPath)) {
2152
+ if (!fs6.existsSync(rPath)) {
2456
2153
  const seeded = { version: 1, runs: [] };
2457
2154
  saveRuns(workspacePath, seeded);
2458
2155
  return seeded;
2459
2156
  }
2460
- const raw = fs11.readFileSync(rPath, "utf-8");
2157
+ const raw = fs6.readFileSync(rPath, "utf-8");
2461
2158
  const parsed = JSON.parse(raw);
2462
2159
  return {
2463
2160
  version: parsed.version ?? 1,
@@ -2466,9 +2163,9 @@ function loadRuns(workspacePath) {
2466
2163
  }
2467
2164
  function saveRuns(workspacePath, value) {
2468
2165
  const rPath = runsPath(workspacePath);
2469
- const dir = path11.dirname(rPath);
2470
- if (!fs11.existsSync(dir)) fs11.mkdirSync(dir, { recursive: true });
2471
- fs11.writeFileSync(rPath, JSON.stringify(value, null, 2) + "\n", "utf-8");
2166
+ const dir = path6.dirname(rPath);
2167
+ if (!fs6.existsSync(dir)) fs6.mkdirSync(dir, { recursive: true });
2168
+ fs6.writeFileSync(rPath, JSON.stringify(value, null, 2) + "\n", "utf-8");
2472
2169
  }
2473
2170
  function getRun(workspacePath, runId) {
2474
2171
  const state = loadRuns(workspacePath);
@@ -2476,6 +2173,9 @@ function getRun(workspacePath, runId) {
2476
2173
  }
2477
2174
  function ensureRunPrimitive(workspacePath, run, actor) {
2478
2175
  const safeTitle = `${run.objective} (${run.id.slice(0, 8)})`;
2176
+ const runPrimitivePath = `runs/${run.id}.md`;
2177
+ const existing = read(workspacePath, runPrimitivePath);
2178
+ if (existing) return;
2479
2179
  create(
2480
2180
  workspacePath,
2481
2181
  "run",
@@ -2492,7 +2192,8 @@ function ensureRunPrimitive(workspacePath, run, actor) {
2492
2192
 
2493
2193
  ${run.objective}
2494
2194
  `,
2495
- actor
2195
+ actor,
2196
+ { pathOverride: runPrimitivePath }
2496
2197
  );
2497
2198
  }
2498
2199
  function syncRunPrimitive(workspacePath, run, actor) {
@@ -2539,6 +2240,14 @@ function renderRunBody(run) {
2539
2240
  lines.push(run.error);
2540
2241
  lines.push("");
2541
2242
  }
2243
+ if (run.context && Object.keys(run.context).length > 0) {
2244
+ lines.push("## Context");
2245
+ lines.push("");
2246
+ lines.push("```json");
2247
+ lines.push(JSON.stringify(run.context, null, 2));
2248
+ lines.push("```");
2249
+ lines.push("");
2250
+ }
2542
2251
  return lines.join("\n");
2543
2252
  }
2544
2253
  var RUN_STATUS_TRANSITIONS = {
@@ -2556,262 +2265,582 @@ function assertRunStatusTransition(from, to, runId) {
2556
2265
  }
2557
2266
  }
2558
2267
 
2559
- // src/onboard.ts
2560
- var onboard_exports = {};
2561
- __export(onboard_exports, {
2562
- onboardWorkspace: () => onboardWorkspace,
2563
- updateOnboardingStatus: () => updateOnboardingStatus
2564
- });
2565
- function onboardWorkspace(workspacePath, options) {
2566
- const spaces = options.spaces && options.spaces.length > 0 ? options.spaces : ["platform", "product", "operations"];
2567
- const spacesCreated = [];
2568
- for (const space of spaces) {
2569
- const title = titleCase2(space);
2570
- const created = create(
2571
- workspacePath,
2572
- "space",
2573
- {
2574
- title,
2575
- description: `${title} workspace lane`,
2576
- members: [options.actor],
2577
- tags: ["onboarded"]
2578
- },
2579
- `# ${title}
2580
-
2581
- Auto-created during onboarding.
2582
- `,
2583
- options.actor
2584
- );
2585
- spacesCreated.push(created.path);
2586
- }
2587
- const threadsCreated = [];
2588
- if (options.createDemoThreads !== false) {
2589
- const templates = [
2590
- { title: "Review workspace policy gates", goal: "Validate sensitive transitions are governed.", space: spacesCreated[0] },
2591
- { title: "Configure board sync cadence", goal: "Set board update expectations for all agents.", space: spacesCreated[1] ?? spacesCreated[0] },
2592
- { title: "Establish daily checkpoint routine", goal: "Agents leave actionable hand-off notes.", space: spacesCreated[2] ?? spacesCreated[0] }
2593
- ];
2594
- for (const template of templates) {
2595
- const created = create(
2596
- workspacePath,
2597
- "thread",
2598
- {
2599
- title: template.title,
2600
- goal: template.goal,
2601
- status: "open",
2602
- priority: "medium",
2603
- space: template.space,
2604
- context_refs: [template.space],
2605
- tags: ["onboarding"]
2606
- },
2607
- `## Goal
2608
-
2609
- ${template.goal}
2610
- `,
2611
- options.actor
2612
- );
2613
- threadsCreated.push(created.path);
2614
- }
2615
- }
2616
- const boardResult = generateKanbanBoard(workspacePath, { outputPath: "ops/Onboarding Board.md" });
2617
- const commandCenterResult = generateCommandCenter(workspacePath, {
2618
- outputPath: "ops/Onboarding Command Center.md",
2619
- actor: options.actor
2268
+ // src/mcp-server.ts
2269
+ var DEFAULT_SERVER_NAME = "workgraph-mcp-server";
2270
+ var DEFAULT_SERVER_VERSION = "0.1.0";
2271
+ function createWorkgraphMcpServer(options) {
2272
+ const server = new McpServer({
2273
+ name: options.name ?? DEFAULT_SERVER_NAME,
2274
+ version: options.version ?? DEFAULT_SERVER_VERSION
2620
2275
  });
2621
- const checkpointResult = checkpoint(
2622
- workspacePath,
2623
- options.actor,
2624
- "Onboarding completed and workspace views initialized.",
2276
+ registerResources(server, options);
2277
+ registerTools(server, options);
2278
+ return server;
2279
+ }
2280
+ async function startWorkgraphMcpServer(options) {
2281
+ const server = createWorkgraphMcpServer(options);
2282
+ const transport = new StdioServerTransport();
2283
+ await server.connect(transport);
2284
+ return server;
2285
+ }
2286
+ function registerResources(server, options) {
2287
+ server.registerResource(
2288
+ "workspace-status",
2289
+ "workgraph://status",
2625
2290
  {
2626
- next: ["Claim your next ready thread via `workgraph thread next --claim`"],
2627
- blocked: [],
2628
- tags: ["onboarding"]
2291
+ title: "Workgraph Status Snapshot",
2292
+ description: "Current thread/claim/primitive counts for the workspace.",
2293
+ mimeType: "application/json"
2294
+ },
2295
+ async () => {
2296
+ const snapshot = statusSnapshot(options.workspacePath);
2297
+ return {
2298
+ contents: [
2299
+ {
2300
+ uri: "workgraph://status",
2301
+ mimeType: "application/json",
2302
+ text: toPrettyJson(snapshot)
2303
+ }
2304
+ ]
2305
+ };
2629
2306
  }
2630
2307
  );
2631
- const onboarding = create(
2632
- workspacePath,
2633
- "onboarding",
2308
+ server.registerResource(
2309
+ "actor-brief",
2310
+ new ResourceTemplate("workgraph://brief/{actor}", { list: void 0 }),
2634
2311
  {
2635
- title: `Onboarding for ${options.actor}`,
2636
- actor: options.actor,
2637
- status: "active",
2638
- spaces: spacesCreated,
2639
- thread_refs: threadsCreated,
2640
- board: boardResult.outputPath,
2641
- command_center: commandCenterResult.outputPath,
2642
- tags: ["onboarding"]
2312
+ title: "Actor Brief",
2313
+ description: "Actor-specific operational brief derived from workspace state.",
2314
+ mimeType: "application/json"
2643
2315
  },
2644
- [
2645
- "# Onboarding",
2646
- "",
2647
- `Actor: ${options.actor}`,
2648
- "",
2649
- "## Spaces",
2650
- "",
2651
- ...spacesCreated.map((space) => `- [[${space}]]`),
2652
- "",
2653
- "## Starter Threads",
2654
- "",
2655
- ...threadsCreated.map((threadRef) => `- [[${threadRef}]]`),
2656
- "",
2657
- `Board: [[${boardResult.outputPath}]]`,
2658
- `Command Center: [[${commandCenterResult.outputPath}]]`,
2659
- ""
2660
- ].join("\n"),
2661
- options.actor
2316
+ async (uri, variables) => {
2317
+ const actor = String(variables.actor ?? options.defaultActor ?? "anonymous");
2318
+ const brief2 = brief(options.workspacePath, actor);
2319
+ return {
2320
+ contents: [
2321
+ {
2322
+ uri: uri.toString(),
2323
+ mimeType: "application/json",
2324
+ text: toPrettyJson(brief2)
2325
+ }
2326
+ ]
2327
+ };
2328
+ }
2662
2329
  );
2663
- return {
2664
- actor: options.actor,
2665
- spacesCreated,
2666
- threadsCreated,
2667
- boardPath: boardResult.outputPath,
2668
- commandCenterPath: commandCenterResult.outputPath,
2669
- checkpointPath: checkpointResult.path,
2670
- onboardingPath: onboarding.path
2671
- };
2672
2330
  }
2673
- function updateOnboardingStatus(workspacePath, onboardingPath, status2, actor) {
2674
- const onboarding = read(workspacePath, onboardingPath);
2675
- if (!onboarding) throw new Error(`Onboarding primitive not found: ${onboardingPath}`);
2676
- if (onboarding.type !== "onboarding") {
2677
- throw new Error(`Target is not an onboarding primitive: ${onboardingPath}`);
2678
- }
2679
- const current = String(onboarding.fields.status ?? "active");
2680
- const allowed = ONBOARDING_STATUS_TRANSITIONS[current] ?? [];
2681
- if (!allowed.includes(status2)) {
2682
- throw new Error(`Invalid onboarding transition: ${current} -> ${status2}. Allowed: ${allowed.join(", ") || "none"}`);
2683
- }
2684
- return update(
2685
- workspacePath,
2686
- onboardingPath,
2687
- { status: status2 },
2688
- void 0,
2689
- actor
2331
+ function registerTools(server, options) {
2332
+ server.registerTool(
2333
+ "workgraph_status",
2334
+ {
2335
+ title: "Workgraph Status",
2336
+ description: "Return a compact status snapshot for the configured workspace.",
2337
+ annotations: {
2338
+ readOnlyHint: true,
2339
+ idempotentHint: true
2340
+ }
2341
+ },
2342
+ async () => {
2343
+ try {
2344
+ const snapshot = statusSnapshot(options.workspacePath);
2345
+ return okResult(snapshot, renderStatusSummary(snapshot));
2346
+ } catch (error) {
2347
+ return errorResult(error);
2348
+ }
2349
+ }
2350
+ );
2351
+ server.registerTool(
2352
+ "workgraph_brief",
2353
+ {
2354
+ title: "Workgraph Brief",
2355
+ description: "Return actor-centric operational brief (claims, blockers, and next work).",
2356
+ inputSchema: {
2357
+ actor: z.string().optional(),
2358
+ recentCount: z.number().int().min(1).max(100).optional(),
2359
+ nextCount: z.number().int().min(1).max(100).optional()
2360
+ },
2361
+ annotations: {
2362
+ readOnlyHint: true,
2363
+ idempotentHint: true
2364
+ }
2365
+ },
2366
+ async (args) => {
2367
+ try {
2368
+ const actor = resolveActor(args.actor, options.defaultActor);
2369
+ const brief2 = brief(options.workspacePath, actor, {
2370
+ recentCount: args.recentCount,
2371
+ nextCount: args.nextCount
2372
+ });
2373
+ return okResult(brief2, `Brief for ${actor}: claims=${brief2.myClaims.length}, blocked=${brief2.blockedThreads.length}`);
2374
+ } catch (error) {
2375
+ return errorResult(error);
2376
+ }
2377
+ }
2378
+ );
2379
+ server.registerTool(
2380
+ "workgraph_query",
2381
+ {
2382
+ title: "Workgraph Query",
2383
+ description: "Query primitives using multi-field filters.",
2384
+ inputSchema: {
2385
+ type: z.string().optional(),
2386
+ status: z.string().optional(),
2387
+ owner: z.string().optional(),
2388
+ tag: z.string().optional(),
2389
+ text: z.string().optional(),
2390
+ pathIncludes: z.string().optional(),
2391
+ updatedAfter: z.string().optional(),
2392
+ updatedBefore: z.string().optional(),
2393
+ createdAfter: z.string().optional(),
2394
+ createdBefore: z.string().optional(),
2395
+ limit: z.number().int().min(0).max(1e3).optional(),
2396
+ offset: z.number().int().min(0).max(1e4).optional()
2397
+ },
2398
+ annotations: {
2399
+ readOnlyHint: true,
2400
+ idempotentHint: true
2401
+ }
2402
+ },
2403
+ async (args) => {
2404
+ try {
2405
+ const results = queryPrimitives(options.workspacePath, {
2406
+ type: args.type,
2407
+ status: args.status,
2408
+ owner: args.owner,
2409
+ tag: args.tag,
2410
+ text: args.text,
2411
+ pathIncludes: args.pathIncludes,
2412
+ updatedAfter: args.updatedAfter,
2413
+ updatedBefore: args.updatedBefore,
2414
+ createdAfter: args.createdAfter,
2415
+ createdBefore: args.createdBefore,
2416
+ limit: args.limit,
2417
+ offset: args.offset
2418
+ });
2419
+ return okResult({ results, count: results.length }, `Query returned ${results.length} primitive(s).`);
2420
+ } catch (error) {
2421
+ return errorResult(error);
2422
+ }
2423
+ }
2424
+ );
2425
+ server.registerTool(
2426
+ "workgraph_thread_list",
2427
+ {
2428
+ title: "Thread List",
2429
+ description: "List workspace threads, optionally filtered by status/space/readiness.",
2430
+ inputSchema: {
2431
+ status: z.string().optional(),
2432
+ readyOnly: z.boolean().optional(),
2433
+ space: z.string().optional()
2434
+ },
2435
+ annotations: {
2436
+ readOnlyHint: true,
2437
+ idempotentHint: true
2438
+ }
2439
+ },
2440
+ async (args) => {
2441
+ try {
2442
+ let threads = args.space ? threadsInSpace(options.workspacePath, args.space) : list(options.workspacePath, "thread");
2443
+ const readySet = new Set(
2444
+ (args.space ? listReadyThreadsInSpace(options.workspacePath, args.space) : listReadyThreads(options.workspacePath)).map((entry) => entry.path)
2445
+ );
2446
+ if (args.status) {
2447
+ threads = threads.filter((entry) => String(entry.fields.status) === args.status);
2448
+ }
2449
+ if (args.readyOnly) {
2450
+ threads = threads.filter((entry) => readySet.has(entry.path));
2451
+ }
2452
+ const enriched = threads.map((entry) => ({
2453
+ ...entry,
2454
+ ready: readySet.has(entry.path)
2455
+ }));
2456
+ return okResult({ threads: enriched, count: enriched.length }, `Thread list returned ${enriched.length} item(s).`);
2457
+ } catch (error) {
2458
+ return errorResult(error);
2459
+ }
2460
+ }
2461
+ );
2462
+ server.registerTool(
2463
+ "workgraph_thread_show",
2464
+ {
2465
+ title: "Thread Show",
2466
+ description: "Read one thread and its ledger history.",
2467
+ inputSchema: {
2468
+ threadPath: z.string().min(1)
2469
+ },
2470
+ annotations: {
2471
+ readOnlyHint: true,
2472
+ idempotentHint: true
2473
+ }
2474
+ },
2475
+ async (args) => {
2476
+ try {
2477
+ const threadEntry = read(options.workspacePath, args.threadPath);
2478
+ if (!threadEntry) {
2479
+ return errorResult(`Thread not found: ${args.threadPath}`);
2480
+ }
2481
+ const history = historyOf(options.workspacePath, args.threadPath);
2482
+ return okResult({ thread: threadEntry, history }, `Thread ${args.threadPath} has ${history.length} ledger event(s).`);
2483
+ } catch (error) {
2484
+ return errorResult(error);
2485
+ }
2486
+ }
2487
+ );
2488
+ server.registerTool(
2489
+ "workgraph_ledger_recent",
2490
+ {
2491
+ title: "Ledger Recent",
2492
+ description: "Read recent ledger events.",
2493
+ inputSchema: {
2494
+ count: z.number().int().min(1).max(500).optional(),
2495
+ actor: z.string().optional()
2496
+ },
2497
+ annotations: {
2498
+ readOnlyHint: true,
2499
+ idempotentHint: true
2500
+ }
2501
+ },
2502
+ async (args) => {
2503
+ try {
2504
+ let entries = recent(options.workspacePath, args.count ?? 20);
2505
+ if (args.actor) {
2506
+ entries = entries.filter((entry) => entry.actor === args.actor);
2507
+ }
2508
+ return okResult({ entries, count: entries.length }, `Ledger returned ${entries.length} event(s).`);
2509
+ } catch (error) {
2510
+ return errorResult(error);
2511
+ }
2512
+ }
2513
+ );
2514
+ server.registerTool(
2515
+ "workgraph_graph_hygiene",
2516
+ {
2517
+ title: "Graph Hygiene",
2518
+ description: "Generate wiki-link graph hygiene report.",
2519
+ annotations: {
2520
+ readOnlyHint: true,
2521
+ idempotentHint: true
2522
+ }
2523
+ },
2524
+ async () => {
2525
+ try {
2526
+ const report = graphHygieneReport(options.workspacePath);
2527
+ return okResult(
2528
+ report,
2529
+ `Graph hygiene: nodes=${report.nodeCount}, edges=${report.edgeCount}, orphans=${report.orphanCount}, broken=${report.brokenLinkCount}`
2530
+ );
2531
+ } catch (error) {
2532
+ return errorResult(error);
2533
+ }
2534
+ }
2535
+ );
2536
+ server.registerTool(
2537
+ "workgraph_thread_claim",
2538
+ {
2539
+ title: "Thread Claim",
2540
+ description: "Claim a thread for an actor (policy-scoped write).",
2541
+ inputSchema: {
2542
+ threadPath: z.string().min(1),
2543
+ actor: z.string().optional()
2544
+ },
2545
+ annotations: {
2546
+ destructiveHint: true,
2547
+ idempotentHint: false
2548
+ }
2549
+ },
2550
+ async (args) => {
2551
+ try {
2552
+ const actor = resolveActor(args.actor, options.defaultActor);
2553
+ const gate = checkWriteGate(options, actor, ["thread:claim", "mcp:write"]);
2554
+ if (!gate.allowed) return errorResult(gate.reason);
2555
+ const updated = claim(options.workspacePath, args.threadPath, actor);
2556
+ return okResult({ thread: updated }, `Claimed ${updated.path} as ${actor}.`);
2557
+ } catch (error) {
2558
+ return errorResult(error);
2559
+ }
2560
+ }
2561
+ );
2562
+ server.registerTool(
2563
+ "workgraph_thread_done",
2564
+ {
2565
+ title: "Thread Done",
2566
+ description: "Mark a thread as done with output summary (policy-scoped write).",
2567
+ inputSchema: {
2568
+ threadPath: z.string().min(1),
2569
+ actor: z.string().optional(),
2570
+ output: z.string().optional()
2571
+ },
2572
+ annotations: {
2573
+ destructiveHint: true,
2574
+ idempotentHint: false
2575
+ }
2576
+ },
2577
+ async (args) => {
2578
+ try {
2579
+ const actor = resolveActor(args.actor, options.defaultActor);
2580
+ const gate = checkWriteGate(options, actor, ["thread:done", "mcp:write"]);
2581
+ if (!gate.allowed) return errorResult(gate.reason);
2582
+ const updated = done(options.workspacePath, args.threadPath, actor, args.output);
2583
+ return okResult({ thread: updated }, `Marked ${updated.path} done as ${actor}.`);
2584
+ } catch (error) {
2585
+ return errorResult(error);
2586
+ }
2587
+ }
2588
+ );
2589
+ server.registerTool(
2590
+ "workgraph_checkpoint_create",
2591
+ {
2592
+ title: "Checkpoint Create",
2593
+ description: "Create a checkpoint primitive for hand-off continuity (policy-scoped write).",
2594
+ inputSchema: {
2595
+ actor: z.string().optional(),
2596
+ summary: z.string().min(1),
2597
+ next: z.array(z.string()).optional(),
2598
+ blocked: z.array(z.string()).optional(),
2599
+ tags: z.array(z.string()).optional()
2600
+ },
2601
+ annotations: {
2602
+ destructiveHint: true,
2603
+ idempotentHint: false
2604
+ }
2605
+ },
2606
+ async (args) => {
2607
+ try {
2608
+ const actor = resolveActor(args.actor, options.defaultActor);
2609
+ const gate = checkWriteGate(options, actor, ["checkpoint:create", "mcp:write"]);
2610
+ if (!gate.allowed) return errorResult(gate.reason);
2611
+ const checkpoint2 = checkpoint(options.workspacePath, actor, args.summary, {
2612
+ next: args.next,
2613
+ blocked: args.blocked,
2614
+ tags: args.tags
2615
+ });
2616
+ return okResult({ checkpoint: checkpoint2 }, `Created checkpoint ${checkpoint2.path}.`);
2617
+ } catch (error) {
2618
+ return errorResult(error);
2619
+ }
2620
+ }
2621
+ );
2622
+ server.registerTool(
2623
+ "workgraph_dispatch_create",
2624
+ {
2625
+ title: "Dispatch Create",
2626
+ description: "Create a dispatch run request (policy-scoped write).",
2627
+ inputSchema: {
2628
+ actor: z.string().optional(),
2629
+ objective: z.string().min(1),
2630
+ adapter: z.string().optional(),
2631
+ idempotencyKey: z.string().optional()
2632
+ },
2633
+ annotations: {
2634
+ destructiveHint: true,
2635
+ idempotentHint: false
2636
+ }
2637
+ },
2638
+ async (args) => {
2639
+ try {
2640
+ const actor = resolveActor(args.actor, options.defaultActor);
2641
+ const gate = checkWriteGate(options, actor, ["dispatch:run", "mcp:write"]);
2642
+ if (!gate.allowed) return errorResult(gate.reason);
2643
+ const run = createRun(options.workspacePath, {
2644
+ actor,
2645
+ objective: args.objective,
2646
+ adapter: args.adapter,
2647
+ idempotencyKey: args.idempotencyKey
2648
+ });
2649
+ return okResult({ run }, `Created run ${run.id} (${run.status}).`);
2650
+ } catch (error) {
2651
+ return errorResult(error);
2652
+ }
2653
+ }
2654
+ );
2655
+ server.registerTool(
2656
+ "workgraph_dispatch_execute",
2657
+ {
2658
+ title: "Dispatch Execute",
2659
+ description: "Execute one queued/running run through its adapter (policy-scoped write).",
2660
+ inputSchema: {
2661
+ actor: z.string().optional(),
2662
+ runId: z.string().min(1),
2663
+ agents: z.array(z.string()).optional(),
2664
+ maxSteps: z.number().int().min(1).max(5e3).optional(),
2665
+ stepDelayMs: z.number().int().min(0).max(5e3).optional(),
2666
+ space: z.string().optional(),
2667
+ createCheckpoint: z.boolean().optional()
2668
+ },
2669
+ annotations: {
2670
+ destructiveHint: true,
2671
+ idempotentHint: false
2672
+ }
2673
+ },
2674
+ async (args) => {
2675
+ try {
2676
+ const actor = resolveActor(args.actor, options.defaultActor);
2677
+ const gate = checkWriteGate(options, actor, ["dispatch:run", "mcp:write"]);
2678
+ if (!gate.allowed) return errorResult(gate.reason);
2679
+ const run = await executeRun(options.workspacePath, args.runId, {
2680
+ actor,
2681
+ agents: args.agents,
2682
+ maxSteps: args.maxSteps,
2683
+ stepDelayMs: args.stepDelayMs,
2684
+ space: args.space,
2685
+ createCheckpoint: args.createCheckpoint
2686
+ });
2687
+ return okResult({ run }, `Executed run ${run.id} -> ${run.status}.`);
2688
+ } catch (error) {
2689
+ return errorResult(error);
2690
+ }
2691
+ }
2692
+ );
2693
+ server.registerTool(
2694
+ "workgraph_dispatch_followup",
2695
+ {
2696
+ title: "Dispatch Follow-up",
2697
+ description: "Send follow-up input to a run (policy-scoped write).",
2698
+ inputSchema: {
2699
+ actor: z.string().optional(),
2700
+ runId: z.string().min(1),
2701
+ input: z.string().min(1)
2702
+ },
2703
+ annotations: {
2704
+ destructiveHint: true,
2705
+ idempotentHint: false
2706
+ }
2707
+ },
2708
+ async (args) => {
2709
+ try {
2710
+ const actor = resolveActor(args.actor, options.defaultActor);
2711
+ const gate = checkWriteGate(options, actor, ["dispatch:run", "mcp:write"]);
2712
+ if (!gate.allowed) return errorResult(gate.reason);
2713
+ const run = followup(options.workspacePath, args.runId, actor, args.input);
2714
+ return okResult({ run }, `Follow-up recorded for ${run.id}.`);
2715
+ } catch (error) {
2716
+ return errorResult(error);
2717
+ }
2718
+ }
2719
+ );
2720
+ server.registerTool(
2721
+ "workgraph_dispatch_stop",
2722
+ {
2723
+ title: "Dispatch Stop",
2724
+ description: "Stop/cancel a run (policy-scoped write).",
2725
+ inputSchema: {
2726
+ actor: z.string().optional(),
2727
+ runId: z.string().min(1)
2728
+ },
2729
+ annotations: {
2730
+ destructiveHint: true,
2731
+ idempotentHint: false
2732
+ }
2733
+ },
2734
+ async (args) => {
2735
+ try {
2736
+ const actor = resolveActor(args.actor, options.defaultActor);
2737
+ const gate = checkWriteGate(options, actor, ["dispatch:run", "mcp:write"]);
2738
+ if (!gate.allowed) return errorResult(gate.reason);
2739
+ const run = stop(options.workspacePath, args.runId, actor);
2740
+ return okResult({ run }, `Stopped run ${run.id}.`);
2741
+ } catch (error) {
2742
+ return errorResult(error);
2743
+ }
2744
+ }
2690
2745
  );
2691
2746
  }
2692
- var ONBOARDING_STATUS_TRANSITIONS = {
2693
- active: ["paused", "completed"],
2694
- paused: ["active", "completed"],
2695
- completed: []
2696
- };
2697
- function titleCase2(value) {
2698
- return value.split(/[-_\s]/g).filter(Boolean).map((part) => part[0].toUpperCase() + part.slice(1)).join(" ");
2747
+ function resolveActor(actor, defaultActor) {
2748
+ const resolved = actor ?? defaultActor ?? "anonymous";
2749
+ return String(resolved);
2699
2750
  }
2700
-
2701
- // src/search-qmd-adapter.ts
2702
- var search_qmd_adapter_exports = {};
2703
- __export(search_qmd_adapter_exports, {
2704
- search: () => search
2705
- });
2706
- function search(workspacePath, text, options = {}) {
2707
- const requestedMode = options.mode ?? "auto";
2708
- const qmdEnabled = process.env.WORKGRAPH_QMD_ENDPOINT && process.env.WORKGRAPH_QMD_ENDPOINT.trim().length > 0;
2709
- if (requestedMode === "qmd" && !qmdEnabled) {
2710
- const results = keywordSearch(workspacePath, text, {
2711
- type: options.type,
2712
- limit: options.limit
2713
- });
2751
+ function checkWriteGate(options, actor, requiredCapabilities) {
2752
+ if (options.readOnly) {
2714
2753
  return {
2715
- mode: "core",
2716
- query: text,
2717
- results,
2718
- fallbackReason: "QMD mode requested but WORKGRAPH_QMD_ENDPOINT is not configured."
2754
+ allowed: false,
2755
+ reason: "MCP server is configured read-only; write tool is disabled."
2719
2756
  };
2720
2757
  }
2721
- if (requestedMode === "qmd" && qmdEnabled) {
2722
- const results = keywordSearch(workspacePath, text, {
2723
- type: options.type,
2724
- limit: options.limit
2725
- });
2758
+ if (actor === "system") {
2759
+ return { allowed: true };
2760
+ }
2761
+ const party = getParty(options.workspacePath, actor);
2762
+ if (!party) {
2726
2763
  return {
2727
- mode: "qmd",
2728
- query: text,
2729
- results,
2730
- fallbackReason: "QMD endpoint configured; using core-compatible local ranking in MVP."
2764
+ allowed: false,
2765
+ reason: `Policy gate blocked MCP write: actor "${actor}" is not a registered party.`
2731
2766
  };
2732
2767
  }
2733
- if (requestedMode === "auto" && qmdEnabled) {
2734
- const results = keywordSearch(workspacePath, text, {
2735
- type: options.type,
2736
- limit: options.limit
2737
- });
2768
+ const hasCapability = requiredCapabilities.some((capability) => party.capabilities.includes(capability));
2769
+ if (!hasCapability) {
2738
2770
  return {
2739
- mode: "qmd",
2740
- query: text,
2741
- results,
2742
- fallbackReason: "Auto mode selected; QMD endpoint detected; using core-compatible local ranking in MVP."
2771
+ allowed: false,
2772
+ reason: `Policy gate blocked MCP write: actor "${actor}" lacks capabilities [${requiredCapabilities.join(", ")}].`
2743
2773
  };
2744
2774
  }
2775
+ return { allowed: true };
2776
+ }
2777
+ function okResult(data, summary) {
2745
2778
  return {
2746
- mode: "core",
2747
- query: text,
2748
- results: keywordSearch(workspacePath, text, {
2749
- type: options.type,
2750
- limit: options.limit
2751
- })
2779
+ content: [
2780
+ {
2781
+ type: "text",
2782
+ text: `${summary}
2783
+
2784
+ ${toPrettyJson(data)}`
2785
+ }
2786
+ ],
2787
+ structuredContent: data
2752
2788
  };
2753
2789
  }
2754
-
2755
- // src/trigger.ts
2756
- var trigger_exports = {};
2757
- __export(trigger_exports, {
2758
- fireTrigger: () => fireTrigger
2759
- });
2760
- import { createHash } from "crypto";
2761
- function fireTrigger(workspacePath, triggerPath, options) {
2762
- const trigger = read(workspacePath, triggerPath);
2763
- if (!trigger) throw new Error(`Trigger not found: ${triggerPath}`);
2764
- if (trigger.type !== "trigger") throw new Error(`Target is not a trigger primitive: ${triggerPath}`);
2765
- const triggerStatus = String(trigger.fields.status ?? "draft");
2766
- if (!["approved", "active"].includes(triggerStatus)) {
2767
- throw new Error(`Trigger must be approved/active to fire. Current status: ${triggerStatus}`);
2768
- }
2769
- const objective = options.objective ?? `Trigger ${String(trigger.fields.title ?? triggerPath)} fired action ${String(trigger.fields.action ?? "run")}`;
2770
- const eventSeed = options.eventKey ?? (/* @__PURE__ */ new Date()).toISOString();
2771
- const idempotencyKey = buildIdempotencyKey(triggerPath, eventSeed, objective);
2772
- const run = createRun(workspacePath, {
2773
- actor: options.actor,
2774
- objective,
2775
- context: {
2776
- trigger_path: triggerPath,
2777
- trigger_event: String(trigger.fields.event ?? ""),
2778
- ...options.context
2779
- },
2780
- idempotencyKey
2781
- });
2782
- append(workspacePath, options.actor, "create", triggerPath, "trigger", {
2783
- fired: true,
2784
- event_key: eventSeed,
2785
- run_id: run.id,
2786
- idempotency_key: idempotencyKey
2787
- });
2790
+ function errorResult(error) {
2791
+ const text = error instanceof Error ? error.message : String(error);
2788
2792
  return {
2789
- triggerPath,
2790
- run,
2791
- idempotencyKey
2793
+ isError: true,
2794
+ content: [
2795
+ {
2796
+ type: "text",
2797
+ text
2798
+ }
2799
+ ]
2792
2800
  };
2793
2801
  }
2794
- function buildIdempotencyKey(triggerPath, eventSeed, objective) {
2795
- return createHash("sha256").update(`${triggerPath}:${eventSeed}:${objective}`).digest("hex").slice(0, 32);
2802
+ function toPrettyJson(value) {
2803
+ return JSON.stringify(value, null, 2);
2804
+ }
2805
+ function renderStatusSummary(snapshot) {
2806
+ return [
2807
+ `threads(total=${snapshot.threads.total}, open=${snapshot.threads.open}, active=${snapshot.threads.active}, blocked=${snapshot.threads.blocked}, done=${snapshot.threads.done})`,
2808
+ `claims(active=${snapshot.claims.active})`,
2809
+ `primitives(total=${snapshot.primitives.total})`
2810
+ ].join(" ");
2796
2811
  }
2797
2812
 
2798
2813
  export {
2814
+ __export,
2799
2815
  THREAD_STATUS_TRANSITIONS,
2816
+ append,
2817
+ historyOf,
2818
+ allClaims,
2819
+ recent,
2800
2820
  ledger_exports,
2821
+ loadRegistry,
2822
+ saveRegistry,
2823
+ listTypes,
2801
2824
  registry_exports,
2825
+ refreshWikiLinkGraphIndex,
2802
2826
  graph_exports,
2827
+ loadPolicyRegistry,
2803
2828
  policy_exports,
2829
+ create,
2830
+ read,
2831
+ list,
2832
+ update,
2804
2833
  store_exports,
2834
+ createThread,
2805
2835
  thread_exports,
2806
- bases_exports,
2807
- workspace_exports,
2808
- command_center_exports,
2809
- skill_exports,
2836
+ keywordSearch,
2810
2837
  query_exports,
2838
+ checkpoint,
2811
2839
  orientation_exports,
2812
- board_exports,
2840
+ CursorCloudAdapter,
2841
+ createRun,
2813
2842
  dispatch_exports,
2814
- onboard_exports,
2815
- search_qmd_adapter_exports,
2816
- trigger_exports
2843
+ createWorkgraphMcpServer,
2844
+ startWorkgraphMcpServer,
2845
+ mcp_server_exports
2817
2846
  };