studiograph 1.3.3 → 1.3.4

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.
Files changed (290) hide show
  1. package/dist/agent/orchestrator.d.ts +10 -0
  2. package/dist/agent/orchestrator.js +26 -7
  3. package/dist/agent/orchestrator.js.map +1 -1
  4. package/dist/agent/skills/sync-configuration.md +4 -29
  5. package/dist/agent/skills/sync-setup.md +2 -4
  6. package/dist/agent/tools/graph-tools.d.ts +5 -1
  7. package/dist/agent/tools/graph-tools.js +161 -9
  8. package/dist/agent/tools/graph-tools.js.map +1 -1
  9. package/dist/agent/tools/ops-tools.js +15 -126
  10. package/dist/agent/tools/ops-tools.js.map +1 -1
  11. package/dist/agent/tools/permission-tools.d.ts +15 -14
  12. package/dist/agent/tools/permission-tools.js +65 -128
  13. package/dist/agent/tools/permission-tools.js.map +1 -1
  14. package/dist/agent/tools/sync-tools.d.ts +7 -6
  15. package/dist/agent/tools/sync-tools.js +205 -178
  16. package/dist/agent/tools/sync-tools.js.map +1 -1
  17. package/dist/cli/commands/about.d.ts +13 -0
  18. package/dist/cli/commands/about.js +97 -0
  19. package/dist/cli/commands/about.js.map +1 -0
  20. package/dist/cli/commands/clone.d.ts +5 -2
  21. package/dist/cli/commands/clone.js +131 -62
  22. package/dist/cli/commands/clone.js.map +1 -1
  23. package/dist/cli/commands/connector.d.ts +2 -16
  24. package/dist/cli/commands/connector.js +32 -109
  25. package/dist/cli/commands/connector.js.map +1 -1
  26. package/dist/cli/commands/deploy.d.ts +0 -1
  27. package/dist/cli/commands/deploy.js +13 -103
  28. package/dist/cli/commands/deploy.js.map +1 -1
  29. package/dist/cli/commands/init.js +6 -93
  30. package/dist/cli/commands/init.js.map +1 -1
  31. package/dist/cli/commands/join.d.ts +3 -2
  32. package/dist/cli/commands/join.js +142 -97
  33. package/dist/cli/commands/join.js.map +1 -1
  34. package/dist/cli/commands/redeploy.js +1 -2
  35. package/dist/cli/commands/redeploy.js.map +1 -1
  36. package/dist/cli/commands/serve.d.ts +1 -3
  37. package/dist/cli/commands/serve.js +29 -109
  38. package/dist/cli/commands/serve.js.map +1 -1
  39. package/dist/cli/commands/start.js +1 -1
  40. package/dist/cli/commands/start.js.map +1 -1
  41. package/dist/cli/commands/sync-collection.d.ts +14 -0
  42. package/dist/cli/commands/sync-collection.js +366 -0
  43. package/dist/cli/commands/sync-collection.js.map +1 -0
  44. package/dist/cli/commands/sync.d.ts +4 -2
  45. package/dist/cli/commands/sync.js +529 -94
  46. package/dist/cli/commands/sync.js.map +1 -1
  47. package/dist/cli/index.js +15 -30
  48. package/dist/cli/index.js.map +1 -1
  49. package/dist/cli/setup-wizard.d.ts +0 -13
  50. package/dist/cli/setup-wizard.js +6 -81
  51. package/dist/cli/setup-wizard.js.map +1 -1
  52. package/dist/core/graph.d.ts +8 -2
  53. package/dist/core/graph.js +11 -7
  54. package/dist/core/graph.js.map +1 -1
  55. package/dist/core/types.d.ts +149 -21
  56. package/dist/core/types.js +16 -4
  57. package/dist/core/types.js.map +1 -1
  58. package/dist/core/workspace-manager.js +1 -5
  59. package/dist/core/workspace-manager.js.map +1 -1
  60. package/dist/core/workspace.d.ts +11 -4
  61. package/dist/core/workspace.js +61 -26
  62. package/dist/core/workspace.js.map +1 -1
  63. package/dist/integrations/asana.d.ts +26 -0
  64. package/dist/integrations/asana.js +77 -0
  65. package/dist/integrations/asana.js.map +1 -0
  66. package/dist/integrations/figma-local.d.ts +16 -0
  67. package/dist/integrations/figma-local.js +10 -0
  68. package/dist/integrations/figma-local.js.map +1 -0
  69. package/dist/integrations/figma.d.ts +17 -0
  70. package/dist/integrations/figma.js +16 -0
  71. package/dist/integrations/figma.js.map +1 -0
  72. package/dist/integrations/granola.d.ts +24 -0
  73. package/dist/integrations/granola.js +59 -0
  74. package/dist/integrations/granola.js.map +1 -0
  75. package/dist/integrations/linear.d.ts +14 -0
  76. package/dist/integrations/linear.js +70 -0
  77. package/dist/integrations/linear.js.map +1 -0
  78. package/dist/integrations/paper-local.d.ts +14 -0
  79. package/dist/integrations/paper-local.js +10 -0
  80. package/dist/integrations/paper-local.js.map +1 -0
  81. package/dist/integrations/paper.d.ts +2 -0
  82. package/dist/integrations/paper.js +10 -0
  83. package/dist/integrations/paper.js.map +1 -0
  84. package/dist/integrations/pipedrive.d.ts +26 -0
  85. package/dist/integrations/pipedrive.js +97 -0
  86. package/dist/integrations/pipedrive.js.map +1 -0
  87. package/dist/integrations/registry.d.ts +15 -0
  88. package/dist/integrations/registry.js +27 -0
  89. package/dist/integrations/registry.js.map +1 -0
  90. package/dist/integrations/types.d.ts +34 -0
  91. package/dist/integrations/types.js +9 -0
  92. package/dist/integrations/types.js.map +1 -0
  93. package/dist/mcp/connector-manager.d.ts +45 -31
  94. package/dist/mcp/connector-manager.js +164 -116
  95. package/dist/mcp/connector-manager.js.map +1 -1
  96. package/dist/mcp/server-oauth-provider.d.ts +56 -0
  97. package/dist/mcp/server-oauth-provider.js +138 -0
  98. package/dist/mcp/server-oauth-provider.js.map +1 -0
  99. package/dist/server/chrome/chrome.css +142 -28
  100. package/dist/server/chrome/chrome.js +46 -220
  101. package/dist/server/collab-authority.d.ts +70 -0
  102. package/dist/server/collab-authority.js +218 -0
  103. package/dist/server/collab-authority.js.map +1 -0
  104. package/dist/server/collab.d.ts +29 -0
  105. package/dist/server/collab.js +195 -0
  106. package/dist/server/collab.js.map +1 -0
  107. package/dist/server/commit-scheduler.d.ts +39 -0
  108. package/dist/server/commit-scheduler.js +113 -0
  109. package/dist/server/commit-scheduler.js.map +1 -0
  110. package/dist/server/index.d.ts +0 -2
  111. package/dist/server/index.js +166 -55
  112. package/dist/server/index.js.map +1 -1
  113. package/dist/server/routes/auth-api.d.ts +6 -0
  114. package/dist/server/routes/auth-api.js +78 -0
  115. package/dist/server/routes/auth-api.js.map +1 -1
  116. package/dist/server/routes/chat.js +4 -0
  117. package/dist/server/routes/chat.js.map +1 -1
  118. package/dist/server/routes/collab.d.ts +6 -0
  119. package/dist/server/routes/collab.js +10 -0
  120. package/dist/server/routes/collab.js.map +1 -0
  121. package/dist/server/routes/git-http.d.ts +23 -0
  122. package/dist/server/routes/git-http.js +251 -0
  123. package/dist/server/routes/git-http.js.map +1 -0
  124. package/dist/server/routes/graph-api.d.ts +6 -2
  125. package/dist/server/routes/graph-api.js +266 -82
  126. package/dist/server/routes/graph-api.js.map +1 -1
  127. package/dist/server/routes/mcp.d.ts +12 -0
  128. package/dist/server/routes/mcp.js +35 -0
  129. package/dist/server/routes/mcp.js.map +1 -0
  130. package/dist/server/routes/permissions-api.d.ts +6 -4
  131. package/dist/server/routes/permissions-api.js +53 -167
  132. package/dist/server/routes/permissions-api.js.map +1 -1
  133. package/dist/server/routes/sync-api.d.ts +26 -0
  134. package/dist/server/routes/sync-api.js +757 -0
  135. package/dist/server/routes/sync-api.js.map +1 -0
  136. package/dist/server/routes/ws.d.ts +9 -0
  137. package/dist/server/routes/ws.js +131 -0
  138. package/dist/server/routes/ws.js.map +1 -0
  139. package/dist/server/session-manager.d.ts +40 -0
  140. package/dist/server/session-manager.js +132 -0
  141. package/dist/server/session-manager.js.map +1 -0
  142. package/dist/server/ws-hub.d.ts +130 -0
  143. package/dist/server/ws-hub.js +250 -0
  144. package/dist/server/ws-hub.js.map +1 -0
  145. package/dist/server/yjs-manager.d.ts +59 -0
  146. package/dist/server/yjs-manager.js +194 -0
  147. package/dist/server/yjs-manager.js.map +1 -0
  148. package/dist/services/auth-service.d.ts +74 -0
  149. package/dist/services/auth-service.js +286 -6
  150. package/dist/services/auth-service.js.map +1 -1
  151. package/dist/services/git.d.ts +6 -0
  152. package/dist/services/git.js +32 -2
  153. package/dist/services/git.js.map +1 -1
  154. package/dist/services/sync/collection-sync.d.ts +73 -0
  155. package/dist/services/sync/collection-sync.js +726 -0
  156. package/dist/services/sync/collection-sync.js.map +1 -0
  157. package/dist/services/sync/commit.js +5 -20
  158. package/dist/services/sync/commit.js.map +1 -1
  159. package/dist/services/sync/data-fetcher.d.ts +31 -0
  160. package/dist/services/sync/data-fetcher.js +12 -0
  161. package/dist/services/sync/data-fetcher.js.map +1 -0
  162. package/dist/services/sync/entity-refresh.d.ts +30 -0
  163. package/dist/services/sync/entity-refresh.js +275 -0
  164. package/dist/services/sync/entity-refresh.js.map +1 -0
  165. package/dist/services/sync/frontmatter-extractor.d.ts +2 -2
  166. package/dist/services/sync/frontmatter-extractor.js +1 -2
  167. package/dist/services/sync/frontmatter-extractor.js.map +1 -1
  168. package/dist/services/sync/graph-match.js +1 -1
  169. package/dist/services/sync/graph-match.js.map +1 -1
  170. package/dist/services/sync/mcp-client.d.ts +16 -4
  171. package/dist/services/sync/mcp-client.js +34 -20
  172. package/dist/services/sync/mcp-client.js.map +1 -1
  173. package/dist/services/sync/prompts.js +1 -1
  174. package/dist/services/sync/reconciler.js +1 -2
  175. package/dist/services/sync/reconciler.js.map +1 -1
  176. package/dist/services/sync/rest-client.d.ts +40 -0
  177. package/dist/services/sync/rest-client.js +100 -0
  178. package/dist/services/sync/rest-client.js.map +1 -0
  179. package/dist/services/sync/source-config.d.ts +23 -1
  180. package/dist/services/sync/source-config.js +112 -16
  181. package/dist/services/sync/source-config.js.map +1 -1
  182. package/dist/services/sync/source-definitions/asana.d.ts +3 -4
  183. package/dist/services/sync/source-definitions/asana.js +7 -13
  184. package/dist/services/sync/source-definitions/asana.js.map +1 -1
  185. package/dist/services/sync/source-definitions/definitions.d.ts +5 -18
  186. package/dist/services/sync/source-definitions/definitions.js +4 -22
  187. package/dist/services/sync/source-definitions/definitions.js.map +1 -1
  188. package/dist/services/sync/source-definitions/granola.d.ts +1 -1
  189. package/dist/services/sync/source-definitions/granola.js +11 -18
  190. package/dist/services/sync/source-definitions/granola.js.map +1 -1
  191. package/dist/services/sync/source-definitions/linear.d.ts +1 -1
  192. package/dist/services/sync/source-definitions/linear.js +17 -15
  193. package/dist/services/sync/source-definitions/linear.js.map +1 -1
  194. package/dist/services/sync/source-definitions/pipedrive.d.ts +1 -1
  195. package/dist/services/sync/source-definitions/pipedrive.js +6 -15
  196. package/dist/services/sync/source-definitions/pipedrive.js.map +1 -1
  197. package/dist/services/sync/staging.js +1 -2
  198. package/dist/services/sync/staging.js.map +1 -1
  199. package/dist/services/sync/structured-extractor.d.ts +8 -2
  200. package/dist/services/sync/structured-extractor.js +243 -35
  201. package/dist/services/sync/structured-extractor.js.map +1 -1
  202. package/dist/services/sync/types.d.ts +192 -23
  203. package/dist/services/sync/unstructured-extractor.d.ts +1 -3
  204. package/dist/services/sync/unstructured-extractor.js +2 -14
  205. package/dist/services/sync/unstructured-extractor.js.map +1 -1
  206. package/dist/utils/git.d.ts +33 -20
  207. package/dist/utils/git.js +119 -62
  208. package/dist/utils/git.js.map +1 -1
  209. package/dist/utils/preflight.d.ts +1 -15
  210. package/dist/utils/preflight.js +1 -35
  211. package/dist/utils/preflight.js.map +1 -1
  212. package/dist/web/_app/immutable/assets/0.CupILLQs.css +1 -0
  213. package/dist/web/_app/immutable/assets/3.CtJi4Cy9.css +1 -0
  214. package/dist/web/_app/immutable/assets/5.CydFyZSu.css +1 -0
  215. package/dist/web/_app/immutable/assets/6.kqeOo0OW.css +1 -0
  216. package/dist/web/_app/immutable/assets/7.CseIx7qQ.css +1 -0
  217. package/dist/web/_app/immutable/assets/8.BYpFDZHK.css +1 -0
  218. package/dist/web/_app/immutable/assets/AppShell.Ch_ef9hJ.css +1 -0
  219. package/dist/web/_app/immutable/assets/ChatPanel.CP-_8txt.css +1 -0
  220. package/dist/web/_app/immutable/chunks/0oxpWEgM.js +1 -0
  221. package/dist/web/_app/immutable/chunks/B1y7Wy5O.js +18 -0
  222. package/dist/web/_app/immutable/chunks/B7eduG_j.js +64 -0
  223. package/dist/web/_app/immutable/chunks/BBLgaWN8.js +1 -0
  224. package/dist/web/_app/immutable/chunks/BCB5cYCz.js +2 -0
  225. package/dist/web/_app/immutable/chunks/{aosHekRC.js → BPUy9_sS.js} +1 -1
  226. package/dist/web/_app/immutable/chunks/BVBRzmeQ.js +7 -0
  227. package/dist/web/_app/immutable/chunks/{CUzqHQY_.js → BXuvR8Ks.js} +2 -1
  228. package/dist/web/_app/immutable/chunks/BeBar3OL.js +1 -0
  229. package/dist/web/_app/immutable/chunks/BuOTIbJu.js +1 -0
  230. package/dist/web/_app/immutable/chunks/CLFba8FK.js +5 -0
  231. package/dist/web/_app/immutable/chunks/CQCkXCml.js +1 -0
  232. package/dist/web/_app/immutable/chunks/CXuhHL4d.js +1 -0
  233. package/dist/web/_app/immutable/chunks/Cg9NOuOl.js +27 -0
  234. package/dist/web/_app/immutable/chunks/Cs5oz2oJ.js +5 -0
  235. package/dist/web/_app/immutable/chunks/Cs_ROD7H.js +2 -0
  236. package/dist/web/_app/immutable/chunks/D2aTbzFm.js +3 -0
  237. package/dist/web/_app/immutable/chunks/D4FXhiC2.js +1 -0
  238. package/dist/web/_app/immutable/chunks/D4VHRYeB.js +1 -0
  239. package/dist/web/_app/immutable/chunks/DCGSm8Hl.js +1 -0
  240. package/dist/web/_app/immutable/chunks/DP09rP34.js +2 -0
  241. package/dist/web/_app/immutable/chunks/DiP47fAp.js +1 -0
  242. package/dist/web/_app/immutable/chunks/DptGlK8O.js +1 -0
  243. package/dist/web/_app/immutable/chunks/O0fx2ss6.js +1 -0
  244. package/dist/web/_app/immutable/chunks/xBRYfpah.js +1 -0
  245. package/dist/web/_app/immutable/entry/app.FgnywZP_.js +2 -0
  246. package/dist/web/_app/immutable/entry/start.Bsa-zlPf.js +1 -0
  247. package/dist/web/_app/immutable/nodes/0.D3SW-LMc.js +10 -0
  248. package/dist/web/_app/immutable/nodes/1.y0c5TQTP.js +1 -0
  249. package/dist/web/_app/immutable/nodes/2.BQfSep9-.js +1 -0
  250. package/dist/web/_app/immutable/nodes/3.CC4Y-xMM.js +11 -0
  251. package/dist/web/_app/immutable/nodes/{5.BBpmYkAu.js → 4.Dp0Z-oPW.js} +2 -2
  252. package/dist/web/_app/immutable/nodes/5.gjZ03DON.js +2 -0
  253. package/dist/web/_app/immutable/nodes/6.dRNIwcJQ.js +1 -0
  254. package/dist/web/_app/immutable/nodes/7.I4Gjes3o.js +2 -0
  255. package/dist/web/_app/immutable/nodes/8.Dj14D7uH.js +1 -0
  256. package/dist/web/_app/version.json +1 -1
  257. package/dist/web/index.html +10 -12
  258. package/package.json +12 -2
  259. package/dist/web/_app/immutable/assets/0.CDbX4Cwz.css +0 -1
  260. package/dist/web/_app/immutable/assets/3.BJy7pVXi.css +0 -1
  261. package/dist/web/_app/immutable/assets/4.Ad16uh9o.css +0 -1
  262. package/dist/web/_app/immutable/assets/6.Bm2i7O0j.css +0 -1
  263. package/dist/web/_app/immutable/assets/AppShell.D0rmbdqF.css +0 -1
  264. package/dist/web/_app/immutable/assets/ChatPanel.RFD5GGYI.css +0 -1
  265. package/dist/web/_app/immutable/assets/editor.CPAf2SRV.css +0 -1
  266. package/dist/web/_app/immutable/chunks/479TgXB4.js +0 -1
  267. package/dist/web/_app/immutable/chunks/4QY4j-jX.js +0 -1
  268. package/dist/web/_app/immutable/chunks/BFb0g4TQ.js +0 -64
  269. package/dist/web/_app/immutable/chunks/Bopa-Ask.js +0 -1
  270. package/dist/web/_app/immutable/chunks/COwytaCP.js +0 -1
  271. package/dist/web/_app/immutable/chunks/DEJSHbC3.js +0 -1
  272. package/dist/web/_app/immutable/chunks/DNywhIex.js +0 -23
  273. package/dist/web/_app/immutable/chunks/DTUXhwEY.js +0 -1
  274. package/dist/web/_app/immutable/chunks/DThXpa0U.js +0 -6
  275. package/dist/web/_app/immutable/chunks/Dh_H7Owr.js +0 -18
  276. package/dist/web/_app/immutable/chunks/Dml-u95b.js +0 -2
  277. package/dist/web/_app/immutable/chunks/DnlgZ_Tk.js +0 -5
  278. package/dist/web/_app/immutable/chunks/DtVH--hH.js +0 -6
  279. package/dist/web/_app/immutable/chunks/DvKVaE7M.js +0 -1
  280. package/dist/web/_app/immutable/chunks/MbiSz-iW.js +0 -2
  281. package/dist/web/_app/immutable/chunks/bSAC733J.js +0 -1
  282. package/dist/web/_app/immutable/entry/app.BvodXQQ0.js +0 -2
  283. package/dist/web/_app/immutable/entry/start.Bkui3Kyw.js +0 -1
  284. package/dist/web/_app/immutable/nodes/0.DfbCOBhn.js +0 -2
  285. package/dist/web/_app/immutable/nodes/1.vtxUGpe6.js +0 -1
  286. package/dist/web/_app/immutable/nodes/2.Cq29oW4h.js +0 -1
  287. package/dist/web/_app/immutable/nodes/3.SquslPZy.js +0 -1
  288. package/dist/web/_app/immutable/nodes/4.COV8FR8b.js +0 -16
  289. package/dist/web/_app/immutable/nodes/6.BBbh6z9I.js +0 -2
  290. /package/dist/web/_app/immutable/assets/{5.BhKgiXd2.css → 4.BhKgiXd2.css} +0 -0
@@ -1,10 +1,11 @@
1
1
  /**
2
- * Sync & Enrich Tools for Studiograph Agent
2
+ * Sync & Workspace Tools for Studiograph Agent
3
3
  *
4
- * These tools allow the agent to orchestrate sync and enrich operations:
5
- * - Run the sync pipeline, apply staging, commit
6
- * - Run LLM enrichment on entities
7
- * - Manage source configs
4
+ * These tools allow the agent to:
5
+ * - Sync external data into collections (collection-scoped sync)
6
+ * - Refresh individual synced entities from their source
7
+ * - Manage workspace git operations (commit, push, pull, status)
8
+ * - Inspect MCP connectors for sync configuration
8
9
  */
9
10
  import { Type as T } from '@sinclair/typebox';
10
11
  import { spawnSync } from 'child_process';
@@ -12,13 +13,13 @@ import { gitPush, gitPull } from '../../utils/git.js';
12
13
  import { existsSync } from 'fs';
13
14
  import { join } from 'path';
14
15
  import { homedir } from 'os';
15
- import { runSync } from '../../services/sync/sync-runner.js';
16
- import { commitStaging, applyStaging, commitRepos } from '../../services/sync/commit.js';
17
- import { runEnrichment } from '../../services/sync/enrichment.js';
16
+ import { commitRepos } from '../../services/sync/commit.js';
18
17
  import { StagingManager } from '../../services/sync/staging.js';
19
18
  import { SourceConfigManager } from '../../services/sync/source-config.js';
20
19
  import { ConnectorManager } from '../../mcp/connector-manager.js';
21
20
  import { SOURCE_DEFINITIONS } from '../../services/sync/source-definitions/definitions.js';
21
+ import { syncRepo, syncWorkspaceRule } from '../../services/sync/collection-sync.js';
22
+ import { refreshEntity } from '../../services/sync/entity-refresh.js';
22
23
  import { wrapToolResult } from './graph-tools.js';
23
24
  import { VectorService } from '../../services/vector-service.js';
24
25
  import { BaseGraphManager } from '../../core/graph.js';
@@ -29,30 +30,36 @@ import { loadUserConfig } from '../../core/user-config.js';
29
30
  export function createSyncTools(workspacePath, workspaceConfig, registryRef) {
30
31
  const getRegistry = () => registryRef?.registry ?? null;
31
32
  return [
32
- // Run sync pipeline
33
+ // ── Collection-scoped sync tools ─────────────────────────────────────────
34
+ // Sync a collection — runs collection-level rules AND workspace-level rules targeting it
33
35
  {
34
- name: 'sync_run',
35
- label: 'Run Sync',
36
- description: 'Run the sync pipeline to extract data from external sources into staging. Review with sync_status before committing.',
36
+ name: 'sync_collection',
37
+ label: 'Sync Collection',
38
+ description: 'Pull new entities from external sources into a collection. Runs both collection-level sync rules and any workspace-level sync rules that target this collection. Creates new entities only — existing entities (matched by source_ref) are never updated. Always confirm with the user before calling.',
37
39
  parameters: T.Object({
38
- sources: T.Optional(T.Array(T.String(), { description: 'Source names to sync (default: all enabled)' })),
39
- dryRun: T.Optional(T.Boolean({ description: 'Preview without writing staging files' })),
40
- incremental: T.Optional(T.Boolean({ description: 'Only sync records updated since last sync' })),
40
+ collection: T.String({ description: 'Collection name to sync' }),
41
41
  }),
42
42
  execute: async (toolCallId, params) => {
43
43
  try {
44
44
  const progress = [];
45
- const result = await runSync({
45
+ const { collectionResults, workspaceResults } = await syncRepo({
46
46
  workspacePath,
47
- sources: params.sources,
48
- dryRun: params.dryRun,
49
- incremental: params.incremental,
47
+ collectionName: params.collection,
48
+ workspaceConfig,
50
49
  schemaExtensions: workspaceConfig.schema_extensions,
51
- }, (msg) => progress.push(msg));
50
+ onProgress: (msg) => progress.push(msg),
51
+ });
52
+ const totalCreated = collectionResults.reduce((s, r) => s + r.created + r.derived, 0) +
53
+ workspaceResults.reduce((s, r) => r.targets.reduce((ts, t) => ts + t.created + t.derived, 0) + s, 0);
54
+ const totalSkipped = collectionResults.reduce((s, r) => s + r.skipped, 0) +
55
+ workspaceResults.reduce((s, r) => r.targets.reduce((ts, t) => ts + t.skipped, 0) + s, 0);
52
56
  return wrapToolResult({
53
57
  success: true,
54
- result,
58
+ collectionResults,
59
+ workspaceResults,
60
+ summary: `Created ${totalCreated}, skipped ${totalSkipped}`,
55
61
  progress: progress.join('\n'),
62
+ nextSteps: totalCreated > 0 ? 'Commit changes with workspace_commit' : undefined,
56
63
  });
57
64
  }
58
65
  catch (error) {
@@ -63,20 +70,37 @@ export function createSyncTools(workspacePath, workspaceConfig, registryRef) {
63
70
  }
64
71
  },
65
72
  },
66
- // Check staging status
73
+ // Run all workspace-level sync rules
67
74
  {
68
- name: 'sync_status',
69
- label: 'Sync Status',
70
- description: 'Check what is currently in staging (pending review). Shows counts of creates, updates, and skips by entity type.',
75
+ name: 'sync_workspace',
76
+ label: 'Sync Workspace',
77
+ description: 'Run all workspace-level sync rules defined in workspace.json. Each rule fetches from its source once and distributes records to multiple target collections. Use this instead of sync_collection when multiple collections share the same source. Always confirm with the user before calling.',
71
78
  parameters: T.Object({}),
72
- execute: async (toolCallId, params) => {
79
+ execute: async (toolCallId, _params) => {
73
80
  try {
74
- const staging = new StagingManager(workspacePath);
75
- const summary = staging.getSummary();
81
+ const rules = workspaceConfig.sync ?? [];
82
+ if (rules.length === 0) {
83
+ return wrapToolResult({ success: true, results: [], summary: 'No workspace-level sync rules configured' });
84
+ }
85
+ const progress = [];
86
+ const results = [];
87
+ for (const rule of rules) {
88
+ const result = await syncWorkspaceRule({
89
+ workspacePath,
90
+ rule,
91
+ workspaceConfig,
92
+ schemaExtensions: workspaceConfig.schema_extensions,
93
+ onProgress: (msg) => progress.push(msg),
94
+ });
95
+ results.push(result);
96
+ }
97
+ const totalCreated = results.reduce((s, r) => r.targets.reduce((ts, t) => ts + t.created + t.derived, 0) + s, 0);
76
98
  return wrapToolResult({
77
99
  success: true,
78
- hasStaging: summary.total > 0,
79
- summary,
100
+ results,
101
+ summary: `Ran ${rules.length} workspace rule(s), created ${totalCreated} entities`,
102
+ progress: progress.join('\n'),
103
+ nextSteps: totalCreated > 0 ? 'Commit changes with workspace_commit' : undefined,
80
104
  });
81
105
  }
82
106
  catch (error) {
@@ -87,23 +111,80 @@ export function createSyncTools(workspacePath, workspaceConfig, registryRef) {
87
111
  }
88
112
  },
89
113
  },
90
- // Apply staging to entity files (no git)
114
+ // Refresh a single synced entity
91
115
  {
92
- name: 'sync_apply',
93
- label: 'Apply Staging',
94
- description: 'Apply staging files to the knowledge graph (writes entity files, does not git commit). Always confirm with the user before calling this.',
116
+ name: 'sync_refresh',
117
+ label: 'Refresh Synced Entity',
118
+ description: 'Refresh a single entity from its external source. Shows what fields have changed. Does NOT apply changes automatically — use the apply parameter or tell the user about the changes.',
95
119
  parameters: T.Object({
96
- force: T.Optional(T.Boolean({ description: 'Force apply even with uncommitted changes in repos' })),
120
+ entity_id: T.String({ description: 'Entity ID to refresh' }),
121
+ collection: T.Optional(T.String({ description: 'Collection name (auto-detected if omitted)' })),
122
+ entity_type: T.Optional(T.String({ description: 'Entity type (auto-detected if omitted)' })),
123
+ apply: T.Optional(T.Boolean({ description: 'Apply changes immediately (default: false, just show diff)' })),
97
124
  }),
98
125
  execute: async (toolCallId, params) => {
99
126
  try {
100
- const progress = [];
101
- const result = await applyStaging(workspacePath, (msg) => progress.push(msg), { force: params.force });
127
+ let { collection, entity_type, entity_id, apply } = params;
128
+ // Auto-detect collection and entity_type if not provided
129
+ if (!collection || !entity_type) {
130
+ for (const repo of workspaceConfig.repos) {
131
+ try {
132
+ const repoPath = join(workspacePath, repo.path);
133
+ if (!existsSync(repoPath))
134
+ continue;
135
+ const graph = new BaseGraphManager({
136
+ repoPath,
137
+ repoName: repo.name,
138
+ gitUser: { id: 'agent', name: 'Agent', email: 'agent@studiograph.local' },
139
+ });
140
+ const entities = graph.list();
141
+ const match = entities.find(e => e.data?.entity_id === entity_id);
142
+ if (match) {
143
+ collection = repo.name;
144
+ entity_type = match.entityType;
145
+ break;
146
+ }
147
+ }
148
+ catch { /* skip */ }
149
+ }
150
+ }
151
+ if (!collection || !entity_type) {
152
+ return wrapToolResult({
153
+ success: false,
154
+ error: `Entity "${entity_id}" not found in any collection`,
155
+ });
156
+ }
157
+ const result = await refreshEntity({
158
+ workspacePath,
159
+ collectionName: collection,
160
+ entityType: entity_type,
161
+ entityId: entity_id,
162
+ workspaceConfig: workspaceConfig,
163
+ schemaExtensions: workspaceConfig.schema_extensions,
164
+ apply: apply ?? false,
165
+ });
166
+ if (result.diffs.length === 0 && !result.body_changed) {
167
+ return wrapToolResult({
168
+ success: true,
169
+ message: 'Entity is already up to date with the source.',
170
+ entity_id,
171
+ source_ref: result.source_ref,
172
+ });
173
+ }
102
174
  return wrapToolResult({
103
175
  success: true,
104
- result: { created: result.created, updated: result.updated, skipped: result.skipped },
105
- progress: progress.join('\n'),
106
- nextSteps: 'Review changes with workspace_review, then commit with workspace_commit or studiograph commit',
176
+ entity_id,
177
+ source_ref: result.source_ref,
178
+ applied: result.applied,
179
+ changes: result.diffs.map(d => ({
180
+ field: d.field,
181
+ from: d.old_value,
182
+ to: d.new_value,
183
+ })),
184
+ body_changed: result.body_changed,
185
+ nextSteps: result.applied
186
+ ? 'Changes applied. Commit with workspace_commit.'
187
+ : 'Review the changes above and call sync_refresh with apply: true to apply them.',
107
188
  });
108
189
  }
109
190
  catch (error) {
@@ -114,21 +195,72 @@ export function createSyncTools(workspacePath, workspaceConfig, registryRef) {
114
195
  }
115
196
  },
116
197
  },
117
- // Git commit across workspace repos
198
+ // Show sync status across collections
118
199
  {
119
- name: 'workspace_commit',
120
- label: 'Commit Changes',
121
- description: 'Git commit entity changes across all workspace repos. Works after sync_apply, enrich_run, or manual edits. Always confirm with the user before calling this.',
200
+ name: 'sync_status',
201
+ label: 'Sync Status',
202
+ description: 'Show which collections have sync rules configured, their sources, and last sync time. Use this when users ask about sync configuration or what has been synced.',
122
203
  parameters: T.Object({
123
- messagePrefix: T.Optional(T.String({ description: 'Commit message prefix (default: "update")' })),
204
+ collection: T.Optional(T.String({ description: 'Filter to a specific collection' })),
124
205
  }),
125
206
  execute: async (toolCallId, params) => {
126
207
  try {
127
- const progress = [];
128
- await commitRepos(workspacePath, (msg) => progress.push(msg), { messagePrefix: params.messagePrefix ?? 'update' });
208
+ const repos = params.collection
209
+ ? workspaceConfig.repos.filter(r => r.name === params.collection)
210
+ : workspaceConfig.repos;
211
+ const collections = [];
212
+ for (const repo of repos) {
213
+ const syncRules = repo.sync;
214
+ if (!syncRules || syncRules.length === 0)
215
+ continue;
216
+ let syncedCount = 0;
217
+ let lastSync = null;
218
+ try {
219
+ const repoPath = join(workspacePath, repo.path);
220
+ if (!existsSync(repoPath))
221
+ continue;
222
+ const graph = new BaseGraphManager({
223
+ repoPath,
224
+ repoName: repo.name,
225
+ gitUser: { id: 'agent', name: 'Agent', email: 'agent@studiograph.local' },
226
+ });
227
+ for (const entity of graph.list()) {
228
+ const syncedAt = entity.data?.synced_at;
229
+ if (syncedAt) {
230
+ syncedCount++;
231
+ if (!lastSync || syncedAt > lastSync)
232
+ lastSync = syncedAt;
233
+ }
234
+ }
235
+ }
236
+ catch { /* skip */ }
237
+ collections.push({ name: repo.name, syncRules, syncedCount, lastSync });
238
+ }
239
+ // Also list available source definitions
240
+ const configMgr = new SourceConfigManager(workspacePath);
241
+ const sources = configMgr.getAll().map(s => s.name);
242
+ // Check for new-format too
243
+ const { readdirSync } = await import('fs');
244
+ const sourcesDir = join(workspacePath, '.studiograph', 'sources');
245
+ if (existsSync(sourcesDir)) {
246
+ for (const file of readdirSync(sourcesDir)) {
247
+ if (!file.endsWith('.json'))
248
+ continue;
249
+ const name = file.replace('.json', '');
250
+ if (!sources.includes(name)) {
251
+ const def = configMgr.getDefinition(name);
252
+ if (def)
253
+ sources.push(name);
254
+ }
255
+ }
256
+ }
129
257
  return wrapToolResult({
130
258
  success: true,
131
- progress: progress.join('\n'),
259
+ collections,
260
+ availableSources: sources,
261
+ message: collections.length > 0
262
+ ? `${collections.length} collection(s) with sync rules`
263
+ : 'No collections have sync rules configured.',
132
264
  });
133
265
  }
134
266
  catch (error) {
@@ -139,22 +271,20 @@ export function createSyncTools(workspacePath, workspaceConfig, registryRef) {
139
271
  }
140
272
  },
141
273
  },
142
- // Commit staging to graph (deprecated — kept for backwards compatibility)
274
+ // Git commit across workspace repos
143
275
  {
144
- name: 'sync_commit',
145
- label: 'Commit Staging (deprecated)',
146
- description: 'DEPRECATED: Use sync_apply then workspace_commit instead. Commits staging files and auto git-commits.',
276
+ name: 'workspace_commit',
277
+ label: 'Commit Changes',
278
+ description: 'Git commit entity changes across all workspace repos. Works after sync_apply, enrich_run, or manual edits. Always confirm with the user before calling this.',
147
279
  parameters: T.Object({
148
- force: T.Optional(T.Boolean({ description: 'Force commit even with uncommitted changes in repos' })),
280
+ messagePrefix: T.Optional(T.String({ description: 'Commit message prefix (default: "update")' })),
149
281
  }),
150
282
  execute: async (toolCallId, params) => {
151
283
  try {
152
284
  const progress = [];
153
- progress.push('Note: sync_commit is deprecated. Use sync_apply then workspace_commit instead.');
154
- const result = await commitStaging(workspacePath, (msg) => progress.push(msg), { force: params.force, noGit: false });
285
+ await commitRepos(workspacePath, (msg) => progress.push(msg), { messagePrefix: params.messagePrefix ?? 'update' });
155
286
  return wrapToolResult({
156
287
  success: true,
157
- result,
158
288
  progress: progress.join('\n'),
159
289
  });
160
290
  }
@@ -166,31 +296,6 @@ export function createSyncTools(workspacePath, workspaceConfig, registryRef) {
166
296
  }
167
297
  },
168
298
  },
169
- // Abort staging
170
- {
171
- name: 'sync_abort',
172
- label: 'Abort Staging',
173
- description: 'Discard all staging files without committing.',
174
- parameters: T.Object({}),
175
- execute: async (toolCallId, params) => {
176
- try {
177
- const staging = new StagingManager(workspacePath);
178
- const hadStaging = staging.hasStaging();
179
- staging.clear();
180
- return wrapToolResult({
181
- success: true,
182
- hadStaging,
183
- message: hadStaging ? 'Staging cleared.' : 'No staging files to clear.',
184
- });
185
- }
186
- catch (error) {
187
- return wrapToolResult({
188
- success: false,
189
- error: error instanceof Error ? error.message : 'Unknown error',
190
- });
191
- }
192
- },
193
- },
194
299
  // List sources and connectors
195
300
  {
196
301
  name: 'sync_list_sources',
@@ -202,9 +307,9 @@ export function createSyncTools(workspacePath, workspaceConfig, registryRef) {
202
307
  const configMgr = new SourceConfigManager(workspacePath);
203
308
  const sources = configMgr.getAll();
204
309
  // Load connectors but strip sensitive fields
205
- const connectors = ConnectorManager.loadConfigs().map(c => ({
310
+ const connectors = ConnectorManager.loadConfigs(workspacePath).map(c => ({
206
311
  name: c.name,
207
- type: c.type,
312
+ url: c.url,
208
313
  description: c.description,
209
314
  }));
210
315
  const availableDefinitions = Object.keys(SOURCE_DEFINITIONS);
@@ -227,11 +332,10 @@ export function createSyncTools(workspacePath, workspaceConfig, registryRef) {
227
332
  {
228
333
  name: 'sync_setup_source',
229
334
  label: 'Setup Sync Source',
230
- description: 'Add or reconfigure a sync source. Uses default mappings from source definitions when available.',
335
+ description: 'Add or reconfigure a sync source using built-in source definitions. The collection assignment is managed via sync rules in collection settings, not here.',
231
336
  parameters: T.Object({
232
- name: T.String({ description: 'Source name (lowercase, alphanumeric, hyphens/underscores)' }),
337
+ name: T.String({ description: 'Source name — must match a built-in source definition (e.g. "linear", "pipedrive", "granola", "asana")' }),
233
338
  connector: T.Optional(T.String({ description: 'Connector name (defaults to source name)' })),
234
- entity_mappings: T.Optional(T.Array(T.Object({}, { additionalProperties: true }), { description: 'Entity mappings (overrides defaults from source definition)' })),
235
339
  force: T.Optional(T.Boolean({ description: 'Overwrite existing source config' })),
236
340
  }),
237
341
  execute: async (toolCallId, params) => {
@@ -247,7 +351,7 @@ export function createSyncTools(workspacePath, workspaceConfig, registryRef) {
247
351
  });
248
352
  }
249
353
  // Verify connector exists
250
- const connectors = ConnectorManager.loadConfigs();
354
+ const connectors = ConnectorManager.loadConfigs(workspacePath);
251
355
  const connector = connectors.find(c => c.name === connectorName);
252
356
  if (!connector) {
253
357
  return wrapToolResult({
@@ -256,30 +360,27 @@ export function createSyncTools(workspacePath, workspaceConfig, registryRef) {
256
360
  availableConnectors: connectors.map(c => c.name),
257
361
  });
258
362
  }
259
- // Build source config
363
+ // Built-in definition required — don't allow custom configs
260
364
  const definition = SOURCE_DEFINITIONS[params.name] ?? SOURCE_DEFINITIONS[connectorName];
261
- const mappings = params.entity_mappings ?? definition?.defaultMappings ?? [];
262
- const config = {
263
- name: params.name,
264
- connector: connectorName,
265
- enabled: true,
266
- added_at: new Date().toISOString(),
267
- entity_mappings: mappings,
268
- };
269
- // Validate
270
- const validation = configMgr.validate(config);
271
- if (!validation.valid) {
365
+ if (!definition || !definition.entities || Object.keys(definition.entities).length === 0) {
272
366
  return wrapToolResult({
273
367
  success: false,
274
- error: 'Invalid source config',
275
- validationErrors: validation.errors,
368
+ error: `No built-in source definition for "${params.name}". Available sources: ${Object.keys(SOURCE_DEFINITIONS).join(', ')}`,
276
369
  });
277
370
  }
278
- configMgr.save(config);
371
+ // Save as new-format source definition
372
+ const savedDef = {
373
+ ...definition,
374
+ name: params.name,
375
+ enabled: true,
376
+ added_at: new Date().toISOString(),
377
+ };
378
+ configMgr.saveDefinition(params.name, savedDef);
379
+ const entityTypes = Object.keys(definition.entities);
279
380
  return wrapToolResult({
280
381
  success: true,
281
- message: `Source "${params.name}" configured with ${mappings.length} entity mapping(s).`,
282
- config,
382
+ message: `Source "${params.name}" configured with ${entityTypes.length} entity type(s): ${entityTypes.join(', ')}. Assign it to a collection via sync rules in collection settings.`,
383
+ definition: savedDef,
283
384
  });
284
385
  }
285
386
  catch (error) {
@@ -573,80 +674,6 @@ export function createSyncTools(workspacePath, workspaceConfig, registryRef) {
573
674
  }
574
675
  },
575
676
  },
576
- // ── Enrich tools ──────────────────────────────────────────────────────────
577
- // Run enrichment
578
- {
579
- name: 'enrich_run',
580
- label: 'Enrich Entities',
581
- description: 'Enrich entities with cross-source context via LLM. Writes changes in-place (no staging). Review with workspace_review, then commit with workspace_commit. Always confirm with the user before calling this.',
582
- parameters: T.Object({
583
- entityTypes: T.Optional(T.Array(T.String(), { description: 'Only enrich specific entity types' })),
584
- dryRun: T.Optional(T.Boolean({ description: 'Preview without writing changes' })),
585
- minRefs: T.Optional(T.Number({ description: 'Minimum context references required (default: 2)' })),
586
- maxCompleteness: T.Optional(T.Number({ description: 'Maximum completeness to qualify (default: 0.6)' })),
587
- }),
588
- execute: async (toolCallId, params) => {
589
- try {
590
- const progress = [];
591
- const result = await runEnrichment({
592
- workspacePath,
593
- entityTypes: params.entityTypes,
594
- dryRun: params.dryRun,
595
- minRefs: params.minRefs,
596
- maxCompleteness: params.maxCompleteness,
597
- schemaExtensions: workspaceConfig.schema_extensions,
598
- }, (msg) => progress.push(msg));
599
- return wrapToolResult({
600
- success: true,
601
- result,
602
- progress: progress.join('\n'),
603
- nextSteps: result.enriched > 0 && !params.dryRun
604
- ? 'Review changes with workspace_review, then commit with workspace_commit or studiograph commit'
605
- : undefined,
606
- });
607
- }
608
- catch (error) {
609
- return wrapToolResult({
610
- success: false,
611
- error: error instanceof Error ? error.message : 'Unknown error',
612
- });
613
- }
614
- },
615
- },
616
- // Abort enrichment (revert uncommitted changes)
617
- {
618
- name: 'enrich_abort',
619
- label: 'Abort Enrichment',
620
- description: 'Discard uncommitted enrichment changes by reverting all entity files (git checkout + git clean).',
621
- parameters: T.Object({}),
622
- execute: async (toolCallId, _params) => {
623
- try {
624
- const repos = workspaceConfig.repos ?? [];
625
- const reverted = [];
626
- for (const repo of repos) {
627
- const repoPath = join(workspacePath, repo.path);
628
- if (!existsSync(repoPath))
629
- continue;
630
- spawnSync('git', ['checkout', '--', '.'], { cwd: repoPath, encoding: 'utf-8' });
631
- spawnSync('git', ['clean', '-fd'], { cwd: repoPath, encoding: 'utf-8' });
632
- reverted.push(repo.name);
633
- }
634
- return wrapToolResult({
635
- success: true,
636
- reverted,
637
- message: reverted.length > 0
638
- ? `Reverted changes in: ${reverted.join(', ')}`
639
- : 'No changes to revert.',
640
- });
641
- }
642
- catch (error) {
643
- return wrapToolResult({
644
- success: false,
645
- error: String(error instanceof Error ? error.message : error),
646
- });
647
- }
648
- },
649
- },
650
677
  // Reset workspace (discard uncommitted changes)
651
678
  {
652
679
  name: 'workspace_reset',