farming-code 2.2.20 → 2.2.22
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/README.md +5 -5
- package/README.zh_cn.md +5 -5
- package/THIRD_PARTY_NOTICES.md +5 -8
- package/backend/acp/client-services.js +37 -5
- package/backend/acp/patch-decisions.js +19 -3
- package/backend/acp-runtime.js +1201 -198
- package/backend/acp-transcript.js +66 -0
- package/backend/agent-lifecycle-journal.js +133 -0
- package/backend/agent-manager.js +2715 -354
- package/backend/agent-order.js +8 -4
- package/backend/agent-runtime-binding.js +1 -0
- package/backend/bundle-update-helper.js +5 -1
- package/backend/config-manager.js +16 -0
- package/backend/control-api.js +146 -20
- package/backend/farming-app-cli.js +335 -10
- package/backend/farming-session-store.js +283 -36
- package/backend/json-cli-runtime.js +120 -6
- package/backend/npm-update-helper.js +97 -18
- package/backend/provider-adapters.js +18 -6
- package/backend/review-session-service.js +31 -17
- package/backend/server.js +332 -102
- package/backend/storage-layout.js +10 -0
- package/backend/theme-manager.js +23 -5
- package/backend/update-service.js +239 -27
- package/backend/workspace-file-router.js +9 -3
- package/backend/workspace-file-service.js +292 -62
- package/dist/acp/codex-acp-1.1.4.js +32 -1
- package/dist/assets/App-CmZdNavW.js +210 -0
- package/dist/assets/{FileEditorMarkdownPreview-R0jwwLME.js → FileEditorMarkdownPreview-KbnE8VC8.js} +1 -1
- package/dist/assets/FileEditorPane-5_HUejjd.js +2 -0
- package/dist/assets/IconGlyphs-C2WFcoJS.js +1 -0
- package/dist/assets/ProjectFilesSection-C1RAJWOE.js +12 -0
- package/dist/assets/{ReviewPage-DBKNOWsJ.js → ReviewPage-L1vh1Ggs.js} +3 -3
- package/dist/assets/code-dark-BKtvf0Xc.css +1 -0
- package/dist/assets/file-icons-CHWt5nSy.js +1 -0
- package/dist/assets/{index-DuMjesL2.js → index-RvUiMpGP.js} +2 -2
- package/dist/assets/main-XMXgyJJt.css +1 -0
- package/dist/assets/workspace-view-state-BqB4HM7r.js +7 -0
- package/dist/index.html +1 -1
- package/frontend/skins/crt/app.js +199 -81
- package/package.json +9 -3
- package/dist/assets/App-BupehIbc.js +0 -210
- package/dist/assets/FileEditorPane-CJs6kfZO.js +0 -2
- package/dist/assets/IconGlyphs-CZzDr5o2.js +0 -1
- package/dist/assets/ProjectFilesSection-DWCV07lA.js +0 -12
- package/dist/assets/code-dark-BAt0Q_Op.css +0 -1
- package/dist/assets/file-icons-DpbrD5ZP.js +0 -1
- package/dist/assets/main-y2aK_56c.css +0 -1
- package/dist/assets/workspace-view-state-BpPINu84.js +0 -7
|
@@ -89,6 +89,7 @@ const crtStructuredPreviewTimers = new Map();
|
|
|
89
89
|
let sessionRuntime = null;
|
|
90
90
|
let structuredSessionPoller = null;
|
|
91
91
|
let structuredSessionLoading = false;
|
|
92
|
+
let structuredSessionGeneration = 0;
|
|
92
93
|
let structuredSessionRenderedAt = '';
|
|
93
94
|
let structuredSessionSnapshot = null;
|
|
94
95
|
let structuredSessionControlsLoading = false;
|
|
@@ -107,6 +108,8 @@ let runtimeSwitchPending = false;
|
|
|
107
108
|
let pendingRuntimeSwitchAgentId = '';
|
|
108
109
|
let runtimeSwitchRequestSequence = 0;
|
|
109
110
|
let crtTerminalReplication = null;
|
|
111
|
+
const globalSettingsSaveRevisions = new Map();
|
|
112
|
+
let globalSettingsSaveTail = Promise.resolve();
|
|
110
113
|
const terminalPreviewSnapshots = new Map();
|
|
111
114
|
const crtBrandPulseTimers = new Map();
|
|
112
115
|
const SESSION_LINK_LIMIT = 6;
|
|
@@ -128,6 +131,7 @@ const CRT_BILLING_TOTAL_ANIMATION_MS = 900;
|
|
|
128
131
|
const CRT_BILLING_DAY_DETAIL_RETRY_MS = 750;
|
|
129
132
|
const CRT_BILLING_DAY_DETAIL_MAX_RETRIES = 4;
|
|
130
133
|
const CRT_TERMINAL_CHECKPOINT_REQUEST_TIMEOUT_MS = 5_000;
|
|
134
|
+
const CRT_SETTINGS_REQUEST_TIMEOUT_MS = 15_000;
|
|
131
135
|
const CRT_TERMINAL_RESIZE_SETTLE_MS = 250;
|
|
132
136
|
const CRT_TERMINAL_MIN_COLS = 40;
|
|
133
137
|
const CRT_TERMINAL_MIN_ROWS = 10;
|
|
@@ -849,8 +853,14 @@ function fallbackCopyText(text) {
|
|
|
849
853
|
return copied;
|
|
850
854
|
}
|
|
851
855
|
|
|
852
|
-
|
|
853
|
-
if (!
|
|
856
|
+
function isCurrentCrtSession(agentId, sessionToken = null) {
|
|
857
|
+
if (!agentId || focusedAgentId !== agentId) return false;
|
|
858
|
+
const runtime = getSessionRuntime();
|
|
859
|
+
return sessionToken === null || !runtime || runtime.isCurrentSession(agentId, sessionToken);
|
|
860
|
+
}
|
|
861
|
+
|
|
862
|
+
async function pasteTerminalText(text, agentId = focusedAgentId, sessionToken = null) {
|
|
863
|
+
if (!text || !isCurrentCrtSession(agentId, sessionToken)) {
|
|
854
864
|
return false;
|
|
855
865
|
}
|
|
856
866
|
|
|
@@ -877,9 +887,13 @@ async function pasteFromClipboard() {
|
|
|
877
887
|
return false;
|
|
878
888
|
}
|
|
879
889
|
|
|
890
|
+
const agentId = focusedAgentId;
|
|
891
|
+
const runtime = getSessionRuntime();
|
|
892
|
+
const sessionToken = runtime ? runtime.getSessionToken() : null;
|
|
893
|
+
if (!agentId) return false;
|
|
880
894
|
try {
|
|
881
895
|
const text = await navigator.clipboard.readText();
|
|
882
|
-
return pasteTerminalText(text);
|
|
896
|
+
return pasteTerminalText(text, agentId, sessionToken);
|
|
883
897
|
} catch (error) {
|
|
884
898
|
console.warn('Clipboard API paste failed:', error);
|
|
885
899
|
return false;
|
|
@@ -2792,9 +2806,19 @@ async function loadThemes() {
|
|
|
2792
2806
|
}
|
|
2793
2807
|
}
|
|
2794
2808
|
|
|
2809
|
+
async function fetchCrtSettingsRequest(url, init) {
|
|
2810
|
+
const controller = new window.AbortController();
|
|
2811
|
+
const timeout = setTimeout(() => controller.abort(), CRT_SETTINGS_REQUEST_TIMEOUT_MS);
|
|
2812
|
+
try {
|
|
2813
|
+
return await fetch(url, { ...init, signal: controller.signal });
|
|
2814
|
+
} finally {
|
|
2815
|
+
clearTimeout(timeout);
|
|
2816
|
+
}
|
|
2817
|
+
}
|
|
2818
|
+
|
|
2795
2819
|
async function loadGlobalSettings() {
|
|
2796
2820
|
try {
|
|
2797
|
-
const response = await
|
|
2821
|
+
const response = await fetchCrtSettingsRequest(farmingApiPath('/settings'));
|
|
2798
2822
|
const data = await response.json();
|
|
2799
2823
|
globalSettings = {
|
|
2800
2824
|
...globalSettings,
|
|
@@ -2813,31 +2837,48 @@ async function loadGlobalSettings() {
|
|
|
2813
2837
|
}
|
|
2814
2838
|
}
|
|
2815
2839
|
|
|
2816
|
-
async function saveGlobalSettings() {
|
|
2817
|
-
|
|
2818
|
-
|
|
2819
|
-
|
|
2820
|
-
|
|
2821
|
-
|
|
2822
|
-
|
|
2823
|
-
|
|
2824
|
-
|
|
2825
|
-
|
|
2840
|
+
async function saveGlobalSettings(settingsPatch) {
|
|
2841
|
+
const patch = settingsPatch && typeof settingsPatch === 'object'
|
|
2842
|
+
? { ...settingsPatch }
|
|
2843
|
+
: {};
|
|
2844
|
+
const keys = Object.keys(patch);
|
|
2845
|
+
if (keys.length === 0) return;
|
|
2846
|
+
const revisions = new Map(keys.map((key) => {
|
|
2847
|
+
const revision = (globalSettingsSaveRevisions.get(key) || 0) + 1;
|
|
2848
|
+
globalSettingsSaveRevisions.set(key, revision);
|
|
2849
|
+
return [key, revision];
|
|
2850
|
+
}));
|
|
2851
|
+
const save = globalSettingsSaveTail.catch(() => {}).then(async () => {
|
|
2852
|
+
try {
|
|
2853
|
+
const response = await fetchCrtSettingsRequest(farmingApiPath('/settings'), {
|
|
2854
|
+
method: 'POST',
|
|
2855
|
+
headers: {
|
|
2856
|
+
'Content-Type': 'application/json'
|
|
2857
|
+
},
|
|
2858
|
+
body: JSON.stringify(patch)
|
|
2859
|
+
});
|
|
2826
2860
|
|
|
2827
|
-
|
|
2828
|
-
|
|
2829
|
-
|
|
2830
|
-
|
|
2831
|
-
|
|
2832
|
-
|
|
2833
|
-
|
|
2834
|
-
|
|
2861
|
+
const data = await response.json();
|
|
2862
|
+
if (data.success) {
|
|
2863
|
+
const savedSettings = data.settings || {};
|
|
2864
|
+
keys.forEach((key) => {
|
|
2865
|
+
if (globalSettingsSaveRevisions.get(key) !== revisions.get(key)) return;
|
|
2866
|
+
globalSettings[key] = Object.prototype.hasOwnProperty.call(savedSettings, key)
|
|
2867
|
+
? savedSettings[key]
|
|
2868
|
+
: patch[key];
|
|
2869
|
+
});
|
|
2870
|
+
if (globalSettings.instanceName) {
|
|
2871
|
+
document.title = `${globalSettings.instanceName} · Farming CRT`;
|
|
2872
|
+
}
|
|
2873
|
+
syncWorkspaceSettings();
|
|
2835
2874
|
}
|
|
2836
|
-
|
|
2875
|
+
} catch (error) {
|
|
2876
|
+
console.error('Failed to save global settings:', error);
|
|
2877
|
+
await loadGlobalSettings();
|
|
2837
2878
|
}
|
|
2838
|
-
}
|
|
2839
|
-
|
|
2840
|
-
|
|
2879
|
+
});
|
|
2880
|
+
globalSettingsSaveTail = save;
|
|
2881
|
+
return save;
|
|
2841
2882
|
}
|
|
2842
2883
|
|
|
2843
2884
|
async function setTheme(themeId) {
|
|
@@ -2941,7 +2982,7 @@ function scheduleCrtTerminalFontSizeSave() {
|
|
|
2941
2982
|
if (crtTerminalFontSizeSaveTimer) clearTimeout(crtTerminalFontSizeSaveTimer);
|
|
2942
2983
|
crtTerminalFontSizeSaveTimer = setTimeout(() => {
|
|
2943
2984
|
crtTerminalFontSizeSaveTimer = null;
|
|
2944
|
-
void saveGlobalSettings();
|
|
2985
|
+
void saveGlobalSettings({ crtTerminalFontSize: globalSettings.crtTerminalFontSize });
|
|
2945
2986
|
}, 150);
|
|
2946
2987
|
}
|
|
2947
2988
|
|
|
@@ -2959,7 +3000,7 @@ function initDisplaySettings() {
|
|
|
2959
3000
|
crtToggle.onchange = async () => {
|
|
2960
3001
|
globalSettings.crtSkinEffectsEnabled = crtToggle.checked;
|
|
2961
3002
|
applyCRTEffects(crtToggle.checked);
|
|
2962
|
-
await saveGlobalSettings();
|
|
3003
|
+
await saveGlobalSettings({ crtSkinEffectsEnabled: crtToggle.checked });
|
|
2963
3004
|
};
|
|
2964
3005
|
}
|
|
2965
3006
|
} else {
|
|
@@ -2974,7 +3015,7 @@ function initDisplaySettings() {
|
|
|
2974
3015
|
dynamicHeatToggle.onchange = async () => {
|
|
2975
3016
|
globalSettings.crtDynamicHeatEnabled = dynamicHeatToggle.checked;
|
|
2976
3017
|
renderState();
|
|
2977
|
-
await saveGlobalSettings();
|
|
3018
|
+
await saveGlobalSettings({ crtDynamicHeatEnabled: dynamicHeatToggle.checked });
|
|
2978
3019
|
};
|
|
2979
3020
|
}
|
|
2980
3021
|
|
|
@@ -2994,7 +3035,9 @@ function initSessionEngineSettings() {
|
|
|
2994
3035
|
skipPermissionCheckToggle.checked = globalSettings.dangerouslySkipAgentPermissionsByDefault === true;
|
|
2995
3036
|
skipPermissionCheckToggle.onchange = async () => {
|
|
2996
3037
|
globalSettings.dangerouslySkipAgentPermissionsByDefault = skipPermissionCheckToggle.checked;
|
|
2997
|
-
await saveGlobalSettings(
|
|
3038
|
+
await saveGlobalSettings({
|
|
3039
|
+
dangerouslySkipAgentPermissionsByDefault: skipPermissionCheckToggle.checked,
|
|
3040
|
+
});
|
|
2998
3041
|
};
|
|
2999
3042
|
}
|
|
3000
3043
|
|
|
@@ -5358,11 +5401,16 @@ async function startCrtAgent(agent, workspaceToUse, asMainAgent) {
|
|
|
5358
5401
|
}
|
|
5359
5402
|
if (JSON.stringify(getWorkspaceHistory()) !== previousHistory) {
|
|
5360
5403
|
refreshWorkspaceMemoryUI();
|
|
5361
|
-
|
|
5404
|
+
syncWorkspaceSettings();
|
|
5405
|
+
await saveGlobalSettings({
|
|
5406
|
+
workspace: globalSettings.workspace,
|
|
5407
|
+
workspaceHistory: globalSettings.workspaceHistory,
|
|
5408
|
+
});
|
|
5362
5409
|
}
|
|
5363
5410
|
|
|
5364
5411
|
ws.send(JSON.stringify({
|
|
5365
5412
|
type: 'start-agent',
|
|
5413
|
+
requestId: globalThis.crypto?.randomUUID?.(),
|
|
5366
5414
|
command: agent.name,
|
|
5367
5415
|
workspace: workspaceToUse,
|
|
5368
5416
|
asMain: asMainAgent,
|
|
@@ -6303,7 +6351,8 @@ function flushStructuredComposerFollowUp(agent) {
|
|
|
6303
6351
|
structuredComposerPendingFollowUps.set(agent.id, queue);
|
|
6304
6352
|
return;
|
|
6305
6353
|
}
|
|
6306
|
-
|
|
6354
|
+
const generation = structuredSessionGeneration;
|
|
6355
|
+
setTimeout(() => void refreshStructuredSession(agent.id, true, generation), 160);
|
|
6307
6356
|
}
|
|
6308
6357
|
|
|
6309
6358
|
function structuredContextUsage(session) {
|
|
@@ -6378,6 +6427,14 @@ function structuredConfigValueLabel(option) {
|
|
|
6378
6427
|
return selected && selected.name ? selected.name : String(option.currentValue || '');
|
|
6379
6428
|
}
|
|
6380
6429
|
|
|
6430
|
+
function isCurrentStructuredSession(agentId, generation) {
|
|
6431
|
+
return Boolean(
|
|
6432
|
+
agentId
|
|
6433
|
+
&& agentId === focusedAgentId
|
|
6434
|
+
&& generation === structuredSessionGeneration
|
|
6435
|
+
);
|
|
6436
|
+
}
|
|
6437
|
+
|
|
6381
6438
|
function resetStructuredSessionControls() {
|
|
6382
6439
|
structuredSessionSnapshot = null;
|
|
6383
6440
|
structuredSessionControlsLoading = false;
|
|
@@ -6502,19 +6559,28 @@ function backStructuredComposerMenu() {
|
|
|
6502
6559
|
closeStructuredComposerMenu();
|
|
6503
6560
|
}
|
|
6504
6561
|
|
|
6505
|
-
async function patchStructuredAcpSession(patch) {
|
|
6506
|
-
if (!
|
|
6562
|
+
async function patchStructuredAcpSession(agentId, generation, patch) {
|
|
6563
|
+
if (!isCurrentStructuredSession(agentId, generation)) return;
|
|
6507
6564
|
const openerId = structuredComposerMenuOpenerId;
|
|
6508
|
-
|
|
6509
|
-
|
|
6510
|
-
|
|
6511
|
-
|
|
6512
|
-
|
|
6513
|
-
|
|
6565
|
+
let response;
|
|
6566
|
+
let body;
|
|
6567
|
+
try {
|
|
6568
|
+
response = await fetch(farmingApiPath(`/agents/${encodeURIComponent(agentId)}/acp-session`), {
|
|
6569
|
+
method: 'PATCH',
|
|
6570
|
+
headers: { 'Content-Type': 'application/json' },
|
|
6571
|
+
body: JSON.stringify(patch)
|
|
6572
|
+
});
|
|
6573
|
+
body = await response.json().catch(() => null);
|
|
6574
|
+
} catch (error) {
|
|
6575
|
+
if (!isCurrentStructuredSession(agentId, generation)) return;
|
|
6576
|
+
throw error;
|
|
6577
|
+
}
|
|
6578
|
+
if (!isCurrentStructuredSession(agentId, generation)) return;
|
|
6514
6579
|
if (!response.ok) throw new Error(body && body.error ? body.error : `Failed to update ACP session (${response.status})`);
|
|
6515
6580
|
structuredComposerMenu = '';
|
|
6516
6581
|
structuredComposerConfigId = '';
|
|
6517
|
-
await refreshStructuredSessionControls(
|
|
6582
|
+
await refreshStructuredSessionControls(agentId, true, generation);
|
|
6583
|
+
if (!isCurrentStructuredSession(agentId, generation)) return;
|
|
6518
6584
|
const opener = openerId ? document.getElementById(openerId) : null;
|
|
6519
6585
|
if (opener && !opener.hidden && !opener.disabled) opener.focus();
|
|
6520
6586
|
}
|
|
@@ -6542,6 +6608,8 @@ function renderStructuredComposerMenu() {
|
|
|
6542
6608
|
const retainedFocus = captureStructuredComposerMenuFocus(menu);
|
|
6543
6609
|
menu.replaceChildren();
|
|
6544
6610
|
const session = structuredSessionSnapshot;
|
|
6611
|
+
const agentId = focusedAgentId;
|
|
6612
|
+
const generation = structuredSessionGeneration;
|
|
6545
6613
|
if (!structuredComposerMenu || !session) {
|
|
6546
6614
|
menu.hidden = true;
|
|
6547
6615
|
return;
|
|
@@ -6583,7 +6651,7 @@ function renderStructuredComposerMenu() {
|
|
|
6583
6651
|
const current = session.currentModeId || (session.modes && session.modes.currentModeId) || '';
|
|
6584
6652
|
modes.forEach((mode) => {
|
|
6585
6653
|
items.appendChild(structuredMenuButton(mode.name || mode.id, mode.description || '', mode.id === current, () => {
|
|
6586
|
-
void patchStructuredAcpSession({ modeId: mode.id }).catch(showStructuredComposerError);
|
|
6654
|
+
void patchStructuredAcpSession(agentId, generation, { modeId: mode.id }).catch(showStructuredComposerError);
|
|
6587
6655
|
}, `mode:${mode.id}`));
|
|
6588
6656
|
});
|
|
6589
6657
|
} else if (!selectedConfig) {
|
|
@@ -6603,13 +6671,13 @@ function renderStructuredComposerMenu() {
|
|
|
6603
6671
|
} else if (selectedConfig.type === 'boolean') {
|
|
6604
6672
|
[false, true].forEach((value) => {
|
|
6605
6673
|
items.appendChild(structuredMenuButton(value ? 'ON' : 'OFF', selectedConfig.description || '', selectedConfig.currentValue === value, () => {
|
|
6606
|
-
void patchStructuredAcpSession({ configId: selectedConfig.id, value }).catch(showStructuredComposerError);
|
|
6674
|
+
void patchStructuredAcpSession(agentId, generation, { configId: selectedConfig.id, value }).catch(showStructuredComposerError);
|
|
6607
6675
|
}, `boolean:${value}`));
|
|
6608
6676
|
});
|
|
6609
6677
|
} else {
|
|
6610
6678
|
structuredSelectOptions(selectedConfig).forEach((candidate) => {
|
|
6611
6679
|
items.appendChild(structuredMenuButton(candidate.name || candidate.value, candidate.description || '', candidate.value === selectedConfig.currentValue, () => {
|
|
6612
|
-
void patchStructuredAcpSession({ configId: selectedConfig.id, value: candidate.value }).catch(showStructuredComposerError);
|
|
6680
|
+
void patchStructuredAcpSession(agentId, generation, { configId: selectedConfig.id, value: candidate.value }).catch(showStructuredComposerError);
|
|
6613
6681
|
}, `option:${candidate.value}`));
|
|
6614
6682
|
});
|
|
6615
6683
|
}
|
|
@@ -6644,8 +6712,12 @@ function renderStructuredSessionControls() {
|
|
|
6644
6712
|
renderStructuredComposerMenu();
|
|
6645
6713
|
}
|
|
6646
6714
|
|
|
6647
|
-
async function refreshStructuredSessionControls(
|
|
6648
|
-
|
|
6715
|
+
async function refreshStructuredSessionControls(
|
|
6716
|
+
agentId = focusedAgentId,
|
|
6717
|
+
force = false,
|
|
6718
|
+
generation = structuredSessionGeneration
|
|
6719
|
+
) {
|
|
6720
|
+
if (!isCurrentStructuredSession(agentId, generation) || structuredSessionControlsLoading) return;
|
|
6649
6721
|
const agent = state && state.agents.find((candidate) => candidate.id === agentId);
|
|
6650
6722
|
if (!agent || agent.runtimeBinding?.kind !== 'acp') {
|
|
6651
6723
|
resetStructuredSessionControls();
|
|
@@ -6660,6 +6732,7 @@ async function refreshStructuredSessionControls(agentId = focusedAgentId, force
|
|
|
6660
6732
|
try {
|
|
6661
6733
|
const response = await fetch(farmingApiPath(`/agents/${encodeURIComponent(agentId)}/acp-session`));
|
|
6662
6734
|
const body = await response.json().catch(() => null);
|
|
6735
|
+
if (!isCurrentStructuredSession(agentId, generation)) return;
|
|
6663
6736
|
if (!response.ok || !body || !body.session) {
|
|
6664
6737
|
throw new Error(body && body.error ? body.error : `Failed to read ACP session (${response.status})`);
|
|
6665
6738
|
}
|
|
@@ -6667,9 +6740,12 @@ async function refreshStructuredSessionControls(agentId = focusedAgentId, force
|
|
|
6667
6740
|
structuredSessionControlsRevision = revision || String(body.session.updatedAt || Date.now());
|
|
6668
6741
|
renderStructuredSessionControls();
|
|
6669
6742
|
} catch (error) {
|
|
6743
|
+
if (!isCurrentStructuredSession(agentId, generation)) return;
|
|
6670
6744
|
showStructuredComposerError(error);
|
|
6671
6745
|
} finally {
|
|
6672
|
-
|
|
6746
|
+
if (isCurrentStructuredSession(agentId, generation)) {
|
|
6747
|
+
structuredSessionControlsLoading = false;
|
|
6748
|
+
}
|
|
6673
6749
|
}
|
|
6674
6750
|
}
|
|
6675
6751
|
|
|
@@ -6741,7 +6817,7 @@ function setStructuredComposerActive(active) {
|
|
|
6741
6817
|
}
|
|
6742
6818
|
}
|
|
6743
6819
|
|
|
6744
|
-
function updateStructuredComposerState(agent) {
|
|
6820
|
+
function updateStructuredComposerState(agent, { refreshControls = true } = {}) {
|
|
6745
6821
|
const input = document.getElementById('crt-structured-input');
|
|
6746
6822
|
const sendButton = document.getElementById('crt-structured-send');
|
|
6747
6823
|
const statusNode = document.getElementById('crt-structured-composer-status');
|
|
@@ -6767,7 +6843,9 @@ function updateStructuredComposerState(agent) {
|
|
|
6767
6843
|
statusNode.textContent = error || `${kind} ${String(runtimeStatus || 'idle').toUpperCase()}`;
|
|
6768
6844
|
statusNode.classList.toggle('error', Boolean(error));
|
|
6769
6845
|
renderStructuredPermissions(agent);
|
|
6770
|
-
if (agent && agent.runtimeBinding?.kind === 'acp')
|
|
6846
|
+
if (refreshControls && agent && agent.runtimeBinding?.kind === 'acp') {
|
|
6847
|
+
void refreshStructuredSessionControls(agent.id);
|
|
6848
|
+
}
|
|
6771
6849
|
}
|
|
6772
6850
|
|
|
6773
6851
|
function structuredAttachmentId(file) {
|
|
@@ -6926,6 +7004,8 @@ function renderStructuredPermissions(agent) {
|
|
|
6926
7004
|
if (!container) return;
|
|
6927
7005
|
container.replaceChildren();
|
|
6928
7006
|
if (!agent || agent.runtimeBinding?.kind !== 'acp') return;
|
|
7007
|
+
const agentId = agent.id;
|
|
7008
|
+
const generation = structuredSessionGeneration;
|
|
6929
7009
|
const runtime = agent.runtimeBinding;
|
|
6930
7010
|
const requests = Array.isArray(runtime.pendingPermissions) && runtime.pendingPermissions.length
|
|
6931
7011
|
? runtime.pendingPermissions
|
|
@@ -6944,27 +7024,47 @@ function renderStructuredPermissions(agent) {
|
|
|
6944
7024
|
const button = document.createElement('button');
|
|
6945
7025
|
button.type = 'button';
|
|
6946
7026
|
button.textContent = `[${option.name}]`;
|
|
6947
|
-
button.onclick = () => void respondToStructuredPermission(
|
|
7027
|
+
button.onclick = () => void respondToStructuredPermission(
|
|
7028
|
+
agentId,
|
|
7029
|
+
generation,
|
|
7030
|
+
request.requestId,
|
|
7031
|
+
option.optionId,
|
|
7032
|
+
false,
|
|
7033
|
+
);
|
|
6948
7034
|
actions.appendChild(button);
|
|
6949
7035
|
});
|
|
6950
7036
|
const decline = document.createElement('button');
|
|
6951
7037
|
decline.type = 'button';
|
|
6952
7038
|
decline.textContent = '[DECLINE]';
|
|
6953
|
-
decline.onclick = () => void respondToStructuredPermission(
|
|
7039
|
+
decline.onclick = () => void respondToStructuredPermission(
|
|
7040
|
+
agentId,
|
|
7041
|
+
generation,
|
|
7042
|
+
request.requestId,
|
|
7043
|
+
undefined,
|
|
7044
|
+
true,
|
|
7045
|
+
);
|
|
6954
7046
|
actions.appendChild(decline);
|
|
6955
7047
|
panel.append(title, description, actions);
|
|
6956
7048
|
container.appendChild(panel);
|
|
6957
7049
|
});
|
|
6958
7050
|
}
|
|
6959
7051
|
|
|
6960
|
-
async function respondToStructuredPermission(requestId, optionId, cancelled) {
|
|
6961
|
-
if (!
|
|
6962
|
-
|
|
6963
|
-
|
|
6964
|
-
|
|
6965
|
-
|
|
6966
|
-
|
|
6967
|
-
|
|
7052
|
+
async function respondToStructuredPermission(agentId, generation, requestId, optionId, cancelled) {
|
|
7053
|
+
if (!isCurrentStructuredSession(agentId, generation)) return;
|
|
7054
|
+
let response;
|
|
7055
|
+
let body;
|
|
7056
|
+
try {
|
|
7057
|
+
response = await fetch(farmingApiPath(`/agents/${encodeURIComponent(agentId)}/acp-permission`), {
|
|
7058
|
+
method: 'POST',
|
|
7059
|
+
headers: { 'Content-Type': 'application/json' },
|
|
7060
|
+
body: JSON.stringify({ requestId, optionId, cancelled: cancelled === true })
|
|
7061
|
+
});
|
|
7062
|
+
body = await response.json().catch(() => null);
|
|
7063
|
+
} catch (error) {
|
|
7064
|
+
if (isCurrentStructuredSession(agentId, generation)) showStructuredComposerError(error);
|
|
7065
|
+
return;
|
|
7066
|
+
}
|
|
7067
|
+
if (!isCurrentStructuredSession(agentId, generation)) return;
|
|
6968
7068
|
if (!response.ok) {
|
|
6969
7069
|
showStructuredComposerError(new Error(body && body.error ? body.error : `Permission response failed (${response.status})`));
|
|
6970
7070
|
}
|
|
@@ -7204,13 +7304,13 @@ async function refreshCrtStructuredPreview(agent) {
|
|
|
7204
7304
|
}
|
|
7205
7305
|
}
|
|
7206
7306
|
|
|
7207
|
-
function renderStructuredTranscript(transcript, force = false) {
|
|
7307
|
+
function renderStructuredTranscript(transcript, force = false, agentId = focusedAgentId) {
|
|
7208
7308
|
const container = document.getElementById('terminal-output');
|
|
7209
|
-
if (!container) return;
|
|
7309
|
+
if (!container || agentId !== focusedAgentId) return;
|
|
7210
7310
|
const updatedAt = String(transcript && transcript.updatedAt || '');
|
|
7211
7311
|
if (!force && updatedAt && updatedAt === structuredSessionRenderedAt) return;
|
|
7212
7312
|
const nearBottom = container.scrollHeight - container.scrollTop - container.clientHeight < 80;
|
|
7213
|
-
if (!nearBottom
|
|
7313
|
+
if (!nearBottom) saveCrtStructuredReadingAnchor(agentId);
|
|
7214
7314
|
const turns = structuredTranscriptTurns(transcript);
|
|
7215
7315
|
|
|
7216
7316
|
const markdownRenderer = typeof window !== 'undefined' ? window.FarmingCrtMarkdownRenderer : null;
|
|
@@ -7234,15 +7334,21 @@ function renderStructuredTranscript(transcript, force = false) {
|
|
|
7234
7334
|
return;
|
|
7235
7335
|
}
|
|
7236
7336
|
structuredSessionRenderedAt = updatedAt;
|
|
7237
|
-
if (!
|
|
7337
|
+
if (!restoreCrtStructuredReadingAnchor(agentId)) {
|
|
7238
7338
|
if (force || nearBottom) container.scrollTop = container.scrollHeight;
|
|
7239
7339
|
}
|
|
7240
|
-
container.onscroll = () =>
|
|
7340
|
+
container.onscroll = () => {
|
|
7341
|
+
if (focusedAgentId === agentId) saveCrtStructuredReadingAnchor(agentId);
|
|
7342
|
+
};
|
|
7241
7343
|
requestAnimationFrame(updateStructuredTranscriptScrollState);
|
|
7242
7344
|
}
|
|
7243
7345
|
|
|
7244
|
-
async function refreshStructuredSession(
|
|
7245
|
-
|
|
7346
|
+
async function refreshStructuredSession(
|
|
7347
|
+
agentId = focusedAgentId,
|
|
7348
|
+
force = false,
|
|
7349
|
+
generation = structuredSessionGeneration
|
|
7350
|
+
) {
|
|
7351
|
+
if (!isCurrentStructuredSession(agentId, generation) || structuredSessionLoading) return;
|
|
7246
7352
|
const agent = state && state.agents.find((candidate) => candidate.id === agentId);
|
|
7247
7353
|
if (!agent || !isStructuredRuntimeAgent(agent)) return;
|
|
7248
7354
|
structuredSessionLoading = true;
|
|
@@ -7250,12 +7356,14 @@ async function refreshStructuredSession(agentId = focusedAgentId, force = false)
|
|
|
7250
7356
|
const endpoint = structuredTranscriptEndpoint(agent);
|
|
7251
7357
|
const response = await fetch(farmingApiPath(`/agents/${encodeURIComponent(agentId)}/${endpoint}?maxTurns=80`));
|
|
7252
7358
|
const body = await response.json().catch(() => null);
|
|
7359
|
+
if (!isCurrentStructuredSession(agentId, generation)) return;
|
|
7253
7360
|
if (!response.ok || !body || !body.transcript) {
|
|
7254
7361
|
throw new Error(body && body.error ? body.error : `Failed to read conversation (${response.status})`);
|
|
7255
7362
|
}
|
|
7256
|
-
renderStructuredTranscript(body.transcript, force);
|
|
7363
|
+
renderStructuredTranscript(body.transcript, force, agentId);
|
|
7257
7364
|
updateStructuredComposerState(agent);
|
|
7258
7365
|
} catch (error) {
|
|
7366
|
+
if (!isCurrentStructuredSession(agentId, generation)) return;
|
|
7259
7367
|
const container = document.getElementById('terminal-output');
|
|
7260
7368
|
if (container && !container.querySelector('.crt-structured-transcript')) {
|
|
7261
7369
|
const message = document.createElement('div');
|
|
@@ -7265,23 +7373,28 @@ async function refreshStructuredSession(agentId = focusedAgentId, force = false)
|
|
|
7265
7373
|
}
|
|
7266
7374
|
updateStructuredComposerState(agent);
|
|
7267
7375
|
} finally {
|
|
7268
|
-
|
|
7376
|
+
if (isCurrentStructuredSession(agentId, generation)) {
|
|
7377
|
+
structuredSessionLoading = false;
|
|
7378
|
+
}
|
|
7269
7379
|
}
|
|
7270
7380
|
}
|
|
7271
7381
|
|
|
7272
|
-
function stopStructuredSessionPolling() {
|
|
7382
|
+
function stopStructuredSessionPolling(revokeOwnership = true) {
|
|
7273
7383
|
if (structuredSessionPoller) {
|
|
7274
7384
|
clearInterval(structuredSessionPoller);
|
|
7275
7385
|
structuredSessionPoller = null;
|
|
7276
7386
|
}
|
|
7277
7387
|
structuredSessionLoading = false;
|
|
7278
7388
|
structuredSessionRenderedAt = '';
|
|
7389
|
+
if (revokeOwnership) structuredSessionGeneration += 1;
|
|
7279
7390
|
}
|
|
7280
7391
|
|
|
7281
|
-
function startStructuredSessionPolling(agentId) {
|
|
7282
|
-
stopStructuredSessionPolling();
|
|
7392
|
+
function startStructuredSessionPolling(agentId, generation) {
|
|
7393
|
+
stopStructuredSessionPolling(false);
|
|
7283
7394
|
structuredSessionPoller = setInterval(() => {
|
|
7284
|
-
if (
|
|
7395
|
+
if (isCurrentStructuredSession(agentId, generation)) {
|
|
7396
|
+
void refreshStructuredSession(agentId, false, generation);
|
|
7397
|
+
}
|
|
7285
7398
|
}, 1000);
|
|
7286
7399
|
}
|
|
7287
7400
|
|
|
@@ -7452,9 +7565,11 @@ function setupStructuredSessionComposer() {
|
|
|
7452
7565
|
const agent = state && state.agents.find((candidate) => candidate.id === focusedAgentId);
|
|
7453
7566
|
const action = structuredComposerAction(agent, input.value);
|
|
7454
7567
|
if (!agent || action === 'disabled') return;
|
|
7568
|
+
const agentId = agent.id;
|
|
7569
|
+
const generation = structuredSessionGeneration;
|
|
7455
7570
|
if (action === 'interrupt') {
|
|
7456
7571
|
structuredComposerRestoreFocusAfterInterrupt = true;
|
|
7457
|
-
const interrupted = getSessionClient()?.interruptAgent(
|
|
7572
|
+
const interrupted = getSessionClient()?.interruptAgent(agentId);
|
|
7458
7573
|
if (!interrupted) {
|
|
7459
7574
|
structuredComposerRestoreFocusAfterInterrupt = false;
|
|
7460
7575
|
showStructuredComposerError(new Error('Connection unavailable'));
|
|
@@ -7468,7 +7583,7 @@ function setupStructuredSessionComposer() {
|
|
|
7468
7583
|
if (!message && promptAttachments.length === 0) return;
|
|
7469
7584
|
const statusNode = document.getElementById('crt-structured-composer-status');
|
|
7470
7585
|
const completeSubmission = () => {
|
|
7471
|
-
addStructuredComposerHistory(
|
|
7586
|
+
addStructuredComposerHistory(agentId, draft);
|
|
7472
7587
|
input.value = '';
|
|
7473
7588
|
structuredComposerAttachments = [];
|
|
7474
7589
|
renderStructuredComposerAttachments();
|
|
@@ -7478,11 +7593,11 @@ function setupStructuredSessionComposer() {
|
|
|
7478
7593
|
statusNode.classList.remove('error');
|
|
7479
7594
|
}
|
|
7480
7595
|
updateStructuredComposerState(agent);
|
|
7481
|
-
setTimeout(() => void refreshStructuredSession(
|
|
7596
|
+
setTimeout(() => void refreshStructuredSession(agentId, true, generation), 160);
|
|
7482
7597
|
};
|
|
7483
7598
|
const sent = action === 'send' && structuredRuntimeStatus(agent) !== 'idle'
|
|
7484
|
-
? (queueStructuredComposerFollowUp(
|
|
7485
|
-
: getSessionClient()?.sendComposerMessage(
|
|
7599
|
+
? (queueStructuredComposerFollowUp(agentId, message, promptAttachments), true)
|
|
7600
|
+
: getSessionClient()?.sendComposerMessage(agentId, message, promptAttachments);
|
|
7486
7601
|
if (!sent) {
|
|
7487
7602
|
if (statusNode) {
|
|
7488
7603
|
statusNode.textContent = 'Connection unavailable';
|
|
@@ -7498,14 +7613,17 @@ async function openStructuredSession(agent, sessionToken) {
|
|
|
7498
7613
|
const runtime = getSessionRuntime();
|
|
7499
7614
|
const container = document.getElementById('terminal-output');
|
|
7500
7615
|
if (!container) return;
|
|
7616
|
+
const generation = ++structuredSessionGeneration;
|
|
7501
7617
|
container.classList.add('crt-structured-session');
|
|
7502
7618
|
setStructuredComposerActive(true);
|
|
7503
7619
|
setupStructuredSessionComposer();
|
|
7504
|
-
updateStructuredComposerState(agent);
|
|
7505
|
-
await refreshStructuredSessionControls(agent.id, true);
|
|
7506
|
-
|
|
7620
|
+
updateStructuredComposerState(agent, { refreshControls: false });
|
|
7621
|
+
await refreshStructuredSessionControls(agent.id, true, generation);
|
|
7622
|
+
if (!isCurrentStructuredSession(agent.id, generation)) return;
|
|
7623
|
+
await refreshStructuredSession(agent.id, true, generation);
|
|
7624
|
+
if (!isCurrentStructuredSession(agent.id, generation)) return;
|
|
7507
7625
|
if (runtime && !runtime.isCurrentSession(agent.id, sessionToken)) return;
|
|
7508
|
-
startStructuredSessionPolling(agent.id);
|
|
7626
|
+
startStructuredSessionPolling(agent.id, generation);
|
|
7509
7627
|
const input = document.getElementById('crt-structured-input');
|
|
7510
7628
|
if (input) {
|
|
7511
7629
|
resizeStructuredComposerInput(input);
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "farming-code",
|
|
3
|
-
"version": "2.2.
|
|
4
|
-
"description": "
|
|
3
|
+
"version": "2.2.22",
|
|
4
|
+
"description": "Farming Code is a self-hosted browser workspace for running and supervising AI coding agents",
|
|
5
5
|
"main": "backend/server.js",
|
|
6
6
|
"bin": {
|
|
7
7
|
"farming": "bin/farming"
|
|
@@ -80,10 +80,16 @@
|
|
|
80
80
|
},
|
|
81
81
|
"keywords": [
|
|
82
82
|
"ai-agent",
|
|
83
|
+
"ai-agent-workspace",
|
|
84
|
+
"browser-terminal",
|
|
85
|
+
"claude-code",
|
|
83
86
|
"coding-agent",
|
|
84
87
|
"codex",
|
|
85
|
-
"
|
|
88
|
+
"codex-cli",
|
|
89
|
+
"developer-tools",
|
|
90
|
+
"farming-code",
|
|
86
91
|
"remote-workspace",
|
|
92
|
+
"self-hosted",
|
|
87
93
|
"terminal"
|
|
88
94
|
],
|
|
89
95
|
"author": "",
|