farming-code 2.2.8 → 2.2.11

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 (102) hide show
  1. package/README.md +36 -23
  2. package/README.zh_cn.md +36 -23
  3. package/THIRD_PARTY_NOTICES.md +11 -2
  4. package/backend/acp-checkpoint-store.js +198 -0
  5. package/backend/acp-runtime.js +306 -83
  6. package/backend/acp-session-state.js +202 -6
  7. package/backend/acp-transcript.js +112 -0
  8. package/backend/agent-activity.js +6 -157
  9. package/backend/agent-manager.js +1482 -730
  10. package/backend/agent-provider-session.js +17 -242
  11. package/backend/agent-runtime-binding.js +219 -0
  12. package/backend/agent-session-history.js +66 -1
  13. package/backend/auth.js +79 -6
  14. package/backend/codex-models.js +81 -84
  15. package/backend/codex-session-archive.js +45 -0
  16. package/backend/codex-terminal-profile.js +500 -0
  17. package/backend/codex-transcript-sanitizer.js +12 -0
  18. package/backend/codex-transcript.js +230 -8
  19. package/backend/config-manager.js +30 -0
  20. package/backend/control-api.js +192 -17
  21. package/backend/farming-net-pass.js +285 -0
  22. package/backend/farming-net-registry.js +112 -0
  23. package/backend/farming-net-server.js +298 -0
  24. package/backend/farming-session-store.js +5 -13
  25. package/backend/git-worktree-info.js +181 -0
  26. package/backend/local-session-engine.js +411 -186
  27. package/backend/main-page-session.js +5 -2
  28. package/backend/native-pty-controller-generation.js +106 -0
  29. package/backend/native-pty-host-client.js +275 -7
  30. package/backend/native-pty-host-identity.js +86 -0
  31. package/backend/native-pty-host.js +813 -114
  32. package/backend/native-session-engine.js +100 -28
  33. package/backend/packaged-node-pty.js +22 -2
  34. package/backend/provider-adapters.js +253 -0
  35. package/backend/provider-session-service.js +241 -0
  36. package/backend/runtime-observation.js +81 -0
  37. package/backend/server.js +294 -86
  38. package/backend/session-engine-bridge.js +21 -2
  39. package/backend/session-engine-router.js +1 -1
  40. package/backend/session-engine.js +1 -1
  41. package/backend/session-stream-protocol.js +185 -0
  42. package/backend/storage-layout.js +55 -0
  43. package/backend/terminal-attach-checkpoint.js +74 -0
  44. package/backend/terminal-exit-quiescence.js +39 -0
  45. package/backend/terminal-reducer-flow-control.js +97 -0
  46. package/backend/terminal-screen-state.js +11 -2
  47. package/backend/terminal-screen-worker-pool.js +59 -6
  48. package/backend/terminal-screen-worker-thread.js +97 -57
  49. package/backend/terminal-screen-worker.js +133 -51
  50. package/backend/terminal-state-serialization.js +127 -0
  51. package/backend/terminal-status.js +23 -4
  52. package/backend/usage-monitor.js +81 -4
  53. package/backend/workspace-directory.js +152 -0
  54. package/backend/workspace-file-router.js +182 -76
  55. package/backend/workspace-file-service.js +319 -4
  56. package/backend/workspace-root-registry.js +164 -0
  57. package/dist/assets/App-CHYpgECZ.js +208 -0
  58. package/dist/assets/{FileEditorMarkdownPreview-elKWc8Im.js → FileEditorMarkdownPreview--Cri80nQ.js} +92 -92
  59. package/dist/assets/FileEditorPane-DGJlEKdf.js +2 -0
  60. package/dist/assets/IconGlyphs-Cc14sepw.js +1 -0
  61. package/dist/assets/ProjectFilesSection-Cz1wG4NT.js +12 -0
  62. package/dist/assets/{ReviewPage-BaXu1ZdX.js → ReviewPage-LonRc6Fz.js} +1 -1
  63. package/dist/assets/code-dark-B1pAtO_P.css +1 -0
  64. package/dist/assets/file-icons-Bw2qd5iT.js +1 -0
  65. package/dist/assets/{index-BrbljRqn.js → index-miO2r9IZ.js} +3 -3
  66. package/dist/assets/main-DNe0jPw-.css +1 -0
  67. package/dist/assets/workspace-editor-model-BQol4qbA.js +1 -0
  68. package/dist/assets/workspace-editor-monaco-5gFbeDPF.js +1 -0
  69. package/dist/assets/workspace-editor-monaco-xPv8JzHH.js +4 -0
  70. package/dist/assets/workspace-view-state-DvYG_9PH.js +7 -0
  71. package/dist/assets/workspace-working-copy-D8-s_Sgh.js +1 -0
  72. package/dist/index.html +1 -1
  73. package/frontend/farming-net/app.css +625 -0
  74. package/frontend/farming-net/app.js +268 -0
  75. package/frontend/farming-net/index.html +86 -0
  76. package/frontend/reading-anchor.js +198 -0
  77. package/frontend/session-bridge.js +12 -3
  78. package/frontend/session-modal-bridge.js +5 -12
  79. package/frontend/skins/crt/app.js +1976 -793
  80. package/frontend/skins/crt/index.html +313 -23
  81. package/frontend/skins/crt/styles/billing.css +294 -223
  82. package/frontend/skins/crt/styles/monochrome-green.css +7 -2
  83. package/frontend/terminal-replay.js +372 -0
  84. package/package.json +10 -3
  85. package/shared/browser-protocol.d.ts +5 -0
  86. package/shared/browser-protocol.js +130 -0
  87. package/dist/assets/App-8dYAM6ql.js +0 -124
  88. package/dist/assets/FileEditorPane-RWiFD2cq.js +0 -5
  89. package/dist/assets/IconGlyphs-DfL0EBnj.js +0 -1
  90. package/dist/assets/ProjectFilesSection-Q4PDsWmM.js +0 -12
  91. package/dist/assets/code-dark-CDkOQAtK.css +0 -1
  92. package/dist/assets/file-icons-EFUGSSwf.js +0 -1
  93. package/dist/assets/main-D073SnW4.css +0 -1
  94. package/dist/assets/qoder-C9LmmOSf.svg +0 -1
  95. package/dist/assets/qoder-Cf9gl0Y5.svg +0 -1
  96. package/dist/assets/qoder-gHCinseV.svg +0 -1
  97. package/dist/assets/workspace-view-state-CTyDzk2D.js +0 -1
  98. package/dist/assets/zsh-CLpveKlF.svg +0 -1
  99. package/dist/assets/zsh-FxSpMPbz.svg +0 -1
  100. /package/dist/assets/{api-D1lyBYIQ.js → api-D8nyOEbz.js} +0 -0
  101. /package/dist/assets/{core-ZlAPicox.js → core-D0LFJkDt.js} +0 -0
  102. /package/dist/assets/{useWorkspaceMenuKeyboard-CneKAZUJ.js → useWorkspaceMenuKeyboard-Brws6Ar9.js} +0 -0
@@ -2,13 +2,40 @@ const express = require('express');
2
2
  const fs = require('fs');
3
3
  const os = require('os');
4
4
  const path = require('path');
5
+ const { inspectGitWorktree } = require('./git-worktree-info');
5
6
  const { WorkspaceFileError } = require('./workspace-file-service');
7
+ const {
8
+ GLOBAL_WORKSPACE_FILES_AGENT_ID,
9
+ GLOBAL_WORKSPACE_FILES_ROOT,
10
+ GLOBAL_WORKSPACE_ROOT_ID,
11
+ PROJECT_FILES_WORKSPACE_PREFIX,
12
+ WorkspaceRootRegistry,
13
+ projectWorkspaceFromLegacyRef,
14
+ } = require('./workspace-root-registry');
6
15
 
7
- const GLOBAL_WORKSPACE_FILES_AGENT_ID = '__farming_global_files__';
8
- const GLOBAL_WORKSPACE_FILES_ROOT = '/';
16
+ const ROOT_REGISTRIES = new WeakMap();
9
17
 
10
18
  function isGlobalWorkspaceFilesAgentId(agentId) {
11
- return agentId === GLOBAL_WORKSPACE_FILES_AGENT_ID;
19
+ return agentId === GLOBAL_WORKSPACE_FILES_AGENT_ID || agentId === GLOBAL_WORKSPACE_ROOT_ID;
20
+ }
21
+
22
+ function projectWorkspaceFromFilesId(filesId) {
23
+ return projectWorkspaceFromLegacyRef(filesId);
24
+ }
25
+
26
+ function workspaceRootRegistryFor(agentManager) {
27
+ let registry = ROOT_REGISTRIES.get(agentManager);
28
+ if (!registry) {
29
+ registry = new WorkspaceRootRegistry(agentManager);
30
+ ROOT_REGISTRIES.set(agentManager, registry);
31
+ }
32
+ return registry;
33
+ }
34
+
35
+ function workspaceRef(source) {
36
+ return source && typeof source.rootId === 'string' && source.rootId.trim()
37
+ ? source.rootId
38
+ : source?.agentId;
12
39
  }
13
40
 
14
41
  function normalizeAbsolutePath(value) {
@@ -51,6 +78,7 @@ function collectCandidateAllowedRoots(agentManager) {
51
78
  for (const agent of state.agents || []) {
52
79
  roots.push(agent && agent.projectWorkspace);
53
80
  roots.push(agent && agent.cwd);
81
+ roots.push(agent && agent.gitWorktree && agent.gitWorktree.workspace);
54
82
  }
55
83
  for (const entry of state.taskHistory || []) {
56
84
  roots.push(entry && entry.projectWorkspace);
@@ -63,6 +91,7 @@ function collectCandidateAllowedRoots(agentManager) {
63
91
  : null;
64
92
  if (settings) {
65
93
  roots.push(...(Array.isArray(settings.workspaceHistory) ? settings.workspaceHistory : []));
94
+ roots.push(...(Array.isArray(settings.projectWorkspaces) ? settings.projectWorkspaces : []));
66
95
  }
67
96
  return roots;
68
97
  }
@@ -157,29 +186,8 @@ function sendWorkspaceFileError(res, error) {
157
186
  res.status(500).json({ error: 'workspace file operation failed' });
158
187
  }
159
188
 
160
- function resolveWorkspaceRoot(agentManager, agentId) {
161
- if (typeof agentId !== 'string' || !agentId.trim()) {
162
- throw new WorkspaceFileError('agentId is required', 400);
163
- }
164
-
165
- if (isGlobalWorkspaceFilesAgentId(agentId)) {
166
- return GLOBAL_WORKSPACE_FILES_ROOT;
167
- }
168
-
169
- if (agentManager && typeof agentManager.getAgentWorkspaceRoot === 'function') {
170
- const root = agentManager.getAgentWorkspaceRoot(agentId);
171
- if (root) return root;
172
- }
173
-
174
- const state = agentManager && typeof agentManager.getState === 'function'
175
- ? agentManager.getState()
176
- : { agents: [] };
177
- const agent = (state.agents || []).find(candidate => candidate.id === agentId);
178
- if (!agent) {
179
- throw new WorkspaceFileError('agent not found', 404);
180
- }
181
-
182
- return agent.projectWorkspace || agent.cwd;
189
+ function resolveWorkspaceRoot(agentManager, rootRef) {
190
+ return workspaceRootRegistryFor(agentManager).resolve(rootRef).canonicalPath;
183
191
  }
184
192
 
185
193
  function assertWritableWorkspaceAgent(agentId) {
@@ -194,18 +202,29 @@ function readOptionsForAgent(agentManager, agentId) {
194
202
  : { allowedExternalRoots: globalWorkspaceAllowedRoots(agentManager) };
195
203
  }
196
204
 
197
- function createWorkspaceFileRouter(agentManager, fileService) {
205
+ function createWorkspaceFileRouter(agentManager, fileService, options = {}) {
198
206
  const router = express.Router();
207
+ const rootRegistry = options.rootRegistry || workspaceRootRegistryFor(agentManager);
199
208
 
200
209
  router.use(express.json({ limit: '3mb' }));
201
210
 
211
+ router.get('/roots', (_req, res) => {
212
+ res.json({ roots: rootRegistry.list() });
213
+ });
214
+
215
+ const resolveRequestRoot = source => {
216
+ const workspaceRoot = rootRegistry.resolve(workspaceRef(source));
217
+ return { root: workspaceRoot.canonicalPath, rootId: workspaceRoot.rootId };
218
+ };
219
+
202
220
  router.get('/tree', async (req, res) => {
203
221
  try {
204
- const root = resolveWorkspaceRoot(agentManager, req.query.agentId);
205
- const tree = isGlobalWorkspaceFilesAgentId(req.query.agentId)
222
+ const rootRef = workspaceRef(req.query);
223
+ const { root, rootId } = resolveRequestRoot(req.query);
224
+ const tree = isGlobalWorkspaceFilesAgentId(rootRef)
206
225
  ? await listGlobalWorkspaceTree(agentManager, fileService, req.query.path || '')
207
- : await fileService.listTree(root, req.query.path || '', readOptionsForAgent(agentManager, req.query.agentId));
208
- res.json({ root, tree });
226
+ : await fileService.listTree(root, req.query.path || '', readOptionsForAgent(agentManager, rootRef));
227
+ res.json({ rootId, root, tree });
209
228
  } catch (error) {
210
229
  sendWorkspaceFileError(res, error);
211
230
  }
@@ -213,12 +232,13 @@ function createWorkspaceFileRouter(agentManager, fileService) {
213
232
 
214
233
  router.get('/file', async (req, res) => {
215
234
  try {
216
- if (isGlobalWorkspaceFilesAgentId(req.query.agentId)) {
235
+ const rootRef = workspaceRef(req.query);
236
+ if (isGlobalWorkspaceFilesAgentId(rootRef)) {
217
237
  assertGlobalWorkspacePathAllowed(agentManager, req.query.path || '');
218
238
  }
219
- const root = resolveWorkspaceRoot(agentManager, req.query.agentId);
220
- const file = await fileService.readFile(root, req.query.path || '', readOptionsForAgent(agentManager, req.query.agentId));
221
- res.json({ root, file });
239
+ const { root, rootId } = resolveRequestRoot(req.query);
240
+ const file = await fileService.readFile(root, req.query.path || '', readOptionsForAgent(agentManager, rootRef));
241
+ res.json({ rootId, root, file });
222
242
  } catch (error) {
223
243
  sendWorkspaceFileError(res, error);
224
244
  }
@@ -226,11 +246,12 @@ function createWorkspaceFileRouter(agentManager, fileService) {
226
246
 
227
247
  router.get('/raw', async (req, res) => {
228
248
  try {
229
- if (isGlobalWorkspaceFilesAgentId(req.query.agentId)) {
249
+ const rootRef = workspaceRef(req.query);
250
+ if (isGlobalWorkspaceFilesAgentId(rootRef)) {
230
251
  assertGlobalWorkspacePathAllowed(agentManager, req.query.path || '');
231
252
  }
232
- const root = resolveWorkspaceRoot(agentManager, req.query.agentId);
233
- const file = await fileService.readPreviewFile(root, req.query.path || '', readOptionsForAgent(agentManager, req.query.agentId));
253
+ const { root } = resolveRequestRoot(req.query);
254
+ const file = await fileService.readPreviewFile(root, req.query.path || '', readOptionsForAgent(agentManager, rootRef));
234
255
  res
235
256
  .status(200)
236
257
  .type(file.preview.mediaType)
@@ -246,13 +267,14 @@ function createWorkspaceFileRouter(agentManager, fileService) {
246
267
  router.put('/file', async (req, res) => {
247
268
  try {
248
269
  const body = req.body || {};
249
- assertWritableWorkspaceAgent(body.agentId);
250
- const root = resolveWorkspaceRoot(agentManager, body.agentId);
270
+ const rootRef = workspaceRef(body);
271
+ assertWritableWorkspaceAgent(rootRef);
272
+ const { root, rootId } = resolveRequestRoot(body);
251
273
  const file = await fileService.writeFile(root, body.path || '', body.content, {
252
274
  baseSha1: body.baseSha1,
253
275
  overwrite: body.overwrite === true,
254
276
  });
255
- res.json({ root, file });
277
+ res.json({ rootId, root, file });
256
278
  } catch (error) {
257
279
  sendWorkspaceFileError(res, error);
258
280
  }
@@ -261,10 +283,11 @@ function createWorkspaceFileRouter(agentManager, fileService) {
261
283
  router.post('/move', async (req, res) => {
262
284
  try {
263
285
  const body = req.body || {};
264
- assertWritableWorkspaceAgent(body.agentId);
265
- const root = resolveWorkspaceRoot(agentManager, body.agentId);
286
+ const rootRef = workspaceRef(body);
287
+ assertWritableWorkspaceAgent(rootRef);
288
+ const { root, rootId } = resolveRequestRoot(body);
266
289
  const move = await fileService.moveEntry(root, body.sourcePath || '', body.targetDirectory || '');
267
- res.json({ root, move });
290
+ res.json({ rootId, root, move });
268
291
  } catch (error) {
269
292
  sendWorkspaceFileError(res, error);
270
293
  }
@@ -273,10 +296,11 @@ function createWorkspaceFileRouter(agentManager, fileService) {
273
296
  router.post('/entry', async (req, res) => {
274
297
  try {
275
298
  const body = req.body || {};
276
- assertWritableWorkspaceAgent(body.agentId);
277
- const root = resolveWorkspaceRoot(agentManager, body.agentId);
299
+ const rootRef = workspaceRef(body);
300
+ assertWritableWorkspaceAgent(rootRef);
301
+ const { root, rootId } = resolveRequestRoot(body);
278
302
  const created = await fileService.createEntry(root, body.parentPath || '', body.name || '', body.entryType || 'file', body.content || '');
279
- res.status(201).json({ root, ...created });
303
+ res.status(201).json({ rootId, root, ...created });
280
304
  } catch (error) {
281
305
  sendWorkspaceFileError(res, error);
282
306
  }
@@ -285,10 +309,11 @@ function createWorkspaceFileRouter(agentManager, fileService) {
285
309
  router.patch('/entry', async (req, res) => {
286
310
  try {
287
311
  const body = req.body || {};
288
- assertWritableWorkspaceAgent(body.agentId);
289
- const root = resolveWorkspaceRoot(agentManager, body.agentId);
312
+ const rootRef = workspaceRef(body);
313
+ assertWritableWorkspaceAgent(rootRef);
314
+ const { root, rootId } = resolveRequestRoot(body);
290
315
  const move = await fileService.renameEntry(root, body.path || '', body.name || '');
291
- res.json({ root, move });
316
+ res.json({ rootId, root, move });
292
317
  } catch (error) {
293
318
  sendWorkspaceFileError(res, error);
294
319
  }
@@ -297,12 +322,13 @@ function createWorkspaceFileRouter(agentManager, fileService) {
297
322
  router.delete('/entry', async (req, res) => {
298
323
  try {
299
324
  const body = req.body || {};
300
- const agentId = body.agentId || req.query.agentId;
325
+ const rootRef = workspaceRef(body) || workspaceRef(req.query);
301
326
  const targetPath = body.path || req.query.path || '';
302
- assertWritableWorkspaceAgent(agentId);
303
- const root = resolveWorkspaceRoot(agentManager, agentId);
327
+ assertWritableWorkspaceAgent(rootRef);
328
+ const workspaceRoot = rootRegistry.resolve(rootRef);
329
+ const { canonicalPath: root, rootId } = workspaceRoot;
304
330
  const deleted = await fileService.deleteEntry(root, targetPath);
305
- res.json({ root, deleted });
331
+ res.json({ rootId, root, deleted });
306
332
  } catch (error) {
307
333
  sendWorkspaceFileError(res, error);
308
334
  }
@@ -310,10 +336,11 @@ function createWorkspaceFileRouter(agentManager, fileService) {
310
336
 
311
337
  router.get('/search', async (req, res) => {
312
338
  try {
313
- if (isGlobalWorkspaceFilesAgentId(req.query.agentId)) {
339
+ const rootRef = workspaceRef(req.query);
340
+ if (isGlobalWorkspaceFilesAgentId(rootRef)) {
314
341
  assertGlobalWorkspacePathAllowed(agentManager, req.query.path || '');
315
342
  }
316
- const root = resolveWorkspaceRoot(agentManager, req.query.agentId);
343
+ const { root, rootId } = resolveRequestRoot(req.query);
317
344
  const settings = agentManager?.configManager?.getSettings?.() || {};
318
345
  const results = await fileService.search(root, req.query.q || '', {
319
346
  includeIgnored: req.query.includeIgnored === 'true',
@@ -321,7 +348,7 @@ function createWorkspaceFileRouter(agentManager, fileService) {
321
348
  limit: req.query.limit,
322
349
  timeoutMs: settings.searchTimeoutMs,
323
350
  });
324
- res.json({ root, results });
351
+ res.json({ rootId, root, results });
325
352
  } catch (error) {
326
353
  sendWorkspaceFileError(res, error);
327
354
  }
@@ -329,12 +356,13 @@ function createWorkspaceFileRouter(agentManager, fileService) {
329
356
 
330
357
  router.get('/diff', async (req, res) => {
331
358
  try {
332
- if (isGlobalWorkspaceFilesAgentId(req.query.agentId)) {
359
+ const rootRef = workspaceRef(req.query);
360
+ if (isGlobalWorkspaceFilesAgentId(rootRef)) {
333
361
  assertGlobalWorkspacePathAllowed(agentManager, req.query.path || '', { allowMissing: true });
334
362
  }
335
- const root = resolveWorkspaceRoot(agentManager, req.query.agentId);
363
+ const { root, rootId } = resolveRequestRoot(req.query);
336
364
  const diff = await fileService.diff(root, req.query.path || '');
337
- res.json({ root, diff });
365
+ res.json({ rootId, root, diff });
338
366
  } catch (error) {
339
367
  sendWorkspaceFileError(res, error);
340
368
  }
@@ -342,14 +370,15 @@ function createWorkspaceFileRouter(agentManager, fileService) {
342
370
 
343
371
  router.get('/changes', async (req, res) => {
344
372
  try {
345
- if (isGlobalWorkspaceFilesAgentId(req.query.agentId)) {
373
+ const rootRef = workspaceRef(req.query);
374
+ if (isGlobalWorkspaceFilesAgentId(rootRef)) {
346
375
  throw new WorkspaceFileError('global files do not support workspace changes', 403);
347
376
  }
348
- const root = resolveWorkspaceRoot(agentManager, req.query.agentId);
377
+ const { root, rootId } = resolveRequestRoot(req.query);
349
378
  const changes = await fileService.changes(root, {
350
379
  limit: req.query.limit,
351
380
  });
352
- res.json({ root, changes });
381
+ res.json({ rootId, root, changes });
353
382
  } catch (error) {
354
383
  sendWorkspaceFileError(res, error);
355
384
  }
@@ -357,12 +386,82 @@ function createWorkspaceFileRouter(agentManager, fileService) {
357
386
 
358
387
  router.get('/branch', async (req, res) => {
359
388
  try {
360
- if (isGlobalWorkspaceFilesAgentId(req.query.agentId)) {
389
+ const rootRef = workspaceRef(req.query);
390
+ if (isGlobalWorkspaceFilesAgentId(rootRef)) {
361
391
  throw new WorkspaceFileError('global files do not support git branches', 403);
362
392
  }
363
- const root = resolveWorkspaceRoot(agentManager, req.query.agentId);
393
+ const { root, rootId } = resolveRequestRoot(req.query);
364
394
  const branch = await fileService.gitBranch(root);
365
- res.json({ root, branch });
395
+ res.json({ rootId, root, branch });
396
+ } catch (error) {
397
+ sendWorkspaceFileError(res, error);
398
+ }
399
+ });
400
+
401
+ router.get('/worktrees', async (req, res) => {
402
+ try {
403
+ const rootRef = workspaceRef(req.query);
404
+ if (isGlobalWorkspaceFilesAgentId(rootRef)) {
405
+ throw new WorkspaceFileError('global files do not support git worktrees', 403);
406
+ }
407
+ const { root, rootId } = resolveRequestRoot(req.query);
408
+ const info = await inspectGitWorktree(root, { cacheMs: 0 });
409
+ res.json({
410
+ rootId,
411
+ root,
412
+ worktrees: info
413
+ ? {
414
+ isGitRepo: true,
415
+ commonDir: info.commonDir,
416
+ currentWorkspace: info.workspace,
417
+ mainWorkspace: info.mainWorkspace,
418
+ items: info.worktrees,
419
+ }
420
+ : {
421
+ isGitRepo: false,
422
+ commonDir: '',
423
+ currentWorkspace: root,
424
+ mainWorkspace: '',
425
+ items: [],
426
+ },
427
+ });
428
+ } catch (error) {
429
+ sendWorkspaceFileError(res, error);
430
+ }
431
+ });
432
+
433
+ router.get('/history', async (req, res) => {
434
+ try {
435
+ const rootRef = workspaceRef(req.query);
436
+ if (isGlobalWorkspaceFilesAgentId(rootRef)) {
437
+ throw new WorkspaceFileError('global files do not support git history', 403);
438
+ }
439
+ const { root, rootId } = resolveRequestRoot(req.query);
440
+ const history = await fileService.gitHistory(root, {
441
+ limit: req.query.limit,
442
+ skip: req.query.skip,
443
+ scope: req.query.scope,
444
+ });
445
+ res.json({ rootId, root, history });
446
+ } catch (error) {
447
+ sendWorkspaceFileError(res, error);
448
+ }
449
+ });
450
+
451
+ router.get('/history/changes', async (req, res) => {
452
+ try {
453
+ const rootRef = workspaceRef(req.query);
454
+ if (isGlobalWorkspaceFilesAgentId(rootRef)) {
455
+ throw new WorkspaceFileError('global files do not support git history', 403);
456
+ }
457
+ const { root, rootId } = resolveRequestRoot(req.query);
458
+ const changes = await fileService.gitHistoryChanges(
459
+ root,
460
+ req.query.commit,
461
+ req.query.parent,
462
+ { limit: req.query.limit }
463
+ );
464
+ res.json({ rootId, root, changes });
366
465
  } catch (error) {
367
466
  sendWorkspaceFileError(res, error);
368
467
  }
@@ -370,17 +469,18 @@ function createWorkspaceFileRouter(agentManager, fileService) {
370
469
 
371
470
  router.get('/line-changes', async (req, res) => {
372
471
  try {
373
- if (isGlobalWorkspaceFilesAgentId(req.query.agentId)) {
472
+ const rootRef = workspaceRef(req.query);
473
+ if (isGlobalWorkspaceFilesAgentId(rootRef)) {
374
474
  assertGlobalWorkspacePathAllowed(agentManager, req.query.path || '');
375
475
  }
376
- const root = resolveWorkspaceRoot(agentManager, req.query.agentId);
476
+ const { root, rootId } = resolveRequestRoot(req.query);
377
477
  const changes = await fileService.lineChanges(
378
478
  root,
379
479
  req.query.path || '',
380
480
  req.query.lineNumber,
381
481
  req.query.mode || 'working'
382
482
  );
383
- res.json({ root, changes });
483
+ res.json({ rootId, root, changes });
384
484
  } catch (error) {
385
485
  sendWorkspaceFileError(res, error);
386
486
  }
@@ -388,12 +488,13 @@ function createWorkspaceFileRouter(agentManager, fileService) {
388
488
 
389
489
  router.get('/blame', async (req, res) => {
390
490
  try {
391
- if (isGlobalWorkspaceFilesAgentId(req.query.agentId)) {
491
+ const rootRef = workspaceRef(req.query);
492
+ if (isGlobalWorkspaceFilesAgentId(rootRef)) {
392
493
  assertGlobalWorkspacePathAllowed(agentManager, req.query.path || '');
393
494
  }
394
- const root = resolveWorkspaceRoot(agentManager, req.query.agentId);
495
+ const { root, rootId } = resolveRequestRoot(req.query);
395
496
  const blame = await fileService.blame(root, req.query.path || '');
396
- res.json({ root, blame });
497
+ res.json({ rootId, root, blame });
397
498
  } catch (error) {
398
499
  sendWorkspaceFileError(res, error);
399
500
  }
@@ -401,16 +502,17 @@ function createWorkspaceFileRouter(agentManager, fileService) {
401
502
 
402
503
  router.get('/blame-capability', async (req, res) => {
403
504
  try {
404
- if (isGlobalWorkspaceFilesAgentId(req.query.agentId)) {
505
+ const rootRef = workspaceRef(req.query);
506
+ if (isGlobalWorkspaceFilesAgentId(rootRef)) {
405
507
  assertGlobalWorkspacePathAllowed(agentManager, req.query.path || '');
406
508
  }
407
- const root = resolveWorkspaceRoot(agentManager, req.query.agentId);
509
+ const { root, rootId } = resolveRequestRoot(req.query);
408
510
  const capability = await fileService.blameCapability(
409
511
  root,
410
512
  req.query.path || '',
411
- readOptionsForAgent(agentManager, req.query.agentId)
513
+ readOptionsForAgent(agentManager, rootRef)
412
514
  );
413
- res.json({ root, capability });
515
+ res.json({ rootId, root, capability });
414
516
  } catch (error) {
415
517
  sendWorkspaceFileError(res, error);
416
518
  }
@@ -422,10 +524,14 @@ function createWorkspaceFileRouter(agentManager, fileService) {
422
524
  module.exports = {
423
525
  GLOBAL_WORKSPACE_FILES_AGENT_ID,
424
526
  GLOBAL_WORKSPACE_FILES_ROOT,
527
+ PROJECT_FILES_WORKSPACE_PREFIX,
528
+ PROJECT_FILES_AGENT_PREFIX: PROJECT_FILES_WORKSPACE_PREFIX,
425
529
  assertGlobalWorkspacePathAllowed,
426
530
  createWorkspaceFileRouter,
427
531
  globalWorkspaceAllowedRoots,
428
532
  isGlobalWorkspaceFilesAgentId,
533
+ projectWorkspaceFromFilesId,
534
+ projectWorkspaceFromFilesAgentId: projectWorkspaceFromFilesId,
429
535
  resolveWorkspaceRoot,
430
536
  sendWorkspaceFileError,
431
537
  };