farming-code 2.2.17 → 2.2.19
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/backend/agent-manager.js +226 -49
- package/backend/bundle-update-helper.js +109 -0
- package/backend/config-manager.js +110 -4
- package/backend/farming-session-store.js +62 -2
- package/backend/server.js +364 -58
- package/backend/update-service.js +46 -23
- package/dist/assets/App-CbXYBccJ.js +210 -0
- package/dist/assets/{FileEditorPane-DNy3LAqS.js → FileEditorPane-CJs6kfZO.js} +1 -1
- package/dist/assets/{IconGlyphs-BsUfqpKN.js → IconGlyphs-CZzDr5o2.js} +1 -1
- package/dist/assets/{ProjectFilesSection-BhDEUVHA.js → ProjectFilesSection-DWCV07lA.js} +1 -1
- package/dist/assets/{ReviewPage-ChtByBe3.js → ReviewPage-DBKNOWsJ.js} +1 -1
- package/dist/assets/code-dark-qIBiLBWV.css +1 -0
- package/dist/assets/{index-C8ZFKye1.js → index-DQdtPfCk.js} +2 -2
- package/dist/assets/main-yzZEfgft.css +1 -0
- package/dist/index.html +1 -1
- package/frontend/skins/crt/app.js +91 -9
- package/frontend/skins/crt/index.html +16 -28
- package/package.json +1 -1
- package/dist/assets/App-6yWdwzj8.js +0 -210
- package/dist/assets/code-dark-5esj0bRu.css +0 -1
- package/dist/assets/main-DSO3i_Gs.css +0 -1
package/backend/server.js
CHANGED
|
@@ -36,6 +36,7 @@ const {
|
|
|
36
36
|
resumedAgentSource,
|
|
37
37
|
} = require('./main-page-session');
|
|
38
38
|
const { discoverAgentWorkspaces } = require('./workspace-discovery');
|
|
39
|
+
const { inspectGitWorktree } = require('./git-worktree-info');
|
|
39
40
|
const { createWorkspaceDirectoryRouter } = require('./workspace-directory');
|
|
40
41
|
const { createControlRouter } = require('./control-api');
|
|
41
42
|
const { WorkspaceFileService, WorkspaceFileError } = require('./workspace-file-service');
|
|
@@ -1433,6 +1434,40 @@ app.post(routePath(BASE_PATH, '/api/agents/:agentId/fork'), express.json(), asyn
|
|
|
1433
1434
|
return;
|
|
1434
1435
|
}
|
|
1435
1436
|
|
|
1437
|
+
try {
|
|
1438
|
+
const membership = configManager.mountProjectWorkspace(result.workspace);
|
|
1439
|
+
result.projectWorkspaces = membership.projectWorkspaces;
|
|
1440
|
+
result.pinnedProjectWorkspaces = membership.pinnedProjectWorkspaces;
|
|
1441
|
+
} catch (error) {
|
|
1442
|
+
let rollbackError = '';
|
|
1443
|
+
try {
|
|
1444
|
+
if (mode === 'new-worktree') {
|
|
1445
|
+
const rollback = await agentManager.deleteForkWorktreeProject(result.workspace, { force: true });
|
|
1446
|
+
if (!rollback?.deleted || rollback.cleanupError) {
|
|
1447
|
+
rollbackError = rollback?.error || rollback?.cleanupError || 'Failed to delete the fork worktree';
|
|
1448
|
+
}
|
|
1449
|
+
} else {
|
|
1450
|
+
const rollback = await agentManager.archiveAgent(result.agentId, {
|
|
1451
|
+
reason: 'project-mount-failed',
|
|
1452
|
+
recordHistory: false,
|
|
1453
|
+
requireEngineExit: true,
|
|
1454
|
+
scheduleProviderArchive: false,
|
|
1455
|
+
});
|
|
1456
|
+
if (rollback?.error) rollbackError = rollback.error;
|
|
1457
|
+
}
|
|
1458
|
+
} catch (cleanupError) {
|
|
1459
|
+
rollbackError = cleanupError?.message || String(cleanupError);
|
|
1460
|
+
}
|
|
1461
|
+
broadcastState();
|
|
1462
|
+
const mountError = error.message || 'Failed to create Project';
|
|
1463
|
+
res.status(500).json({
|
|
1464
|
+
error: rollbackError
|
|
1465
|
+
? `${mountError}. Rollback failed: ${rollbackError}`
|
|
1466
|
+
: mountError,
|
|
1467
|
+
...(rollbackError ? { rollbackError } : {}),
|
|
1468
|
+
});
|
|
1469
|
+
return;
|
|
1470
|
+
}
|
|
1436
1471
|
broadcastState();
|
|
1437
1472
|
res.status(201).json(result);
|
|
1438
1473
|
});
|
|
@@ -1464,54 +1499,132 @@ app.post(routePath(BASE_PATH, '/api/projects/reveal'), express.json(), async (re
|
|
|
1464
1499
|
}
|
|
1465
1500
|
});
|
|
1466
1501
|
|
|
1502
|
+
app.post(routePath(BASE_PATH, '/api/projects/mount'), express.json(), async (req, res) => {
|
|
1503
|
+
try {
|
|
1504
|
+
const workspace = await canonicalProjectWorkspace(req.body?.workspace);
|
|
1505
|
+
const membership = configManager.mountProjectWorkspace(workspace);
|
|
1506
|
+
broadcastState();
|
|
1507
|
+
res.json(membership);
|
|
1508
|
+
} catch (error) {
|
|
1509
|
+
res.status(400).json({ error: error.message || 'Failed to create Project' });
|
|
1510
|
+
}
|
|
1511
|
+
});
|
|
1512
|
+
|
|
1513
|
+
app.post(routePath(BASE_PATH, '/api/projects/remove'), express.json(), (req, res) => {
|
|
1514
|
+
try {
|
|
1515
|
+
const membership = configManager.removeProjectWorkspace(req.body?.workspace);
|
|
1516
|
+
broadcastState();
|
|
1517
|
+
res.json(membership);
|
|
1518
|
+
} catch (error) {
|
|
1519
|
+
res.status(400).json({ error: error.message || 'Failed to remove Project' });
|
|
1520
|
+
}
|
|
1521
|
+
});
|
|
1522
|
+
|
|
1523
|
+
app.post(routePath(BASE_PATH, '/api/projects/pin'), express.json(), (req, res) => {
|
|
1524
|
+
try {
|
|
1525
|
+
const membership = configManager.setProjectWorkspacePinned(
|
|
1526
|
+
req.body?.workspace,
|
|
1527
|
+
req.body?.pinned === true
|
|
1528
|
+
);
|
|
1529
|
+
broadcastState();
|
|
1530
|
+
res.json(membership);
|
|
1531
|
+
} catch (error) {
|
|
1532
|
+
res.status(400).json({ error: error.message || 'Failed to update Project pin' });
|
|
1533
|
+
}
|
|
1534
|
+
});
|
|
1535
|
+
|
|
1467
1536
|
app.post(routePath(BASE_PATH, '/api/projects/create-worktree'), express.json(), async (req, res) => {
|
|
1468
1537
|
let created = null;
|
|
1469
|
-
let previousProjects = null;
|
|
1470
1538
|
try {
|
|
1471
1539
|
const root = resolveProjectActionRoot(req.body?.rootId);
|
|
1472
1540
|
created = await agentManager.createPermanentWorktree(root.canonicalPath);
|
|
1473
|
-
|
|
1474
|
-
|
|
1475
|
-
projectWorkspaces: [
|
|
1476
|
-
created.workspace,
|
|
1477
|
-
...previousProjects.filter(workspace => workspace !== created.workspace),
|
|
1478
|
-
],
|
|
1479
|
-
});
|
|
1541
|
+
const membership = configManager.mountProjectWorkspace(created.workspace);
|
|
1542
|
+
broadcastState();
|
|
1480
1543
|
res.status(201).json({
|
|
1481
1544
|
workspace: created.workspace,
|
|
1482
1545
|
branch: created.branch,
|
|
1483
|
-
projectWorkspaces:
|
|
1546
|
+
projectWorkspaces: membership.projectWorkspaces,
|
|
1547
|
+
pinnedProjectWorkspaces: membership.pinnedProjectWorkspaces,
|
|
1484
1548
|
});
|
|
1485
1549
|
} catch (error) {
|
|
1486
|
-
|
|
1487
|
-
if (
|
|
1488
|
-
|
|
1489
|
-
|
|
1490
|
-
} catch {
|
|
1491
|
-
// Keep the original error visible; the failed settings write is already the root cause.
|
|
1492
|
-
}
|
|
1550
|
+
let rollbackError = '';
|
|
1551
|
+
if (created) {
|
|
1552
|
+
const rollback = await agentManager.rollbackPermanentWorktree(created);
|
|
1553
|
+
if (rollback?.rolledBack === false) rollbackError = rollback.error || 'Unknown rollback failure';
|
|
1493
1554
|
}
|
|
1494
1555
|
const status = error instanceof WorkspaceFileError ? error.statusCode : 400;
|
|
1495
|
-
|
|
1556
|
+
const createError = error.message || 'Failed to create permanent worktree';
|
|
1557
|
+
res.status(status).json({
|
|
1558
|
+
error: rollbackError
|
|
1559
|
+
? `${createError}. Rollback failed: ${rollbackError}`
|
|
1560
|
+
: createError,
|
|
1561
|
+
...(rollbackError ? { rollbackError } : {}),
|
|
1562
|
+
});
|
|
1496
1563
|
}
|
|
1497
1564
|
});
|
|
1498
1565
|
|
|
1499
1566
|
app.post(routePath(BASE_PATH, '/api/projects/delete-worktree'), express.json(), async (req, res) => {
|
|
1500
1567
|
const body = req.body || {};
|
|
1501
1568
|
const workspace = typeof body.workspace === 'string' ? body.workspace : '';
|
|
1502
|
-
const
|
|
1503
|
-
if (
|
|
1504
|
-
if (
|
|
1505
|
-
res.status(409).json(
|
|
1569
|
+
const inspected = await agentManager.inspectForkWorktreeProject(workspace);
|
|
1570
|
+
if (inspected.error || (inspected.requiresForce && body.force !== true)) {
|
|
1571
|
+
if (inspected.requiresForce) {
|
|
1572
|
+
res.status(409).json({
|
|
1573
|
+
...inspected,
|
|
1574
|
+
error: 'Worktree has uncommitted or untracked files',
|
|
1575
|
+
});
|
|
1506
1576
|
return;
|
|
1507
1577
|
}
|
|
1508
|
-
const status =
|
|
1509
|
-
res.status(status).json(
|
|
1578
|
+
const status = inspected.error === 'Workspace not found' || inspected.error === 'Workspace is required' ? 404 : 400;
|
|
1579
|
+
res.status(status).json(inspected);
|
|
1510
1580
|
return;
|
|
1511
1581
|
}
|
|
1512
1582
|
|
|
1583
|
+
const wasPinned = (configManager.getSettings().pinnedProjectWorkspaces || []).includes(inspected.workspace);
|
|
1584
|
+
let membership;
|
|
1585
|
+
try {
|
|
1586
|
+
membership = configManager.removeProjectWorkspace(inspected.workspace);
|
|
1587
|
+
} catch (error) {
|
|
1588
|
+
res.status(500).json({
|
|
1589
|
+
...inspected,
|
|
1590
|
+
error: error.message || 'Project removal failed; the worktree was not deleted',
|
|
1591
|
+
});
|
|
1592
|
+
return;
|
|
1593
|
+
}
|
|
1594
|
+
const result = await agentManager.deleteForkWorktreeProject(inspected.workspace, { force: body.force === true });
|
|
1595
|
+
if (result.error) {
|
|
1596
|
+
let rollbackError = '';
|
|
1597
|
+
try {
|
|
1598
|
+
configManager.mountProjectWorkspace(inspected.workspace);
|
|
1599
|
+
if (wasPinned) configManager.setProjectWorkspacePinned(inspected.workspace, true);
|
|
1600
|
+
} catch (error) {
|
|
1601
|
+
rollbackError = error.message || String(error);
|
|
1602
|
+
}
|
|
1603
|
+
broadcastState();
|
|
1604
|
+
const status = result.requiresForce ? 409 : 400;
|
|
1605
|
+
res.status(status).json({
|
|
1606
|
+
...result,
|
|
1607
|
+
...(rollbackError ? {
|
|
1608
|
+
error: `${result.error}. Project restore failed: ${rollbackError}`,
|
|
1609
|
+
rollbackError,
|
|
1610
|
+
} : {}),
|
|
1611
|
+
});
|
|
1612
|
+
return;
|
|
1613
|
+
}
|
|
1614
|
+
if (result.cleanupError) {
|
|
1615
|
+
broadcastState();
|
|
1616
|
+
res.status(500).json({
|
|
1617
|
+
...result,
|
|
1618
|
+
error: `Worktree was deleted, but Agent cleanup failed: ${result.cleanupError}`,
|
|
1619
|
+
});
|
|
1620
|
+
return;
|
|
1621
|
+
}
|
|
1513
1622
|
broadcastState();
|
|
1514
|
-
res.json(
|
|
1623
|
+
res.json({
|
|
1624
|
+
...result,
|
|
1625
|
+
projectWorkspaces: membership.projectWorkspaces,
|
|
1626
|
+
pinnedProjectWorkspaces: membership.pinnedProjectWorkspaces,
|
|
1627
|
+
});
|
|
1515
1628
|
});
|
|
1516
1629
|
|
|
1517
1630
|
app.post(routePath(BASE_PATH, '/api/codex/sessions/:sessionId/resume'), express.json(), async (req, res) => {
|
|
@@ -1523,6 +1636,7 @@ app.post(routePath(BASE_PATH, '/api/agent-sessions/:provider/:sessionId/resume')
|
|
|
1523
1636
|
});
|
|
1524
1637
|
|
|
1525
1638
|
const pendingResumeStarts = new Map();
|
|
1639
|
+
const pendingProjectWorkspaceResolutions = new Map();
|
|
1526
1640
|
|
|
1527
1641
|
function resumedAgentStartKey(provider, sessionId, options = {}) {
|
|
1528
1642
|
return [
|
|
@@ -1584,6 +1698,14 @@ function forgetMainPageAgentSession(provider, sessionId, providerHomeId = '') {
|
|
|
1584
1698
|
});
|
|
1585
1699
|
}
|
|
1586
1700
|
|
|
1701
|
+
function savedFarmingAgentSession(provider, sessionId, providerHomeId = '') {
|
|
1702
|
+
if (typeof configManager.getAgentSessionRecordForProviderSessionKey !== 'function') return null;
|
|
1703
|
+
const sessionKey = mainPageAgentSessionKey(provider, sessionId, providerHomeId);
|
|
1704
|
+
return sessionKey
|
|
1705
|
+
? configManager.getAgentSessionRecordForProviderSessionKey(sessionKey)
|
|
1706
|
+
: null;
|
|
1707
|
+
}
|
|
1708
|
+
|
|
1587
1709
|
async function resumeAgentSessionById(provider, rawSessionId, options = {}) {
|
|
1588
1710
|
const normalizedProvider = normalizeProvider(provider);
|
|
1589
1711
|
const sessionId = String(rawSessionId || '').trim();
|
|
@@ -1604,7 +1726,12 @@ async function resumeAgentSessionById(provider, rawSessionId, options = {}) {
|
|
|
1604
1726
|
const existingAgent = findResumedAgent(normalizedProvider, sessionId, providerHomeId);
|
|
1605
1727
|
if (existingAgent) {
|
|
1606
1728
|
if (shouldRememberMainPageSession) rememberMainPageAgentSession(normalizedProvider, sessionId, providerHomeId);
|
|
1607
|
-
|
|
1729
|
+
const projectWorkspace = requestedAsMain
|
|
1730
|
+
? ''
|
|
1731
|
+
: await canonicalProjectWorkspace(
|
|
1732
|
+
existingAgent.gitWorktree?.workspace || existingAgent.projectWorkspace || existingAgent.cwd
|
|
1733
|
+
);
|
|
1734
|
+
return { agentId: existingAgent.id, projectWorkspace, reused: true };
|
|
1608
1735
|
}
|
|
1609
1736
|
}
|
|
1610
1737
|
const pendingStart = pendingResumeStarts.get(pendingResumeId);
|
|
@@ -1616,6 +1743,7 @@ async function resumeAgentSessionById(provider, rawSessionId, options = {}) {
|
|
|
1616
1743
|
if (shouldRememberMainPageSession) rememberMainPageAgentSession(normalizedProvider, sessionId, providerHomeId);
|
|
1617
1744
|
return {
|
|
1618
1745
|
agentId: result.agentId,
|
|
1746
|
+
projectWorkspace: result.projectWorkspace || '',
|
|
1619
1747
|
reused: true,
|
|
1620
1748
|
pending: true,
|
|
1621
1749
|
...(result.claimed ? { claimed: true } : {}),
|
|
@@ -1663,7 +1791,10 @@ async function resumeAgentSessionById(provider, rawSessionId, options = {}) {
|
|
|
1663
1791
|
});
|
|
1664
1792
|
if (claimingAgent) {
|
|
1665
1793
|
if (shouldRememberMainPageSession) rememberMainPageAgentSession(normalizedProvider, sessionId, providerHomeId);
|
|
1666
|
-
|
|
1794
|
+
const projectWorkspace = await canonicalProjectWorkspace(
|
|
1795
|
+
claimingAgent.gitWorktree?.workspace || claimingAgent.projectWorkspace || claimingAgent.cwd
|
|
1796
|
+
);
|
|
1797
|
+
return { agentId: claimingAgent.id, projectWorkspace, reused: true, claimed: true };
|
|
1667
1798
|
}
|
|
1668
1799
|
}
|
|
1669
1800
|
|
|
@@ -1672,7 +1803,20 @@ async function resumeAgentSessionById(provider, rawSessionId, options = {}) {
|
|
|
1672
1803
|
return { error: 'session is not a Main Agent session', status: 400 };
|
|
1673
1804
|
}
|
|
1674
1805
|
|
|
1806
|
+
const savedSession = shouldFork
|
|
1807
|
+
? null
|
|
1808
|
+
: savedFarmingAgentSession(normalizedProvider, sessionId, session
|
|
1809
|
+
? (session.providerHomeId || providerHomeId)
|
|
1810
|
+
: providerHomeId);
|
|
1811
|
+
const hasRequestedCustomTitle = Object.prototype.hasOwnProperty.call(options, 'customTitle');
|
|
1812
|
+
const savedAttentionSeq = Number(savedSession?.attentionSeq) || 0;
|
|
1813
|
+
const savedReadAttentionSeq = Number(savedSession?.readAttentionSeq) || 0;
|
|
1675
1814
|
const workingDirectory = session && (session.cwd || session.workspace) ? (session.cwd || session.workspace) : null;
|
|
1815
|
+
const projectWorkspace = resumeAsMain
|
|
1816
|
+
? ''
|
|
1817
|
+
: await canonicalProjectWorkspace(
|
|
1818
|
+
savedSession?.projectWorkspace || (session ? (session.workspace || session.cwd || '') : workingDirectory)
|
|
1819
|
+
);
|
|
1676
1820
|
const command = buildAgentSessionResumeCommand(normalizedProvider, sessionId, {
|
|
1677
1821
|
fork: shouldFork,
|
|
1678
1822
|
cwd: workingDirectory,
|
|
@@ -1699,19 +1843,38 @@ async function resumeAgentSessionById(provider, rawSessionId, options = {}) {
|
|
|
1699
1843
|
return;
|
|
1700
1844
|
}
|
|
1701
1845
|
|
|
1702
|
-
resolve({ agentId });
|
|
1846
|
+
resolve({ agentId, projectWorkspace });
|
|
1703
1847
|
}, {
|
|
1704
1848
|
wantsMain: resumeAsMain,
|
|
1705
|
-
task: session ? session.title : '',
|
|
1706
|
-
|
|
1849
|
+
task: savedSession?.task || (session ? session.title : ''),
|
|
1850
|
+
workflowTemplate: savedSession?.workflowTemplate || '',
|
|
1851
|
+
customTitle: hasRequestedCustomTitle
|
|
1852
|
+
? (typeof options.customTitle === 'string' ? options.customTitle : '')
|
|
1853
|
+
: (savedSession?.customTitle || ''),
|
|
1854
|
+
customTitleExplicit: hasRequestedCustomTitle,
|
|
1707
1855
|
requiredCliVersion: normalizedProvider === 'codex' && session ? session.cliVersion : '',
|
|
1708
|
-
projectWorkspace
|
|
1856
|
+
projectWorkspace,
|
|
1709
1857
|
source: shouldFork ? resumeSource.replace('-history:', '-history-fork:') : resumeSource,
|
|
1710
1858
|
agentRuntimeMode: ['chat', 'acp'].includes(options.agentRuntimeMode) ? 'chat' : 'terminal',
|
|
1711
1859
|
acpHistoryMode: options.acpHistoryMode === 'resume' ? 'resume' : 'load',
|
|
1712
1860
|
providerHomeId: resolvedProviderHomeId,
|
|
1713
1861
|
providerHomePath: session ? (session.providerHomePath || '') : '',
|
|
1714
|
-
|
|
1862
|
+
providerSessionTitle: session?.title || savedSession?.providerSessionTitle || '',
|
|
1863
|
+
persistentSessionId: savedSession?.id || '',
|
|
1864
|
+
pinned: savedSession?.pinned === true,
|
|
1865
|
+
projectOrder: savedSession?.projectOrder,
|
|
1866
|
+
pinnedOrder: savedSession?.pinnedOrder,
|
|
1867
|
+
attentionSeq: savedAttentionSeq,
|
|
1868
|
+
readAttentionSeq: savedReadAttentionSeq,
|
|
1869
|
+
attentionUpdatedAt: savedSession?.attentionUpdatedAt,
|
|
1870
|
+
readAttentionAt: savedSession?.readAttentionAt,
|
|
1871
|
+
attentionReason: savedSession?.attentionReason,
|
|
1872
|
+
attentionOutputEpoch: savedSession?.attentionOutputEpoch,
|
|
1873
|
+
attentionOutputSeq: savedSession?.attentionOutputSeq,
|
|
1874
|
+
readOutputEpoch: savedSession?.readOutputEpoch,
|
|
1875
|
+
readOutputSeq: savedSession?.readOutputSeq,
|
|
1876
|
+
autoReadInitialAttention: options.autoReadInitialAttention === true
|
|
1877
|
+
&& savedAttentionSeq <= savedReadAttentionSeq,
|
|
1715
1878
|
preserveProviderSessionProfile: normalizedProvider === 'codex',
|
|
1716
1879
|
});
|
|
1717
1880
|
Promise.resolve(startResult).catch((error) => {
|
|
@@ -1734,23 +1897,73 @@ async function resumeAgentSessionById(provider, rawSessionId, options = {}) {
|
|
|
1734
1897
|
if (result.reused) {
|
|
1735
1898
|
return {
|
|
1736
1899
|
agentId: result.agentId,
|
|
1900
|
+
projectWorkspace: result.projectWorkspace || '',
|
|
1737
1901
|
reused: true,
|
|
1738
1902
|
...(result.claimed ? { claimed: true } : {}),
|
|
1739
1903
|
};
|
|
1740
1904
|
}
|
|
1741
|
-
return {
|
|
1905
|
+
return {
|
|
1906
|
+
agentId: result.agentId,
|
|
1907
|
+
projectWorkspace: result.projectWorkspace || '',
|
|
1908
|
+
};
|
|
1909
|
+
}
|
|
1910
|
+
|
|
1911
|
+
async function canonicalProjectWorkspace(workspace) {
|
|
1912
|
+
const candidate = configManager.expandWorkspacePath(String(workspace || '').trim());
|
|
1913
|
+
if (!candidate) return '';
|
|
1914
|
+
const pending = pendingProjectWorkspaceResolutions.get(candidate);
|
|
1915
|
+
if (pending) return pending;
|
|
1916
|
+
|
|
1917
|
+
const resolution = (async () => {
|
|
1918
|
+
try {
|
|
1919
|
+
const worktree = await inspectGitWorktree(candidate);
|
|
1920
|
+
if (worktree?.workspace) return worktree.workspace;
|
|
1921
|
+
} catch (error) {
|
|
1922
|
+
console.warn('Failed to resolve project worktree:', candidate, error?.message || error);
|
|
1923
|
+
}
|
|
1924
|
+
try {
|
|
1925
|
+
return fs.realpathSync(path.resolve(candidate));
|
|
1926
|
+
} catch {
|
|
1927
|
+
return candidate;
|
|
1928
|
+
}
|
|
1929
|
+
})();
|
|
1930
|
+
pendingProjectWorkspaceResolutions.set(candidate, resolution);
|
|
1931
|
+
try {
|
|
1932
|
+
return await resolution;
|
|
1933
|
+
} finally {
|
|
1934
|
+
if (pendingProjectWorkspaceResolutions.get(candidate) === resolution) {
|
|
1935
|
+
pendingProjectWorkspaceResolutions.delete(candidate);
|
|
1936
|
+
}
|
|
1937
|
+
}
|
|
1742
1938
|
}
|
|
1743
1939
|
|
|
1744
1940
|
async function startResumedAgentSession(req, res, provider, rawSessionId) {
|
|
1745
1941
|
const shouldFork = req.body && req.body.fork === true;
|
|
1746
1942
|
const requestedAsMain = req.body && req.body.asMain === true && !shouldFork;
|
|
1747
1943
|
const allowUnarchiveArchived = req.body && req.body.unarchiveArchived === true && !shouldFork && !requestedAsMain;
|
|
1944
|
+
const providerHomeId = req.body && typeof req.body.providerHomeId === 'string'
|
|
1945
|
+
? req.body.providerHomeId
|
|
1946
|
+
: '';
|
|
1947
|
+
const sessionKey = mainPageAgentSessionKey(provider, rawSessionId, providerHomeId);
|
|
1948
|
+
const mainPageSessionWasRemembered = !shouldFork
|
|
1949
|
+
&& !requestedAsMain
|
|
1950
|
+
&& configManager.getMainPageSessionKeys().includes(sessionKey);
|
|
1951
|
+
if (
|
|
1952
|
+
req.body
|
|
1953
|
+
&& Object.prototype.hasOwnProperty.call(req.body, 'customTitle')
|
|
1954
|
+
&& typeof req.body.customTitle !== 'string'
|
|
1955
|
+
) {
|
|
1956
|
+
res.status(400).json({ error: 'customTitle must be a string' });
|
|
1957
|
+
return;
|
|
1958
|
+
}
|
|
1748
1959
|
const result = await resumeAgentSessionById(provider, rawSessionId, {
|
|
1749
1960
|
fork: shouldFork,
|
|
1750
1961
|
asMain: requestedAsMain,
|
|
1751
1962
|
allowUnarchiveArchived,
|
|
1752
|
-
providerHomeId
|
|
1753
|
-
|
|
1963
|
+
providerHomeId,
|
|
1964
|
+
...(req.body && Object.prototype.hasOwnProperty.call(req.body, 'customTitle')
|
|
1965
|
+
? { customTitle: req.body.customTitle }
|
|
1966
|
+
: {}),
|
|
1754
1967
|
agentRuntimeMode: req.body && ['chat', 'acp'].includes(req.body.agentRuntimeMode) ? 'chat' : 'terminal',
|
|
1755
1968
|
acpHistoryMode: req.body && req.body.acpHistoryMode === 'resume' ? 'resume' : 'load',
|
|
1756
1969
|
});
|
|
@@ -1760,10 +1973,49 @@ async function startResumedAgentSession(req, res, provider, rawSessionId) {
|
|
|
1760
1973
|
return;
|
|
1761
1974
|
}
|
|
1762
1975
|
|
|
1976
|
+
let projectMembership = {
|
|
1977
|
+
projectWorkspaces: configManager.getSettings().projectWorkspaces || [],
|
|
1978
|
+
pinnedProjectWorkspaces: configManager.getSettings().pinnedProjectWorkspaces || [],
|
|
1979
|
+
};
|
|
1980
|
+
try {
|
|
1981
|
+
if (!requestedAsMain && result.projectWorkspace) {
|
|
1982
|
+
projectMembership = configManager.mountProjectWorkspace(result.projectWorkspace);
|
|
1983
|
+
}
|
|
1984
|
+
} catch (error) {
|
|
1985
|
+
let rollbackError = '';
|
|
1986
|
+
if (!result.reused) {
|
|
1987
|
+
try {
|
|
1988
|
+
const rollback = await agentManager.archiveAgent(result.agentId, {
|
|
1989
|
+
reason: 'project-mount-failed',
|
|
1990
|
+
recordHistory: false,
|
|
1991
|
+
requireEngineExit: true,
|
|
1992
|
+
scheduleProviderArchive: false,
|
|
1993
|
+
});
|
|
1994
|
+
if (rollback?.error) rollbackError = rollback.error;
|
|
1995
|
+
} catch (cleanupError) {
|
|
1996
|
+
rollbackError = cleanupError?.message || String(cleanupError);
|
|
1997
|
+
}
|
|
1998
|
+
}
|
|
1999
|
+
if (!mainPageSessionWasRemembered && !rollbackError) {
|
|
2000
|
+
forgetMainPageAgentSession(provider, rawSessionId, providerHomeId || 'default');
|
|
2001
|
+
}
|
|
2002
|
+
broadcastState();
|
|
2003
|
+
const mountError = error.message || 'Failed to create Project';
|
|
2004
|
+
res.status(500).json({
|
|
2005
|
+
error: rollbackError
|
|
2006
|
+
? `${mountError}. Rollback failed: ${rollbackError}`
|
|
2007
|
+
: mountError,
|
|
2008
|
+
...(rollbackError ? { rollbackError } : {}),
|
|
2009
|
+
});
|
|
2010
|
+
return;
|
|
2011
|
+
}
|
|
1763
2012
|
broadcastState();
|
|
1764
2013
|
if (result.reused) {
|
|
1765
2014
|
res.status(200).json({
|
|
1766
2015
|
agentId: result.agentId,
|
|
2016
|
+
...(result.projectWorkspace ? { projectWorkspace: result.projectWorkspace } : {}),
|
|
2017
|
+
projectWorkspaces: projectMembership.projectWorkspaces,
|
|
2018
|
+
pinnedProjectWorkspaces: projectMembership.pinnedProjectWorkspaces,
|
|
1767
2019
|
reused: true,
|
|
1768
2020
|
...(result.claimed ? { claimed: true } : {}),
|
|
1769
2021
|
...(result.pending ? { pending: true } : {}),
|
|
@@ -1771,7 +2023,12 @@ async function startResumedAgentSession(req, res, provider, rawSessionId) {
|
|
|
1771
2023
|
return;
|
|
1772
2024
|
}
|
|
1773
2025
|
|
|
1774
|
-
res.status(201).json({
|
|
2026
|
+
res.status(201).json({
|
|
2027
|
+
agentId: result.agentId,
|
|
2028
|
+
...(result.projectWorkspace ? { projectWorkspace: result.projectWorkspace } : {}),
|
|
2029
|
+
projectWorkspaces: projectMembership.projectWorkspaces,
|
|
2030
|
+
pinnedProjectWorkspaces: projectMembership.pinnedProjectWorkspaces,
|
|
2031
|
+
});
|
|
1775
2032
|
}
|
|
1776
2033
|
|
|
1777
2034
|
async function autoResumeMainPageAgentSessions() {
|
|
@@ -1847,7 +2104,10 @@ async function autoResumeMainPageAgentSessions() {
|
|
|
1847
2104
|
}
|
|
1848
2105
|
|
|
1849
2106
|
app.post(routePath(BASE_PATH, '/api/settings'), express.json(), (req, res) => {
|
|
1850
|
-
|
|
2107
|
+
const settingsPatch = { ...(req.body || {}) };
|
|
2108
|
+
delete settingsPatch.projectWorkspaces;
|
|
2109
|
+
delete settingsPatch.pinnedProjectWorkspaces;
|
|
2110
|
+
configManager.updateSettings(settingsPatch);
|
|
1851
2111
|
agentSessionsCache.invalidate();
|
|
1852
2112
|
res.json({
|
|
1853
2113
|
success: true,
|
|
@@ -2089,27 +2349,70 @@ function handleMessage(ws, data) {
|
|
|
2089
2349
|
break;
|
|
2090
2350
|
case 'start-agent': {
|
|
2091
2351
|
const workspace = data.workspace || null;
|
|
2092
|
-
|
|
2093
|
-
|
|
2094
|
-
|
|
2095
|
-
|
|
2096
|
-
|
|
2097
|
-
|
|
2098
|
-
|
|
2099
|
-
|
|
2100
|
-
|
|
2101
|
-
|
|
2102
|
-
|
|
2103
|
-
|
|
2104
|
-
|
|
2105
|
-
|
|
2106
|
-
|
|
2107
|
-
|
|
2108
|
-
|
|
2109
|
-
|
|
2110
|
-
|
|
2111
|
-
|
|
2112
|
-
|
|
2352
|
+
void (async () => {
|
|
2353
|
+
const projectWorkspace = data.asMain === true
|
|
2354
|
+
? ''
|
|
2355
|
+
: await canonicalProjectWorkspace(
|
|
2356
|
+
typeof data.projectWorkspace === 'string' && data.projectWorkspace.trim()
|
|
2357
|
+
? data.projectWorkspace
|
|
2358
|
+
: workspace
|
|
2359
|
+
);
|
|
2360
|
+
agentManager.startAgent(data.command, workspace, (agentId, error) => {
|
|
2361
|
+
if (error) {
|
|
2362
|
+
ws.send(JSON.stringify({ type: 'error', message: error }));
|
|
2363
|
+
} else if (agentId) {
|
|
2364
|
+
void (async () => {
|
|
2365
|
+
try {
|
|
2366
|
+
if (projectWorkspace) configManager.mountProjectWorkspace(projectWorkspace);
|
|
2367
|
+
} catch (mountError) {
|
|
2368
|
+
let rollbackError = '';
|
|
2369
|
+
try {
|
|
2370
|
+
const rollback = await agentManager.archiveAgent(agentId, {
|
|
2371
|
+
reason: 'project-mount-failed',
|
|
2372
|
+
recordHistory: false,
|
|
2373
|
+
requireEngineExit: true,
|
|
2374
|
+
scheduleProviderArchive: false,
|
|
2375
|
+
});
|
|
2376
|
+
if (rollback?.error) rollbackError = rollback.error;
|
|
2377
|
+
} catch (cleanupError) {
|
|
2378
|
+
rollbackError = cleanupError?.message || String(cleanupError);
|
|
2379
|
+
}
|
|
2380
|
+
broadcastState();
|
|
2381
|
+
const errorMessage = mountError.message || 'Failed to create Project';
|
|
2382
|
+
ws.send(JSON.stringify({
|
|
2383
|
+
type: 'error',
|
|
2384
|
+
message: rollbackError
|
|
2385
|
+
? `${errorMessage}. Rollback failed: ${rollbackError}`
|
|
2386
|
+
: errorMessage,
|
|
2387
|
+
}));
|
|
2388
|
+
return;
|
|
2389
|
+
}
|
|
2390
|
+
ws.agentId = agentId;
|
|
2391
|
+
broadcastState();
|
|
2392
|
+
ws.send(JSON.stringify({ type: 'agent-started', agentId }));
|
|
2393
|
+
})().catch(callbackError => {
|
|
2394
|
+
console.warn('Failed to finish started Agent transition:', agentId, callbackError?.message || callbackError);
|
|
2395
|
+
});
|
|
2396
|
+
}
|
|
2397
|
+
}, {
|
|
2398
|
+
wantsMain: data.asMain === true,
|
|
2399
|
+
projectWorkspace,
|
|
2400
|
+
task: typeof data.task === 'string' ? data.task : '',
|
|
2401
|
+
workflowTemplate: typeof data.workflowTemplate === 'string' ? data.workflowTemplate : '',
|
|
2402
|
+
customTitle: typeof data.customTitle === 'string' ? data.customTitle : '',
|
|
2403
|
+
codexApprovalMode: typeof data.codexApprovalMode === 'string' ? data.codexApprovalMode : undefined,
|
|
2404
|
+
agentRuntimeMode: ['json', 'acp', 'chat'].includes(data.agentRuntimeMode) ? data.agentRuntimeMode : 'terminal',
|
|
2405
|
+
acpHistoryMode: data.acpHistoryMode === 'resume' ? 'resume' : 'load',
|
|
2406
|
+
providerHomeId: typeof data.providerHomeId === 'string' ? data.providerHomeId : '',
|
|
2407
|
+
...(Array.isArray(data.additionalDirectories) ? { additionalDirectories: data.additionalDirectories } : {}),
|
|
2408
|
+
...(Array.isArray(data.mcpServers) ? { mcpServers: data.mcpServers } : {}),
|
|
2409
|
+
...(data.dangerouslySkipPermissions === true ? { dangerouslySkipPermissions: true } : {}),
|
|
2410
|
+
});
|
|
2411
|
+
})().catch(error => {
|
|
2412
|
+
ws.send(JSON.stringify({
|
|
2413
|
+
type: 'error',
|
|
2414
|
+
message: error.message || 'Failed to resolve Project workspace',
|
|
2415
|
+
}));
|
|
2113
2416
|
});
|
|
2114
2417
|
break;
|
|
2115
2418
|
}
|
|
@@ -2262,6 +2565,7 @@ async function watchWorkspaceFiles(ws, data) {
|
|
|
2262
2565
|
|
|
2263
2566
|
function buildStatePayload() {
|
|
2264
2567
|
const state = agentManager.getState();
|
|
2568
|
+
const settings = configManager.getSettings();
|
|
2265
2569
|
return {
|
|
2266
2570
|
...state,
|
|
2267
2571
|
agents: state.agents.map(agent => ({
|
|
@@ -2271,7 +2575,9 @@ function buildStatePayload() {
|
|
|
2271
2575
|
workspaceRoots: workspaceRootRegistry.list(),
|
|
2272
2576
|
mainPageSessionKeys: typeof configManager.getMainPageSessionKeys === 'function'
|
|
2273
2577
|
? configManager.getMainPageSessionKeys()
|
|
2274
|
-
: (Array.isArray(
|
|
2578
|
+
: (Array.isArray(settings.mainPageSessionKeys) ? settings.mainPageSessionKeys : []),
|
|
2579
|
+
projectWorkspaces: settings.projectWorkspaces || [],
|
|
2580
|
+
pinnedProjectWorkspaces: settings.pinnedProjectWorkspaces || [],
|
|
2275
2581
|
};
|
|
2276
2582
|
}
|
|
2277
2583
|
|